粘包問題、socketserver模組實現併發

认真的六六發表於2024-06-22

TCP協議------------黏包現象

 1 1.服務端連續執行三次recv
 2 2.客戶端連續執行三次send
 3 問題:服務端一次性接收到了客戶端三次的訊息 該現象稱為"黏包現象"
 4 --------------------------------------
 5 黏包現象產生的原因:
 6     1.收訊息的時候,不知道每次接收的資料到底有多大!!!!!!
 7             recv()括號裡面規定了每次收訊息的位元組數,所以如果客戶端連續發幾次訊息,但是總位元組數小於括號裡面規定收訊息的位元組數
 8     2.TCP也稱為流式協議:資料像水流一樣,沒有間隔。
 9 TCP裡面有一個演算法會針對資料量較小且傳送間隔較短的多條資料一次性合併打包傳送,服務端就會一次性全都接收到!!!
10 ---------------
11 避免黏包現象的核心思路\關鍵點:
12     如何明確即將接收的資料具體的位元組數有多大!!!!!!
13 ---------------
14 ps:如何將長度變化的資料全部製作成固定長度的資料??????

解決粘包問題辦法一:

 1 服務端:
 2 
 3 import subprocess
 4 from socket import *
 5 import struct
 6 
 7 server = socket(AF_INET, SOCK_STREAM)
 8 server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
 9 server.bind(('127.0.0.1', 8891))
10 server.listen(5)
11 
12 # 服務端應該做兩件事:
13 # 1.迴圈的從半連結池中取出連結請求與其建立雙向連結,拿到連結物件
14 while True:
15     conn, client_addr = server.accept()
16     print('Connected by', client_addr)
17     # 2.拿到連結物件,與其進行通訊迴圈
18     while True:
19         try:
20             res = conn.recv(1024)
21             if len(res) == 0: break
22             obj = subprocess.Popen(res.decode('utf-8')
23                                    , shell=True,
24                                    stdout=subprocess.PIPE,
25                                    stderr=subprocess.PIPE)
26             stdout_res = obj.stdout.read()
27             stderr_res = obj.stderr.read()
28             total_size = len(stdout_res) + len(stderr_res)
29             # print(len(stderr_res)+len(stdout_res))
30             #  把執行結果返回給客戶端(錯誤和正確都返回)
31 
32             # 1.先發頭資訊(固定長度的bytes):對資料進行描述
33             # int--->固定長度的bytes(使用import struct模組),組包
34             header = struct.pack('i', total_size)
35             conn.send(header)
36 
37             # 2.再發真實的資料
38             conn.send(stdout_res)
39             conn.send(stderr_res)
40             # with open("1.mp4",mode='rb') as f:
41             #     for lin in f:
42             #         conn.send(lin)
43         except Exception:
44             break
45 
46     conn.close()
47 
48 
49 ==============================================
50 客戶端
51 
52 from socket import *
53 import struct
54 
55 #  建立socket物件
56 client = socket(AF_INET, SOCK_STREAM)
57 client.connect(('127.0.0.1', 8891))
58 
59 while True:
60     msg = input('>>:').strip()
61     if len(msg) == 0: continue
62     client.send(msg.encode('utf-8'))
63     # 解決粘包問題的思路:
64     # 1.先收固定長度的頭,解析出資料的描述資訊,包括資料的總大小,total_size
65     header = client.recv(4)
66     #  解析頭資訊,獲取資料的總大小struct.unpack
67     total_size = struct.unpack('i', header)[0]
68     print('total_size:', total_size)
69     # 2.根據解析出的描述資訊,接收真實的資料:recv_size=0,迴圈接收,每接收一次,recv_size+=接收的長度
70     # 3.當recv_size==total_size時,說明接收完畢
71     recv_size = 0
72     while recv_size < total_size:
73         recv_data = client.recv(1024)
74         recv_size += len(recv_data)
75         print(recv_data.decode('gbk'), end='')
76     else:
77         print()

程式碼實現終極解決粘包問題

 1 服務端
 2 
 3 import subprocess
 4 from socket import *
 5 import struct
 6 import json
 7 
 8 server = socket(AF_INET, SOCK_STREAM)
 9 server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
10 server.bind(('127.0.0.1', 8891))
11 server.listen(5)
12 
13 # 服務端應該做兩件事:
14 # 1.迴圈的從半連結池中取出連結請求與其建立雙向連結,拿到連結物件
15 while True:
16     conn, client_addr = server.accept()
17     print('Connected by', client_addr)
18     # 2.拿到連結物件,與其進行通訊迴圈
19     while True:
20         try:
21             res = conn.recv(1024)
22             if len(res) == 0: break
23             obj = subprocess.Popen(res.decode('utf-8')
24                                    , shell=True,
25                                    stdout=subprocess.PIPE,
26                                    stderr=subprocess.PIPE)
27             stdout_res = obj.stdout.read()
28             stderr_res = obj.stderr.read()
29             total_size = len(stdout_res) + len(stderr_res)
30             # print(len(stderr_res)+len(stdout_res))
31             # 1.檔案真實存在:製作頭(頭資訊)
32             header_dic = {
33                 'filename': 'a.txt',
34                 'total_size': total_size,
35                 'md5': '123456'
36             }
37             # conn.send(json.dumps(header_dic).encode('utf-8'))
38             json_str = json.dumps(header_dic)
39             json_str_bytes = json_str.encode('utf-8')
40 
41             # 2.先發頭的長度資訊(固定長度的bytes):對資料進行描述
42             header_size = struct.pack('i', len(json_str_bytes))
43             conn.send(header_size)
44             # 3.發頭資訊
45             conn.send(json_str_bytes)
46 
47             # 4.再發真實的資料
48             conn.send(stdout_res)
49             conn.send(stderr_res)
50 
51 
52         except Exception:
53             break
54 
55     conn.close()
56 
57 
58 ================================================
59 客戶端
60 
61 from socket import *
62 import struct
63 import json
64 
65 #  建立socket物件
66 client = socket(AF_INET, SOCK_STREAM)
67 client.connect(('127.0.0.1', 8891))
68 
69 while True:
70     msg = input('>>:').strip()
71     if len(msg) == 0: continue
72     client.send(msg.encode('utf-8'))
73     # 接收端
74     # 1.先收4個位元組,從中提取接下來要收的頭的長度
75     header_size = client.recv(4)
76     header_len = struct.unpack('i', header_size)[0]
77     # 2.接收頭,並解析
78     json_str_bytes = client.recv(header_len)
79     json_str = json_str_bytes.decode('utf-8')
80     header_dic = json.loads(json_str)
81     print(header_dic)
82     total_size = header_dic['total_size']
83     # 提取資訊:total_size=header_dic['total_size'],filename,md5
84     # 3.接收真實的資料
85     recv_size = 0
86     while recv_size < total_size:
87         recv_data = client.recv(1024)
88         recv_size += len(recv_data)
89         print(recv_data.decode('gbk'), end='')
90     else:
91         print()

PS補充:

【socketserver模組實現併發】

(基於tcp)

 1 服務端
 2 
 3 import socketserver
 4 
 5 
 6 class MyRequestHandler(socketserver.BaseRequestHandler):  # 繼承BaseRequestHandler類
 7     def handle(self):
 8         # print(self.request)  # 如果tcp協議,self.request==>conn
 9         print(self.client_address)
10 
11         while True:
12             try:
13                 msg = self.request.recv(1024)
14                 if len(msg) == 0: break
15                 self.request.send(msg.upper())
16             except Exception:
17                 break
18 
19         self.request.close()
20 
21 
22 # 1.建立一個服務端物件,繫結埠,啟動服務
23 s = socketserver.ThreadingTCPServer(('127.0.0.1', 8081), MyRequestHandler)
24 s.serve_forever()
25 # 等同於while True:
26 # conn, client_addr = server.accept()
27 # 啟動一個執行緒(conn,client_addr)
28 
29 # 2.拿到連結物件,與其進行通訊迴圈====》handle
30 
31 
32 =================================================
33 客戶端
34 
35 from socket import *
36 
37 client = socket(AF_INET, SOCK_STREAM)
38 client.connect(('127.0.0.1', 8081))
39 
40 while True:
41     msg = input('>>:').strip()
42     if len(msg) == 0: continue
43     client.send(msg.encode('utf-8'))
44     res = client.recv(1024)
45     print(res.decode('utf-8'))

(基於udp)

 1 import socketserver
 2 
 3 
 4 class MyRequestHandler(socketserver.BaseRequestHandler):
 5     def handle(self):
 6         client_data = self.request[0]
 7         server = self.request[1]
 8         client_address = self.client_address
 9         print('客戶端發來的資料%s' % client_data)
10         server.sendto(client_data.upper(), client_address)
11 
12 
13 s = socketserver.ThreadingUDPServer(('127.0.0.1', 8089), MyRequestHandler)
14 s.serve_forever()
15 
16 # 相當於:只負責迴圈的收
17 # while True:
18 #     data,client_addr=server.recvfrom(1024)
19 #       啟動一個執行緒處理後續的事情
20 #     server.sendto(data.upper(),client_addr)
21 
22 
23 ==============================================
24 客戶端
25 
26 import socket
27 
28 client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
29 while True:
30     msg = input('>>:').strip()
31     client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8089))
32     data, server_addr = client.recvfrom(1024)
33     print('Receive from server:', data.decode('utf-8'))
34 
35 client.close()

相關文章