zoukankan      html  css  js  c++  java
  • Python全栈之路系列----之-----网络编程(粘包与命令执行/数据传输)

    粘包

    什么是粘包

    1. 只有tcp有粘包现象,udp永远不会粘包
    2. 所谓粘包问题,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
    3. tcp协议会根据自身特性,将间隔时间短和数据量小的数据,合并成一个数据块,然后进行封包和算法优化后进行流式发送到客户端
    4. tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住

    粘包产生的原因

    1. 发送端(服务端):发送数据时间间隔很短,数据量小,会合到一起,产生粘包,要等缓冲区满才发送出去
    2. 接收端(客户端):服务端发送一段数据,客户端只接收一小部分(限流,1024),客户端下次在接收的时候还是从缓存区拿上次遗留的数据,产生粘包

    拆包的发生情况

    当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。

    补充问题一:为何tcp是可靠传输,udp是不可靠传输

    基于tcp的数据传输请参考我的另一篇文章http://www.cnblogs.com/linhaifeng/articles/5937962.html,tcp在数据传输时,发送端先把数据发送到自己的缓存中,然后协议控制将缓存中的数据发往对端,对端返回一个ack=1,发送端则清理缓存中的数据,对端返回ack=0,则重新发送数据,所以tcp是可靠的

    而udp发送数据,对端是不会返回确认信息的,因此不可靠

    补充问题二:send(字节流)和recv(1024)及sendall

    recv里指定的1024意思是从缓存里一次拿出1024个字节的数据

     

    send的字节流是先放入己端缓存,然后由协议控制将缓存内容发往对端,如果待发送的字节流大小大于缓存剩余空间,那么数据丢失,用sendall就会循环调用send,数据不会丢失

     

    解决粘包问题*

    • 为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据

    • struct模块:  该模块可以把一个类型,如数字,转成固定长度的bytes     >>>>struct.pack('i',1111111111111)
    • 问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据
    • 我们可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)
    1. 发送时:
    2. 先发报头长度
    3. 再编码报头内容然后发送最后发真实内容
    4. 接收时
    5. 先收报头长度,用struct取出来
    6. 根据取出的长度收取报头内容,然后解码,反序列化
    7. 从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容

    基于通道的双向连接和标准输入,标准错误,来发送数据的描述信息(长度)来实现命令的执行和解决粘包

    import socket
    import struct
    #1、买手机
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    #2、打电话
    phone.connect(('127.0.0.1',8091))
    
    #3、发收消息
    while True:
        cmd=input('>>: ').strip()
        if cmd == 'quit':break
        if not cmd:continue
        phone.send(cmd.encode('utf-8'))
        #先收报头
        header=phone.recv(4)
        total_size=struct.unpack('i',header)[0]
    
        #循环收:10241
        total_data=b''
        recv_size=0
        while recv_size < total_size:
            recv_data=phone.recv(1024)
            total_data+=recv_data
            recv_size+=len(recv_data)
        print(total_data.decode('gbk'))
    
    #4、挂电话
    phone.close()
    客户端1.0
    import socket
    import subprocess
    import struct
    #1、买手机
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    #2、绑定手机卡
    # phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加
    phone.bind(('127.0.0.1',8091))
    
    #3、开机
    phone.listen(5)
    
    #4、等电话连接
    print('starting...')
    while True: #连接循环
        conn,addr=phone.accept()
        print('IP:%s,PORT:%s' %(addr[0],addr[1]))
    
        #5、收发消息
        while True: #通信循环
            try:
                cmd=conn.recv(1024) #最大收1024
                if not cmd:break #针对linux
                #执行命令
                obj=subprocess.Popen(cmd.decode('utf-8'),
                                 shell=True,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE)
                stdout=obj.stdout.read()
                stderr=obj.stderr.read()
                #发送数据的描述信息:长度
                header=struct.pack('i',len(stdout)+len(stderr))
                conn.send(header)
                #发送真实数据
                conn.send(stdout)
                conn.send(stderr)
            except Exception:
                break
    
        #6、挂电话
        conn.close()
    
    #7、关机
    phone.close()
    服务端1.0

    基于自定义报头和struct模块来彻底解决粘包问题

    import socket
    import struct
    import json
    #1、买手机
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    #2、打电话
    phone.connect(('127.0.0.1',8092))
    
    #3、发收消息
    while True:
        cmd=input('>>: ').strip()
        if cmd == 'quit':break
        if not cmd:continue
        phone.send(cmd.encode('utf-8'))
        #先收报头长度
        obj=phone.recv(4)
        header_size=struct.unpack('i',obj)[0]
    
        #再收报头
        header_bytes=phone.recv(header_size)
        header_json=header_bytes.decode('utf-8')
        header_dic=json.loads(header_json)
        print(header_dic)
        #最后循环收真实的数据
        total_size=header_dic['total_size']
        filename=header_dic['filename']
    
        total_data=b''
        recv_size=0
        with open(filename,'wb') as f:
            while recv_size < total_size:
                recv_data=phone.recv(1024)
                total_data+=recv_data
                recv_size+=len(recv_data)
        print(total_data.decode('gbk'))
    
    #4、挂电话
    phone.close()
    客户端2.0
    import socket
    import subprocess
    import struct
    import json
    #1、买手机
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    #2、绑定手机卡
    # phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加
    phone.bind(('127.0.0.1',8092))
    
    #3、开机
    phone.listen(5)
    
    #4、等电话连接
    print('starting...')
    while True: #连接循环
        conn,addr=phone.accept()
        print('IP:%s,PORT:%s' %(addr[0],addr[1]))
    
        #5、收发消息
        while True: #通信循环
            try:
                cmd=conn.recv(1024) #最大收1024
                if not cmd:break #针对linux
    
                #执行命令
                obj=subprocess.Popen(cmd.decode('utf-8'),
                                 shell=True,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE)
                stdout=obj.stdout.read()
                stderr=obj.stderr.read()
                #制作报头
                header_dic = {'filename': 'a.txt',
                              'total_size': len(stdout)+len(stderr),
                              'md5': 'asdfa123xvc123'}
    
                header_json = json.dumps(header_dic)
    
                header_bytes = header_json.encode('utf-8')
    
    
                #先发报头的长度
                conn.send(struct.pack('i',len(header_bytes)))
                #再发送报头
                conn.send(header_bytes)
                #最后发送真实数据
                conn.send(stdout)
                conn.send(stderr)
            except Exception:
                break
    
        #6、挂电话
        conn.close()
    
    #7、关机
    服务端2.0

    文件上传

    import socket
    import os
    
    # 创建一个socket对象
    obj = socket.socket()
    
    # 服务端的IP和端口
    obj.connect(('127.0.0.1', 6542))
    
    # 用os模块获取要传送的文件总大小
    size = os.stat("old_file.txt").st_size
    
    # 把文件总大小发送给服务端
    obj.sendall(bytes(str(size), encoding="utf-8"))
    
    # 接受服务端返回的信息
    obj.recv(1024)
    
    # 以rb的模式打开一个要发送的文件d
    with open("old_file.txt", "rb") as f:
    
        # 循环文件的所有内容
        for line in f:
    
            # 发送给服务端
            obj.sendall(line)
    
    # 关闭退出
    客户端
    import socket
    # 创建一个socket对象
    sk = socket.socket()
    # 允许连接的IP和端口
    sk.bind(('127.0.0.1', 6542))
    # 最大连接数
    sk.listen(5)
    
    while True:
        # 会一直阻塞,等待接收客户端的请求,如果有客户端连接会获取两个值,conn=创建的连接,address=客户端的IP和端口
        conn, address = sk.accept()
    
        # 客户端发送过来的文件大小
        file_size = str(conn.recv(1024),encoding="utf-8")
    
        # 给客户端发送已经收到文件大小
        conn.sendall(bytes("ack", encoding="utf-8"))
    
        # 文件大小转换成int类型
        total_size = int(file_size)
    
        # 创建一个默认的值
        has_recv = 0
    
        # 打开一个新文件,以wb模式打开
        f = open('new_file.txt', 'wb')
    
        # 进入循环
        while True:
    
            # 如果传送过来的大小等于文件总大小,那么就退出
            if total_size == has_recv:
                break
    
            # 接受客户端发送过来的内容
            data = conn.recv(1024)
    
            # 写入到文件当中
            f.write(data)
    
            # 现在的大小加上客户端发送过来的大小
            has_recv += len(data)
    
        # 关闭
        f.close()
    服务端

    文件下载

    from socket import *
    import struct
    import json
    import hashlib
    
    download_dir=r'D:\'
    
    client=socket(AF_INET,SOCK_STREAM)
    client.connect(('127.0.0.1',8000))
    
    while True:
        cmd=input('>>: ').strip() #get a.txt
        if not cmd:continue
        client.send(cmd.encode('utf-8'))
        #先收报头长度
        obj=client.recv(4)
        header_size=struct.unpack('i',obj)[0]
    
        #再收报头
        header_bytes=client.recv(header_size)
        header_json=header_bytes.decode('utf-8')
        header_dic=json.loads(header_json)
    
        filename=header_dic['filename']
        abs_path=r'%s\%s' %(download_dir,filename)
        size=header_dic['size']
        print(header_dic)
        #再收真实数据
        recv_size=0
        with open(abs_path,'wb') as f:
            m=hashlib.md5()
            while recv_size < size:
                line=client.recv(1024)
                f.write(line)
                m.update(line)
                recv_size+=len(line)
        client_md5=m.hexdigest()
        #最后收md5值
        server_md5=client.recv(1024).decode('utf-8')
        if client_md5 !=  server_md5:
            os.remove(abs_path)
            print('文件已损坏,请重写下载')
    
    
    
    client.close()
    客户端(md5)
    from socket import *
    import os
    import json
    import struct
    import hashlib
    
    server=socket(AF_INET,SOCK_STREAM)
    server.bind(('127.0.0.1',8000))
    server.listen(5)
    
    
    def get(filepath,conn):
        #制作报头
        header_dic={
            'filename':os.path.basename(filepath),# C:\\1.png
            'size':os.path.getsize(filepath),
        }
        header_json=json.dumps(header_dic)
        header_bytes=header_json.encode('utf-8')
    
        #先发送报头的长度
        conn.send(struct.pack('i',len(header_bytes)))
    
        #再发送报头
        conn.send(header_bytes)
    
        #再发送真实的数据
        with open(filepath,'rb') as f:
            m=hashlib.md5()
            for line in f:
                conn.send(line)
                m.update(line)
        #最后发送md5值
        md5=m.hexdigest()
        print(md5)
        conn.send(md5.encode('utf-8'))
    while True:
        conn,addr=server.accept()
        while True:
            try:
                data=conn.recv(1024)
                cmd,filepath=data.decode('utf-8').split() #get C:\\1.png
                if cmd == 'get':
                    get(filepath,conn)
                if not data:break
    
            except Exception as e:
                print(e)
                break
        conn.close()
    
    server.close()
    服务端(md5)

    UDP套接字

    from socket import *
    
    client=socket(AF_INET,SOCK_DGRAM)
    
    while True:
        client.sendto(b'hello',('127.0.0.1',8082))
        data,server_addr=client.recvfrom(1024)
        print(data.decode('utf-8'))
    客户端
    from socket import *
    import os
    
    
    print(os.getpid())
    server=socket(AF_INET,SOCK_DGRAM)
    server.bind(('127.0.0.1',8080))
    
    while True:
        data,client_addr=server.recvfrom(1024)
        server.sendto(data.upper(),client_addr)
    
    server.close()
    服务端

    多道基础预习

    http://www.cnblogs.com/zgd1234/p/7155523.html

  • 相关阅读:
    C:把算术表达式分成Token
    JavaScript数据结构——链表的实现与应用
    JavaScript数据结构——队列的实现与应用
    JavaScript数据结构——栈的实现与应用
    由“RangeError: Invalid status code: 0”错误所引发的思考
    工作是最好的投资——图书摘录
    Node.js在指定的图片模板上生成二维码图片并附带底部文字说明
    苹果手机对网页上样式为position:fixed的弹窗支持不好的解决办法
    自定义react数据验证组件
    Ubuntu 18.04中截图工具Shutter的编辑按钮不可用的解决办法
  • 原文地址:https://www.cnblogs.com/zgd1234/p/7597735.html
Copyright © 2011-2022 走看看