發送端能夠是一K一K地發送數據,而接收端的應用程序能夠兩K兩K地提走數據,固然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據,也就是說,應用程序所看到的數據是一個總體,或說是一個流(stream),一條消息有多少字節對應用程序是不可見的,所以TCP協議是面向流的協議,這也是容易出現粘包問題的緣由。而UDP是面向消息的協議,每一個UDP段都是一條消息,應用程序必須以消息爲單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不一樣的。怎樣定義消息呢?能夠認爲對方一次性write/send的數據爲一個消息,須要明白的是當對方send一條信息的時候,不管底層怎樣分段分片,TCP協議層會把構成整條消息的數據段排序完成後才呈如今內核緩衝區。python
發送方引發的粘包是由TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一個TCP段。若連續幾回須要send的數據都不多,一般TCP會根據優化算法把這些數據合成一個TCP段後一次發送出去,這樣接收方就收到了粘包數據。算法
主要是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的。shell
發送方緣由編程
TCP默認使用Nagle算法(主要做用:減小網絡中報文段的數量),而Nagle算法主要作兩件事:json
Nagle算法形成了發送方可能會出現粘包問題。緩存
接收方緣由服務器
TCP接收到數據包時,並不會立刻交到應用層進行處理,或者說應用層並不會當即處理。實際上,TCP將接收到的數據包保存在接收緩存裏,而後應用程序主動從緩存讀取收到的分組。這樣一來,若是TCP接收數據包到緩存的速度大於應用程序從緩存中讀取數據包的速度,多個包就會被緩存,應用程序就有可能讀取到多個首尾相接粘到一塊兒的包。網絡
發送端須要等緩衝區滿才發送出去,形成粘包(發送數據時間間隔很短,數據了很小,會合到一塊兒,產生粘包)併發
# server端 import socket ip_port=('127.0.0.1',8080) tcp_socket_server=socket(AF_INET,SOCK_STREAM) tcp_socket_server.bind(ip_port) tcp_socket_server.listen(5) conn,addr=tcp_socket_server.accept() data1=conn.recv(10) data2=conn.recv(10) print('----->',data1.decode('utf-8')) print('----->',data2.decode('utf-8')) conn.close()
# client端 import socket BUFSIZE=1024 ip_port=('127.0.0.1',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(ip_port) s.send('hello'.encode('utf-8')) s.send('egg'.encode('utf-8'))
接收方的緩存機制 接收方不及時接收緩衝區的包,形成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候仍是從緩衝區拿上次遺留的數據,產生粘包)socket
# server端 from socket import * ip_port=('127.0.0.1',8080) tcp_socket_server=socket(AF_INET,SOCK_STREAM) tcp_socket_server.bind(ip_port) tcp_socket_server.listen(5) conn,addr=tcp_socket_server.accept() data1=conn.recv(2) #一次沒有收完整 data2=conn.recv(10)#下次收的時候,會先取舊的數據,而後取新的 print('----->',data1.decode('utf-8')) print('----->',data2.decode('utf-8')) conn.close()
# client端 import socket BUFSIZE=1024 ip_port=('127.0.0.1',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(ip_port) s.send('hello egg'.encode('utf-8'))
struct 模塊: 是用來將整型的數字轉成固定長度的bytes。
按照指定格式將Python數據轉換爲字符串,該字符串爲字節流,如網絡傳輸時,不能傳輸int,此時先將int轉化爲字節流,而後再發送;
按照指定格式將字節流轉換爲Python指定的數據類型
# 按照給定的格式(fmt),把數據封裝成字符串(其實是相似於c結構體的字節流) pack(fmt, v1, v2, ...) # 按照給定的格式(fmt)解析字節流string,返回解析出來的tuple unpack(fmt,string)
fmt 支持的格式包括:
# 服務端 from socket import * import subprocess import struct import json server=socket(AF_INET,SOCK_STREAM) server.bind(('127.0.0.1',8080)) server.listen(5) while True: conn,client_addr=server.accept() print(conn,client_addr)#(鏈接對象,客戶端的ip和端口) while True: try: cmd=conn.recv(1024) obj=subprocess.Popen( cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout=obj.stdout.read() stderr=obj.stderr.read() #一、製做報頭 header_dic={ 'total_size':len(stdout)+len(stderr), 'md5':'dgdsfsdfdsdfsfewrewge', 'file_name':'a.txt' } header_json=json.dumps(header_dic) header_bytes=header_json.encode('utf-8') #二、先發送報頭的長度 header_size=len(header_bytes) conn.send(struct.pack('i',header_size)) #三、發送報頭 conn.send(header_bytes) #四、發送真實的數據 conn.send(stdout) conn.send(stderr) except ConnectionResetError: break conn.close() server.close()
# 客戶端 from socket import * import json import struct client=socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8080)) while True: cmd=input(">>:").strip() if not cmd:continue client.send(cmd.encode('utf-8')) #一、接收報文頭的長度 header_size=struct.unpack('i',client.recv(4))[0] #二、接收報文 header_bytes=client.recv(header_size) #三、解析報文 header_json=header_bytes.decode('utf-8') header_dic=json.loads(header_json) print(header_dic) #四、獲取真實數據的長度 totol_size=header_dic['total_size'] #五、獲取數據 recv_size=0 res=b'' while recv_size<totol_size: recv_date=client.recv(1024) res+=recv_date recv_size+=len(recv_date) print(res.decode('gbk')) client.close()
import struct #struct是用來將整型的數字轉成固定長度的bytes. import json header_dic={ 'total_size':32322, 'md5':'gdssfsfsdfsf', 'filename':'a.txt' } #一、將報頭字典序列化。 header_json=json.dumps(header_dic) #二、將序列後的字典轉成字節 header_bytes=header_json.encode('utf-8') #三、獲取序列的字字典轉成字節的個數 header_size=len(header_bytes) print(header_size) #四、將這個個數轉成固字長度的字節表示 obj=struct.pack('i',header_size) print(obj,len(obj)) #、這個固定長度的字節通過反轉後是一個元組。 res=struct.unpack('i',obj) #、經過按索取值就可等到報頭字典長度。 header_size=res[0]
# 服務端 import socket server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 數據報協議-》UDP server.bind(('127.0.0.1', 8080)) while True: data, client_addr = server.recvfrom(1024) print('===>', data, client_addr) server.sendto(data.upper(), client_addr) server.close()
# 客戶端 import socket client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 數據報協議-》UDP while True: msg = input('>>: ').strip() # msg='' client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080)) data, server_addr = client.recvfrom(1024) print(data) client.close()
# 服務端 import socket server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 數據報協議-》udp server.bind(('127.0.0.1', 8080)) data, client_addr = server.recvfrom(1024) # b'hello'==>b'h' print('第一次:', client_addr, data) data, client_addr = server.recvfrom(1024) # b'world' =>b'world' print('第二次:', client_addr, data) # # data,client_addr=server.recvfrom(1024) # print('第三次:',client_addr,data) server.close()
# 客戶端 import socket client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 數據報協議-》udp client.sendto('hello'.encode('utf-8'), ('127.0.0.1', 8080)) client.sendto('world'.encode('utf-8'), ('127.0.0.1', 8080)) # client.sendto(''.encode('utf-8'),('127.0.0.1',8080)) client.close()
# 服務端 import socket server = socket.socket(type=socket.SOCK_DGRAM) server.bind( ('127.0.0.1', 9527) ) while True: # 服務端接收客戶端傳過來的消息 msg, addr = server.recvfrom(1024) # (消息,客戶端地址) msg1, addr1 = server.recvfrom(1024) # (消息,客戶端地址) msg2, addr2 = server.recvfrom(1024) # (消息,客戶端地址) print(addr) print(addr1) print(addr2) print(msg.decode('utf-8')) print(msg1.decode('utf-8')) print(msg2.decode('utf-8')) # 服務端往客戶端發送消息 send_msg = input('服務端發送消息:').encode('utf-8') server.sendto(send_msg, addr) server.sendto(send_msg, addr1) server.sendto(send_msg, addr2)
# 客戶端 1 import socket client = socket.socket(type=socket.SOCK_DGRAM) server_ip_port = ('127.0.0.1', 9527) while True: send_msg = input('客戶端1: ').encode('utf-8') # 發送消息必需要加上對方地址 client.sendto(send_msg, server_ip_port) # 能接收任何人的消息 msg = client.recv(1024) print(msg.decode('utf-8'))
# 客戶端 2 import socket client = socket.socket(type=socket.SOCK_DGRAM) server_ip_port = ('127.0.0.1', 9527) while True: send_msg = input('客戶端2: ').encode('utf-8') # 發送消息必需要加上對方地址 client.sendto(send_msg, server_ip_port) # 能接收任何人的消息 msg = client.recv(1024) print(msg.decode('utf-8'))
# 客戶端 3 import socket client = socket.socket(type=socket.SOCK_DGRAM) server_ip_port = ('127.0.0.1', 9527) while True: send_msg = input('客戶端3: ').encode('utf-8') # 發送消息必需要加上對方地址 client.sendto(send_msg, server_ip_port) # 能接收任何人的消息 msg = client.recv(1024) print(msg.decode('utf-8'))
# 服務端 import socketserver class MyHandler(socketserver.BaseRequestHandler): def handle(self): # 通訊循環 while True: # print(self.client_address) # print(self.request) #self.request=conn try: data = self.request.recv(1024) if len(data) == 0: break self.request.send(data.upper()) except ConnectionResetError: break if __name__ == '__main__': s = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyHandler, bind_and_activate=True) s.serve_forever() # 表明鏈接循環 # 循環創建鏈接,每創建一個鏈接就會啓動一個線程(服務員)+調用Myhanlder類產生一個對象,調用該對象下的handle方法,專門與剛剛創建好的鏈接作通訊循環
# 客戶端 1 import socket phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.connect(('127.0.0.1', 8080)) # 指定服務端ip和端口 while True: # msg=input('>>: ').strip() #msg='' msg = 'client33333' # msg='' if len(msg) == 0: continue phone.send(msg.encode('utf-8')) data = phone.recv(1024) print(data) phone.close()
# 客戶端 2 import socket phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.connect(('127.0.0.1', 8080)) # 指定服務端ip和端口 while True: # msg=input('>>: ').strip() #msg='' msg = 'client11111' # msg='' if len(msg) == 0: continue phone.send(msg.encode('utf-8')) data = phone.recv(1024) print(data) phone.close()
# 服務端 import socketserver class MyHandler(socketserver.BaseRequestHandler): def handle(self): # 通訊循環 print(self.client_address) print(self.request) data = self.request[0] print('客戶消息', data) self.request[1].sendto(data.upper(), self.client_address) if __name__ == '__main__': s = socketserver.ThreadingUDPServer(('127.0.0.1', 8080), MyHandler) s.serve_forever()
# 客戶端 1 import socket client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 數據報協議-》udp while True: # msg=input('>>: ').strip() #msg='' msg = 'client1111' client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080)) data, server_addr = client.recvfrom(1024) print(data) client.close()
# 客戶端 2 import socket client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 數據報協議-》udp while True: # msg=input('>>: ').strip() #msg='' msg = 'client2222' client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080)) data, server_addr = client.recvfrom(1024) print(data) client.close()