• 网络编程之粘包


    粘包:
    传输层协议有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数据包有报头,故不会产生粘包问题

  • 相关阅读:
    (收藏)Wp7开发中文网站
    (收藏)Andriod中文翻译组
    (1) BlackBerry 环境的配置:安装standalone版的BlackBerry安装包
    (2)把BlackBerry作为插件安装到已有的Eclipse中
    (收藏)一个很不错的编程网站
    Android Architecture
    在Eclipse Android中设置模拟器屏幕大小
    (收藏)C#网站
    (Android) Binding to Data with AdapterView
    ListView 的理解
  • 原文地址:https://www.cnblogs.com/robert-zhou/p/10182485.html
Copyright © 2020-2023  润新知