黏包
黏包的起因:
连续send多个小数据,会发生黏包现象,这个是tcp协议优化算法造成的
当发送一个数据超过本次接收的最大范围之后,剩下的数据会留到下次接收时接收
黏包的现象:
import socket sk = socket.socket() sk.bind(('127.0.0.1',8090)) sk.listen() conn,addr = sk.accept() ret = conn.recv(2) #在这里先接收一个两字节的内容 ret2 = conn.recv(10) #然后在这里在接收一个长度为10字节的内容 print(ret) print(ret2) conn.close() sk.close()
import socket sk = socket.socket() sk.connect(('127.0.0.1',8090)) sk.send(b'helloeve') #在这里发送一个长度为8的字节 sk.close()
b'he'
b'lloeve'
可以看见接收的内容被分开了
import socket sk = socket.socket() sk.bind(('127.0.0.1',8090)) sk.listen() conn,addr = sk.accept() ret1 = conn.recv(12) #在这里接收两次 print(ret1) ret2 = conn.recv(12) print(ret2) conn.close() sk.close()
import socket sk = socket.socket() sk.connect(('127.0.0.1',8090)) sk.send(b'hello') #这里连续发送两次数据 sk.send(b'eve') sk.close()
b'helloeve'
b''
原本预计的接收两个内容被合成一个接收了
这就是黏包
所以,黏包的根本原因就是,接收方不知道本次接收的数据具体大小,才会发生黏包
如何解决黏包
竟然知道是什么原因才发生黏包,那么,只要每次发送数据前,告诉对方我要发多少数据不就行了
import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() conn,addr = sk.accept() while True: cmd = input('>>>') if cmd == 'q': conn.send(b'q') break conn.send(cmd.encode('gbk')) num = conn.recv(1024).decode('utf-8') #将接到的长度赋给一个变量 conn.send(b'ok') #想用户端发送消息表示已经接收到长度 res = conn.recv(int(num)).decode('gbk') # 将接收到的长度值给接收字节数 print(res) conn.close() sk.close()
import socket import subprocess #调用subprocess模块 sk = socket.socket() sk.connect(('127.0.0.1',8080)) while True: cmd = sk.recv(1024).decode('gbk') if cmd == 'q': break res = subprocess.Popen(cmd,shell=True, stdout=subprocess.PIPE, #将取到的东西放到管道里 stderr=subprocess.PIPE) # PIPE 管道 std_out = res.stdout.read() #将管道里的数据赋给一个变量 std_err = res.stderr.read() #因为管道里的东西只能取一次 sk.send(str(len(std_out)+len(std_err)).encode('utf-8')) #叫测量的长度发给服务端 sk.recv(1024) #接收服务端的反馈 sk.send(std_out) # 将数据发过去 sk.send(std_err) sk.close()
这样每次发送前都将本次要发送的数据字节数发过去,让对方对应接收就不会发生黏包现象了
不过,虽然不会黏包,但是每次发送都需要一次交互,增加了代码的工作
怎么才能一次交互就解决这些问题呢
这时候就要用到
struct模块
struct模块可以把一个类型转成固定长度的bytes
我们这次只需要用到int
import struct import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() conn,addr = sk.accept() while True: cmd = input('>>>') if cmd == 'q': conn.send(b'q') break conn.send(cmd.encode('gbk')) num = conn.recv(4) #接受客户端的传来的 num = struct.unpack('i',num)[0] #反向得到字节数 res = conn.recv(int(num)).decode('gbk') #将字节数传给接收端当参数 print(res) conn.close() sk.close()
import struct #这时候就需要用到struct模块 import socket import subprocess sk = socket.socket() sk.connect(('127.0.0.1',8080)) while True: cmd = sk.recv(1024).decode('gbk') if cmd == 'q': break res = subprocess.Popen(cmd,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) std_out = res.stdout.read() std_err = res.stderr.read() len_num = len(std_out)+len(std_err) #将计算的字节数赋给一个变量 num_by = struct.pack('i',len_num) #在将字节数用strect模块转成长度为4的bytes sk.send(num_by) #然后将它发给服务端 sk.send(std_out) sk.send(std_err) sk.close()
在网络上传输的所有数据 都叫数据包
数据包里的所有数据 都叫报文
报文里不止有你的数据 还有 ip地址 mac地址 端口号
所有的报文 都有 报头