TCP粘包问题
TCP是一种流式协议,数据像水流一样,没有任何边界区分
收数据没有收干净,有残留就会和下一次结果混淆在一起
所以解决的方法就是:每次都收拾干净不留任何残留
什么是粘包:
首先要强调的一点是粘包指挥在TCP协议传输中出现,而不会在UDP中出现,那么他的原因要从socket收发消息原理说起
tcp是面向连接的,面向流的提供可靠性服务,因此为了将多个发往接收端的包更有效的发到对方处,使用了优化方法,
将多次间隔较小且数据量小的数据合并成一个更大的数据块(降低网络IO频率),然后进行封包,这样造成的结果就是一大堆数据
混合到了一起,接收端很难分辨。
udp协议并不是流的方式,而是每一次发送就是一个数据包,那么如果发送的部分大于收到的部分会产生什么的情况呢?多余部分会
被无情抛弃,这也就是我们所谓的丢包过程
对于程序运行中为何tcp不能传输空消息,是因为对于数据流来说如果产生了一部分的空白值程序就会卡死在当前位置,就好比是水流
断掉,想想也是够恐怖,而udp协议就不会产生相似的结果,因为udp是基于数据包工作的,即使你发送了一个空的消息段,也会在其中
包含报头的信息。
所以对于接收端来说tcp是水流并不包含边界,但是可靠性更高不会出现数据传输丢包的情况,而对于udp来说每一次send既是一段数据
有明显的边界,所以不会发生粘包现象,但是可能会丢弃了多余部分
以下两种方式会发生粘包情况
1.发送端需要等缓冲区满才发送,造成粘包,也就是多次数据TCP根据算法自动帮助我们整合才发送
2.接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,
服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
这里再提一种拆包的发生情况即当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。
解决粘包问题的思路:既然已经知道了发生粘包的原因,那么就着手这几个原因开始解决
1.拿到数据的大小
2.循环接受,直到将整个数据接受完毕在结束循环
一 先收固定数据的头,解析数据属性包括大小 数据类型转换bytes类型 就需要导入模块 struct
二 传输真实数据
tcp解决粘包服务端 import socket import subprocess import struct import json sever = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sever.bind(("127.0.0.1", 8080)) while True: conn, msg = sever.accept() while True: try: cmd = conn.recv(1024) if cmd == 0: break cmd,file_name = cmd.decode("gbk").split(" ") if cmd == "get": msg = subprocess.Popen(f"type {file_name}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) str_out = msg.stdout.read() str_err = msg.stderr.read() total_size = len(str_out) + len(str_err) header_dic = { "filename":file_name, "total_size":total_size, "md5":1234657897897 } json_str = json.dumps(header_dic) json_str_bytes = json_str.encode("utf-8") header = struct.pack("i", len(json_str_bytes)) conn.send(header) conn.send(json_str) conn.send(str_out) conn.send(str_err) except Exception as err: print(err) break conn.close()
tcp解决粘包客户端 import socket import struct import json client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.bind(("127.0.0.1",8080)) while True: cmd = input("请输入命令").strip() client.send(cmd.encode("gbk")) header = client.recv(4) total_size_header = struct.unpack("i",header)[0] json_str_bytes = client.recv(total_size_header) json_str = json_str_bytes.decode("utf-8") head_dic = json.loads(json_str) res_size = 0 while res_size<head_dic["total_size"]: res_data = client.recv(1024) res_size+=len(res_data) print(res_data.decode("utf-8"),end="") client.close()
socketsever并发:
tcp并发 服务端 import socketserver class MyRequesrHandle(socketserver.BaseRequestHandler): def handle(self): '''self.request在tcp中就是conn,self.client_address就是addr''' print(self.request) print(self.client_address) while True: try: data = self.request.recv(1024) if len(data) == 0: break print(data.decode("utf-8")) self.request.send( data.upper() ) except Exception as err: print(err) break conn.close() s = socketserver.ThreadingTCPServer(("127.0.0.1", 8080), MyRequesrHandle) s.serve_forever() # 等同于开机过程,启动一个线程
udp实现并发服务端 import socketserver class MyRequestHandle(socketserver.BaseRequestHandler): def handle(self): data = self.request[0] client_address = self.client_address server = self.request[1] server.sendto(data.upper(),client_address) s = socketserver.ThreadingUDPServer(("127.0.0.1",8080),MyRequestHandle) s.serve_forever()
因为实现并发与客户端并没有太多关系所以不做重复粘贴