zoukankan      html  css  js  c++  java
  • 六. 网络编程(解决黏包TCP)

    一 .解决黏包TCP(

     1.解决方案一

    问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,
    如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。
    存在的问题:
    程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗
    刚刚的方法,问题在于我们我们在发送
    
    我们可以借助一个模块,这个模块可以把要发送的数据长度转换成固定长度的字节。
    这样客户端每次接收消息之前只要先接受这个固定长度字节的内容看一看接下来要接收的信息大小,
    那么最终接受的数据只要达到这个值就停止,就能刚好不多不少的接收完整的数据了
    server
    
    import  socket
    server=socket.socket()
    server.bind(("192.168.59.1",8100))
    server.listen(5)
    conn,addr=server.accept()
    while True:
            aa = input("请输入命令:").strip()
            conn.send(aa.encode("gbk"))
            num = conn.recv(1024).decode("utf-8")
            conn.send(b"ok")
            res = conn.recv(int(num)).decode("gbk")
            print(res,"1111111111111111111111111111111111111111111111111")
    
    conn.close()
    server.close()
    client
    
    import socket
    import  subprocess
    client=socket.socket()
    while True:
        client.connect(("192.168.59.1", 8100))
        cmd = client.recv(1024).decode("utf-8")
        res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
        stdout = res.stdout.read()
        stderr = res.stderr.read()
        client.send(str(len(stderr) + len(stdout)).encode("utf-8"))
    client.recv(1024) client.send(stdout) client.send(stderr) client.close()
    上面代码
    
    好处:
    确定了我们到底要接收多大的数据
    要在文件中配置一个配置项 就是每一次 recv 的大小   bufer=4096 最大
    当我们要发生大数据时候 要明确知道发送的数据大小 告诉对方方便准确的接收所以数据 也就是这里的 recv
    
    应用场景:
     多用于文件传输过程中
     大文件传输 一定是按照字节读取 每一次读固定字节
     传输的过程中 一边读 一边传 接收端一边收 一边写
     send 这个文件之前 35000字节   send(4096)  35000-4069-4039----->0
     recv 这个文件 recv 35000字节 recv(2048)      35000-2048 ---->0
    
    不好:
    的地方 多了一次交互
    send  sendto  交互过多会导致 程序内存管理 占用过多

     二 .解决黏包TCP struct模块(二)

    1.该模块可以把一个类型,如数字,转成固定长度的bytes

     # struct 该模块可以把一个类型,如数字,转成固定长度的bytes
    import struct
    # 借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。
    # 为什么 要转换成固定长度的bytes
     #  aa = struct.pack('i') i 代表int 就是即将要把一个数字转换成固定长度bytes类型
    
    #一次性发送4096
    #       发送时                                                接收时
    # 先发送struct转换好的数据长度4字节          先接受4个字节使用struct转换成数字来获取要接收的数据长度
    # 再发送数据                              再按照长度接收数据
    
    ret=struct.pack('i',4096)      #     i 代表int 就是即将要把一个数字转换成固定长度bytes类型      打包
    print(ret)  # b'x00x10x00x00'
    
    num=struct.unpack('i',ret)    #  struct.unpack 解包 的结果一定是元组
    print(num)   # (4096,)
    print(num[0]) #  4096
    # 如果发送数据时候
    # 先发送长度   先接收长度
     SERVER
    
    # TCP 黏包
        # 连续 send 两个小数据
        # 两个 recv 第一个特别小
        # 本质: 你不知道到底接收多大的数据
    # 解决问题 例如
    # tcp 完美解决了黏包问题
    import socket
    import struct
    server=socket.socket()
    server.bind(('127.0.0.1',8700))
    server.listen(5)
    conn, addr =server.accept()
    while True:
          cmd=input("》》》")
          if cmd=="q":
              conn.send(b'q')
              break
          conn.send(cmd.encode("gbk"))
          num=conn.recv(4)      # 4
    
          nn=struct.unpack('i',num)[0]  # 2048
          res=conn.recv(int(nn)).decode("gbk")   # 2048
          print(res)
    conn.close()
    server.close()
    CLIENT
    
    
    import socket
    import subprocess
    import struct
    
    s=socket.socket()
    s.connect(('127.0.0.1',8700))
    
    while True:
       cmd=s.recv(1024).decode('gbk')
       if cmd=="q":
           break
       res = subprocess.Popen(cmd, shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
       std_err = res.stderr.read()
       std_out = res.stdout.read()
    
       len_a=len(std_out)+len(std_err)
    
       num_by=struct.pack('i',len_a)
    
       ret=s.send( num_by)   # 4    2048
       s.send(std_err)# 1024
       s.send(std_out) #1024  4+2048
       print(ret)
    s.close()

                                                                                       

    2. 自定制报头(使用struct)

    协议  报文 报头
    
    文件传输
    
      文件名字
    
      文件大小
    
      文件类型
    
      存储路径
    
    例如:
    head={'filemane':'tex.txt','filesize':60000,'filetype':'txt','filepath':r'userin'}
          
    报头 长度                  接收4个字节
    
    send(head) 报头              根据这4个字节获取报头
       
    send(file)报文                 从报头中获取filesize 然后根据filesize接收文件
    
    
    协议就是一堆报文和报头  ------字节
    SERVER   视频上传
    
    import  socket
    import struct
    import json
    buffer=1024   # 4096
    server=socket.socket()
    server.bind(('127.0.0.1',8600))
    server.listen()
    conn,addr=server.accept()
    
    # 接收
    head_len=conn.recv(4) 注意接收到的数据是元祖
    
    datel=struct.unpack('i',head_len)[0]   
    
    json_head=conn.recv(datel).decode("utf-8")
    
    head=json.loads(json_head)
    
    filesize=head['filesize']
    with open(head['filename'],'wb')as f:
        while filesize:
            print(filesize)
            if  filesize>=buffer:
                content=conn.recv(buffer)
                f.write(content)
                filesize-=buffer
            else:
                content=conn.recv(filesize)
                f.write(content)
                break
    
    conn.close()
    server.close()
    CLIENT   视频上传
    
    # 发送端
    import os
    import socket
    import json
    import  struct
    sk=socket.socket()
    sk.connect(('127.0.0.1',8600))
    buffer=1024  # 4096
    # 发送文件
    
    head={'filepath':r'D:Vidoevide',
          'filename':r'aa.mp4',
          'filesize':None
          }
    
    file_path=os.path.join(head['filepath'],head['filename'])
    
    filesize=os.path.getsize(file_path)
    
    head['filesize']=filesize
    
    json_head=json.dumps(head)     #  把字典转换成了str
    # print(json_head)
    # print(type(json_head))
    bytes_head=json_head.encode("utf-8")  #  字符串转换成bytes
    # print(bytes_head)
    # print(type(bytes_head))
    
    
    head_len=len(bytes_head) # 计算hend长度
    # print(head_len)
    pack_len=struct.pack('i',head_len)
    # print(pack_len)
    
    sk.send(pack_len)  # 先发送报头的长度  注意发送过去是元祖类型数据
    sk.send(bytes_head)   # 再发送bytes类型的报头
    with open(file_path,'rb')as f:
        while filesize:
          print(filesize)
          if filesize>=buffer:
            content = f.read(buffer)  # 每次读出的内容
            sk.send(content)
            filesize-=buffer
          else:
            content=f.read(filesize)
            sk.send(content)
            break
    
    sk.close()
    
    # 5.31 MB (5,575,110 字节)  原视频
    # 5.16 MB (5,415,366 字节)  上传的视频

    文件上传案例 

    server1  不黏包 多发送一次send  方法一
    import  socket
    import  json
    import  os
    server=socket.socket()
    server.bind(("192.168.59.1",8600))
    server.listen(5)
    conn,addr=server.accept()
    
    ret_str=conn.recv(1024).decode("utf-8")
    ret=json.loads(ret_str)
    
    file_name=ret.get("filename")
    action=ret.get("action")
    file_size=ret.get("file_size")
    conn.sendall(b"200")
    
    with open(file_name,"wb") as f1:
        conn_len = 0
        while file_size>conn_len:
            msg=conn.recv(1024)
            conn_len+=len(msg)
            f1.write(msg)
            print(f'文件大写{file_size}---文件传输大小{conn_len}')
    
    print("读取失败了")
    client1  不黏包 多发送接收次recv'  方法一
    import  socket
    import json
    import os
    
    client=socket.socket()
    client.connect(("192.168.59.1",8600))
    while True:
        cmd=input("请输入命令:")
        action,filename=cmd.strip().split(" ")
        flie_size=os.path.getsize(filename)
        ret={"action":action,"filename":filename,"file_size":flie_size}
    
        ret_str=json.dumps(ret).encode("utf-8")
        client.sendall(ret_str)
    
        code=client.recv(1024).decode("utf-8")
        if code=="200":
            with open(filename,"rb")as f1:
                for line in  f1:
                    client.sendall(line)
    
        else:
            print("接收失败啦啦啦啦")
    server使用 struct
    
    import  json,socket,struct
    ser=socket.socket()
    ser.bind(("192.168.59.1",8600))
    ser.listen(5)
    conn,addr=ser.accept()
    ret=conn.recv(4)
    pik=struct.unpack('i',ret)[0]
    ret_b=conn.recv(pik).decode("utf-8")
    ret_len=json.loads(ret_b)
    
    filename=ret_len.get("filename")
    action=ret_len.get("action")
    file_size=ret_len.get("file_size")
    buff=0
    with open("tup/"+filename,"wb") as f1:
        while file_size>buff:
            cont=conn.recv(1024)
            buff+=len(cont)
            f1.write(cont)
    client使用 struct
    import socket cli=socket.socket() import os import json,struct cli.connect(("192.168.59.1",8600)) cmd=input("输入指令和文件:") # act aa.jpg action,filename=cmd.strip().split(" ") file_size=os.path.getsize(filename) ret={ "action":action, "filename":filename, "file_size":file_size } ret_str=json.dumps(ret).encode("utf-8") ret_pik=struct.pack("i",len(ret_str)) cli.sendall(ret_pik) cli.sendall(ret_str) with open(filename,"rb")as f1: for line in f1: cli.sendall(line)

    文件上传(使用hashlib 验证文件一次性)

    server   aa 11.txt
    
    import  json,socket,struct,hashlib
    ser=socket.socket()
    ser.bind(("192.168.59.1",8600))
    ser.listen(5)
    conn,addr=ser.accept()
    ret=conn.recv(4)
    pik=struct.unpack('i',ret)[0]
    ret_b=conn.recv(pik).decode("utf-8")
    ret_len=json.loads(ret_b)
    
    filename=ret_len.get("filename")
    action=ret_len.get("action")
    file_size=ret_len.get("file_size")
    buff=0
    md=hashlib.md5()
    with open("tup/"+filename,"wb") as f1:
        while file_size>buff:
            cont=conn.recv(1024)
            buff+=len(cont)
            f1.write(cont)
            md.update(cont)
    
    print("接收成功")
    conn.sendall(b"ok")
    print(md.hexdigest())
    val_md=md.hexdigest()
    cli_md5=conn.recv(1024).decode("utf-8")
    if cli_md5==val_md:
        conn.sendall(b"203")
    else:
        conn.sendall(b"208")
    client       aa  11.txt
    
    import hashlib
    import socket,json,os
    cli=socket.socket()
    import os
    import json,struct
    cli.connect(("192.168.59.1",8600))
    
    cmd=input("输入指令和文件:") # act aa.jpg
    action,filename=cmd.strip().split(" ")
    file_size=os.path.getsize(filename)
    ret={
        "action":action,
        "filename":filename,
        "file_size":file_size
    
    }
    md5=hashlib.md5()
    ret_str=json.dumps(ret).encode("utf-8")
    ret_pik=struct.pack("i",len(ret_str))
    cli.sendall(ret_pik)
    cli.sendall(ret_str)
    
    with open(filename,"rb")as f1:
            for line in f1:
                cli.sendall(line)
                md5.update(line)
    
    data=cli.recv(1024)
    print(data.decode("utf-8"))
    print(md5.hexdigest())
    val_md=md5.hexdigest()
    cli.sendall(val_md.encode("utf-8"))
    is_val=cli.recv(1024).decode("utf-8")
    if is_val=="203":
        print("完整性")
    else:
        print("文件上次失败")

    总结

         可以通过struct模块来定制协议
    解决黏包 问题
    
      为什么会出现黏包现象
      首页只有在tcp协议中才会黏包现象
    
      只是因为tcp协议是面向流的协议
    
      在发送的数据传输过程中还有缓存机制来避免数据丢包
    
      因此 在连续发送小数据的时候 以及接收大小不符的时候容易黏包 现象
    
      本质 还是因为我们在接收数据的时候不知道发送的数据的长短
    
    
    解决黏包问题
    
        在传输大量数据之前先告诉接收端要发送的数据长短
    
         可以通过struct模块来定制协议
    
    struct 模块
        pack   unpack
    
       模式  i
          pack之后的长度  4个字节
         unpack 之后娜到的数据是一个元组 元组的第一个值是pack的值
     
  • 相关阅读:
    自定义事件 Event 、CustomEvent的使用
    移动端适配方案总结
    @media screen媒体查询实现页面自适应布局
    判断页面所有图片加载完成后执行操作
    JQ选择器篇2
    JQ 选择器篇1
    sql 日期转换字符大法
    SQL server从入门精通----3种分页
    SQL server从入门精通----触发器
    SQL server从入门精通---- 事务
  • 原文地址:https://www.cnblogs.com/Sup-to/p/11137325.html
Copyright © 2011-2022 走看看