一、黏包
1、tcp有黏包现象
表现两种情况
发送的数据过小且下面还有一个发送数据,这两个数据会一起发送
发送的数据过大,超过最大缓存空间,超出的部分在下一次发送的时候发送
原因:
tcp是面向流的,根据算法,自动把数据拆分、组合,没有保护边界
2、udp无黏包现象
表现形式
发送的数据包大小超出最大缓存空间,超出的数据直接丢弃
udp不是面向流的,是面向消息的
总结
tcp协议是:可靠的,面向连接的,面向流的,效率低
udp协议是:不可靠的,无连接的,面向对象的,效率高
一般视频下载是tcp协议
聊天软件是udp协议
数据传输,传输的是数据包,数据包的内容是报文,报文有报头等
二、黏包现象
1、接连发生数据较小的数据包,且只接收数据一次
""" Server端 在Client端接连发送两个小的数据包,Server端只有一个接收,且接收文件较大 会出现黏包现象 """ import socket sk = socket.socket() sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk.bind(('127.0.0.1', 8010)) sk.listen() connect, addr = sk.accept() ret = connect.recv(1024) print(ret.decode('utf-8')) connect.close() sk.close()
""" Client端 """ import socket sk = socket.socket() sk.connect(('127.0.0.1', 8010)) sk.send('tom'.encode('utf-8')) sk.send(' is god'.encode('utf-8')) sk.close()
2、发送一个大的数据包,接收多次,且第一次接收的数据比较小
""" Server端 在Client端发送一个数据包,Server端只接收两次,且第一次接受的数据较少 会出现黏包现象 """ import socket sk = socket.socket() sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk.bind(('127.0.0.1', 8010)) sk.listen() connect, addr = sk.accept() ret1 = connect.recv(4).decode('utf-8') ret2 = connect.recv(10).decode('utf-8') print(ret1) print(ret2) connect.close() sk.close()
""" Client端 """ import socket sk = socket.socket() sk.connect(('127.0.0.1', 8010)) sk.send('tom is god'.encode('utf-8')) sk.close()
示例
""" Server端 向Client端发送cmd命令,利用subprocess,执行命令并且发送两次 发送黏包现象 """ import socket sk = socket.socket() sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk.bind(('127.0.0.1', 8010)) sk.listen() connect, addr = sk.accept() while 1: cmd = input('>>>') connect.send(cmd.encode('gbk')) if cmd == 'q': break ret = connect.recv(1024).decode('gbk') print(ret) connect.close() sk.close()
""" Client端 """ import socket import subprocess sk = socket.socket() sk.connect(('127.0.0.1', 8010)) while 1: 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() # bytes数据类型 std_error = res.stderr.read() sk.send(std_out) sk.send(std_error) print(std_out.decode('gbk')) print(std_error.decode('gbk')) sk.close()
三、解决黏包
两种方法
1、预先知道发送端发送数据包的大小
2、使用struct变成固定大小的bytes类型
第一种方法,为了不产生黏包,每执行一次多产生一次网络延迟
""" Server端 """ import socket sk = socket.socket() sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk.bind(('127.0.0.1', 8010)) sk.listen() connect, addr = sk.accept() while 1: cmd = input('>>>') connect.send(cmd.encode('utf-8')) if cmd == 'q': break new_len = int(connect.recv(1024).decode('utf-8')) connect.send(bytes('ok', 'utf-8')) msg = connect.recv(new_len) print(msg.decode('utf-8')) connect.close() sk.close()
""" Client端 subprocess 产生的数据是bytes类型 计数bytes的长度->str """ import socket import subprocess sk = socket.socket() sk.connect(('127.0.0.1', 8010)) while 1: cmd = sk.recv(1024).decode('utf-8') 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() new_len = str(len(std_out + std_err)) sk.send(new_len.encode('utf-8')) sk.recv(1024) sk.send(std_out) sk.send(std_err) print(std_out.decode('utf-8')) print(std_err.decode('utf-8')) sk.close()
第二种方法使用struct
struct的应用
""" 'i'-> int 作用:把数字转换成固定4个字节的bytes类型 注意: unpack 时,要使用pack的返回值,unpack的是一个tuple,需要取第一个值 """ import struct a = struct.pack('i', 1234567) print(a) b = struct.unpack('i', a)[0] print(b, type(b)) """ b'x87xd6x12x00' 1234567 <class 'int'> """
解决黏包方法实现,每一次执行一次,对比上面的方法,少一次网络延迟
""" Server端,接收pack的数据,unpack """ import socket import struct sk = socket.socket() sk.bind(('127.0.0.1', 8010)) sk.listen() connect, addr = sk.accept() while 1: cmd = input('>>>') connect.send(cmd.encode('gbk')) if cmd == 'q': break num = connect.recv(4) b = struct.unpack('i', num)[0] ret = connect.recv(b) print(ret.decode('gbk')) connect.close() sk.close()
""" Client端,将数据的长度pack,并传输 """ import socket import subprocess import struct sk = socket.socket() sk.connect(('127.0.0.1', 8010)) while 1: 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() new_len = len(std_out) + len(std_err) res = struct.pack('i', new_len) sk.send(res) sk.send(std_out) sk.send(std_err) print(std_out.decode('gbk')) print(std_err.decode('gbk')) sk.close()
简单的文件下载
注意:文件的读写速度不一样,读的速度远大于写
""" Server端 接收端 bytes->str->dict """ import socket import struct import json buff = 1024 sk = socket.socket() sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk.bind(('127.0.0.1', 8010)) sk.listen() connect, addr = sk.accept() pack_len = connect.recv(4) head_len = struct.unpack('i', pack_len)[0] head_bytes = connect.recv(head_len) head_str = head_bytes.decode('utf-8') head = json.loads(head_str) print(head) file_size = head['file_size'] with open(file=head['filename'], mode='wb') as f: while file_size: if file_size >= buff: context = connect.recv(buff) f.write(context) file_size -= buff print(file_size) else: try: context = connect.recv(file_size) f.write(context) except TypeError: print('integer argument expected, got float') break connect.close() sk.close()
""" Client端 发送端 dict->str->bytes """ import socket import os import json import struct buff = 1024 sk = socket.socket() sk.connect(('127.0.0.1', 8010)) # 设置文件报头,dict head = {'filepath': r'D:Temp', 'filename': r'test.mp4', 'file_size': None} file_path = os.path.join(head['filepath'], head['filename']) file_size = os.path.getmtime(file_path) head['file_size'] = file_size # dict ->str head_str = json.dumps(head) # str -> bytes head_bytes = head_str.encode('utf-8') # 将长度,转换成固定长度的bytes类型 pack_len = struct.pack('i', len(head_bytes)) sk.send(pack_len) sk.send(head_bytes) print(file_path) with open(file=file_path, mode='rb') as f: while file_size: if file_size >= buff: context = f.read(buff) sk.send(context) file_size -= buff print(file_size) else: try: context = f.read() sk.send(context) except TypeError: print('integer argument expected, got float') break sk.close()