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

歡迎訪問 生活随笔!

生活随笔

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

python

Python-21-socket编程

發(fā)布時間:2023/12/18 python 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Python-21-socket编程 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

一、基礎(chǔ)知識

1. C/S架構(gòu)

C/S架構(gòu)即客戶機/服務(wù)器模式。

它可以分為客戶機和服務(wù)器兩層

第一層:? 在客戶機系統(tǒng)上結(jié)合了界面顯示與業(yè)務(wù)邏輯;

第二層:? 通過網(wǎng)絡(luò)結(jié)合了數(shù)據(jù)庫服務(wù)器。

簡單的說就是第一層是用戶表示層,第二層是數(shù)據(jù)庫層。

這里需要補充的是,客戶端不僅僅是一些簡單的操作,它也是會處理一些運算,業(yè)務(wù)邏輯的處理等。也就是說,客戶端也做著一些本該由服務(wù)器來做的一些事情,如圖所示:


2. TCP/IP模型

互聯(lián)網(wǎng)協(xié)議按照功能不同分為osi七層或tcp/ip五層或tcp/ip四層

每層運行常見物理設(shè)備

我們將應(yīng)用層,表示層,會話層并作應(yīng)用層,從tcp/ip五層協(xié)議的角度來闡述每層的由來與功能,搞清楚了每層的主要協(xié)議就理解了整個互聯(lián)網(wǎng)通信的原理。

首先,用戶感知到的只是最上面一層應(yīng)用層,自上而下每層都依賴于下一層,所以我們從最下一層開始切入,比較好理解

每層都運行特定的協(xié)議,越往上越靠近用戶,越往下越靠近硬件

物理層

物理層由來:上面提到,孤立的計算機之間要想一起玩,就必須接入internet,言外之意就是計算機之間必須完成組網(wǎng)

物理層功能:主要是基于電器特性發(fā)送高低電壓(電信號),高電壓對應(yīng)數(shù)字1,低電壓對應(yīng)數(shù)字0

數(shù)據(jù)鏈路層

數(shù)據(jù)鏈路層由來:單純的電信號0和1沒有任何意義,必須規(guī)定電信號多少位一組,每組什么意思

數(shù)據(jù)鏈路層的功能:定義了電信號的分組方式

以太網(wǎng)協(xié)議:

早期的時候各個公司都有自己的分組方式,后來形成了統(tǒng)一的標(biāo)準(zhǔn),即以太網(wǎng)協(xié)議ethernet

ethernet規(guī)定

  • 一組電信號構(gòu)成一個數(shù)據(jù)包,叫做‘幀’
  • 每一數(shù)據(jù)幀分成:報頭head和數(shù)據(jù)data兩部分
? ? ? ?head? ? ? ? ? ? ? ? ? ? ? ?data ? ? ? ? ? ? ? ? ? ? ? ? ? ??

?

head包含:(固定18個字節(jié))

  • 發(fā)送者/源地址,6個字節(jié)
  • 接收者/目標(biāo)地址,6個字節(jié)
  • 數(shù)據(jù)類型,6個字節(jié)

data包含:(最短46字節(jié),最長1500字節(jié))

  • 數(shù)據(jù)包的具體內(nèi)容

head長度+data長度=最短64字節(jié),最長1518字節(jié),超過最大限制就分片發(fā)送

mac地址:

head中包含的源和目標(biāo)地址由來:ethernet規(guī)定接入internet的設(shè)備都必須具備網(wǎng)卡,發(fā)送端和接收端的地址便是指網(wǎng)卡的地址,即mac地址

mac地址:每塊網(wǎng)卡出廠時都被燒制上一個世界唯一的mac地址,長度為48位2進制,通常由12位16進制數(shù)表示(前六位是廠商編號,后六位是流水線號)

廣播:

有了mac地址,同一網(wǎng)絡(luò)內(nèi)的兩臺主機就可以通信了(一臺主機通過arp協(xié)議獲取另外一臺主機的mac地址)

ethernet采用最原始的方式,廣播的方式進行通信,即計算機通信基本靠吼

網(wǎng)絡(luò)層

網(wǎng)絡(luò)層由來:有了ethernet、mac地址、廣播的發(fā)送方式,世界上的計算機就可以彼此通信了,問題是世界范圍的互聯(lián)網(wǎng)是由

一個個彼此隔離的小的局域網(wǎng)組成的,那么如果所有的通信都采用以太網(wǎng)的廣播方式,那么一臺機器發(fā)送的包全世界都會收到,

這就不僅僅是效率低的問題了,這會是一種災(zāi)難

上圖結(jié)論:必須找出一種方法來區(qū)分哪些計算機屬于同一廣播域,哪些不是,如果是就采用廣播的方式發(fā)送,如果不是,

就采用路由的方式(向不同廣播域/子網(wǎng)分發(fā)數(shù)據(jù)包),mac地址是無法區(qū)分的,它只跟廠商有關(guān)

網(wǎng)絡(luò)層功能:引入一套新的地址用來區(qū)分不同的廣播域/子網(wǎng),這套地址即網(wǎng)絡(luò)地址

IP協(xié)議:

  • 規(guī)定網(wǎng)絡(luò)地址的協(xié)議叫ip協(xié)議,它定義的地址稱之為ip地址,廣泛采用的v4版本即ipv4,它規(guī)定網(wǎng)絡(luò)地址由32位2進制表示
  • 范圍0.0.0.0-255.255.255.255
  • 一個ip地址通常寫成四段十進制數(shù),例:172.16.10.1

ip地址分成兩部分

  • 網(wǎng)絡(luò)部分:標(biāo)識子網(wǎng)
  • 主機部分:標(biāo)識主機

注意:單純的ip地址段只是標(biāo)識了ip地址的種類,從網(wǎng)絡(luò)部分或主機部分都無法辨識一個ip所處的子網(wǎng)

例:172.16.10.1與172.16.10.2并不能確定二者處于同一子網(wǎng)

子網(wǎng)掩碼

所謂”子網(wǎng)掩碼”,就是表示子網(wǎng)絡(luò)特征的一個參數(shù)。它在形式上等同于IP地址,也是一個32位二進制數(shù)字,它的網(wǎng)絡(luò)部分全部為1,主機部分全部為0。比如,IP地址172.16.10.1,如果已知網(wǎng)絡(luò)部分是前24位,主機部分是后8位,那么子網(wǎng)絡(luò)掩碼就是11111111.11111111.11111111.00000000,寫成十進制就是255.255.255.0。

知道”子網(wǎng)掩碼”,我們就能判斷,任意兩個IP地址是否處在同一個子網(wǎng)絡(luò)。方法是將兩個IP地址與子網(wǎng)掩碼分別進行AND運算(兩個數(shù)位都為1,運算結(jié)果為1,否則為0),然后比較結(jié)果是否相同,如果是的話,就表明它們在同一個子網(wǎng)絡(luò)中,否則就不是。

比如,已知IP地址172.16.10.1和172.16.10.2的子網(wǎng)掩碼都是255.255.255.0,請問它們是否在同一個子網(wǎng)絡(luò)?兩者與子網(wǎng)掩碼分別進行AND運算,

172.16.10.1:10101100.00010000.00001010.000000001

255255.255.255.0:11111111.11111111.11111111.00000000

AND運算得網(wǎng)絡(luò)地址結(jié)果:10101100.00010000.00001010.000000001->172.16.10.0

172.16.10.2:10101100.00010000.00001010.000000010

255255.255.255.0:11111111.11111111.11111111.00000000

AND運算得網(wǎng)絡(luò)地址結(jié)果:10101100.00010000.00001010.000000001->172.16.10.0

結(jié)果都是172.16.10.0,因此它們在同一個子網(wǎng)絡(luò)。

總結(jié)一下,IP協(xié)議的作用主要有兩個,一個是為每一臺計算機分配IP地址,另一個是確定哪些地址在同一個子網(wǎng)絡(luò)。

ip數(shù)據(jù)包

ip數(shù)據(jù)包也分為head和data部分,無須為ip包定義單獨的欄位,直接放入以太網(wǎng)包的data部分

head:長度為20到60字節(jié)

data:最長為65,515字節(jié)。

而以太網(wǎng)數(shù)據(jù)包的”數(shù)據(jù)”部分,最長只有1500字節(jié)。因此,如果IP數(shù)據(jù)包超過了1500字節(jié),它就需要分割成幾個以太網(wǎng)數(shù)據(jù)包,分開發(fā)送了。

以太網(wǎng)頭 ??? ? ? ? ? ? ip 頭?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?ip數(shù)據(jù) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

?

ARP協(xié)議

arp協(xié)議由來:計算機通信基本靠吼,即廣播的方式,所有上層的包到最后都要封裝上以太網(wǎng)頭,然后通過以太網(wǎng)協(xié)議發(fā)送,在談及以太網(wǎng)協(xié)議時候,我門了解到

通信是基于mac的廣播方式實現(xiàn),計算機在發(fā)包時,獲取自身的mac是容易的,如何獲取目標(biāo)主機的mac,就需要通過arp協(xié)議

arp協(xié)議功能:廣播的方式發(fā)送數(shù)據(jù)包,獲取目標(biāo)主機的mac地址

協(xié)議工作方式:每臺主機ip都是已知的

例如:主機172.16.10.10/24訪問172.16.10.11/24

一:首先通過ip地址和子網(wǎng)掩碼區(qū)分出自己所處的子網(wǎng)

場景數(shù)據(jù)包地址
同一子網(wǎng)目標(biāo)主機mac,目標(biāo)主機ip
不同子網(wǎng)網(wǎng)關(guān)mac,目標(biāo)主機ip

?

?

?

?

二:分析172.16.10.10/24與172.16.10.11/24處于同一網(wǎng)絡(luò)(如果不是同一網(wǎng)絡(luò),那么下表中目標(biāo)ip為172.16.10.1,通過arp獲取的是網(wǎng)關(guān)的mac

?源mac目標(biāo)mac源ip目標(biāo)ip數(shù)據(jù)部分
發(fā)送端主機發(fā)送端macFF:FF:FF:FF:FF:FF172.16.10.10/24172.16.10.11/24數(shù)據(jù)

?

?

?

三:這個包會以廣播的方式在發(fā)送端所處的自網(wǎng)內(nèi)傳輸,所有主機接收后拆開包,發(fā)現(xiàn)目標(biāo)ip為自己的,就響應(yīng),返回自己的mac

傳輸層

傳輸層的由來:網(wǎng)絡(luò)層的ip幫我們區(qū)分子網(wǎng),以太網(wǎng)層的mac幫我們找到主機,然后大家使用的都是應(yīng)用程序,你的電腦上可能同時開啟qq,暴風(fēng)影音,等多個應(yīng)用程序,

那么我們通過ip和mac找到了一臺特定的主機,如何標(biāo)識這臺主機上的應(yīng)用程序,答案就是端口,端口即應(yīng)用程序與網(wǎng)卡關(guān)聯(lián)的編號。

傳輸層功能:建立端口到端口的通信

補充:端口范圍0-65535,0-1023為系統(tǒng)占用端口

tcp協(xié)議:

可靠傳輸,TCP數(shù)據(jù)包沒有長度限制,理論上可以無限長,但是為了保證網(wǎng)絡(luò)的效率,通常TCP數(shù)據(jù)包的長度不會超過IP數(shù)據(jù)包的長度,以確保單個TCP數(shù)據(jù)包不必再分割。

以太網(wǎng)頭ip 頭 ? ? ? ? ? ? ?tcp頭 ? ? ? ? ? ? ?數(shù)據(jù) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

?

udp協(xié)議:

不可靠傳輸,”報頭”部分一共只有8個字節(jié),總長度不超過65,535字節(jié),正好放進一個IP數(shù)據(jù)包。

以太網(wǎng)頭ip頭 ? ? ? ? ? ? ? ??? ? udp頭 ? ? ? ? ? ? ? ? ? ? ? ? ??數(shù)據(jù) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

?

tcp報文

tcp三次握手和四次揮手

應(yīng)用層

應(yīng)用層由來:用戶使用的都是應(yīng)用程序,均工作于應(yīng)用層,互聯(lián)網(wǎng)是開發(fā)的,大家都可以開發(fā)自己的應(yīng)用程序,數(shù)據(jù)多種多樣,必須規(guī)定好數(shù)據(jù)的組織形式

應(yīng)用層功能:規(guī)定應(yīng)用程序的數(shù)據(jù)格式。

例:TCP協(xié)議可以為各種各樣的程序傳遞數(shù)據(jù),比如Email、WWW、FTP等等。那么,必須有不同協(xié)議規(guī)定電子郵件、網(wǎng)頁、FTP數(shù)據(jù)的格式,這些應(yīng)用程序協(xié)議就構(gòu)成了”應(yīng)用層”。

?

3. socket層

Socket是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層,它是一組接口。在設(shè)計模式中,Socket其實就是一個門面模式,它把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數(shù)據(jù),以符合指定的協(xié)議。

所以,我們無需深入理解tcp/udp協(xié)議,socket已經(jīng)為我們封裝好了,我們只需要遵循socket的規(guī)定去編程,寫出的程序自然就是遵循tcp/udp標(biāo)準(zhǔn)的。

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

二、套接字

1. 套接字的分類

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

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

套接字家族的名字:AF_UNIX

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

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

套接字家族的名字:AF_INET

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

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

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

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

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


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

2.基于TCP的套接字

服務(wù)端:

from socket import *ip_port = ('192.168.50.85', 8000) sever = socket(AF_INET, SOCK_STREAM) # AF_INET 基于網(wǎng)絡(luò) SOCK_STREAM 基于TCP sever.bind(ip_port) # 綁定到套接字 sever.listen(5) # 開始監(jiān)聽 while True:conn, addr = sever.accept() # 等待連接while True:try:msg = conn.recv(1024) # 收消息print('客戶端%s發(fā)來的消息:%s' % (addr, msg.decode('utf-8')))conn.send(msg.upper()) # 發(fā)消息except Exception:breakconn.close() sever.close()

客戶端:

from socket import * client = socket(AF_INET, SOCK_STREAM) client.connect(('192.168.50.85', 8000)) # 連接到服務(wù)器 while True:msg = input('>>:').strip()client.send(msg.encode('utf-8')) # 不能發(fā)送str,必須是二進制格式data = client.recv(1024)print('收到服務(wù)端的消息', data.decode('utf-8')) client.close()

基于TCP遠程執(zhí)行命令

1 from socket import * 2 import subprocess 3 4 ip_port = ('192.168.50.85', 8080) 5 sever = socket(AF_INET, SOCK_STREAM) 6 sever.bind(ip_port) 7 sever.listen(5) 8 while True: 9 conn, addr = sever.accept() 10 while True: 11 try: 12 cmd = conn.recv(1024) 13 if not cmd: 14 break 15 print('收到客戶端的命令', cmd) 16 # 執(zhí)行命令,得到cmd_res 17 res = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, 18 stderr=subprocess.PIPE) 19 err = res.stderr.read() 20 if err: 21 cmd_res = err 22 else: 23 cmd_res = res.stdout.read() 24 conn.send(cmd_res) 25 except Exception as e: 26 print(e) 27 break 28 conn.close() 服務(wù)端 1 from socket import * 2 ip_port = ('192.168.50.85', 8080) 3 client = socket(AF_INET, SOCK_STREAM) 4 client.connect(ip_port) 5 while True: 6 cmd = input('請輸入命令:').strip() 7 if not cmd: 8 continue 9 if cmd == 'quit': 10 break 11 client.send(cmd.encode('utf-8')) 12 data = client.recv(1024) 13 print('得到的結(jié)果是', data.decode('gbk')) 14 client.close() 客戶端 1 請輸入命令:dir 2 得到的結(jié)果是 驅(qū)動器 D 中的卷是 DATA 3 卷的序列號是 A473-AA80 4 5 D:\python\Project\python基礎(chǔ)\網(wǎng)絡(luò)編程 的目錄 6 7 2019/07/19 15:16 <DIR> . 8 2019/07/19 15:16 <DIR> .. 9 2019/07/19 15:13 864 TCP客戶端.py 10 2019/07/19 15:16 1,456 TCP服務(wù)端.py 11 2019/07/19 14:00 675 UDP客戶端1.py 12 2019/07/19 14:00 354 UDP客戶端2.py 13 2019/07/19 14:21 278 UDP服務(wù)端.py 14 5 個文件 3,627 字節(jié) 15 2 個目錄 200,399,257,600 可用字節(jié) 運行結(jié)果

在重啟服務(wù)端時可能會遇到

這個是由于服務(wù)端仍然存在四次揮手的time_wait狀態(tài)在占用地址(如果不懂,請深入研究1.tcp三次握手,四次揮手 2.syn洪水攻擊 3.服務(wù)器高并發(fā)情況下會有大量的time_wait狀態(tài)的優(yōu)化方法)

解決方法:

#加入一條socket配置,重用ip和端口phone=socket(AF_INET,SOCK_STREAM) phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加 phone.bind(('127.0.0.1',8080)) 1 發(fā)現(xiàn)系統(tǒng)存在大量TIME_WAIT狀態(tài)的連接,通過調(diào)整linux內(nèi)核參數(shù)解決, 2 vi /etc/sysctl.conf 3 4 編輯文件,加入以下內(nèi)容: 5 net.ipv4.tcp_syncookies = 1 6 net.ipv4.tcp_tw_reuse = 1 7 net.ipv4.tcp_tw_recycle = 1 8 net.ipv4.tcp_fin_timeout = 30 9 10 然后執(zhí)行 /sbin/sysctl -p 讓參數(shù)生效。 11 12 net.ipv4.tcp_syncookies = 1 表示開啟SYN Cookies。當(dāng)出現(xiàn)SYN等待隊列溢出時,啟用cookies來處理,可防范少量SYN攻擊,默認(rèn)為0,表示關(guān)閉; 13 14 net.ipv4.tcp_tw_reuse = 1 表示開啟重用。允許將TIME-WAIT sockets重新用于新的TCP連接,默認(rèn)為0,表示關(guān)閉; 15 16 net.ipv4.tcp_tw_recycle = 1 表示開啟TCP連接中TIME-WAIT sockets的快速回收,默認(rèn)為0,表示關(guān)閉。 17 18 net.ipv4.tcp_fin_timeout 修改系統(tǒng)默認(rèn)的 TIMEOUT 時間 方法二

3. 基于UDP的套接字

服務(wù)端:

from socket import *ip_port = ('192.168.50.85', 8080) sever = socket(AF_INET, SOCK_DGRAM) sever.bind(ip_port) while True:msg, addr = sever.recvfrom(1024)print(msg, addr)sever.sendto(msg.upper(), addr)

客戶端:

from socket import * ip_port = ('192.168.50.85', 8080) client = socket(AF_INET, SOCK_DGRAM) while True:msg = input('>>:').strip()if not msg:continueclient.sendto(msg.encode('utf-8'), ip_port)msg, addr = client.recvfrom(1024)print(msg.decode('utf-8'), addr)

基于UDP遠程執(zhí)行命令

1 from socket import * 2 import subprocess 3 4 ip_port = ('192.168.50.85', 8080) 5 server = socket(AF_INET, SOCK_DGRAM) 6 server.bind(ip_port) 7 while True: 8 cmd, addr = server.recvfrom(1024) 9 res = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) 10 err = res.stderr.read() 11 if err: 12 cmd_res = err 13 else: 14 cmd_res = res.stdout.read() 15 server.sendto(cmd_res, addr) 服務(wù)端 1 from socket import * 2 3 ip_port = ('192.168.50.85', 8080) 4 client = socket(AF_INET, SOCK_DGRAM) 5 while True: 6 msg = input('>>:').strip() 7 if not msg: 8 continue 9 client.sendto(msg.encode('utf-8'), ip_port) 10 data, addr = client.recvfrom(1024) 11 print('得到結(jié)果:', data.decode('gbk')) 12 client.close() 客戶端 1 >>:dir 2 得到結(jié)果: 驅(qū)動器 D 中的卷是 DATA 3 卷的序列號是 A473-AA80 4 5 D:\python\Project\python基礎(chǔ)\網(wǎng)絡(luò)編程 的目錄 6 7 2019/07/19 16:40 <DIR> . 8 2019/07/19 16:40 <DIR> .. 9 2019/07/19 15:24 862 TCP客戶端.py 10 2019/07/19 15:29 1,523 TCP服務(wù)端.py 11 2019/07/19 16:40 709 UDP客戶端1.py 12 2019/07/19 14:00 354 UDP客戶端2.py 13 2019/07/19 16:35 767 UDP服務(wù)端.py 14 5 個文件 4,215 字節(jié) 15 2 個目錄 200,399,253,504 可用字節(jié) 運行結(jié)果

三、粘包

1. 什么是粘包

須知:只有TCP有粘包現(xiàn)象,UDP永遠不會粘包,為何,且聽我娓娓道來

首先需要掌握一個socket收發(fā)消息的原理

?

發(fā)送端可以是一K一K地發(fā)送數(shù)據(jù),而接收端的應(yīng)用程序可以兩K兩K地提走數(shù)據(jù),當(dāng)然也有可能一次提走3K或6K數(shù)據(jù),或者一次只提走幾個字節(jié)的數(shù)據(jù),也就是說,應(yīng)用程序所看到的數(shù)據(jù)是一個整體,或說是一個流(stream),一條消息有多少字節(jié)對應(yīng)用程序是不可見的,因此TCP協(xié)議是面向流的協(xié)議,這也是容易出現(xiàn)粘包問題的原因。而UDP是面向消息的協(xié)議,每個UDP段都是一條消息,應(yīng)用程序必須以消息為單位提取數(shù)據(jù),不能一次提取任意字節(jié)的數(shù)據(jù),這一點和TCP是很不同的。怎樣定義消息呢?可以認(rèn)為對方一次性write/send的數(shù)據(jù)為一個消息,需要明白的是當(dāng)對方send一條信息的時候,無論底層怎樣分段分片,TCP協(xié)議層會把構(gòu)成整條消息的數(shù)據(jù)段排序完成后才呈現(xiàn)在內(nèi)核緩沖區(qū)。

例如基于tcp的套接字客戶端往服務(wù)端上傳文件,發(fā)送時文件內(nèi)容是按照一段一段的字節(jié)流發(fā)送的,在接收方看了,根本不知道該文件的字節(jié)流從何處開始,在何處結(jié)束

所謂粘包問題主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少字節(jié)的數(shù)據(jù)所造成的。

此外,發(fā)送方引起的粘包是由TCP協(xié)議本身造成的,TCP為提高傳輸效率,發(fā)送方往往要收集到足夠多的數(shù)據(jù)后才發(fā)送一個TCP段。若連續(xù)幾次需要send的數(shù)據(jù)都很少,通常TCP會根據(jù)優(yōu)化算法把這些數(shù)據(jù)合成一個TCP段后一次發(fā)送出去,這樣接收方就收到了粘包數(shù)據(jù)。

  • TCP(transport control protocol,傳輸控制協(xié)議)是面向連接的,面向流的,提供高可靠性服務(wù)。收發(fā)兩端(客戶端和服務(wù)器端)都要有一一成對的socket,因此,發(fā)送端為了將多個發(fā)往接收端的包,更有效的發(fā)到對方,使用了優(yōu)化方法(Nagle算法),將多次間隔較小且數(shù)據(jù)量小的數(shù)據(jù),合并成一個大的數(shù)據(jù)塊,然后進行封包。這樣,接收端,就難于分辨出來了,必須提供科學(xué)的拆包機制。 即面向流的通信是無消息保護邊界的。
  • UDP(user datagram protocol,用戶數(shù)據(jù)報協(xié)議)是無連接的,面向消息的,提供高效率服務(wù)。不會使用塊的合并優(yōu)化算法,, 由于UDP支持的是一對多的模式,所以接收端的skbuff(套接字緩沖區(qū))采用了鏈?zhǔn)浇Y(jié)構(gòu)來記錄每一個到達的UDP包,在每個UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對于接收端來說,就容易進行區(qū)分處理了。?即面向消息的通信是有消息保護邊界的。
  • tcp是基于數(shù)據(jù)流的,于是收發(fā)的消息不能為空,這就需要在客戶端和服務(wù)端都添加空消息的處理機制,防止程序卡住,而udp是基于數(shù)據(jù)報的,即便是你輸入的是空內(nèi)容(直接回車),那也不是空消息,udp協(xié)議會幫你封裝上消息頭,實驗略
  • udp的recvfrom是阻塞的,一個recvfrom(x)必須對唯一一個sendinto(y),收完了x個字節(jié)的數(shù)據(jù)就算完成,若是y>x數(shù)據(jù)就丟失,這意味著udp根本不會粘包,但是會丟數(shù)據(jù),不可靠

    tcp的協(xié)議數(shù)據(jù)不會丟,沒有收完包,下次接收,會繼續(xù)上次繼續(xù)接收,己端總是在收到ack時才會清除緩沖區(qū)內(nèi)容。數(shù)據(jù)是可靠的,但是會粘包。

    兩種情況下會發(fā)生粘包

    • 發(fā)送端需要等緩沖區(qū)滿才發(fā)送出去,造成粘包(發(fā)送數(shù)據(jù)時間間隔很短,數(shù)據(jù)量很小,會合到一起,產(chǎn)生粘包)
    # 服務(wù)端 from socket import * ip_port = ('192.168.50.85', 8000) sever = socket(AF_INET, SOCK_STREAM) # AF_INET 基于網(wǎng)絡(luò) SOCK_STREAM 基于TCP sever.bind(ip_port) # 綁定到套接字 sever.listen(5) # 開始監(jiān)聽 while True:conn, addr = sever.accept() # 等待連接msg1 = conn.recv(1024)print('第一次收到的數(shù)據(jù)', msg1)msg2 = conn.recv(1024)print('第二次收到的數(shù)據(jù)', msg2)msg3 = conn.recv(1024)print('第三次收到的數(shù)據(jù)', msg3)conn.close()# 客戶端 from socket import * client = socket(AF_INET, SOCK_STREAM) client.connect(('192.168.50.85', 8000)) # 連接到服務(wù)器 client.send(b'hello') client.send(b'world') client.send(b'123')# 結(jié)果 第一次收到的數(shù)據(jù) b'helloworld123' 第二次收到的數(shù)據(jù) b'' 第三次收到的數(shù)據(jù) b''
    • 接收方不及時接收緩沖區(qū)的包,造成多個包接收(客戶端發(fā)送了一段數(shù)據(jù),服務(wù)端只收了一小部分,服務(wù)端下次再收的時候還是從緩沖區(qū)拿上次遺留的數(shù)據(jù),產(chǎn)生粘包)
    # 服務(wù)端 from socket import * ip_port = ('192.168.50.85', 8000) sever = socket(AF_INET, SOCK_STREAM) # AF_INET 基于網(wǎng)絡(luò) SOCK_STREAM 基于TCP sever.bind(ip_port) # 綁定到套接字 sever.listen(5) # 開始監(jiān)聽 while True:conn, addr = sever.accept() # 等待連接msg1 = conn.recv(1)print('第一次收到的數(shù)據(jù)', msg1)msg2 = conn.recv(3)print('第二次收到的數(shù)據(jù)', msg2)msg3 = conn.recv(5)print('第三次收到的數(shù)據(jù)', msg3)conn.close()# 客戶端 from socket import * client = socket(AF_INET, SOCK_STREAM) client.connect(('192.168.50.85', 8000)) # 連接到服務(wù)器 client.send(b'helloworld')# 結(jié)果 第一次收到的數(shù)據(jù) b'h' 第二次收到的數(shù)據(jù) b'ell' 第三次收到的數(shù)據(jù) b'oworl'

    2. 解決粘包的方法

    1. 接收端不知道發(fā)送端將要傳送的字節(jié)流的長度,所以解決粘包的方法就是圍繞,如何讓發(fā)送端在發(fā)送數(shù)據(jù)前,把自己將要發(fā)送的字節(jié)流總大小讓接收端知曉,然后接收端來一個死循環(huán)接收完所有數(shù)據(jù)

    from socket import * import subprocess buffer_size = 1024 ip_port = ('192.168.50.85', 8000) sever = socket(AF_INET, SOCK_STREAM) sever.bind(ip_port) sever.listen(5) while True:conn, addr = sever.accept()while True:try:cmd = conn.recv(buffer_size)if not cmd:breakprint('收到客戶端的命令', cmd)# 執(zhí)行命令,得到cmd_resres = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE,stderr=subprocess.PIPE)err = res.stderr.read()if err:cmd_res = errelse:cmd_res = res.stdout.read()# 解決粘包問題length = len(cmd_res)conn.send(str(length).encode('utf-8'))client_ready = conn.recv(buffer_size)if client_ready ==b'ready':conn.send(cmd_res)except Exception as e:print(e)breakconn.close() 服務(wù)端 1 from socket import * 2 3 buffer_size = 1024 4 ip_port = ('192.168.50.85', 8000) 5 client = socket(AF_INET, SOCK_STREAM) 6 client.connect(ip_port) 7 while True: 8 cmd = input('請輸入命令:').strip() 9 if not cmd: 10 continue 11 if cmd == 'quit': 12 break 13 client.send(cmd.encode('utf-8')) 14 15 # 解決粘包 16 length = client.recv(buffer_size) 17 client.send(b'ready') 18 length = int(length.decode('utf-8')) 19 recv_size = 0 20 recv_msg = b'' 21 while recv_size < length: 22 recv_msg += client.recv(buffer_size) 23 recv_size = len(recv_msg) 24 25 print('得到的結(jié)果是', recv_msg.decode('gbk')) 26 client.close() 客戶端

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

    2. 為字節(jié)流加上自定義固定長度報頭,報頭中包含字節(jié)流長度,然后一次send到對端,對端在接收時,先從緩存中取出定長的報頭,然后再取真實數(shù)據(jù)

    1 from socket import * 2 import struct 3 import subprocess 4 buffer_size = 1024 5 ip_port = ('192.168.50.85', 8000) 6 sever = socket(AF_INET, SOCK_STREAM) 7 sever.bind(ip_port) 8 sever.listen(5) 9 while True: 10 conn, addr = sever.accept() 11 while True: 12 try: 13 cmd = conn.recv(buffer_size) 14 if not cmd: 15 break 16 print('收到客戶端的命令', cmd) 17 # 執(zhí)行命令,得到cmd_res 18 res = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, 19 stderr=subprocess.PIPE) 20 err = res.stderr.read() 21 if err: 22 cmd_res = err 23 else: 24 cmd_res = res.stdout.read() 25 26 # 解決粘包問題 27 length = len(cmd_res) 28 29 data_length = struct.pack('i', length) 30 conn.send(data_length) 31 conn.send(cmd_res) 32 33 except Exception as e: 34 print(e) 35 break 36 conn.close() 服務(wù)端 1 from socket import * 2 import struct 3 4 buffer_size = 1024 5 ip_port = ('192.168.50.85', 8000) 6 client = socket(AF_INET, SOCK_STREAM) 7 client.connect(ip_port) 8 while True: 9 cmd = input('請輸入命令:').strip() 10 if not cmd: 11 continue 12 if cmd == 'quit': 13 break 14 client.send(cmd.encode('utf-8')) 15 16 # 解決粘包 17 data_length = client.recv(4) 18 length = struct.unpack('i', data_length)[0] 19 recv_size = 0 20 recv_msg = b'' 21 while recv_size < length: 22 recv_msg += client.recv(buffer_size) 23 recv_size = len(recv_msg) 24 # recv_msg = client.recv(length) 一次接收所有數(shù)據(jù) 25 print('得到的結(jié)果是', recv_msg.decode('gbk')) 26 client.close() 客戶端

    struct模塊

    該模塊可以把一個類型,如數(shù)字,轉(zhuǎn)成固定長度的bytes

    l = struct.pack('i',1111111111111)

    反解

    struct.unpack('i', l)

    四、socketserver模塊實現(xiàn)并發(fā)

    import socketserverclass MyServer(socketserver.BaseRequestHandler):def handle(self):print('conn is:', self.request) # connprint('addr is:', self.client_address) # addrwhile True:try:# 收消息data = self.request.recv(1024)if not data:breakprint('收到的消息是:', data)# 發(fā)消息self.request.sendall(data.upper())except Exception:breakif __name__ == '__main__':s = socketserver.ThreadingTCPServer(('192.168.50.85', 8000), MyServer)s.serve_forever()

    ?

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

    總結(jié)

    以上是生活随笔為你收集整理的Python-21-socket编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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