小時(shí)候不太會(huì)玩電腦,一般自己玩的時(shí)候流程是:開始--附加功能--游戲與娛樂。
倘若旁邊有妹子在圍觀,我便會(huì)win+R-----cmd----ping baidu.com.然后看著屏幕上跳出一行行數(shù)字,假裝自己看得懂的樣子,過一會(huì)兒再慢悠悠地打開太空彈球玩起來
現(xiàn)在想起來真的是圖樣圖森破,甚至還非常naive啊。
這個(gè)ping程序算是我對(duì)計(jì)算機(jī)最早的認(rèn)識(shí)了,作為一個(gè)生活在圖形界面時(shí)代的人類,這也是我第一次知道了命令行形式的人機(jī)交互。
之前學(xué)習(xí)了計(jì)算機(jī)網(wǎng)絡(luò)的知識(shí),知道了它的原理其實(shí)是利用ICMP協(xié)議的回顯請(qǐng)求來實(shí)現(xiàn)的,通過構(gòu)造ICMP報(bào)文向目的主機(jī)發(fā)出,然后接收返回報(bào)文,計(jì)算經(jīng)過的時(shí)間,就能計(jì)算出主機(jī)到目的主機(jī)之間的RTT(Round Trip Time),也就是我們平時(shí)講的延遲。
由于ICMP工作在網(wǎng)絡(luò)層,不能保證交付,也不保證順序,所以發(fā)送多個(gè)請(qǐng)求的時(shí)候,可能會(huì)出現(xiàn)亂序的情況,于是ping程序中在數(shù)據(jù)段保存包本身的發(fā)送時(shí)間,接收到之后用系統(tǒng)時(shí)間減去報(bào)文中讀取的時(shí)間即可得到RTT。為了簡(jiǎn)化過程,我在此只發(fā)送一個(gè)報(bào)文。
要構(gòu)造ICMP包,首先要知道其格式,
Type (8bit) | Code(8bit) | Checksum (16bit) | Identifier (16bit) | Sequence Number (16bit) | Data ...
我么要用到的是回顯請(qǐng)求,對(duì)應(yīng)type是8,code是0,checksum要通過特定的算法獲得,其他部分自己處理即可。
import socket
import array,struct,time,select
def checksum(data):
if(len(data)%2!=0):
data+=b'\x00'
a=array.array('H',data)
s=0
for d in a:
s=s+d
s=(~s)&0xffff
return s
這段代碼將已經(jīng)除checksum以外其它打包好的數(shù)據(jù)進(jìn)行一系列計(jì)算,得到一個(gè)16bit的數(shù),用于差錯(cuò)檢測(cè),如果計(jì)算錯(cuò)誤,服務(wù)器方不會(huì)響應(yīng),你也就接受不到響應(yīng)的報(bào)文了。在編寫網(wǎng)絡(luò)程序的時(shí)候,調(diào)試時(shí)要利用抓包工具查看自己發(fā)出的報(bào)文的具體內(nèi)容,否則難以得知自己錯(cuò)在何處。
def send_packet(my_socket,destination_addr):
header=struct.pack('bbHh',8,0,1,1)
data=0
data=struct.pack('d',data)
checks=checksum(header+data)
packet=struct.pack('bbHHh',8,0,checks,1,1)+data
my_socket.sendto(packet,(destination_addr,1))
t=recive_ping(my_socket, 5)
print(t)
def recive_ping(my_socket,timeout):
timeleft=timeout
while True:
starttime=time.time()
select_=select.select([my_socket],[],[],timeleft)
if select_[0]==[]:
print("timeout")
return -1
t=time.time()-starttime
return t
這兩個(gè)函數(shù)一起完成了報(bào)文的構(gòu)造和發(fā)送,這其中用到了raw類型socket,select用于接收數(shù)據(jù),由于數(shù)據(jù)要翻譯成二進(jìn)制發(fā)送,直接連接字符串肯定是不行的,所以要用struct將數(shù)據(jù)打包。
def do(addr):
icmp=socket.getprotobyname('icmp')
s=socket.socket(socket.AF_INET,socket.SOCK_RAW,icmp)
send_packet(s, addr)
do("220.181.57.217")
這就是程序的入口了,運(yùn)行效果就不貼了,反正就是打印出一個(gè)浮點(diǎn)數(shù)。數(shù)字的含義是延遲時(shí)間(實(shí)際是rtt)。
與ping相似的另一個(gè)traceroute,實(shí)現(xiàn)原理也類似,它利用的是ip頭部的TTL,通過構(gòu)造指向目的主機(jī)的ttl從1遞增的數(shù)據(jù)包,就能獲得源主機(jī)到目的主機(jī)之間所有經(jīng)過的路由,這可以在網(wǎng)上找到不少資料,此處不再贅述。