Python-21-socket編程

一、基礎知識

1. C/S架構

C/S架構即客戶機/服務器模式。

它可以分為客戶機和服務器兩層

第一層:? 在客戶機系統上結合了界面顯示與業務邏輯;

第二層:? 通過網絡結合了數據庫服務器。

簡單的說就是第一層是用戶表示層,第二層是數據庫層。

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


2. TCP/IP模型

互聯網協議按照功能不同分為osi七層或tcp/ip五層或tcp/ip四層

每層運行常見物理設備

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

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

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

物理層

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

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

數據鏈路層

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

數據鏈路層的功能:定義了電信號的分組方式

以太網協議:

早期的時候各個公司都有自己的分組方式,后來形成了統一的標準,即以太網協議ethernet

ethernet規定

  • 一組電信號構成一個數據包,叫做‘幀’
  • 每一數據幀分成:報頭head和數據data兩部分
? ? ? ?head? ? ? ? ? ? ? ? ? ? ? ?data ? ? ? ? ? ? ? ? ? ? ? ? ? ??

?

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

  • 發送者/源地址,6個字節
  • 接收者/目標地址,6個字節
  • 數據類型,6個字節

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

  • 數據包的具體內容

head長度+data長度=最短64字節,最長1518字節,超過最大限制就分片發送

mac地址:

head中包含的源和目標地址由來:ethernet規定接入internet的設備都必須具備網卡,發送端和接收端的地址便是指網卡的地址,即mac地址

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

廣播:

有了mac地址,同一網絡內的兩臺主機就可以通信了(一臺主機通過arp協議獲取另外一臺主機的mac地址)

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

網絡層

網絡層由來:有了ethernet、mac地址、廣播的發送方式,世界上的計算機就可以彼此通信了,問題是世界范圍的互聯網是由

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

這就不僅僅是效率低的問題了,這會是一種災難

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

就采用路由的方式(向不同廣播域/子網分發數據包),mac地址是無法區分的,它只跟廠商有關

網絡層功能:引入一套新的地址用來區分不同的廣播域/子網,這套地址即網絡地址

IP協議:

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

ip地址分成兩部分

  • 網絡部分:標識子網
  • 主機部分:標識主機

注意:單純的ip地址段只是標識了ip地址的種類,從網絡部分或主機部分都無法辨識一個ip所處的子網

例:172.16.10.1與172.16.10.2并不能確定二者處于同一子網

子網掩碼

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

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

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

172.16.10.1:10101100.00010000.00001010.000000001

255255.255.255.0:11111111.11111111.11111111.00000000

AND運算得網絡地址結果: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運算得網絡地址結果:10101100.00010000.00001010.000000001->172.16.10.0

結果都是172.16.10.0,因此它們在同一個子網絡。

總結一下,IP協議的作用主要有兩個,一個是為每一臺計算機分配IP地址,另一個是確定哪些地址在同一個子網絡。

ip數據包

ip數據包也分為head和data部分,無須為ip包定義單獨的欄位,直接放入以太網包的data部分

head:長度為20到60字節

data:最長為65,515字節。

而以太網數據包的”數據”部分,最長只有1500字節。因此,如果IP數據包超過了1500字節,它就需要分割成幾個以太網數據包,分開發送了。

以太網頭 ??? ? ? ? ? ? ip 頭?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?ip數據 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

?

ARP協議

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

通信是基于mac的廣播方式實現,計算機在發包時,獲取自身的mac是容易的,如何獲取目標主機的mac,就需要通過arp協議

arp協議功能:廣播的方式發送數據包,獲取目標主機的mac地址

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

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

一:首先通過ip地址和子網掩碼區分出自己所處的子網

場景數據包地址
同一子網目標主機mac,目標主機ip
不同子網網關mac,目標主機ip

?

?

?

?

二:分析172.16.10.10/24與172.16.10.11/24處于同一網絡(如果不是同一網絡,那么下表中目標ip為172.16.10.1,通過arp獲取的是網關的mac

?源mac目標mac源ip目標ip數據部分
發送端主機發送端macFF:FF:FF:FF:FF:FF172.16.10.10/24172.16.10.11/24數據

?

?

?

三:這個包會以廣播的方式在發送端所處的自網內傳輸,所有主機接收后拆開包,發現目標ip為自己的,就響應,返回自己的mac

傳輸層

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

那么我們通過ip和mac找到了一臺特定的主機,如何標識這臺主機上的應用程序,答案就是端口,端口即應用程序與網卡關聯的編號。

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

補充:端口范圍0-65535,0-1023為系統占用端口

tcp協議:

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

以太網頭ip 頭 ? ? ? ? ? ? ?tcp頭 ? ? ? ? ? ? ?數據 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

?

udp協議:

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

以太網頭ip頭 ? ? ? ? ? ? ? ??? ? udp頭 ? ? ? ? ? ? ? ? ? ? ? ? ??數據 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

?

tcp報文

tcp三次握手和四次揮手

應用層

應用層由來:用戶使用的都是應用程序,均工作于應用層,互聯網是開發的,大家都可以開發自己的應用程序,數據多種多樣,必須規定好數據的組織形式

應用層功能:規定應用程序的數據格式。

例:TCP協議可以為各種各樣的程序傳遞數據,比如Email、WWW、FTP等等。那么,必須有不同協議規定電子郵件、網頁、FTP數據的格式,這些應用程序協議就構成了”應用層”。

?

3. socket層

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

所以,我們無需深入理解tcp/udp協議,socket已經為我們封裝好了,我們只需要遵循socket的規定去編程,寫出的程序自然就是遵循tcp/udp標準的。

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

二、套接字

1. 套接字的分類

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

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

套接字家族的名字:AF_UNIX

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

基于網絡類型的套接字家族

套接字家族的名字:AF_INET

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

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

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

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

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


面向文件的套接字的函數
s.fileno() 套接字的文件描述符
s.makefile() 創建一個與該套接字相關的文件

2.基于TCP的套接字

服務端:

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

客戶端:

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

基于TCP遠程執行命令

 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             # 執行命令,得到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()
服務端
 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('得到的結果是', data.decode('gbk'))
14 client.close()
客戶端
 1 請輸入命令:dir
 2 得到的結果是  驅動器 D 中的卷是 DATA
 3  卷的序列號是 A473-AA80
 4 
 5  D:\python\Project\python基礎\網絡編程 的目錄
 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服務端.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服務端.py
14                5 個文件          3,627 字節
15                2 個目錄 200,399,257,600 可用字節
運行結果

在重啟服務端時可能會遇到

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

解決方法:

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

3. 基于UDP的套接字

服務端:

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遠程執行命令

 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)
服務端
 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('得到結果:', data.decode('gbk'))
12 client.close()
客戶端
 1 >>:dir
 2 得到結果:  驅動器 D 中的卷是 DATA
 3  卷的序列號是 A473-AA80
 4 
 5  D:\python\Project\python基礎\網絡編程 的目錄
 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服務端.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服務端.py
14                5 個文件          4,215 字節
15                2 個目錄 200,399,253,504 可用字節
運行結果

三、粘包

1. 什么是粘包

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

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

?

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

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

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

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

  1. TCP(transport control protocol,傳輸控制協議)是面向連接的,面向流的,提供高可靠性服務。收發兩端(客戶端和服務器端)都要有一一成對的socket,因此,發送端為了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將多次間隔較小且數據量小的數據,合并成一個大的數據塊,然后進行封包。這樣,接收端,就難于分辨出來了,必須提供科學的拆包機制。 即面向流的通信是無消息保護邊界的。
  2. UDP(user datagram protocol,用戶數據報協議)是無連接的,面向消息的,提供高效率服務。不會使用塊的合并優化算法,, 由于UDP支持的是一對多的模式,所以接收端的skbuff(套接字緩沖區)采用了鏈式結構來記錄每一個到達的UDP包,在每個UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對于接收端來說,就容易進行區分處理了。?即面向消息的通信是有消息保護邊界的。
  3. tcp是基于數據流的,于是收發的消息不能為空,這就需要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基于數據報的,即便是你輸入的是空內容(直接回車),那也不是空消息,udp協議會幫你封裝上消息頭,實驗略

udp的recvfrom是阻塞的,一個recvfrom(x)必須對唯一一個sendinto(y),收完了x個字節的數據就算完成,若是y>x數據就丟失,這意味著udp根本不會粘包,但是會丟數據,不可靠

tcp的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端總是在收到ack時才會清除緩沖區內容。數據是可靠的,但是會粘包。

兩種情況下會發生粘包

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

2. 解決粘包的方法

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

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)# 執行命令,得到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()
服務端
 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('得到的結果是', recv_msg.decode('gbk'))
26 client.close()
客戶端

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

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

 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             # 執行命令,得到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()
服務端
 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) 一次接收所有數據
25     print('得到的結果是', recv_msg.decode('gbk'))
26 client.close()
客戶端

struct模塊

該模塊可以把一個類型,如數字,轉成固定長度的bytes

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

反解

struct.unpack('i', l)

四、socketserver模塊實現并發

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)# 發消息self.request.sendall(data.upper())except Exception:breakif __name__ == '__main__':s = socketserver.ThreadingTCPServer(('192.168.50.85', 8000), MyServer)s.serve_forever()

?

轉載于:https://www.cnblogs.com/lsf123456/p/11212605.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/448220.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/448220.shtml
英文地址,請注明出處:http://en.pswp.cn/news/448220.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

解決:VScode 漢化后 、設置中文后 還顯示英文的問題

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 按f1 搜索 Configore Display Language 設置 zh-cn 關閉軟件重啟。 如果重啟菜單等還是英文的&#xff0c;在商店查看已安裝的插件&…

自動擋怎么開-自動擋汽車怎么開?

汽車改用自動變速器后&#xff0c;駕駛員的操作更加簡便、駕駛更加平順&#xff0c;因此裝備自動變速器的新型轎車尤其受到了人們的青睞。不過&#xff0c;很多駕駛者初開自動擋車時&#xff0c;由于對自動變速器的結構和原理不是很了解&#xff0c;行車時經常是一個D擋走完全程…

CreateThread函數

創建一個在調用進程的虛擬地址空間內執行的線程。 要創建在另一個進程的虛擬地址空間中運行的線程&#xff0c;請使用 CreateRemoteThread函數。 語法 HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,SIZE_T dwStackSize,LPTHREAD_START…

nginx 的請求處理、請求的處理流程

nginx的請求處理 前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 nginx使用一個多進程模型來對外提供服務&#xff0c;其中一個master進程&#xff0c;多個worker進程。master進程負責…

如何控制油門更準確?

學員問&#xff1a;平時練車還不錯&#xff0c;可是一換車就容易加大油門&#xff0c;有什么方法能很好的控制油呢&#xff1f;&#xff1f; 如何控制油門更準確&#xff1f;和調的座位有關系嗎&#xff1f;&#xff1f; 答&#xff1a;油門跟剎車被視為汽車控制的靈魂。汽車發…

使用線程——創建線程

CreateThread函數創建一個進程的新的線程。創建線程必須指定新線程要執行的代碼的起始地址。通常&#xff0c;起始地址是程序代碼中定義的函數的名稱&#xff08;有關更多信息&#xff0c;請參閱ThreadProc&#xff09;。此函數采用單個參數并返回DWORD值。一個進程可以讓多個線…

location

location (地址)&#xff1a; 是瀏覽器 window 上的一個對象&#xff0c;不僅能處理當前頁面的網絡地址&#xff0c;還可以實現頁面間的跳轉 頁面的跳轉&#xff1a; 為什么使用它&#xff1f; 使我們也可以通過腳本語言&#xff0c;也能實現 a 鏈接&#xff0c;同樣的效果&…

linux :Docker 方式 安裝 zookeeper、阿里服務器上 Docker 運行 zookeeper

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1. 查找官方鏡像&#xff0c;并下載鏡像&#xff1a; # 搜索鏡像&#xff1a; docker search zookeeper# 拉取鏡像&#xff1a;docker …

使用線程池功能

此示例創建自定義線程池&#xff0c;創建工作項和線程池計時器&#xff0c;并將它們與清理組關聯。該池由一個持久性線程組成。它演示了以下線程池函數的使用&#xff1a; CloseThreadpool CloseThreadpoolCleanupGroupCloseThreadpoolCleanupGroupMembersCloseThreadpoolWait…

制動剎車片六個養護要點

剎車片屬于消耗品&#xff0c;在使用中會逐漸磨損&#xff0c;當磨損到極限位置時&#xff0c;必須更換&#xff0c;否則將降低制動的效果&#xff0c;甚至造成安全事故。 制動剎車片關乎生命安全&#xff0c;必須謹慎對待。 大多數轎車采用前盤后鼓式制動器結構&#xff0c;一…

Learn day4 函數參數\變量\閉包\遞歸

1.函數描述 # ### 函數 """ (1)函數的定義:功能 (包裹一部分代碼 實現某一個功能 達成某一個目的) (2)函數特點:可以反復調用,提高代碼的復用性,提高開發效率,便于維護管理 """# (3) 函數的基本格式 """ # 函數的定義處 def fun…

Java 中去除字符串中空格的方法

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1、方法分類 str.trim(); //去掉首尾空格str.replace(" ",""); //去除所有空格&#xff0c;包括首尾、中間str.re…

使用重定向的輸入和輸出創建子進程

本主題中的示例演示如何使用控制臺進程中的CreateProcess函數創建子進程。它還演示了一種使用匿名管道重定向子進程的標準輸入和輸出句柄的技術。請注意&#xff0c;命名管道也可用于重定向進程I / O. 所述CreatePipe函數使用SECURITY_ATTRIBUTES結構來創建可繼承句柄讀寫兩個…

手動擋停車時掛檔有技巧

徐小姐來電&#xff1a;我家的汽車要年檢了&#xff0c;前幾天&#xff0c;工作人員幫我把車子開進檢測站去檢測&#xff0c;開回來后停在原位上&#xff0c;然后把鑰匙交給我。我拿鑰匙一點火&#xff0c;車子就突然往前動了&#xff0c;根本沒有時間反應&#xff0c;已經撞到…

LOJ 3156: 「NOI2019」回家路線

題目傳送門&#xff1a;LOJ #3156。 題意簡述&#xff1a; 有一張 \(n\) 個點 \(m\) 條邊的有向圖&#xff0c;邊有兩個權值 \(p_i\) 和 \(q_i\)&#xff08;\(p_i<q_i\)&#xff09;表示若 \(p_i\) 時刻在這條邊的起點&#xff0c;則 \(q_i\) 時刻能到達這條邊的終點。 你需…

線程池概述

線程池 一個線程池的工作線程代表應用程序的高效執行異步回調的集合。線程池主要用于減少應用程序線程的數量并提供工作線程的管理。應用程序可以對工作項進行排隊&#xff0c;將工作與可等待的句柄相關聯&#xff0c;根據計時器自動排隊&#xff0c;并與I / O綁定。 線程池架…

WEB 請求處理二:Nginx 請求 反向代理

上一篇《WEB請求處理一&#xff1a;瀏覽器請求發起處理》&#xff0c;我們講述了瀏覽器端請求發起過程&#xff0c;通過DNS域名解析服務器IP&#xff0c;并建立TCP連接&#xff0c;發送HTTP請求。本文將講述請求到達反向代理服務器的一個處理過程&#xff0c;比如&#xff1a;在…

方向盤的正確駕馭方法

如果問您油門踏板和方向盤哪個與駕駛員最“親密”&#xff0c;您會選擇誰呢&#xff1f;恐怕還是方向盤吧。如果汽車行駛過程中您的雙手同時離開了方向盤&#xff0c;那么事故的隱患也就隨之而來。下面我們就為您全面介紹汽車方向盤的正確使用方法。專家介紹&#xff0c;握方向…

SQL server 2005中無法新建作業(Job)的問題

客戶端是使用企業管理其&#xff08;Management Studio&#xff09;新建job&#xff0c;總是無法創建&#xff0c;查找了很多資料&#xff0c;有的說是需要sp2, 但有的又說不是... ... 沒有時間去研究為什么&#xff0c;但確有一種方法解決&#xff1a;到服務器端去創建job&…

線程池API

線程池API 線程池應用程序編程接口&#xff08;API&#xff09;使用基于對象的設計。以下每個對象都由用戶模式數據結構表示&#xff1a; 池對象是一組可用于執行工作的工作線程。每個進程可以根據需要創建具有不同特征的多個隔離池。每個進程都有一個默認池。清理組與一組回…