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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > python >内容正文

python

Python 全栈开发十 socket网络编程

發(fā)布時(shí)間:2023/12/10 python 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Python 全栈开发十 socket网络编程 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、客戶端(client)服務(wù)端(sever)架構(gòu)

  在計(jì)算機(jī)中有很多常見的C/S架構(gòu),例如我們的瀏覽器是客戶端、而百度網(wǎng)站和其他的網(wǎng)站就是服務(wù)端;視頻軟件是客戶端,提供視頻的騰訊、優(yōu)酷、愛奇藝就是服務(wù)端。

C/S與socket的關(guān)系:

  學(xué)習(xí)socket就是為了開發(fā)C/S架構(gòu)。

二、OSI七層

  C/S架構(gòu)的軟件(軟件屬于應(yīng)用層)是基于網(wǎng)絡(luò)進(jìn)行通信的,網(wǎng)絡(luò)的核心即一堆協(xié)議,協(xié)議即標(biāo)準(zhǔn),你想開發(fā)一款基于網(wǎng)絡(luò)通信的軟件,就必須遵循這些標(biāo)準(zhǔn)。所以在學(xué)習(xí)socket之前,先了解一下OSI七層了解基本的網(wǎng)絡(luò)協(xié)議,方便學(xué)習(xí)socket。

在上面的OSI中好像并沒有與socket有關(guān)的信息,那么請(qǐng)看下面這圖:

三、socket是什么

  從上面這個(gè)圖中可以看出,socket就是網(wǎng)絡(luò)層和運(yùn)輸層的抽象結(jié)合。Socket是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層,它是一組接口。在設(shè)計(jì)模式中,Socket其實(shí)就是一個(gè)門面模式,它把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面,對(duì)用戶來說,一組簡(jiǎn)單的接口就是全部,讓Socket去組織數(shù)據(jù),以符合指定的協(xié)議。所以,我們無需深入理解tcp/udp協(xié)議,socket已經(jīng)為我們封裝好了,我們只需要遵循socket的規(guī)定去編程,寫出的程序自然就是遵循tcp/udp標(biāo)準(zhǔn)的。

  當(dāng)然還有另一種解釋:網(wǎng)絡(luò)上的兩個(gè)程序通過一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)連接的一端稱為一個(gè)socket。建立網(wǎng)絡(luò)通信連接至少要一對(duì)端口號(hào)(socket)。socket本質(zhì)是編程接口(API),對(duì)TCP/IP的封裝,TCP/IP也要提供可供程序員做網(wǎng)絡(luò)開發(fā)所用的接口,這就是Socket編程接口;HTTP是轎車,提供了封裝或者顯示數(shù)據(jù)的具體形式;Socket是發(fā)動(dòng)機(jī),提供了網(wǎng)絡(luò)通信的能力。Socket的英文原義是"孔"或"插座"。

  作為BSD UNIX的進(jìn)場(chǎng)通信機(jī)制,取后一種意思。通常也稱作"套接字",用于描述IP地址和端口,是一個(gè)通信鏈的句柄,可以用來實(shí)現(xiàn)不同虛擬機(jī)或不同計(jì)算機(jī)之間的通信。在Internet上的主機(jī)一般運(yùn)行了多個(gè)服務(wù)軟件,同時(shí)提供幾種服務(wù)。每種服務(wù)都打開一個(gè)Socket,并綁定到一個(gè)端口上,不同的端口對(duì)應(yīng)于不同的服務(wù)。Socket正如其英文原意那樣,像一個(gè)多孔插座。一臺(tái)主機(jī)猶如布滿各種插座的房間,每個(gè)插座有一個(gè)編號(hào),有的插座提供220伏交流電, 有的提供110伏交流電,有的則提供有線電視節(jié)目。 客戶軟件將插頭插到不同編號(hào)的插座,就可以得到不同的服務(wù)。

四 套接字發(fā)展史及分類

套接字起源于 20 世紀(jì) 70 年代加利福尼亞大學(xué)伯克利分校版本的 Unix,即人們所說的 BSD Unix。 因此,有時(shí)人們也把套接字稱為“伯克利套接字”或“BSD 套接字”。一開始,套接字被設(shè)計(jì)用在同 一臺(tái)主機(jī)上多個(gè)應(yīng)用程序之間的通訊。這也被稱進(jìn)程間通訊,或 IPC。套接字有兩種(或者稱為有兩個(gè)種族),分別是基于文件型的和基于網(wǎng)絡(luò)型的。?

基于文件類型的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆文件,基于文件的套接字調(diào)用的就是底層的文件系統(tǒng)來取數(shù)據(jù),兩個(gè)套接字進(jìn)程運(yùn)行在同一機(jī)器,可以通過訪問同一個(gè)文件系統(tǒng)間接完成通信

基于網(wǎng)絡(luò)類型的套接字家族

套接字家族的名字:AF_INET

(還有AF_INET6被用于ipv6,還有一些其他的地址家族,不過,他們要么是只用于某個(gè)平臺(tái),要么就是已經(jīng)被廢棄,或者是很少被使用,或者是根本沒有實(shí)現(xiàn),所有地址家族中,AF_INET是使用最廣泛的一個(gè),python支持很多種地址家族,但是由于我們只關(guān)心網(wǎng)絡(luò)編程,所以大部分時(shí)候我么只使用AF_INET)

五、套接字工作流程

根據(jù)連接啟動(dòng)的方式以及本地套接字要連接的目標(biāo),套接字之間的連接過程可以分為三個(gè)步驟:服務(wù)器監(jiān)聽,客戶端請(qǐng)求,連接確認(rèn)。

(1)服務(wù)器監(jiān)聽:是服務(wù)器端套接字并不定位具體的客戶端套接字,而是處于等待連接的狀態(tài),實(shí)時(shí)監(jiān)控網(wǎng)絡(luò)狀態(tài)。

(2)客戶端請(qǐng)求:是指由客戶端的套接字提出連接請(qǐng)求,要連接的目標(biāo)是服務(wù)器端的套接字。為此,客戶端的套接字必須首先描述它要連接的服務(wù)器的套接字,指出服務(wù)器端套接字的地址和端口號(hào),然后就向服務(wù)器端套接字提出連接請(qǐng)求。

(3)連接確認(rèn):是指當(dāng)服務(wù)器端套接字監(jiān)聽到或者說接收到客戶端套接字的連接請(qǐng)求,它就響應(yīng)客戶端套接字的請(qǐng)求,建立一個(gè)新的線程,把服務(wù)端套接字的描述發(fā)給客戶端,一旦客戶端確認(rèn)了此描述,連接就建立好了。而服務(wù)器端套接字繼續(xù)處于監(jiān)聽狀態(tài),繼續(xù)接收其他客戶端套接字的連接請(qǐng)求。

生活中的打電話就是一個(gè)簡(jiǎn)單的套接字工作流程:

五、常見的套接字函數(shù):

服務(wù)端套接字函數(shù)
s.bind() 綁定(主機(jī),端口號(hào))到套接字
s.listen() 開始TCP監(jiān)聽
s.accept() 被動(dòng)接受TCP客戶的連接,(阻塞式)等待連接的到來

客戶端套接字函數(shù)
s.connect() 主動(dòng)初始化TCP服務(wù)器連接
s.connect_ex() connect()函數(shù)的擴(kuò)展版本,出錯(cuò)時(shí)返回出錯(cuò)碼,而不是拋出異常

公共用途的套接字函數(shù)
s.recv() 接收TCP數(shù)據(jù)
s.send() 發(fā)送TCP數(shù)據(jù)(send在待發(fā)送數(shù)據(jù)量大于己端緩存區(qū)剩余空間時(shí),數(shù)據(jù)丟失,不會(huì)發(fā)完)
s.sendall() 發(fā)送完整的TCP數(shù)據(jù)(本質(zhì)就是循環(huán)調(diào)用send,sendall在待發(fā)送數(shù)據(jù)量大于己端緩存區(qū)剩余空間時(shí),數(shù)據(jù)不丟失,循環(huán)調(diào)用send直到發(fā)完)
s.recvfrom() 接收UDP數(shù)據(jù)
s.sendto() 發(fā)送UDP數(shù)據(jù)
s.getpeername() 連接到當(dāng)前套接字的遠(yuǎn)端的地址
s.getsockname() 當(dāng)前套接字的地址
s.getsockopt() 返回指定套接字的參數(shù)
s.setsockopt() 設(shè)置指定套接字的參數(shù)
s.close() 關(guān)閉套接字

面向鎖的套接字方法
s.setblocking() 設(shè)置套接字的阻塞與非阻塞模式
s.settimeout() 設(shè)置阻塞套接字操作的超時(shí)時(shí)間
s.gettimeout() 得到阻塞套接字操作的超時(shí)時(shí)間

面向文件的套接字的函數(shù)
s.fileno() 套接字的文件描述符
s.makefile() 創(chuàng)建一個(gè)與該套接字相關(guān)的文件

六、基于TCP的套接字

  在實(shí)現(xiàn)TCP的套接字之前,小編帶大家了解一下基于TCP的三次握手,四次揮手。

TCP是面向連接的,無論哪一方向另一方發(fā)送數(shù)據(jù)之前,都必須先在雙方之間建立一條連接。在TCP/IP協(xié)議中,TCP 協(xié)議提供可靠的連接服務(wù),連接是通過三次握手進(jìn)行初始化的。三次握手的目的是同步連接雙方的序列號(hào)和確認(rèn)號(hào) 并交換 TCP窗口大小信息。

1.第一次握手:建立連接。

客戶端發(fā)送連接請(qǐng)求報(bào)文段,將SYN位置為1,Sequence Number為x;然后,客戶端進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器的確認(rèn);

2.第二次握手:服務(wù)器收到SYN報(bào)文段。

服務(wù)器收到客戶端的SYN報(bào)文段,需要對(duì)這個(gè)SYN報(bào)文段進(jìn)行確認(rèn),設(shè)置Acknowledgment Number為x+1(Sequence Number+1);同時(shí),自己自己還要發(fā)送SYN請(qǐng)求信息,將SYN位置為1,Sequence Number為y;服務(wù)器端將上述所有信息放到一個(gè)報(bào)文段(即SYN+ACK報(bào)文段)中,一并發(fā)送給客戶端,此時(shí)服務(wù)器進(jìn)入SYN_RECV狀態(tài);

3.第三次握手:客戶端收到服務(wù)器的SYN+ACK報(bào)文段。

然后將Acknowledgment Number設(shè)置為y+1,向服務(wù)器發(fā)送ACK報(bào)文段,這個(gè)報(bào)文段發(fā)送完畢以后,客戶端和服務(wù)器端都進(jìn)入ESTABLISHED狀態(tài),完成TCP三次握手。

完成了三次握手,客戶端和服務(wù)器端就可以開始傳送數(shù)據(jù)。以上就是TCP三次握手的總體介紹。

那四次揮手呢?

當(dāng)客戶端和服務(wù)器通過三次握手建立了TCP連接以后,當(dāng)數(shù)據(jù)傳送完畢,肯定是要斷開TCP連接的啊。那對(duì)于TCP的斷開連接,這里就有了神秘的“四次揮手”。

1.第一次揮手:主機(jī)1(可以使客戶端,也可以是服務(wù)器端),設(shè)置Sequence Number和Acknowledgment Number,向主機(jī)2發(fā)送一個(gè)FIN報(bào)文段;此時(shí),主機(jī)1進(jìn)入FIN_WAIT_1狀態(tài);這表示主機(jī)1沒有數(shù)據(jù)要發(fā)送給主機(jī)2了;

2.第二次揮手:主機(jī)2收到了主機(jī)1發(fā)送的FIN報(bào)文段,向主機(jī)1回一個(gè)ACK報(bào)文段,Acknowledgment Number為Sequence Number加1;主機(jī)1進(jìn)入FIN_WAIT_2狀態(tài);主機(jī)2告訴主機(jī)1,我也沒有數(shù)據(jù)要發(fā)送了,可以進(jìn)行關(guān)閉連接了;

3.第三次揮手:主機(jī)2向主機(jī)1發(fā)送FIN報(bào)文段,請(qǐng)求關(guān)閉連接,同時(shí)主機(jī)2進(jìn)入CLOSE_WAIT狀態(tài);

4.第四次揮手:主機(jī)1收到主機(jī)2發(fā)送的FIN報(bào)文段,向主機(jī)2發(fā)送ACK報(bào)文段,然后主機(jī)1進(jìn)入TIME_WAIT狀態(tài);主機(jī)2收到主機(jī)1的ACK報(bào)文段以后,就關(guān)閉連接;此時(shí),主機(jī)1等待2MSL后依然沒有收到回復(fù),則證明Server端已正常關(guān)閉,那好,主機(jī)1也可以關(guān)閉連接了。

至此,TCP的四次揮手就這么愉快的完成了。當(dāng)你看到這里,你的腦子里會(huì)有很多的疑問,很多的不懂,感覺很凌亂;沒事,我們繼續(xù)總結(jié)。

?

為什么要三次握手?

既然總結(jié)了TCP的三次握手,那為什么非要三次呢?怎么覺得兩次就可以完成了。那TCP為什么非要進(jìn)行三次連接呢?在謝希仁的《計(jì)算機(jī)網(wǎng)絡(luò)》中是這樣說的:

為了防止已失效的連接請(qǐng)求報(bào)文段突然又傳送到了服務(wù)端,因而產(chǎn)生錯(cuò)誤。

在書中同時(shí)舉了一個(gè)例子,如下:

"已失效的連接請(qǐng)求報(bào)文段”的產(chǎn)生在這樣一種情況下:client發(fā)出的第一個(gè)連接請(qǐng)求報(bào)文段并沒有丟失,

而是在某個(gè)網(wǎng)絡(luò)結(jié)點(diǎn)長(zhǎng)時(shí)間的滯留了,以致延誤到連接釋放以后的某個(gè)時(shí)間才到達(dá)server。本來這是一

個(gè)早已失效的報(bào)文段。但server收到此失效的連接請(qǐng)求報(bào)文段后,就誤認(rèn)為是client再次發(fā)出的一個(gè)新

的連接請(qǐng)求。于是就向client發(fā)出確認(rèn)報(bào)文段,同意建立連接。假設(shè)不采用“三次握手”,那么只要server

發(fā)出確認(rèn),新的連接就建立了。由于現(xiàn)在client并沒有發(fā)出建立連接的請(qǐng)求,因此不會(huì)理睬server的確認(rèn),

也不會(huì)向server發(fā)送數(shù)據(jù)。但server卻以為新的運(yùn)輸連接已經(jīng)建立,并一直等待client發(fā)來數(shù)據(jù)。這樣,

server的很多資源就白白浪費(fèi)掉了。采用“三次握手”的辦法可以防止上述現(xiàn)象發(fā)生。例如剛才那種情況,

client不會(huì)向server的確認(rèn)發(fā)出確認(rèn)。server由于收不到確認(rèn),就知道client并沒有要求建立連接。"

這就很明白了,防止了服務(wù)器端的一直等待而浪費(fèi)資源。

為什么要四次揮手?

那四次揮手又是為何呢?TCP協(xié)議是一種面向連接的、可靠的、基于字節(jié)流的運(yùn)輸層通信協(xié)議。TCP是全雙工 模式,這就意味著,當(dāng)主機(jī)1發(fā)出FIN報(bào)文段時(shí),只是表示主機(jī)1已經(jīng)沒有數(shù)據(jù)要發(fā)送了,主機(jī)1告訴主機(jī)2, 它的數(shù)據(jù)已經(jīng)全部發(fā)送完畢了;但是,這個(gè)時(shí)候主機(jī)1還是可以接受來自主機(jī)2的數(shù)據(jù);當(dāng)主機(jī)2返回ACK報(bào)文 段時(shí),表示它已經(jīng)知道主機(jī)1沒有數(shù)據(jù)發(fā)送了,但是主機(jī)2還是可以發(fā)送數(shù)據(jù)到主機(jī)1的;當(dāng)主機(jī)2也發(fā)送了FIN 報(bào)文段時(shí),這個(gè)時(shí)候就表示主機(jī)2也沒有數(shù)據(jù)要發(fā)送了,就會(huì)告訴主機(jī)1,我也沒有數(shù)據(jù)要發(fā)送了,之后彼此 就會(huì)愉快的中斷這次TCP連接。如果要正確的理解四次揮手的原理,就需要了解四次揮手過程中的狀態(tài)變化。

FIN_WAIT_1: 這個(gè)狀態(tài)要好好解釋一下,其實(shí)FIN_WAIT_1和FIN_WAIT_2狀態(tài)的真正含義都是表示等 待對(duì)方的FIN報(bào)文。而這兩種狀態(tài)的區(qū)別是:FIN_WAIT_1狀態(tài)實(shí)際上是當(dāng)SOCKET在ESTABLISHED狀態(tài)時(shí), 它想主動(dòng)關(guān)閉連接,向?qū)Ψ桨l(fā)送了FIN報(bào)文,此時(shí)該SOCKET即進(jìn)入到FIN_WAIT_1狀態(tài)。而當(dāng)對(duì)方回應(yīng)ACK報(bào) 文后,則進(jìn)入到FIN_WAIT_2狀態(tài),當(dāng)然在實(shí)際的正常情況下,無論對(duì)方何種情況下,都應(yīng)該馬上回應(yīng)ACK 報(bào)文,所以FIN_WAIT_1狀態(tài)一般是比較難見到的,而FIN_WAIT_2狀態(tài)還有時(shí)常常可以用netstat看到。 (主動(dòng)方)

FIN_WAIT_2:上面已經(jīng)詳細(xì)解釋了這種狀態(tài),實(shí)際上FIN_WAIT_2狀態(tài)下的SOCKET,表示半連接,也即 有一方要求close連接,但另外還告訴對(duì)方,我暫時(shí)還有點(diǎn)數(shù)據(jù)需要傳送給你(ACK信息),稍后再關(guān)閉連接。 (主動(dòng)方)

CLOSE_WAIT:這種狀態(tài)的含義其實(shí)是表示在等待關(guān)閉。怎么理解呢?當(dāng)對(duì)方close一個(gè)SOCKET后發(fā)送FIN 報(bào)文給自己,你系統(tǒng)毫無疑問地會(huì)回應(yīng)一個(gè)ACK報(bào)文給對(duì)方,此時(shí)則進(jìn)入到CLOSE_WAIT狀態(tài)。接下來呢,實(shí) 際上你真正需要考慮的事情是察看你是否還有數(shù)據(jù)發(fā)送給對(duì)方,如果沒有的話,那么你也就可以 close這個(gè) SOCKET,發(fā)送FIN報(bào)文給對(duì)方,也即關(guān)閉連接。所以你在CLOSE_WAIT狀態(tài)下,需要完成的事情是等待你去關(guān) 閉連接。(被動(dòng)方)

LAST_ACK: 這個(gè)狀態(tài)還是比較容易好理解的,它是被動(dòng)關(guān)閉一方在發(fā)送FIN報(bào)文后,最后等待對(duì)方的ACK報(bào) 文。當(dāng)收到ACK報(bào)文后,也即可以進(jìn)入到CLOSED可用狀態(tài)了。(被動(dòng)方)

TIME_WAIT: 表示收到了對(duì)方的FIN報(bào)文,并發(fā)送出了ACK報(bào)文,就等2MSL后即可回到CLOSED可用狀態(tài)了。 如果FINWAIT1狀態(tài)下,收到了對(duì)方同時(shí)帶FIN標(biāo)志和ACK標(biāo)志的報(bào)文時(shí),可以直接進(jìn)入到TIME_WAIT狀態(tài),而無 須經(jīng)過FIN_WAIT_2狀態(tài)。(主動(dòng)方)

CLOSED: 表示連接中斷。

七、TCP套接字實(shí)踐

服務(wù)端:

1 from socket import * 2 3 #對(duì)數(shù)據(jù)進(jìn)行獨(dú)立化方便更改 4 IP_PORT = ("127.0.0.1",8080) 5 BACK_LOG = 5 6 BUFFER_SIZE = 1024 7 8 #建立TCP連接 9 sever = socket(AF_INET,SOCK_STREAM) #生成套接字對(duì)象 10 sever.bind(IP_PORT) #綁定端口 11 sever.listen(BACK_LOG) #開始監(jiān)聽(BACK_LOG:代表的是允許監(jiān)聽的數(shù)量) 12 conn,addr = sever.accept() 13 14 #連接成功后的數(shù)據(jù)傳輸 15 data = conn.recv(BUFFER_SIZE) 16 de_data = data.decode("utf-8") 17 conn.send(de_data.upper().encode("utf-8")) 18 19 #測(cè)試數(shù)據(jù)發(fā)送成功 20 print("send success") 21 22 #關(guān)閉連接 23 conn.close() 24 sever.close()

客戶端:

1 from socket import * 2 3 IP_PORT = ("127.0.0.1",8080) 4 BUFFER_SIZE = 1024 5 6 #建立連接 7 client = socket(AF_INET,SOCK_STREAM) #生成套接字對(duì)象 8 client.connect(IP_PORT) #連接到對(duì)應(yīng)的端口 9 10 #數(shù)據(jù)傳輸 11 data = input(">>:") 12 client.send(data.encode("utf-8")) 13 sever_data= client.recv(BUFFER_SIZE) 14 print("success recv:" ,sever_data.decode("utf-8"))

上面的代碼就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的基于TCP的套接字服務(wù),但是這僅僅進(jìn)行了一次簡(jiǎn)單的通信,這與電話通信的你一句我一句有較大的差異。接下來是socket的進(jìn)階版:

socket_sever:

1 from socket import * 2 3 #對(duì)數(shù)據(jù)進(jìn)行獨(dú)立化方便更改 4 IP_PORT = ("127.0.0.1",8080) 5 BACK_LOG = 5 6 BUFFER_SIZE = 1024 7 8 sever = socket(AF_INET,SOCK_STREAM) 9 sever.bind(IP_PORT) 10 sever.listen(BACK_LOG) 11 conn,addr = sever.accept() 12 13 try: 14 while True: 15 data = conn.recv(BUFFER_SIZE) 16 print("收到數(shù)據(jù):",data.decode("utf-8")) 17 deal_data = data.decode("utf-8").upper() 18 conn.send(deal_data.encode("utf-8")) 19 print("發(fā)送數(shù)據(jù):",deal_data) 20 except ConnectionResetError as e: 21 print(e) 22 23 sever.close() 24

socket_client:

1 from socket import * 2 3 #對(duì)數(shù)據(jù)進(jìn)行獨(dú)立化方便更改 4 IP_PORT = ("127.0.0.1",8080) 5 BUFFER_SIZE = 1024 6 7 client = socket(AF_INET,SOCK_STREAM) 8 client.connect(IP_PORT) 9 10 while True: 11 data = input(">>:") 12 if not data:continue 13 client.send(data.encode("utf-8")) 14 rv_data = client.recv(BUFFER_SIZE) 15 print(rv_data.decode("utf-8")) 16 17 client.close()

上述的C/S實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的可以無限收發(fā)的簡(jiǎn)單功能,但是此時(shí)的socket_sever僅實(shí)現(xiàn)了一對(duì)一的連接,我們打點(diǎn)電話的時(shí)候還可能有更多的連接進(jìn)來,而上述的功能在第一 連接中斷的時(shí)候,第二個(gè)服務(wù)端也會(huì)被中斷,所以我們對(duì)上述功能再次進(jìn)行了加工:

tcp_socket_sever:

1 from socket import * 2 3 #對(duì)數(shù)據(jù)進(jìn)行獨(dú)立化方便更改 4 IP_PORT = ("127.0.0.1",8080) 5 BACK_LOG = 5 6 BUFFER_SIZE = 1024 7 8 sever = socket(AF_INET,SOCK_STREAM) 9 sever.bind(IP_PORT) 10 sever.listen(BACK_LOG) 11 12 while True: #這個(gè)循環(huán)是為了可以接收多個(gè)循環(huán),但是每次只能有一個(gè)循環(huán)進(jìn)入連接 13 conn, addr = sever.accept() 14 while True: #這個(gè)循環(huán)是為了可以使客戶端和服務(wù)端循環(huán)無限次 15 try: #這是為了解決一個(gè)客戶端不正常斷開的時(shí)候而拋出的異常 16 data = conn.recv(BUFFER_SIZE) 17 print("收到數(shù)據(jù):",data.decode("utf-8")) 18 deal_data = data.decode("utf-8").upper() 19 conn.send(deal_data.encode("utf-8")) 20 print("已發(fā)送數(shù)據(jù):",deal_data) 21 except ConnectionResetError as e: 22 print(e) 23 break

tcp_socket_client:

1 from socket import * 2 3 IP_PORT = ("127.0.0.1",8080) 4 BUFFER_SIZE = 1024 5 6 client = socket(AF_INET,SOCK_STREAM) 7 client.connect(IP_PORT) 8 9 while True: 10 data = input(">>:") 11 if not data:continue 12 client.send(data.encode("utf-8")) 13 print("已發(fā)送:",data) 14 re_data = client.recv(BUFFER_SIZE) 15 print("已接收:",re_data.decode("utf-8"))

接下來運(yùn)用socket寫一個(gè)簡(jiǎn)單的C/S,作用是可以遠(yuǎn)程的允許DOS命令,并且返回一些信息,但是無法做更改刪除等,僅僅查詢功能:

1 from socket import * 2 import subprocess 3 import struct 4 IP_PORT = ("localhost",8080) 5 BACK_LOG = 5 6 BUFFER_SIZE = 1024 7 8 #建立連接 9 sever = socket(AF_INET,SOCK_STREAM) 10 sever.bind(IP_PORT) 11 sever.listen(BACK_LOG) 12 13 while True: 14 conn,addr = sever.accept() 15 print("我準(zhǔn)備接收鏈接了:") 16 while True: 17 try: 18 cmd = conn.recv(BUFFER_SIZE) 19 print("接收到指令:",cmd.decode("utf-8")) 20 21 #對(duì)接收到的數(shù)據(jù)進(jìn)行處理 22 if not cmd:break #這里的判斷解決客戶端正常斷開時(shí)退出連接 23 res = subprocess.Popen( 24 cmd.decode("utf-8"),shell=True, 25 stderr=subprocess.PIPE, 26 stdin=subprocess.PIPE, 27 stdout=subprocess.PIPE 28 ) 29 ''' 30 subprocess模塊:shell=True指的是允許將輸出在shell的內(nèi)容輸入到管道 31 stderr,stdin,stdout:這些都是將輸入流,輸出流接入管道 32 ''' 33 err = res.stderr.read() 34 if err: 35 cmd_res = err 36 else: 37 cmd_res = res.stdout.read() 38 39 #發(fā) 40 if not cmd_res: 41 cmd_res = "執(zhí)行成功".encode("gbk") 42 43 conn.send(cmd_res) 44 except Exception as e: 45 print(e) 46 break cmd_socket_sever 1 from socket import * 2 3 IP_PORT = ("localhost",8080) 4 BUFFER_SIZE = 1024 5 6 client = socket(AF_INET,SOCK_STREAM) 7 client.connect(IP_PORT) 8 9 while True: 10 cmd = input(">>:") 11 print("") 12 if not cmd:continue 13 if cmd == "quit":break 14 client.send(cmd.encode("utf-8")) 15 print("已經(jīng)發(fā)送指令:",cmd) 16 data = client.recv(BUFFER_SIZE) 17 print(data.decode("gbk")) 18 19 client.close() cmd_socket_client

上述的簡(jiǎn)易的C/S服務(wù)還有一個(gè)重要的問題沒有解決,那就是粘包。

八、粘包問題

在前面的C/S服務(wù)我們輸入ipconfig發(fā)送到服務(wù)端,接收返回到的消息時(shí),會(huì)發(fā)現(xiàn)接收到的消息不全,再次輸入其他命令,接收到的消息還是原來ipconfig輸出的信息,這就的典型的一種粘包現(xiàn)象。

?  

?

這是為什么呢,為什么我一個(gè)收,一個(gè)發(fā)為什么會(huì)出現(xiàn)粘包現(xiàn)象,這就要從底層說起。

?

?根據(jù)上圖我們模擬一下簡(jiǎn)單的TCP套接字工作原理:

第一:啟動(dòng)服務(wù)端和客戶端

第二:客戶端的將指令和請(qǐng)求等數(shù)據(jù)信息讀取到用戶態(tài)內(nèi)存,然后內(nèi)核態(tài)將用戶態(tài)的數(shù)據(jù)拷貝

第三:然后通過網(wǎng)卡等硬件層發(fā)送到服務(wù)端的內(nèi)核態(tài)內(nèi)存

第四:服務(wù)端的用戶態(tài)再從內(nèi)核態(tài)拷貝然后對(duì)拷貝過來的數(shù)據(jù)進(jìn)行處理

第五:再按照剛才來的路線進(jìn)行返回。

從上面的流程結(jié)合TCP套接字的實(shí)例可以得出以下結(jié)論:

  • 基于TCP的C/S服務(wù)并不是由用戶到用戶這種直接發(fā)送的模式,而是中間隔著一個(gè)內(nèi)核態(tài)。
  • 客戶端不管發(fā)多少次,都要通過客戶端的內(nèi)核態(tài),這就使得每個(gè)客戶端的send服務(wù)端無需與之對(duì)應(yīng)一個(gè)recv,因?yàn)槭莾?nèi)核態(tài)之間的交互,完全可以一個(gè)recv對(duì)應(yīng)多個(gè)send
  • 由于一個(gè)recv可以對(duì)應(yīng)多個(gè)send這就會(huì)引起第一種粘包狀態(tài)天,例如像服務(wù)端發(fā)送兩次數(shù)據(jù)時(shí),兩個(gè)數(shù)據(jù)黏在一起。
  • 用戶態(tài)和內(nèi)核態(tài)的內(nèi)存都有一定的大小,所以當(dāng)我們一次性傳輸過大的數(shù)據(jù)時(shí),內(nèi)核態(tài)的容量不夠,無法一次性傳輸完畢,只能進(jìn)行多次傳輸。這就產(chǎn)生了粘包的第二種狀態(tài)。

粘包解決方案一:

1 from socket import * 2 import subprocess 3 import struct 4 IP_PORT = ("localhost",8080) 5 BACK_LOG = 5 6 BUFFER_SIZE = 1024 7 8 sever = socket(AF_INET,SOCK_STREAM) 9 sever.bind(IP_PORT) 10 sever.listen(BACK_LOG) 11 12 while True: 13 conn, addr = sever.accept() 14 print("接收到連接:", addr) 15 while True: 16 try: 17 print("開始接收") 18 cmd = conn.recv(BUFFER_SIZE) 19 print("接收到指令:",cmd.decode("utf-8")) 20 if not cmd:break 21 res = subprocess.Popen( 22 cmd.decode("utf-8"),shell=True, 23 stdout=subprocess.PIPE, 24 stdin=subprocess.PIPE, 25 stderr=subprocess.PIPE 26 ) 27 28 res_err = res.stderr.read() 29 if res_err: 30 res_cmd=res_err 31 else: 32 res_cmd = res.stdout.read() 33 34 data_len = len(res_cmd) 35 print(data_len) 36 conn.send(str(data_len).encode("gbk")) #發(fā)送長(zhǎng)度 37 data2 = conn.recv(BUFFER_SIZE).decode("gbk") #為了避免長(zhǎng)度與數(shù)據(jù)的粘包,先接收一個(gè)確認(rèn)數(shù)據(jù) 38 #開始發(fā)送數(shù)據(jù) 39 if data2 == "recv_ready": 40 conn.sendall(res_cmd) #這段代碼會(huì)循環(huán)發(fā)送,直到數(shù)據(jù)被發(fā)送完畢 41 print("發(fā)送完畢") 42 except Exception as e: 43 print(e) 44 break socket_sever_粘包 1 from socket import * 2 3 IP_PORT = ("localhost",8080) 4 BUFFER_SIZE = 1024 5 6 client = socket(AF_INET,SOCK_STREAM) 7 client.connect(IP_PORT) 8 9 ''' 10 解決粘包的方法一就是服務(wù)端在發(fā)送數(shù)據(jù)的時(shí)候,先發(fā)一個(gè)數(shù)據(jù)長(zhǎng)度,然后讓客戶端循環(huán)收,直到收完為止 11 ''' 12 13 while True: 14 cmd = input(">>:") 15 if not cmd:continue 16 if cmd == "quit":break 17 18 client.send(cmd.encode("utf-8")) #發(fā)送命令 19 cmd_data_lenth = int(client.recv(BUFFER_SIZE).decode("gbk")) #接收長(zhǎng)度 20 print(cmd_data_lenth) 21 client.send("recv_ready".encode("utf-8")) 22 cmd_data = "" 23 data_lenth= 0 24 while data_lenth < cmd_data_lenth: 25 cmd_data += client.recv(BUFFER_SIZE).decode("gbk") 26 print(cmd_data) 27 data_lenth += len(cmd_data) 28 print("數(shù)據(jù)接收完畢",cmd_data) 29 30 client.close() socket_client_粘包

該方案程序的運(yùn)行速度遠(yuǎn)快于網(wǎng)絡(luò)傳輸速度,所以在發(fā)送一段字節(jié)前,先用send去發(fā)送該字節(jié)流長(zhǎng)度,這種方式會(huì)放大網(wǎng)絡(luò)延遲帶來的性能損耗

粘包解決方案二:

?

轉(zhuǎn)載于:https://www.cnblogs.com/tashanzhishi/p/9000783.html

總結(jié)

以上是生活随笔為你收集整理的Python 全栈开发十 socket网络编程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。