網絡編程-之------粘包現象

1、什麼是粘包shell

須知:只有TCP有粘包現象,UDP永遠不會粘包json

粘包不必定會發生服務器

若是發生了:1.多是在客戶端已經粘了socket

      2.客戶端沒有粘,多是在服務端粘了ide

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

應用程序所看到的數據是一個總體,或說是一個流(stream),一條消息有多少字節對應用程序是不可見的,所以TCP協議是面向流的協議,這也是容易出現粘包問題的緣由。(由於TCP是流式協議,不知道啥時候開始,啥時候結束)。而UDP是面向消息的協議,每一個UDP段都是一條消息,應用程序必須以消息爲單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不一樣的。怎樣定義消息呢?能夠認爲對方一次性write/send的數據爲一個消息,須要明白的是當對方send一條信息的時候,不管底層怎樣分段分片,TCP協議層會把構成整條消息的數據段排序完成後才呈如今內核緩衝區。code

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

2、發生粘包的兩種狀況排序

發送端須要等緩衝區滿才發送出去,形成粘包(發送數據時間間隔很短,數據了很小,會當作一個包發出去,產生粘包)ip

 1 from socket import *
 2 phone = socket(AF_INET,SOCK_STREAM)
 3 phone.setsockopt(SOL_SOCKET,SOCK_STREAM,1)
 4 phone.bind(('127.0.0.1',8080))
 5 phone.listen(5)
 6 print('start running...')
 7 
 8 coon,addr = phone.accept() #等待鏈接
 9 
10 data1 = coon.recv(10)
11 data2 = coon.recv(10)
12 
13 print('------------>',data1.decode('utf-8'))
14 print('------------>',data2.decode('utf-8'))
15 coon.close()
16 phone.close()
服務端
1 from socket import *
2 import time
3 phone = socket(AF_INET,SOCK_STREAM)
4 phone.connect(('127.0.0.1',8080))
5 
6 phone.send('hello'.encode('utf-8'))
7 phone.send('helloworld'.encode('utf-8'))
8 phone.close()
客戶端

接收方不及時接收緩衝區的包,形成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候仍是從緩衝區拿上次遺留的數據,產生粘包) 

 1 from socket import *
 2 phone = socket(AF_INET,SOCK_STREAM)
 3 phone.setsockopt(SOL_SOCKET,SOCK_STREAM,1)
 4 phone.bind(('127.0.0.1',8080))
 5 phone.listen(5)
 6 print('start running...')
 7 
 8 coon,addr = phone.accept() #等待鏈接
 9 
10 data1 = coon.recv(2) #一次沒有接收完整
11 data2 = coon.recv(10)  #下一次接收的時候會先取舊的數據,而後取新的
12 # data3 = coon.recv(1024)  #接收等5秒後的信息
13 print('------------>',data1.decode('utf-8'))
14 print('------------>',data2.decode('utf-8'))
15 # print('------------>',data3.decode('utf-8'))
16 coon.close()
17 phone.close()
服務端
1 from socket import *
2 import time
3 phone = socket(AF_INET,SOCK_STREAM)
4 phone.connect(('127.0.0.1',8080))
5 
6 phone.send('hello'.encode('utf-8'))
7 time.sleep(5)
8 phone.send('haiyan'.encode('utf-8'))
9 phone.close()
客戶端

3、解決粘包的方法

問題的根源在於,接收端不知道發送端將要傳送的字節流的長度,因此解決粘包的方法就是圍繞,如何讓發送端在發送數據前,把本身將要發送的字節流總大小讓接收端知曉,而後接收端來一個死循環接收完全部數據

 1 import socket
 2 import subprocess
 3 import struct
 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #買手機
 5 phone.bind(('127.0.0.1',8080)) #綁定手機卡
 6 phone.listen(5) #阻塞的最大數
 7 print('start runing.....')
 8 while True: #連接循環
 9     coon,addr = phone.accept()# 等待接電話
10     print(coon,addr)
11     while True: #通訊循環
12         # 收發消息
13         cmd = coon.recv(1024) #接收的最大數
14         print('接收的是:%s'%cmd.decode('utf-8'))
15         #處理過程
16         res = subprocess.Popen(cmd.decode('utf-8'),shell = True,
17                                           stdout=subprocess.PIPE, #標準輸出
18                                           stderr=subprocess.PIPE #標準錯誤
19                                 )
20         stdout = res.stdout.read()
21         stderr = res.stderr.read()
22         #先發報頭(轉成固定長度的bytes類型,那麼怎麼轉呢?就用到了struct模塊)
23         #len(stdout) + len(stderr)#統計數據的長度
24         header = struct.pack('i',len(stdout)+len(stderr))#製做報頭
25         coon.send(header)
26         #再發命令的結果
27         coon.send(stdout)
28         coon.send(stderr)
29     coon.close()
30 phone.close()
服務端
 1 import socket
 2 import struct
 3 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 4 phone.connect(('127.0.0.1',8080)) #鏈接服
 5 while True:
 6     # 發收消息
 7     cmd = input('請你輸入命令>>:').strip()
 8     if not cmd:continue
 9     phone.send(cmd.encode('utf-8')) #發送
10     #先收報頭
11     header_struct = phone.recv(4) #收四個
12     unpack_res = struct.unpack('i',header_struct)
13     total_size = unpack_res[0]  #總長度
14     #後收數據
15     recv_size = 0
16     total_data=b''
17     while recv_size<total_size: #循環的收
18         recv_data = phone.recv(1024) #1024只是一個最大的限制
19         recv_size+=len(recv_data) #
20         total_data+=recv_data #
21     print('返回的消息:%s'%total_data.decode('gbk'))
22 phone.close()
客戶端

4、解決粘包問題升級版:完整的解決了

 1 import socket
 2 import subprocess
 3 import struct
 4 import json
 5 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #買手機
 6 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
 7 phone.bind(('127.0.0.1',8080)) #綁定手機卡
 8 phone.listen(5) #阻塞的最大數
 9 print('start runing.....')
10 while True: #連接循環
11     coon,addr = phone.accept()# 等待接電話
12     print(coon,addr)
13     while True: #通訊循環
14         # 收發消息
15         cmd = coon.recv(1024) #接收的最大數
16         print('接收的是:%s'%cmd.decode('utf-8'))
17         #處理過程
18         res = subprocess.Popen(cmd.decode('utf-8'),shell = True,
19                                           stdout=subprocess.PIPE, #標準輸出
20                                           stderr=subprocess.PIPE #標準錯誤
21                                 )
22         stdout = res.stdout.read()
23         stderr = res.stderr.read()
24         # 製做報頭
25         header_dic = {
26             'total_size': len(stdout)+len(stderr),  # 總共的大小
27             'filename': None,
28             'md5': None
29         }
30         header_json = json.dumps(header_dic) #字符串類型
31         header_bytes = header_json.encode('utf-8')  #轉成bytes類型(可是長度是可變的)
32         #先發報頭的長度
33         coon.send(struct.pack('i',len(header_bytes))) #發送固定長度的報頭
34         #再發報頭
35         coon.send(header_bytes)
36         #最後發命令的結果
37         coon.send(stdout)
38         coon.send(stderr)
39     coon.close()
40 phone.close()
服務端
 1 import socket
 2 import struct
 3 import json
 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 5 phone.connect(('127.0.0.1',8080)) #鏈接服務器
 6 while True:
 7     # 發收消息
 8     cmd = input('請你輸入命令>>:').strip()
 9     if not cmd:continue
10     phone.send(cmd.encode('utf-8')) #發送
11     #先收報頭的長度
12     header_len = struct.unpack('i',phone.recv(4))[0]  #吧bytes類型的反解
13     #在收報頭
14     header_bytes = phone.recv(header_len) #收過來的也是bytes類型
15     header_json = header_bytes.decode('utf-8')   #拿到json格式的字典
16     header_dic = json.loads(header_json)  #反序列化拿到字典了
17     total_size = header_dic['total_size']  #就拿到數據的總長度了
18     #最後收數據
19     recv_size = 0
20     total_data=b''
21     while recv_size<total_size: #循環的收
22         recv_data = phone.recv(1024) #1024只是一個最大的限制
23         recv_size+=len(recv_data) #有可能接收的不是1024個字節,或許比1024多呢,
24         # 那麼接收的時候就接收不全,因此還要加上接收的那個長度
25         total_data+=recv_data #最終的結果
26     print('返回的消息:%s'%total_data.decode('gbk'))
27 phone.close()
客戶端

5、struct模塊

 1 #該模塊能夠把一個類型,如數字,轉成固定長度的bytes類型
 2 import struct
 3 res = struct.pack('i',12345)
 4 print(res,len(res),type(res))  #長度是4
 5 
 6 res2 = struct.pack('i',12345111)
 7 print(res,len(res),type(res2))  #長度也是4
 8 
 9 unpack_res =struct.unpack('i',res2)
10 print(unpack_res)  #(12345111,)
11 print(unpack_res[0]) #12345111
struct
相關文章
相關標籤/搜索