zoukankan      html  css  js  c++  java
  • python3 使用struct模块解决tcp黏包

    struct模块是如何使用的呢?

    import struct
    
    
    msg = "我正在学习python的网络编程。"
    msg_bs = msg.encode("utf-8")  # 将数据编码转为字节
    res = struct.pack("i", len(msg_bs))  # 将字节数据的长度打包成固定长度(4)
    print(res)
    
    bs = b"'x00x00x00"
    res = struct.unpack("i", bs)  # 解包,解包后得到一个数组,取第一个元素即可
    print(res)
    msg_bs_len = res[0]
    print(msg_bs_len)

    执行结果:

    b"'x00x00x00"
    (39,)
    39

    注意:

      这里的i是int的意思,4个字节,就是4*8=32位,2**32次方就是可以打包的长度。也就是可以一次满足4G大小数据的打包。

    看一组使用struct模块的tcp通信流程

    ---------------------------------------------struct_server.py-------------------------------------------
    
    # coding:utf-8
    import struct
    import socket
    import subprocess
    
    server = socket.socket()
    ip_port = ("127.0.0.1", 8001)
    server.bind(ip_port)
    server.listen(5)
    conn, addr = server.accept()
    while 1:
        from_client_msg = conn.recv(1024)
        print("来自客户端的消息:", from_client_msg.decode("utf-8"))
        if not from_client_msg.decode("utf-8"):
            print("stop")
            break
        res = subprocess.Popen(
            from_client_msg.decode("utf-8"),
            shell=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        cmd_result = res.stderr.read()
        if not cmd_result:
            cmd_result = res.stdout.read()
        cmd_result_len = len(cmd_result)
        print("一会要发送数据的真实长度:", cmd_result_len)
        conn.send(struct.pack('i', cmd_result_len))  # 先发送struct打包后的数据长度
        conn.sendall(cmd_result)  # 再发送真实数据
    
    conn.close()
    server.close()
    ---------------------------------------------struct_client.py-------------------------------------------
    
    # coding:utf-8
    import socket
    import struct
    
    client = socket.socket()
    ip_port = ("127.0.0.1", 8001)
    client.connect(ip_port)
    
    while 1:
        cmd = input(">>>: ").strip()
        if not cmd:
            break
        client.send(cmd.encode("utf-8"))  # 发送dir或ipconfig命令
        from_server_msg = client.recv(4)  # 先接收struct打包的后数据长度
        from_server_msg_len = struct.unpack('i', from_server_msg)  # struct解包
        from_server_msg_len = from_server_msg_len[0]  # 获取真实数据的长度
        print("来自服务端的消息:", from_server_msg, from_server_msg_len)
        from_server_real_msg = client.recv(1024)  # 再接收真实数据
        real_msg_len = len(from_server_real_msg)  # 获取第一次获取真实数据的长度
        while from_server_msg_len > real_msg_len:  # 如果一次没有获取完真实数据
            from_server_real_msg += client.recv(1024)  # 再次接收真实数据
            real_msg_len = len(from_server_real_msg)
    
        print("来自服务端的消息:", from_server_real_msg.decode("gbk"))
    
    client.close()

    总结:

      1、使用struct模块先把要发送的数据打包成固定长度(4)的字节发送出去,再发送数据。

    再看一组struct模块使用的实例

    # tcp_server.py
    import socket
    import struct
    
    
    sk = socket.socket()  # 创建socket对象
    sk.bind(("127.0.0.1", 9998))  # 绑定IP和端口
    sk.listen()  # 开启监听
    conn, address = sk.accept()  # 等待客户端连接 阻塞
    while 1:  # 让服务端和客户端循环通信
        send_msg = input(">>>:").strip()  # 要发送的消息
        send_msg_bs = send_msg.encode("utf-8")  # 消息编码为字节
        conn.send(struct.pack("i", len(send_msg_bs)))  # 先发送消息的长度(打包成固定4个字节长度发送出去)
        conn.send(send_msg_bs)  # 再发送消息给客户端
        if send_msg.upper() == "BYE":  # 如果发送的消息是BYE就退出循环
            break
        msg_len = struct.unpack("i", conn.recv(4))[0]  # 先接收4个字节的消息长度
        recv_msg = conn.recv(msg_len)  # 再接收消息
        print("来自客户端的消息:", recv_msg.decode("utf-8"))
        if recv_msg.decode("utf-8").upper() == "BYE":  # 如果来自客户端的消息是BYE就退出循环
            break
    conn.close()  # 关闭conn连接
    sk.close()  # 关闭sk连接
    # tcp_client.py
    import socket
    import struct
    
    
    sk = socket.socket()  # 创建socket对象
    sk.connect(("127.0.0.1", 9998))  # 连接服务端
    while 1:  # 服务端和客户端循环通讯
        msg_len = struct.unpack("i", sk.recv(4))[0]  # 先接收消息长度
        recv_msg = sk.recv(msg_len)  # 再接收消息
        print("来自服务端的消息:", recv_msg.decode("utf-8"))
        if recv_msg.decode("utf-8").upper() == "BYE":  # 如果来自服务端的消息是BYE就直接退出循环
            break
        send_msg = input(">>>:").strip()  # 要发送给服务端的消息
        send_msg_bs = send_msg.encode("utf-8")
        sk.send(struct.pack("i", len(send_msg_bs)))  # 先发送消息的长度
        sk.send(send_msg_bs)  # 再发送消息
        if send_msg.upper() == "BYE":  # 如果发送的消息是BYE就直接退出循环
            break
    sk.close()  # 关闭sk连接

     把发送消息和接收消息封装到函数中

    # server_tcp.py
    import socket
    import struct
    
    
    def my_send(sk, msg):
        """
        发送消息
        :param msg:
        :return:
        """
        msg_bs = msg.encode("utf-8")
        struct_len = struct.pack("i", len(msg_bs))
        sk.send(struct_len)
        sk.send(msg_bs)
    
    
    def my_recv(sk):
        """
        接收来自消息
        :param sk:
        :return:
        """
        msg_len = struct.unpack("i", sk.recv(4))[0]
        msg_bs = sk.recv(msg_len)
        return msg_bs.decode("utf-8")
    
    
    if __name__ == '__main__':
        sk = socket.socket()  # 创建socket对象
        sk.bind(("127.0.0.1", 6666))  # 绑定IP和端口号
        sk.listen()  # 开启监听
        print("开启监听!")
        conn, address = sk.accept()  # 等待客户端连接 阻塞
        print("客户端连接成功!")
        while 1:  # 开始和客户端聊天,以下程序是服务端先发送消息
            send_msg = input(">>>:").strip()
            my_send(conn, send_msg)
            if send_msg.upper() == "BYE":
                break
            msg = my_recv(conn)
            print(f"来自客户端的消息:{msg}")
            if msg.upper() == "BYE":
                break
        conn.close()
        sk.close()
    # client_tcp.py
    import socket
    import struct
    
    
    def my_send(sk, msg):
        """
        发送消息
        :param msg:
        :return:
        """
        msg_bs = msg.encode("utf-8")
        struct_len = struct.pack("i", len(msg_bs))
        sk.send(struct_len)
        sk.send(msg_bs)
    
    
    def my_recv(sk):
        """
        接收来自消息
        :param sk:
        :return:
        """
        msg_len = struct.unpack("i", sk.recv(4))[0]
        msg_bs = sk.recv(msg_len)
        return msg_bs.decode("utf-8")
    
    
    if __name__ == '__main__':
        sk = socket.socket()  # 创建socket对象
        sk.connect(("127.0.0.1", 6666))  # 连接服务端
        while 1:
            recv_msg = my_recv(sk)
            print("来自服务端的消息:", recv_msg)
            if recv_msg.upper() == "BYE":
                break
            send_msg = input(">>>:").strip()
            my_send(sk, send_msg)
            if send_msg.upper() == "BYE":
                break
        sk.close()

    总结:

      1、以上例子都是最简单的一发一收的过程,并且一次发送的数据量都非常的小。

    如果遇到一次发送数据比较大的时候,应该怎么办呢?

  • 相关阅读:
    论文引用标记设置
    悬浮图层特效
    SocketInputStream.socketRead0引起线程池提交任务后,futureTask.get超时
    线程池中的线程何时死亡?
    AppClassLoader
    《Java高并发编程详解-多线程架构与设计》Java Classloader
    Tomcat的类加载器初步认识
    《Java高并发编程详解-多线程架构与设计》Thread API
    《Java高并发编程详解-多线程架构与设计》JVM类加载器
    SpringMVC中的RootWebApplicationContext与ServletWebApplicationContext
  • 原文地址:https://www.cnblogs.com/lilyxiaoyy/p/10930304.html
Copyright © 2011-2022 走看看