网络编程 - 粘包
1.粘包:
多个包 多个命令的结果 粘到一起了 因为recv 1024限制了 导致的结果
参考:http://www.cnblogs.com/linhaifeng/articles/6129246.html
粘包底层原理分析:
1.运行一个软件 和 哪几个硬件 有关
硬盘 内存 cpu
2.启动程序:
硬盘程序 加载到 内存 启一个软件就占一个内存空间
os 就有一个内存空间
os的内存空间 和 软件的内存空间 彼此互相隔离
须知:只有TCP有粘包现象,UDP永远不会粘包。
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
3.send recv 底层原理
send 应用程序的代码 把自己的数据 发出去 存放到自己的内存空间里
发到os的内存里调网卡发数据
程序的内存和os的内存两个内存互相隔离
数据copy给os的内存
send 发给了 自己的os的内存 os会照着tcp协议去发
recv 通知os 去调网卡 收数据
1.send发到数据到服务端os的内存 # 慢
2.os的内存 copy 给程序 # 快
站在应用程序角度上:
send: 1.数据发给本地的os # 耗时短一些
recv:1.通知os收数据 2.os内存数据copy到应用程序内存中 # 耗时长一些
4.send recv 总结:
1:不管是recv还是send都不是直接接收对方的数据,而是操作自己的操作系统内存--->不是一个send对应一个recv # 一发可以对应多收 一收对应多发
2.:recv:
wait data 耗时非常长
copy data
send:
copy data
3.数据量比较小 时间间隔比较短 就合并成一个包 再发
使用了优化方法(Nagle算法)
5.recv 有关部门建议的不要超过 8192,再大反而会出现影响收发速度和不稳定的情况
服务端
1 ''' 2 解决粘包的办法: 3 明确知道对方给我发的包的长度 4 ''' 5 import time 6 import socket 7 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 8 server.bind(('127.0.0.1',8080)) 9 server.listen(5) 10 print('strating...') 11 12 conn,addr=server.accept() 13 14 # 第一次接收5 15 # res1 = conn.recv(2) 16 # res2 = conn.recv(2) 17 res1 = conn.recv(5) 18 print('第一次:',res1) 19 time.sleep(6) 20 #第二次接收 21 res3 = conn.recv(6).decode('utf-8') 22 print('第二次:',res3) 23 24 ''' 25 第一次: b'helloworld' 26 第二次: b'' 27 ''' 28 29 ''' 30 第一次: b'hello' 31 第二次: b'world' 32 ''' 33 34 ''' 35 第一次: b'h' 36 第二次: b'ello' 37 ''' 38 39 ''' 40 第一次: b'h' 41 第二次: b'elloworld' # 客户端粘了 服务端粘了 42 '''
客户端
1 ''' 2 粘包不一定会发生: 3 数据量比较小 时间比较短 才会发生粘包 4 5 粘包是tcp协议底层优化算法决定的!(Nagle算法) 6 ''' 7 import socket 8 import time 9 client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 10 client.connect(('127.0.0.1',8080)) 11 12 client.send('hello'.encode('utf-8')) 13 time.sleep(5) # 解决了粘包 low 14 client.send('我们'.encode('utf-8')) 15 # 两个粘成一个包
2.简单版 - 解决粘包
strcut 模块的使用
1 import struct 2 import json 3 res = struct.pack('i',429496) 4 print(res,type(res),len(res)) # 数字转成了 bytes 型 5 # b'\xb8\x8d\x06\x00' <class 'bytes'> 4 6 7 # client.recv(4) 8 obj = struct.unpack('i',res) 9 print(obj) 10 11 # res = struct.pack('i',1231213123123) # 数据 不合理 若是发一个文件的话 就有可能 很大 12 13 # q Q d 是8位 i l L 是4位 14 res = struct.pack('d',120000223232123123123121231) 15 print(res,type(res),len(res)) 16 # b'{\x00\x00\x00' <class 'bytes'> 4
参考: http://blog.csdn.net/w83761456/article/details/21171085
http://www.cnblogs.com/linhaifeng/articles/6129246.html
1 header_dic = { 2 'filename': 'a.txt', 3 'md5': 'xxxxxxx', 4 'total_size': 1231231321321232132131232321321321321323221231312123123213213 5 } 6 header_json = json.dumps(header_dic) 7 print(header_json,type(header_json)) 8 header_bytes = header_json.encode('utf-8') 9 print(header_bytes,type(header_bytes)) 10 print(len(header_bytes)) # 会变 11 12 res = struct.pack('i',len(header_bytes)) # 固定长度 13 print(res,len(res)) 14 data = struct.unpack('i',res) 15 print(data)
服务端
1 import subprocess 2 import socket 3 import struct 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 # phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 重用ip和端口 任然存在4次挥手的状态 解决办法 6 phone.bind(('127.0.0.1',8080)) 7 phone.listen(0) 8 print('strating...') 9 while True: 10 conn,client_addr = phone.accept() 11 print(client_addr) 12 13 while True: 14 try: 15 # 1.收命令 16 cmd = conn.recv(8096) # 8096 一次接收完 # 系统规定不能超过8096个 17 if not cmd:break 18 print('客户端数据:',cmd) 19 20 # 2.执行命令,拿到结果 21 obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, # 客户端用 utf-8发的 22 stdout=subprocess.PIPE, 23 stderr=subprocess.PIPE) 24 stdout = obj.stdout.read() 25 stderr = obj.stderr.read() 26 27 # 3.把命令的结果返回给客户端 28 # 第一步:制作固定长度的报头 29 print(len(stdout) + len(stderr)) 30 total_size = len(stdout) + len(stderr) 31 header = struct.pack('i',total_size) 32 # 第二部:把报头发给客户端 33 conn.send(header) 34 # 第三步:再发真实的数据 35 # conn.send(stdout+stderr) # 有效率问题的 这里 之后 可以优化 36 conn.send(stdout) 37 conn.send(stderr) # 这样会自动粘包 比 + 号的效率高 38 except ConnectionResetError: 39 break 40 conn.close() 41 42 phone.close()
客户端
1 ''' 2 粘包现象 解决了 仔细想想 是有问题的 报头里面 应该 包含对真实数据的描述 就不能这样了 3 ''' 4 import socket 5 import struct 6 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 7 phone.connect(('127.0.0.1',8080)) 8 while True: 9 # 1.发命令 10 cmd = input('msg>>>:').strip() # dir ls 11 if not cmd:continue 12 phone.send(cmd.encode('utf-8')) 13 14 # 2.拿到命令的结果,并打印 8096 再大 就没 意义了 15 # 第一步 先收报头 收到有用的信息 16 obj = phone.recv(4) 17 # total_size = 10241 18 # 第二步:从报头中解析出对真实数据的描述 数据的长度 19 total_size = struct.unpack('i', obj)[0] # i 表示 整数 20 # data = phone.recv(1024) # 这里是个坑 有可能会大于1024 接收数据量的最大限制 21 # 第三步:接收真实的数据 22 # data = phone.recv(526) # 这里是个坑 从自己的os的内存里 拿数据 不可能无限大 所以 那个数字不可能无限大 23 # data=phone.recv(526) 24 # data=phone.recv(526) 25 recv_size = 0 26 recv_data = b'' 27 while recv_size < total_size: 28 res = phone.recv(526) 29 recv_data += res 30 recv_size+=len(res) 31 32 print(recv_data.decode('gbk')) 33 34 # print(data.decode('gbk')) # linux:utf-8 windows:gbk 35 36 phone.close()
3.终极版 - 解决粘包
服务端
1 import subprocess 2 import socket 3 import struct 4 import json 5 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 6 phone.bind(('127.0.0.1',8080)) 7 phone.listen(5) 8 print('strating...') 9 while True: 10 conn,client_addr = phone.accept() 11 print(client_addr) 12 13 while True: 14 try: 15 # 1.收命令 16 cmd = conn.recv(8096) # 8096 一次接收完 17 if not cmd:break 18 print('客户端数据:',cmd) 19 20 # 2.执行命令,拿到结果 21 obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, # 客户端用 utf-8发的 22 stdout=subprocess.PIPE, 23 stderr=subprocess.PIPE) 24 stdout = obj.stdout.read() 25 stderr = obj.stderr.read() 26 27 # 3.把命令的结果返回给客户端 28 29 # 第一步:制作固定长度的报头 # 将字典转成 str 转成 bytes 用到了序列化 30 header_dic = { 31 'filename':'a.txt', 32 'md5':'xxxxxxx', 33 'total_size': len(stdout)+len(stderr) 34 } 35 header_json = json.dumps(header_dic) 36 header_bytes = header_json.encode('utf-8') # 这里不知道 多长 会粘包!! 37 38 # 第二步先发送报头的长度 39 conn.send(struct.pack('i',len(header_bytes))) 40 41 # 第三步:再发报头 42 conn.send(header_bytes) 43 44 # 第四部:在发真实的数据 45 conn.send(stdout) 46 conn.send(stderr) # 这样会自动粘包 比 + 号的效率高 47 except ConnectionResetError: 48 break 49 conn.close() 50 51 phone.close()
客户端
1 ''' 2 思路: 3 1.处理报头 准备发送的字典 先发报头的长度 再收报头 得到数据的长度 在收数据 4 做字典 能容纳 很多信息量 5 解决了:1.报头信息量少 2.i 格式有限的 解决了 6 ''' 7 import json 8 import socket 9 import struct 10 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 11 phone.connect(('127.0.0.1',8080)) 12 while True: 13 # 1.发命令 14 cmd = input('msg>>>:').strip() # dir ls 15 if not cmd:continue 16 phone.send(cmd.encode('utf-8')) 17 18 # 2.拿到命令的结果,并打印 19 # 第一步 先收报头的长度 20 obj = phone.recv(4) 21 header_size = struct.unpack('i',obj)[0] 22 23 # 第二步:在收报头 24 header_bytes = phone.recv(header_size) 25 26 # 第三步:从报头中解析出对真实数据的描述 27 header_json = header_bytes.decode('utf-8') 28 header_dic = json.loads(header_json) 29 print(header_dic) 30 total_size = header_dic['total_size'] 31 32 # 第四步:接收真实的数据 33 recv_size = 0 34 recv_data = b'' 35 while recv_size < total_size: 36 res = phone.recv(526) 37 recv_data += res 38 recv_size+=len(res) 39 40 print(recv_data.decode('gbk')) # linux:utf-8 windows:gbk 41 42 43 phone.close()