日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python网络编程项目_python网络编程详解

發(fā)布時間:2023/12/2 python 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python网络编程项目_python网络编程详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

最近在看《UNIX網絡編程 卷1》和《FREEBSD操作系統設計與實現》這兩本書,我重點關注了TCP協議相關的內容,結合自己后臺開發(fā)的經驗,寫下這篇文章,一方面是為了幫助有需要的人,更重要的是方便自己整理思路,加深理解。

理論基礎

OSI網絡模型

OSI模型是一個七層模型,實際工程中,層次的劃分沒有這么細致。一般來說,物理層和數據層對應著硬件和設備驅動程序,例如網卡和網卡驅動。傳輸層和網絡層由操作系統內核實現,當用戶進程需要通過網絡傳輸數據,通過系統調用的方式讓內核將數據封裝為相應的協議格式,進而調用網卡驅動傳輸數據。頂上三層對應具體的網絡應用協議:FTP、HTTP等,這些應用層協議不需要知道具體的通信細節(jié)。

傳輸層

在實際工程中,我們常用的應用層服務(例如:HTTP服務、數據庫服務、緩存服務)通信的直接底層就是傳輸層,下圖是一些常用命令涉及的通信協議。

IPv4(Internet Protocol version 4)全稱是網際協議版本4,它使用32地址,平時常說的IP協議就是指IPv4,類似于192.168.99.100的地址可以看成4位256進制數據,也就是32網絡地址。但隨著網絡設備爆炸式增長,32地址面臨這用完的風險,IPv6(Internet Protocol version 6)應運而生。IPv6使用128位地址,但IPv4地址耗盡的問題有了新的解決方案,目前普遍使用的還是IPv4,IPv6全面取代IPv4還有很長的距離。

UDP (User Datagram Protocol),全稱用戶數據報協議。UDP提供面向無連接的服務,客戶端和服務端不存在任何長期的關系。UDP不提供可靠的通信,它不保證數據報一定送達,也不保證數據包送達的先后順序,也不保證每份數據報只送達一次。雖然UDP可靠性差,但是消耗資源少,適用在網絡環(huán)境較好的局域網中,例如不需要精確統計的監(jiān)控服務(eg: Statsd)。由于使用了UDP,客戶端每次打點統計只需要一次發(fā)送UDP數據報的IO開銷,服務性能損失很小,而且在內網環(huán)境數據包一般都能正常到達服務端,也能保證較高的可行度。

TCP(Transmission Control Protocl),全稱傳輸控制協議。和UDP相反,TCP提供了面向連接的服務,而且提供了可靠性保障。平常我們使用的應用層協議,例如HTTP,FTP等,幾乎都是建立在TCP協議之上,深入了解TCP的細節(jié)對于開發(fā)高質量的后臺開發(fā)和客戶端開發(fā)都有很好的借鑒意義。下面開始重點介紹TCP協議的細節(jié)。

TCP協議

狀態(tài)轉換

為了提供可靠的通信服務,TCP通過三次分節(jié)建立連接,四次分節(jié)關閉連接,心跳檢查判斷連接是否正常,因此需要記錄連接的狀態(tài),TCP一共定義了11種不同的狀態(tài)。

通過netstat命令可以查看所有的tcp狀態(tài)。

三路握手

在三路握手之前,服務器必須準備好接收外來的連接。這通常通過調用bind和listen完成被動打開,此時服務進程有一個套接字處于LISTEN狀態(tài)。在客戶端發(fā)通過調用connect送一個SYN分節(jié)后,服務進程必須確認(ACK)此分節(jié),同時也發(fā)送一個SYN分節(jié),這兩步在同一分節(jié)中完成,通過上面的轉臺扭轉圖,可以知道服務進程中會生成一個處于SYN_RCVD狀態(tài)的套接字。當再次收到客戶端的ACK分節(jié)后,服務端的套接字狀態(tài)轉變?yōu)镋STABLISHED。

客戶端通過connect函數發(fā)起主動打開,在此之前客戶端套接字狀態(tài)為CLOSED。調用connect導致客戶TCP發(fā)送一個SYN分節(jié),此時套接字狀態(tài)有CLOSED變?yōu)镾YN_SENT,在收到服務器的SYN和ACK后,客戶端socket再發(fā)送ACK分節(jié),套接字狀態(tài)變?yōu)镋STABLISHED,此時connect返回。

備注:SYN分節(jié)中除了有序列號之外,還會有最大分節(jié)大小、窗口規(guī)模選項、時間戳等TCP參數,具體可以參考協議詳細規(guī)定。

終止連接

上圖展示了客戶端執(zhí)行主動關閉的情形,實際上無論客戶端還是服務器,都可以執(zhí)行主動關閉。一般情況下客戶端執(zhí)行主動關閉較多,所以使用客戶端主動關閉為例講解。

客戶端調用close,執(zhí)行主動關閉時,發(fā)送FIN分節(jié),此時客戶端套接字狀態(tài)由ESTABLISED變?yōu)镕IN_WAIT_1。服務器收到這個FIN,會執(zhí)行被動關閉,并向客戶端發(fā)送ACK,FIN的接受也作為一個文件結束符傳遞給服務進程,如果此時服務進程調用套接字的方法,無論緩存區(qū)是否有數據都會返回EOF,服務端套接字狀態(tài)由ESTABLISED變?yōu)闉镃LOSE_WAIT。客戶端接收到ACK后,客戶端套接字狀態(tài)由FIN_WAIT_1變?yōu)镕IN_WAIT_2。

一段時間后,當服務進程調用close或者shutdown時,也會發(fā)生送FIN分節(jié),服務端套接字狀態(tài)由CLOSE_WAIT變?yōu)長AST_ACK。客戶端在接收到FIN分節(jié)后,發(fā)送ACK分節(jié),客戶端套接字狀態(tài)由FIN_WAIT_2變?yōu)門IME_WAIT。服務器段接收到客戶端的ACK分節(jié),狀態(tài)變成CLOSED。

在某些情況下,第二和第三分節(jié)可能會合并發(fā)送。調用close可能會觸發(fā)主動關閉,當進程正常或者非正常退出時,內核會將該進程所使用的文件描述符對應的打開次數執(zhí)行減一操作,當某個文件打開次數為0時,也就是說所有的進程都沒有使用此文件時,也會觸發(fā)TCP的主動關閉操作。

TIME_WAIT狀態(tài)

在終止連接的過程中,主動關閉方套接字最終的狀態(tài)是TIME_WAIT,在經過2MSL(maximun segment lifetime,每個IP數據報都包含一個跳限的字段,表明數據報能經過的路由最大個數,因此默認每個數據報在因特網中有一個最大存活時間)時間后狀態(tài)才變?yōu)镃LOSED,為什么這樣設計呢?

這樣的設計出于兩個考慮:

可靠地實現TCP全雙工連接的終止。上圖的四次分節(jié)關閉連接是在正常流程,實際情況中,任何一次分節(jié)都可能出現發(fā)送失敗的情況。主動關閉方最后的一個ACK分節(jié)可能會因為路由問題發(fā)送失敗,為了保證可靠性,需要重新發(fā)送保證另一方正確關閉套接字,因此此時的狀態(tài)不能為CLOSED。

允許老的重復分界在網絡中消失。加入10.10.89.9的3400端口和206.168.12.12的80端口建立了一個TCP連接,此連接中斷后,之前發(fā)送的TCP分節(jié)可能因為路由循環(huán)的問題還在因特網中游蕩,而此時這兩個機器相同的端口再建立起新的連接后,原來在網絡中游蕩的分解會對新的連接造成干擾。為了避免這種情況,設置一個2MSL的超時時間,保證之前還在網絡中游蕩的數據包完全消失。

套接字編程

下圖是C語言的套接字函數,考慮Python的socket庫只是底層C庫的簡單封裝,接口參數大同小異,而且Python方便上手調試,語法上也更通俗易懂,所以本文使用Python的socket庫作為講解實例。

socket

socket是python套接字類,通過構造函數生成套接字對象,構造函數簽名如下

其中family參數指協議族;type參數指套接字類型;protocol值協議類型,或者設置為0,以選擇所給定family和type組合的系統默認值;fileno指文件描述符(我從來沒用過)。

family

說明

AF_INET

IPv4協議

AF_INET6

IPv6協議

AF_LOCAL

Unix域協議

AF_ROUTE

路由套接字

AF_KEY

密鑰套接字

type

說明

SOCK_STREAM

字節(jié)流套接字

SOCK_DGRAM

數據包套接字

SOCK_SEQPACKET

有序分組套接字

SOCK_RAW

原始套接字

protocol

說明

IPPROTO_TCP

TCP傳輸協議

IPPROTO_UDP

UDP傳輸協議

并非所有套接字family和type的組合都是有效的,下表給出了一些有效的組合和對應的協議,其中標是的項也是有效的,但是沒有找到便捷的縮略詞,而空白項是無效組合。

connect

connect用于客戶端和服務器建立連接,函數簽名如下:

客戶端在調用connect之前不必非得調用bind函數,內核會確定源IP地址,并選擇一個臨時端口作為源端口。如果使用TCP協議,connect將激發(fā)TCP的三路握手過程,TCP狀態(tài)由CLOSED變?yōu)镾YN_SENT,最終變?yōu)镋STABLISHED,在三路握手的過程中,可能會出現下面幾種情況導致connect報錯。connect失敗則套接字不可用,必須關閉,不能對這樣的套接字再次調connect函數。

TCP客戶端沒有是收到SYN分節(jié)響應,一般發(fā)生在服務端backlog隊列已滿的情況下,服務器會對收到的SYN分節(jié)不做任何處理。客戶端等待一段時間后會重新發(fā)送SYN分節(jié),直到等待時間超過上限,才會拋出ETIMEDOUT錯誤(對應的python異常是TimeoutError)。

對客戶端SYN的響應是RST,表明服務端在指定的端口上沒有進程在等待與之連接,客戶端馬上會拋出ECONNRFUSED錯誤。下圖是用python連接一個未使用的端口,拋出異常ConnectionRefusedError,該異常錯誤號碼111,errno中查找正是ECONNRFUSED對應的錯誤碼。

如果發(fā)出的SYN在中間的嗎某個路由器上引發(fā)了目的地不可達錯誤,客戶端會等待一段時間后重新發(fā)送,直到等待時間超過上限(和第一種情況類似),此時會拋出ENETUNREACH或者EHOSTUNREACH錯誤。下圖為關閉本機網絡后,用python調用connect,由于網絡不可達,異常的錯誤碼為101,errno中查找正是ENETUNREACH錯誤碼。

bind

bind方法把一個本地協議地址賦予給一個套接字,方法簽名如下:

在不調用bind的情況下,內核會確定IP地址,并分配臨時端口,這種情況很適合客戶端,因此客戶端在調用connect之前不調用bind方法。而服務端需要一個確定的ip和端口,因此需要調用bind指定地址和端口。一般情況下,服務器都有多個ip地址,除了環(huán)路地址127.0.0.1外,還有局域網和公網地址,如果bind綁定的是環(huán)路地址127.0.0.1,則只有本機通過環(huán)路地址才能訪問,如果需要通過任一ip地址都能訪問到,可以綁定通配地址0.0.0.0。當指定的端口為0時,內核會分配一個臨時端口。

如果端口已經在使用,會拋出EADDRINUSE(errno對應錯誤碼是98)異常,可以通過設置SO_REUSEADDR和SO_REUSEPORT這兩個套接字參數讓多個進程使用同一個TCP連接。

listen

當創(chuàng)建一個套接字時,默認為主動套接字,也就是說,是一個將調用connect發(fā)起連接的客戶套接字。listen方法把一個未連接的套接字轉換為一個被動套接字,指示內核應接受指向該套接字的狀態(tài)請求。根據TCP狀態(tài)轉換圖,調用listen導致套接字從CLOSED狀態(tài)轉換到LISTEN狀態(tài)。此方法參數規(guī)定了內核應該為相應套接字排隊的最大連接個數,在bind之后,并在accept之前調用。

為了理解backlog參數,我們必須認識到內核為其中任何一個給定的監(jiān)聽套接字維護兩個隊列:

未完成連接隊列,每個這樣的SYN分節(jié)對應其中一項:已由某個客戶發(fā)出并到達服務器,而服務器正在等待完成相應的TCP三路握手過程,這些套接字處于SYN_RCVD狀態(tài)。

已完成連接隊列,每個已完成TCP三路握手過程的客戶對應其中一項,這些套接字處于ESTABLISHED狀態(tài)。

RTT指的是未連接隊列中的任何一項在隊列中的存活時間。linux下的backlog指的是已完成連接隊列的容量,如果服務器長時間未調用accept從此隊列中取走數據,當新的客戶端通過三路握手重新建立連接時,服務器不會處理收到的SYN分節(jié),而客戶端會一直等待并不斷重試直到超時。在服務器負載很大的情況下,就會造成客戶端連接時間長,所以需要合理設置backlog大小。

accept

accept用于從已完成連接隊列頭返回下一個已完成連接,如果已完成連接隊列為空,那么進程會被投入睡眠(套接字為阻塞方式)。

accept會自動生成一個全新的文件描述符,代表與所返回客戶的TCP連接。需要注意的是,此處有兩個套接字對象,一個是監(jiān)聽套接字,一個返回的已連接套接字。區(qū)分這兩個套接字很重要,一個服務器通常僅僅創(chuàng)建一個監(jiān)聽套接字,它在該服務器的生命周期內一直存在,內核為每個由服務器進程接受的客戶連接創(chuàng)建一個已連接套接字(也就是說TCP三路握手已經完成),當服務器完成對某個給定客戶的服務時,相應的已連接套接字會被關閉。

close

close方法用來關閉套接字,方法簽名如下:

需要注意的是,close方法并不一定會觸發(fā)TCP的四分組連接終止序列,當一個已連接套接字被多個進程打開時,關閉套接字只會導致此進程相應描述符的計數值減1,只有所有進程都將該套接字關閉后,套接字的引用計數值小于1以后,系統內核才會開始終止連接操作,這一點在多進程開發(fā)過程中需要格外注意。如果確實想在某個TCP連接上發(fā)送FIN觸發(fā)主動關閉,可以調用shutdown方法。

send

send方法用于TCP發(fā)送數據,方法簽名如下:

每一個TCP套接字都有一個發(fā)送緩沖區(qū),默認大小通過socket.SO_SNDBUF查看,當某個進程調用send時,內核從該應用進程的緩沖區(qū)復制所有數據到所寫套接字的發(fā)送緩沖區(qū),如果該套接字的發(fā)送緩沖區(qū)容不下該應用進程的所有數據(或是應用進程的緩沖區(qū)大小大于套接字的發(fā)送緩沖區(qū),或是套接字的發(fā)送緩沖區(qū)已有其他數據),該應用進程將被投入睡眠(套接字阻塞的情況),內核將不從系統調用返回,直到應用進程緩沖區(qū)的所有數據都復制到套接字發(fā)送緩存區(qū)。當對端確認收到數據后,會發(fā)送ACK分節(jié),隨著對端ACK的不斷到達,本端TCP才能從套接字發(fā)送緩存區(qū)中丟棄已確認的數據。

在類似于HTTP的應用層協議中,客戶端在發(fā)送完請求數據之后,可以調用s.shutdown(socket.SHUT_WR)告訴服務端所有的數據已經發(fā)送完成,服務端通過recv會讀取到空字符串,之后就可以處理請求數據了。

recv

recv方法用于TCP接收數據,方法簽名如下:

每一個TCP套接字也都有一個接受緩存區(qū),默認大小通過socket.SO_RCVBUF查看。當某個進程調用recv而且緩存區(qū)沒有數據時,該進程會被投入睡眠(套接字阻塞的情況),內核將不從系統調用返回。

在《Unix網絡編程》中,所有C語言調用accept,read, write函數都會檢查errno是否等于EINTR,這是因為進程在執(zhí)行這些系統調用的時候可能會被信號打斷,導致系統調用返回。而我自己用python2.7嘗試的時候發(fā)現并沒有此問題,猜測是python針對系統調用被信號打斷的情況,自動重新執(zhí)行系統調用,stackoverflow上也證實了這一點: http://stackoverflow.com/questions/16094618/python-socket-recv-and-signals。

IO多路復用

在做服務器開發(fā)的時候,經常會碰到處理多個套接字的情形,此時可以通過多進程或這多線程的模型解決此問題。用一個主進程或者主線程負責監(jiān)聽套接字,其它每個進程或線程負責一個已連接套接字,這樣還可以利用操作系統的線程切換實現多并發(fā),提高機器利用率。但是機器資源有限,不可能無限制的生成新線程或進程,IO多路復用應運而生。當內核一旦發(fā)現進程指定的一個或者多個IO條件就緒,它就通知進程。

IO模型

Unix下有5中IO模型:

阻塞式IO

非阻塞式IO

IO復用

信號驅動IO

異步IO

已讀取數據為例,講解這物種IO模型的區(qū)別。每次讀取數據包括以下兩個階段,而這五種模型的不同之處也體現在這兩個階段不同的處理。

等待數據準備好

從內核想進程復制數據

阻塞式IO

socket套接字默認就是阻塞式IO。以recvfrom為例,用戶進程通過系統調用獲取TCP數據,如果套接字緩存區(qū)沒有數據,系統調用不會返回,造成用戶進程一直阻塞。直到緩存區(qū)有可用數據,內核將緩存區(qū)數據拷貝至用戶進程空間,系統調用才會返回。

非阻塞式IO

python可以通過調用s.setblocking(False)或者s.settimeout(0.0)將一個套接字設置為非阻塞式IO。以recvfrom為例,當沒有可用的數據時,用戶進程不會阻塞,而是馬上拋出EWOULDBLOCK錯誤(或者EAGAIN,對應的errno錯誤碼都是11),只有當數據復制到內核空間后,才會正確返回數據。

IO多路復用

在有多個IO操作時,先阻塞于select調用,等待數據報套接字變?yōu)榭勺x,然后再通過recvfrom把緩存區(qū)數據復制到用戶進程空間。和阻塞是IO相比,當處理的套接字個數較少的時候,多路復其實沒有性能上的優(yōu)勢,它的優(yōu)勢在于可以方便操作很多套接字。

信號驅動式IO

通過信號處理的方式讀取數據。

異步IO

當數據包被復制到用戶進程后,用戶通過callback的方式獲取數據。

模型對比

可以發(fā)現,前四種IO模型——阻塞式IO、非阻塞式IO、IO復用、信號驅動IO都是同步IO模型,因為真正的IO操作(recvfrom)將阻塞進程,只有異步IO模型才不會導致用戶進程阻塞。

python使用

較早的時候使用的多路復用是select函數,但是由于時間復雜度較高,很快就被其他的函數替代:linux下的epoll,unix下的kqueue,windows下的iocp。為了屏蔽不同系統下的不同實現,跨平臺的第三方庫出現:libuv、libev、libevent等,這些庫根據平臺的不同,調用不同的底層代碼。

如果想直接使用底層的epoll或者select,它們封裝在python的select庫中;libuv、libev都有相應的python封裝,庫名叫做pyuv、pyev,通過pip安裝后即可使用。

python示例

一般情況下,為了提升服務的承載量,都會采用進程+IO多路復用或者線程+IO多路復用的開發(fā)模式。IO多路復用是為了一個并發(fā)單位管理多個套接字,而多進程或者多線程是為了充分利用多核。由于GIL的存在,python多線程模型并不能充分多核,因此我們常見的wsgi server,例如:gunicorn、uwsgi、tornado等都是使用的多進程+IO多路服用開發(fā)模式。

tornado使用epoll管理多個套接字,gunicorn和uwsgi都可以使用gevent,gevent是一個python網絡庫,用greenlet做協程切換,每個協程管理一個套接字,主協程通過libevent輪詢查找可用的套接字。因為gevent可以通過monkey patch將socket設置為非阻塞模式,因此當服務器有數據庫、緩存或者其他網絡請求的時候,相比tornado,uwsgi和gunicorn可以充分利用這部分的阻塞時間。和gunicorn相比,uwsgi是c語言實現,直觀感覺這三個server的性能應該是:uwsgi > gunicorn > tornado,和網上的benchmark大致匹配。

django的作者在github上實現了一個wsgi server,項目地址: https://github.com/jonashaag/bjoern,使用C語言實現,代碼量很少,性能據說比uwsgi還好,十分適合網絡開發(fā)進階學習。參考這份代碼,我用python實現了一個thrift server,項目地址:https://github.com/LiuRoy/dracula,和thriftpy的TThreadedServer做了一個簡單的性能對比。

50

100

150

200

250

300

350

400

450

libev

92

181

269.9

355.2

362.6

367.1

373.8

378.5

315(3%)

thread

88.9

180.5

266.1

354.8

428.9

460.2

486.5(2%)

477.9(7%)

486.5(22%)

橫坐標是連接個數,縱坐標是qps,括號內的數字表示錯誤率。在連接數較少的情況下,使用libev管理socket和多線程性能相差不大,在連接數超過200后,libev模型的請求耗時會增加,導致qps增加的并不多,但是線程模型在連接數很多的情況下,會導致部分請求一直得不到處理,在連接個數350的時候就會出現部分請求超時,而libev模型在450的時候才會出現。

總結

以上是生活随笔為你收集整理的python网络编程项目_python网络编程详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。