网络编程之粘包

粘包:
传输层协议有tcp和udp两种
tcp:Transmission Control Protocol
传输控制协议,基于数据流,收发的消息不能为空,需要在客户端和服务端都添加空消息的处理机制
tcp是可靠性协议,数据的收发都需要确认信息,这就降低了传输效率,故为了减少确认次数,tcp采用了nagle算法
将多次间隔短且数据小的数据合成一个数据流,然后发送,tcp的数据没有明确的界限,无法区分数据的开始和结束,
这就导致了可能将多个信息并为一条信息,也就是粘包
还有一种粘包情况是当一个数据包过大时,操作系统的缓存只会提供一部分数据给应用程序,导致我们只读了包的一部分内容,其余内容当我们再次接收时会发送过来

综上 tcp粘包问题的产生,是因为传输数据没有具体的界限,所以解决粘包问题需要知道数据的明确长度,当长度小时我们明确接受该长度的数据,当长度过大时,我们采用循环接受的方法来取

具体操作:1.发送方获取消息的具体长度
2.发送方先将该长度发送给接收方
3.接收方通过此长度准确接收消息
上述操作使用了第三方模块struct
struct的作用,pack方法将一个整数,转换成固定长度的bytes类型
unpack方法将一个bytes类型的长度转换为整数
解决粘包问题的核心,给数据流添加自定义报头
报头可以存放发送时间,真实信息大小,通过json模块保存成字典或列表,以便我们可以更方便的取值
具体代码(以cmd实现客户端系统命令为例子):

服务端

from socket import *
import json
import struct
import subprocess
import datetime
s = socket()#创建套接字对象
s.bind(("127.0.0.1",8888))#绑定ip和端口
s.listen(5)#监听,规定半连接池大小
while True:
    conn,addr = s.accept()#接收客户端连接,三次握手
    while True:
        try:
            cmd = conn.recv(1024)#接收cmd命令
            if not cmd:#linux系统客户端强退时会不停发空消息,故此处为防止客户端突然退出,跨平台性
                conn.close()
                break
            obj  = subprocess.Popen(cmd.decode("utf-8"),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)#使用subprocess模块完成系统命令
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            msg = stdout +stderr

            s_dic = {}#自定义报头
            s_dic["time"] = str(datetime.datetime.now())#添加key  time
            s_dic["size"] = len(msg)#添加key size
            head_data = json.dumps(s_dic)#使用json序列化成json格式的字符串
            head_len = struct.pack("i",len(head_data))#使用struct模块获取报头长度,将整形转换为二进制,固定为4个字节大小
            conn.send(head_len)#先发送报头长,此时使用的“i”模式为4字节,故客户端以4个字节接收,接收后使用struct模块反解出报头信息长准确接收报头信息,
#防止报头信息和具体信息粘包
            conn.send(head_data.encode("utf-8"))#将json格式的字符串转换成二进制,发送报头信息,报头信息的作用,客户端可以通过报头信息获取具体信息长度
            conn.send(msg)#发送具体信息
        except ConnectionResetError:
            conn.close()
            break
客户端:
     c = socket()#创建客户端套接字对象
     c.connect(("127.0.0.1",8888))#链接服务器

 while True:
     cmd = input(">>>:").strip().encode("utf-8")#接收命令
     c.send(cmd)#发送命令给服务器
     lenth = c.recv(4)#接收报头长度
     head_lenth = struct.unpack("i",lenth)[0]#unpack出整形报头信息的长度
     head_data = c.recv(head_lenth)#准确接收报头信息
     dic = json.loads(head_data.decode("utf-8"))#获取报头信息
     print(dic)
     rev_size = 0
     all_data = b""
     while rev_size < dic["size"]:#由报头信息或得的具体信息长
         data = c.recv(1024)#循环接收
         rev_size += len(data)
         all_data += data

     print(all_data.decode("gbk"))

udp协议
user datagram protocol,用户数据报协议
没有链接,面向消息,效率高,udp数据包有报头,故不会产生粘包问题

posted @ 2018-12-26 23:06  robertzhou  阅读(219)  评论(0编辑  收藏  举报