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"'\x00\x00\x00"
res = struct.unpack("i", bs)  # 解包,解包后得到一個數組,取第一個元素即可
print(res)
msg_bs_len = res[0]
print(msg_bs_len)

執行結果:

b"'\x00\x00\x00"
(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、以上例子都是最簡單的一發一收的過程,並且一次發送的數據量都非常的小。

 

如果遇到一次發送數據比較大的時候,應該怎么辦呢?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM