網(wǎng)絡(luò)編程離不開(kāi)socket上荡,接觸不多時(shí)總覺(jué)得簡(jiǎn)單,無(wú)非是bind()馒闷;listen()酪捡;accept();send()纳账;recv()幾個(gè)函數(shù)癡癡用上逛薇,能跑就不管三七二十一了。最近需要寫(xiě)個(gè)http代理疏虫,遇上幾個(gè)問(wèn)題永罚。
socket的本質(zhì)
從前認(rèn)死理啤呼,怎么就recv()收到網(wǎng)絡(luò)報(bào)文,功能還這么強(qiáng)大呢袱,嗅探官扣,代理,服務(wù)器都用上它羞福,就差沒(méi)往硬件上想惕蹄,現(xiàn)在想來(lái)也算是和自己和解了,在我的理解治专,socket是從傳輸層提取出的一套程序接口卖陵,供應(yīng)用層使用,使我們不必面對(duì)實(shí)際的tcp/ip復(fù)雜的傳輸過(guò)程张峰。它是一套規(guī)則和機(jī)制泪蔫,靈感來(lái)源于Unix一切皆文件的設(shè)計(jì),socket就像一個(gè)文件夾挟炬,創(chuàng)建鸥滨,讀取,寫(xiě)出谤祖,關(guān)閉婿滓,只不過(guò)這里對(duì)應(yīng)的是網(wǎng)絡(luò)數(shù)據(jù)。
socket中的accept()
accept()函數(shù)返回值是客戶端socket和客戶端地址信息粥喜,它創(chuàng)建了一個(gè)新的socket凸主,稱它為cli_socket,而原先綁定的socket稱為soc额湘。這里使我疑惑的地方就在于為什么同一個(gè)端口下有兩個(gè)socket的存在卿吐。不是一個(gè)端口標(biāo)識(shí)唯一的socket嗎?
事實(shí)上锋华,不是一個(gè)端口唯一標(biāo)識(shí)嗡官,而是五元組標(biāo)識(shí)一個(gè)socket,(source ip, source port, destination ip, destination port, protocol)毯焕,而本地綁定的socket并不算一個(gè)完整的socket衍腥,它只包含三元組,即(destination ip, destination port, protocol)纳猫,而accept()函數(shù)接受了來(lái)自客戶端的信息婆咸,至此完成一個(gè)完整的socket鏈接,這個(gè)cli_socket用于接收和發(fā)送數(shù)據(jù)芜辕,而soc則繼續(xù)用于監(jiān)聽(tīng)客戶端的connect請(qǐng)求尚骄。
再說(shuō)明一點(diǎn),雖說(shuō)五元組唯一標(biāo)識(shí)一個(gè)socket侵续,并不是說(shuō)這個(gè)端口就只能給一個(gè)socket使用倔丈,若是五元組中有不同的元素憨闰,比如說(shuō)客戶端ip和端口不同,那就是另一個(gè)socket需五,它和服務(wù)器端相同的端口和ip構(gòu)成了新的五元組起趾,比如說(shuō)服務(wù)器端的80端口供多個(gè)客戶端使用。
總的來(lái)說(shuō)這些都是前人留下的規(guī)定警儒,有的也不見(jiàn)得多明智,但目前還是主流眶根,所以還是有必要知道蜀铲。
參考1
參考2
socket中的數(shù)據(jù)接收問(wèn)題
因?yàn)閷?xiě)的是一個(gè)代理,所以既要當(dāng)客戶端也要當(dāng)服務(wù)端属百,在充當(dāng)客戶端過(guò)程中记劝,需要和web服務(wù)器通信,很簡(jiǎn)單的一個(gè)建立連接族扰,接收數(shù)據(jù)厌丑,是這樣的:
SerSock = socket(AF_INET, SOCK_STREAM)
SerSock.connect(SerAddr)
SerSock.send(CMessage)
message = SerSock.recv(4096)
SerSock.close()
就是創(chuàng)建,連接渔呵,發(fā)送怒竿,接收,關(guān)閉的過(guò)程扩氢,但是報(bào)錯(cuò)耕驰,broken pipe。說(shuō)是服務(wù)端還沒(méi)傳完數(shù)據(jù)录豺,客戶端就關(guān)閉了連接朦肘。
將接收到的數(shù)據(jù)輸出了一下,發(fā)現(xiàn)數(shù)據(jù)都沒(méi)傳完双饥,是這種:
細(xì)想媒抠,才知服務(wù)器傳送數(shù)據(jù)并不是一次性傳完的,學(xué)過(guò)計(jì)算機(jī)網(wǎng)絡(luò)的都知道咏花,傳輸層會(huì)將數(shù)據(jù)進(jìn)行分片傳輸趴生,選擇不同的路徑傳送過(guò)來(lái)再組織到一起。那么分片的大小是多大呢迟螺?每個(gè)以太網(wǎng)幀都有最小的大小64bytes最大不能超過(guò)1518bytes冲秽,參考。
將代碼一改:
SerSock = socket(AF_INET, SOCK_STREAM)
SerSock.connect(SerAddr)
SerSock.send(CMessage)
FromSMessage = ''
while True:
message = SerSock.recv(4096)
if not message:
break
FromSMessage += message
SerSock.close()
可順利運(yùn)行矩父。
socket中的非阻塞問(wèn)題
代理程序雖然能運(yùn)行锉桑,但訪問(wèn)一多就會(huì)崩掉。這里需要知道的是窍株,socket中的send()民轴,recv()函數(shù)都是阻塞函數(shù)攻柠,也就是說(shuō)若數(shù)據(jù)沒(méi)有發(fā)送完畢或者沒(méi)有接收完畢,函數(shù)是不會(huì)返回的后裸,那么隨著accept的客戶端請(qǐng)求多了瑰钮,內(nèi)存占用溢出,就會(huì)崩掉微驶。怎樣設(shè)計(jì)一個(gè)非阻塞的socket程序呢浪谴?
這里主要講一下select這個(gè)對(duì)象,利用select對(duì)象的select()函數(shù)可以實(shí)現(xiàn)非阻塞因苹,簡(jiǎn)單看一下:
select.select(rlist, wlist, xlist[, timeout])
select()的參數(shù)為3個(gè)列表:第一列表為讀取輸入數(shù)據(jù)的對(duì)象苟耻;第2個(gè)接收要發(fā)送的數(shù)據(jù),第3個(gè)存放errors扶檐,加上一個(gè)超時(shí)設(shè)置凶杖。
返回值有三個(gè):readable,writable款筑,exceptional
readable有3種可能:對(duì)于用來(lái)偵聽(tīng)連接主服務(wù)器socket智蝠,表示已準(zhǔn)備好接受一個(gè)到來(lái)的連接;對(duì)于已經(jīng)建立并發(fā)送數(shù)據(jù)的鏈接奈梳,表示有數(shù)據(jù)到來(lái)杈湾;如果沒(méi)數(shù)據(jù)到來(lái),表示鏈接已經(jīng)關(guān)閉攘须。
writable的情況:連接隊(duì)列中有數(shù)據(jù)毛秘,發(fā)送下一條消息。如果隊(duì)列中無(wú)數(shù)據(jù)阻课,則從output隊(duì)列中刪除叫挟。
socket有錯(cuò)誤,也要從output隊(duì)列中刪除限煞。
所以這里實(shí)現(xiàn)非阻塞大體流程就是抹恳,select函數(shù)輪循,看有那個(gè)socket隊(duì)列有需要讀或?qū)懙臄?shù)據(jù)署驻,有就全部接收或發(fā)送奋献,沒(méi)有就忙別的。代碼大體如下:
def nonblocking(self):
inputs=[self.client,self.target]
while True:
readable,writeable,errs=select.select(inputs,[],inputs,3)
if errs:
break
for soc in readable:
data=soc.recv(self.BUFSIZE)
if data:
if soc is self.client:
self.target.send(data)
elif soc is self.target:
self.client.send(data)
else:
break
self.client.close()
self.target.close()
至此旺上,socket問(wèn)題肯定不止于此瓶蚂,經(jīng)事尚少,暫且留意了這些宣吱,做個(gè)總結(jié)窃这,也希望能對(duì)他人有所幫助,不對(duì)之處還望指正征候。