tcp 下的粘包问题
半连接数的产生有两个方面原因组成的。
1 是由客户端恶意发起洪水攻击造成的,在三次握手操作的时候,客户端刻意不往服务器发出第三次握手指令。这样的操作如果过多,会导致内存空间占满,这时候如果有正常数据进来的话,服务器无法处理。这样程序就会崩溃掉。
2 是因为请求过多,服务无法及时处理,这样也是造成半连接数的一个原因。
在socket中listen可以让我们自行规定最大的半连接数。保护程序的正常运行。
当需要在传输数据
发送端
1.发送报头长度 2 发送报头数据 3 发送文件内容。
接收端:
接收报头长度 接收报头信息 接收文件内容。
数据粘包是只有在TCP下才会有,因为TCP是流式协议,,数据之间并没有一个明显的分割线,但数据长度可以看作一个数据之间分割线,别的再无其他。数据长度也是我们用来处理粘包的问题入手点。
首先说一下粘包问题出现的原因,造成粘包问题是双向的,首先在客户端往服务器发送数据时,当数据小并且间隔时间断,nagle的算法优化机制就会将这些短且小的数据进行打包一起发送,又因为TCP是流式协议,数据之间并无分割,数据就会混在一起,就像一杯雪碧倒入了一盆自来水中,鬼见愁。
而服务如果没有及时处理数据,即使数据从客户端发送过来时候没有发生粘包问题,也会在服务器的缓存区发生粘包问题。
#服务器 server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) client,addr = server.accept() data = client.recv(1024).decode('utf-8') data1 = client.recv(1024).decode('utf-8') print(data) print(data1)
#客户端代码
client = socket.socket()
client.connect(('127.0.0.1',8080))
client.send('hello'.encode('utf-8'))
client.send('world'.encode('utf-8'))
#打印结果
helloworld
可以出两次向服务器发送的信息被一起打印了出来,出现了粘包问题。
解决办法就是从数据长度上去入手,先向服务器发送每条数据的长度,再发送数据的真实信息,以数据长度来作为接收数据的参照。这样就可以解决粘包问题。
#服务器代码 server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) client,addr = server.accept() data = client.recv(5).decode('utf-8') data1 = client.recv(6).decode('utf-8') print(data) print(data1) client.close() #客户端代码 client = socket.socket() client.connect(('127.0.0.1',8080)) client.send('hello'.encode('utf-8')) client.send('world'.encode('utf-8')) #打印结果 hello world
这里是将信息的字节长度发送给服务器,通过字节长度来接收数据这样就解决了粘包问题。
有了这个解决思路就可以用来完成文件的上传下载
#客户端下载代码
import socket import struct import json client = socket.socket() try: client.connect(('127.0.0.1', 8080)) print('连接成功') # 接收报头长度 head_size = struct.unpack('q',client.recv(8))[0] # 接收报头数据 head_str = client.recv(head_size).decode('utf-8') file_info = json.loads(head_str) print('报头数据:',file_info) file_size = file_info.get('file_size') print(type(file_size)) file_name = file_info.get('file_name') recv_size = 0 buffer_size = 2048 f = open(file_name,'wb') while True: if int(file_size) - recv_size >= buffer_size: a = client.recv(buffer_size) else: a = client.recv(int(file_size) - recv_size) f.write(a) recv_size += len(a) print('已下载%s%%'%(recv_size/file_size*100)) if recv_size == file_size: break f.close() except Exception as e: print( '错误信息', e)
#服务器提供给客户端下载数据代码
import socket import os import json import struct server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.bind(('127.0.0.1',8080)) server.listen(5) f = None while True: client,addr = server.accept() try: path = r"C:Users胡心亚Desktop2.今日内容.mp4" #拼接出文件的绝对路径 file_path = os.path.getsize(path) #通过getsize方法可以获取文件的字节大小 print(file_path) file_info = {"file_name":r"F:day183.time模块.mp4","file_size":file_path} #创建报头,包头中包含文件名,和文件字节数 json_str = json.dumps(file_info).encode('utf-8') #使用json模块将报头信息序列化成json格式的字符串,用于传输,主要是为了方便传输 # 发送报头长度 client.send(struct.pack('q',len(json_str))) #在文件下载时为了防止粘包的情况发生,要先将文件的字节长度传到客户端,规定好客户端需要接收多少字节的内容 # 发送报头 client.send(json_str) #再将文件信息传给客户端 # 发送文件 f = open(path,'rb') while True: temp = f.read(2048) # 文件大小不一致,有可能会超过缓存区的大小, # 这时候为了保证文件可以正常传输,可以采用循环传输的方式传输, # 一次传输2048个字节,当文件信息传输完了之后做出判断,停止进程 # if not temp: break client.send(temp) print('文件发送成功') except Exception as e : print('出错了',e) finally: if f: f.close() # 在最后做一个异常处理,是判断文件是否一开始就是走不通的, # 先定义了一个f 为None 如果f 走的同就保证f是有值的,没有值就做异常处理 client.close()
总结解决粘包办法总结
1.使用struct 将真实数据的长度转为固定的字节数据
2.发送长度数据
3.发送真实数据
接收端
1.先收长度数据 字节数固定
2.再收真实数据 真实可能很长 需要循环接收
发送端和接收端必须都处理粘包 才算真正的解决了