python---基础知识回顾(六)网络编程2(处理粘包)
前戏:
之前在python---基础知识回顾(六)网络编程异步模块中提到过粘包现象,而且在使用twisted中提到过一种处理办法,按行接收lineReceived,当收到\r\n换行符时,才去缓冲区中获取到数据。
from twisted.internet import reactor from twisted.internet.protocol import Protocol,Factory from twisted.protocols.basic import LineReceiver class EchoServer(LineReceiver): def connectionMade(self): #建立连接的时候 print("Get connect from",self.transport.client) self.factory.numPorts = self.factory.numPorts + 1 if self.factory.numPorts > 3: self.transport.write("Too many connections , try later".encode("utf-8")) self.transport.loseConnection() self.factory.numPorts = self.factory.numPorts - 1 else: self.transport.write("Successful connections".encode("utf-8")) def connectionLost(self, reason): #连接断开的时候 print(self.transport.client,"disconnect") self.factory.numPorts = self.factory.numPorts - 1 def lineReceived(self,line): #当收到一行数据的时候 data = "reve a line :%s"%line.decode("utf-8") print(data) self.transport.write(data.encode("utf-8")) factory = Factory() factory.protocol = EchoServer port = 8080 reactor.listenTCP(port,factory) reactor.run() #进入循环
import socket ip_port = ("127.0.0.1",8080) #用于绑定服务器端的地址和端口 sk = socket.socket() sk.connect(ip_port) #与服务器连接 data = sk.recv(1024) print(data.decode("utf-8")) while True: sk.sendall("fafwagawgwa".encode("utf-8")) data = input(">>>:") if not data: continue if data == "hh": data += "\r\n" try: sk.sendall(data.encode("utf-8")) # 向服务器端发送数据 data = sk.recv(1024) print(data.decode("utf-8")) except ConnectionAbortedError: break sk.close()
sk.sendall("fafwagawgwa".encode("utf-8")) data = input(">>>:") if not data: continue if data == "hh": data += "\r\n" sk.sendall(data.encode("utf-8")) # 向服务器端发送数据
在这里我们先发送fafwagawgwa数据到服务器端的缓冲区中(未被获取),然后在客户端输入hh后,会发送换行符到服务器端,此时缓冲区中的数据“fafwagawgwahh\r\n”,当获取到换行符后,服务器会将数据获取打印
reve a line :fafwagawgwahh
粘包现象演示:
粘包问题产生的原因:
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
产生情况:
1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
import socket ip_port = ("0.0.0.0",8080) sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sk.bind(ip_port) sk.listen(5) while True: conn,addr = sk.accept() print("Connect from %s(%s)"%(addr[0],addr[1])) count = 0 while True: count += 1 bt_data = conn.recv(1024) if not bt_data: #对方断开了连接 conn.close() break data = bt_data.decode("utf-8") print(count,data) sk.close()
import socket ip_port = ("127.0.0.1",8080) #用于绑定服务器端的地址和端口 sk = socket.socket() sk.connect(ip_port) #与服务器连接 message_list = ['1','2','3','4','5','6','7','8','9','10'] num = 0 while True: try: data = message_list[num] except IndexError: break send_data = data.encode("utf-8") sk.sendall(send_data) num += 1 sk.close()
索引 数据 Connect from 127.0.0.1(3395) 1 123 2 4 3 5 4 6 5 7 6 8 7 9 8 10
从输出结果,可以看出在服务器端接收的数据产生了粘包现象
2.接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
import socket ip_port = ("0.0.0.0",8080) sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sk.bind(ip_port) sk.listen(5) while True: conn,addr = sk.accept() print("Connect from %s(%s)"%(addr[0],addr[1])) count = 0 while True: count += 1 bt_data = conn.recv(5) if not bt_data: #对方断开了连接 conn.close() break data = bt_data.decode("utf-8") print(count,data) sk.close()
import socket,time ip_port = ("127.0.0.1",8080) #用于绑定服务器端的地址和端口 sk = socket.socket() sk.connect(ip_port) #与服务器连接 message_list = ['1dsafwawf','2faafaw'] num = 0 while True: try: data = message_list[num] except IndexError: break send_data = data.encode("utf-8") sk.sendall(send_data) num += 1 time.sleep(5) sk.close()
Connect from 127.0.0.1(3531) 1 1dsaf 2 wawf 3 2faaf 4 aw
简单版本:
import socket ip_port = ("0.0.0.0",8080) sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sk.bind(ip_port) sk.listen(5) while True: conn,addr = sk.accept() print("Connect from %s(%s)"%(addr[0],addr[1])) buffer = "" while True: bt_data = conn.recv(5) if not bt_data: #对方断开了连接 conn.close() break data = bt_data.decode("utf-8") buffer += data if '\r\n' in buffer: index = buffer.find('\r\n') data = buffer[:index] buffer = buffer[index+2:] print(data) sk.close()
import socket,time ip_port = ("127.0.0.1",8080) #用于绑定服务器端的地址和端口 sk = socket.socket() sk.connect(ip_port) #与服务器连接 message_list = ['1dsafwawf\r\n','2faafaw\r\n'] num = 0 while True: try: data = message_list[num] except IndexError: break send_data = data.encode("utf-8") sk.sendall(send_data) num += 1 time.sleep(5) sk.close()
Connect from 127.0.0.1(4167) 1dsafwawf 2faafaw
改进版本(先发送一个报头,告诉服务端要接收的数据大小):
import socket import struct ip_port = ("0.0.0.0",8080) sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sk.bind(ip_port) sk.listen(5) while True: conn,addr = sk.accept() print("Connect from %s(%s)"%(addr[0],addr[1])) while True: head_data = conn.recv(4) #先接收报头,含有文件大小 if not head_data: #对方断开了连接 conn.close() break #设置接收的大小以及接收数据 recv_size = struct.unpack('i',head_data)[0] #unpack返回元组 recv_data = bytes() while recv_size: recv_data += conn.recv(recv_size) recv_size -= len(recv_data) data = recv_data.decode("utf-8") print(data) sk.close()
import socket,time import struct ip_port = ("127.0.0.1",8080) #用于绑定服务器端的地址和端口 sk = socket.socket() sk.connect(ip_port) #与服务器连接 message_list = ['1dsafwaffdawwawf','2faafwfwafwafgrehrssafawfaw'] num = 0 while True: try: data = message_list[num] except IndexError: break send_data = data.encode("utf-8") head_data = struct.pack('i',len(send_data)) #i是int类型4字节 sk.send(head_data) sk.sendall(send_data) num += 1 time.sleep(5) sk.close()
Connect from 127.0.0.1(5982) 1dsafwaffdawwawf 2faafwfwafwafgrehrssafawfaw
最终版本:
(对于一些文件的发送,我们需要先发送一个报头,其中是文件信息的字节长度,
然后我们将文件的详细信息,以及文件的md5值发送过去,对方根据详细信息获取数据,对数据的检测更加完善)
补充:使用hashlib进行文本(文件)比较
python---基础知识回顾(九)图形用户界面-------Tkinter
其中有一段提及使用md5值去比较文件一致性。
import socket import struct import os import hashlib import json ip_port = ("0.0.0.0",8080) sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sk.bind(ip_port) sk.listen(5) def getMd5(path): fp = open(path,"r") content = fp.read() data = hashlib.md5(content.encode("utf-8")) fp.close() return data.hexdigest(),content while True: conn,addr = sk.accept() print("Connect from %s(%s)"%(addr[0],addr[1])) while True: try: path = conn.recv(1024) except ConnectionResetError: conn.close() break if not path: conn.close() break path = path.decode("utf-8") header_data = {'file_size':None,'file_md5':None,'file_name':None} #用来封装文件的信息 header_info = None #用来标志长度 这个先发送,根据这个长度去获取上面的数据(里面有详细信息) if not os.path.isfile(path): header_info = struct.pack('i',-1) #-1代表失败 conn.send(header_info) # 注意pack后的数据已经是字节型,所以我们不需要去编码 continue header_data['file_size'] = os.stat(path).st_size header_data['file_name'] = os.path.basename(path) header_data['file_md5'],content = getMd5(path) header_data = json.dumps(header_data).encode("utf-8") header_info = struct.pack('i',len(header_data)) conn.send(header_info) #发送header_info,其中是header_data的长度 conn.send(header_data) #这里的数据在上面已经编码了,不需要我们处理 conn.sendall(content.encode("utf-8")) #发送文件的内容 sk.close()
import socket import struct import json import hashlib ip_port = ("127.0.0.1",8080) #用于绑定服务器端的地址和端口 sk = socket.socket() sk.connect(ip_port) #与服务器连接 def getMd5(content): data = hashlib.md5(content) return data.hexdigest() while True: path = input(">>>(请输入下载的文件名):").strip() if not path: continue if path == "quit": break sk.send(path.encode("utf-8")) #发送文件路径 status = sk.recv(4) #接收准备状态 status = struct.unpack('i',status)[0] #转换数据类型,记得unpack返回的是一个元组 if status == -1: print("请输入正确的文件路径") continue header_data = sk.recv(status) header_data = header_data.decode("utf-8") header_data = json.loads(header_data) content = sk.recv(header_data['file_size']) file_md5 = getMd5(content) if file_md5 != header_data['file_md5']: print("文件下载出错,请重试") del content continue data = content.decode("utf-8") #获取到文件的所有数据 with open(header_data['file_name'],"w") as fp: fp.write(data) print("文件下载完毕!") sk.close()