day 29

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.再收真实数据 真实可能很长 需要循环接收

发送端和接收端必须都处理粘包 才算真正的解决了

 

原文地址:https://www.cnblogs.com/1624413646hxy/p/10952040.html