24日目、ソケットの送受信の原則。tcpプロトコルのNagleアルゴリズム。tcpカスタムプロトコルは、スティッキーパケットの問題を解決します。structモジュールの使用。udpプロトコルの使用。同時プログラミングtcpとudpの実装


スティッキーパケットの問題が発生するのはなぜですか?tcpだけにスティッキーパケットがありますが、udpにはスティッキーパケットはありません
。2つの場合、スティッキーパケットが発生します。
1.送信者は、送信する前にバッファがいっぱいになるのを待つ必要があり、スティッキーパケットが発生します(データを送信する時間間隔が非常に短く、データが非常に小さく、それらがマージされてスティッキーパケットになります)
2。受信者はバッファ内のパケットを時間内に受信しないため、複数のパケットを受信します(クライアントはデータの一部を送信し、サーバーはごく一部しか受信しません。サーバーが次に受信するときは、残りの最後のデータを取得します。スティッキーパケットを生成するためのバッファ)

メッセージの送受信の原理
ソケットクライアントはソケットサーバーにメッセージを送信します。最初に、クライアントはメッセージをオペレーティングシステムのキャッシュに送信します。オペレーティングシステムはネットワークカードを呼び出し、それを他のキャッシュに送信します。オペレーティング
システム。キャッシュ内のデータを受信します。データを送信する場合でも受信する場合でも、独自のオペレーティングシステムのキャッシュを処理します。
したがって、送信が受信に対応するわけではありません。
ここに画像の説明を挿入


tcpプロトコルのNagleアルゴリズム: tcpプロトコルは、複数のデータを比較的少量のデータと比較的短い送信間隔で1つのデータブロックに結合し、一度にパケットを送信します。これにより、効率を向上させることができます。
このように、受信側を区別するのが難しいため、スティッキーパケットの問題が発生する可能性があります。

構造体モジュールの使用

import struct

header = struct.pack("i", 12312312)  # struct.pack可以将一个整型打包成bytes类型,并且bytes固定长度为4个字节
print(header)  # b'\xf8\xde\xbb\x00'
print(len(header))  # 4

total_size = struct.unpack("i", header)[0]  # struct.unpack会将打包好的bytes类型,反解成打包前的整型数字(12312312,)
print(total_size)  # 12312312


使用struct.pack中的“i”将int类型打包成bytes类型,数字有长度的限制,bytes的固定长度为4个字节,
使用struct.pack中的“q”将int类型打包成bytes类型,固定长度会增加一些,bytes的固定长度为8个字节

スティッキーパッケージコード表示の問題を解決するためのTcpカスタムプロトコル
int型をバイト型にパッケージ化
するための構造体モジュールのインポートバイト型をint型に分解するための構造体モジュールのインポート

クライアント

import socket
import struct

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))

while True:
    cmd = input("shu ru").strip()
    if len(cmd) == 0:
        continue
    client.send(cmd.encode("utf-8"))

    # 先接收数据的长度
    header = client.recv(4)
    total_size = struct.unpack("i", header)[0]  # struct.unpack会将打包好的bytes类型,反解成打包前的整型数字

    # 再接收真正的数据
    recv_size = 0
    res = b""
    while recv_size < total_size:  # 循环取值,直到把total_size取空为止
        data = client.recv(1024)  # 接收服务端传过来的数据bytes类型
        recv_size += len(data)  # 统计传过来的数据长度
        res += data  # 统计服务端过来的bytes类型的数据

    print(res.decode("gbk"))

サーバ

import socket
import subprocess
import struct

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5)

while True:
    conn, client_addr = server.accept()
    print(client_addr)
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0:
                break
            cmd = data.decode("utf-8")
            obj = subprocess.Popen(cmd, shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE
                                   )
            res1 = obj.stdout.read()
            res2 = obj.stderr.read()
            total_size = len(res1) + len(res2)

            # 先把数据的长度发过去
            header = struct.pack("i", total_size)    # struct.pack可以将一个total_size打包成bytes类型,并且bytes固定长度为4个字节
            conn.send(header)  # 数据的长度

            # 再发送真正的数据
            conn.send(res1)
            conn.send(res2)

        except Exception:
            break
    conn.close()

tcpカスタムプロトコルはパッケージに固執する問題を解決し、アップグレードされたバージョンのコードは、
インポートヘッダーの説明を辞書の形式で表示し、データ型変換用のstructモジュールをインポートし、エンコードとデコード用にjsonモジュールをインポートします

クライアント

粘包问题升级版
import socket
import struct
import json

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))

while True:
    cmd = input("shu ru").strip()
    if len(cmd) == 0:
        continue
    client.send(cmd.encode("utf-8"))

    # 先收4个字节,提取header_bytes的长度
    header_bytes_len = struct.unpack("i", client.recv(4))[0]

    # 再收报头字典header——bytes,提取header_dic
    header_bytes = client.recv(header_bytes_len)
    header_json = header_bytes.decode("utf-8")
    header_dic = json.loads(header_json)
    print(header_dic)
    total_size = header_dic["total_size"]

    # 再接收真正的数据
    recv_size = 0
    res = b""
    while recv_size < total_size:  # 循环取值,直到把total_size取空为止
        data = client.recv(1024)  # 接收服务端传过来的数据bytes类型
        recv_size += len(data)  # 统计传过来的数据长度
        res += data  # 统计服务端传过来的bytes类型的数据

    print(res.decode("gbk"))

サーバ

粘包问题升级版
import socket
import subprocess
import struct
import json

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5)

while True:
    conn, client_addr = server.accept()
    print(client_addr)
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0:
                break
            cmd = data.decode("utf-8")
            obj = subprocess.Popen(cmd, shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE
                                   )
            res1 = obj.stdout.read()
            res2 = obj.stderr.read()

            header_dic = {
    
    
                "filename": "a.txt",
                "total_size": len(res1) + len(res2),
                "md5": "55lala55"
            }
            header_json = json.dumps(header_dic)
            header_bytes = header_json.encode("utf-8")

            # 先发4个字节
            conn.send(struct.pack("i", len(header_bytes)))
            # 再发报头字典
            conn.send(header_bytes)
            # 最后再发送真正的数据
            conn.send(res1)
            conn.send(res2)

        except Exception:
            break
    conn.close()

udpプロトコルの使用
udpはリンクなしで、どちらの端が最初に開始されたかについてエラーは報告されません
。udpプロトコルはこの時間の最大範囲のみを受信し、余分なデータは直接失われます。tcpなどの機能はありません。プロトコルキャッシングとスティッキーパケット。udpプロトコル信頼性の低いプロトコル

クライアント

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)   # udp协议又称为数据报

while True:
    msg = input("shuru").strip()
    client.sendto(msg.encode("utf-8"), ("127.0.0.1", 8080))
    data, client_addr = client.recvfrom(1024)
    print(data.decode("utf-8"))

client.close()

サーバ

import socket

server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)    # udp协议又称为数据报
server.bind(("127.0.0.1", 8080))

while True:
    conn, client_addr = server.recvfrom(1024)
    print(conn.decode("utf-8"))
    server.sendto(conn.upper(), client_addr)

server.close()

並行性と並列
並行性:これは疑似並列です。つまり、同時に実行されているように見えます。単一のCPU +マルチチャネルテクノロジーで並行性を実現できます(並列処理も並行性に属します)
並列:同時に実行し、複数のCPUでのみ並列処理を実現できます

tcpプロトコルで同時機能を実装するためのコード

クライアント

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))

while True:
    msg = input("shu ru")
    if len(msg) == 0:
        continue
    client.send(msg.encode("utf-8"))
    data = client.recv(1024)
    print(data.decode("utf-8"))

socket.close()

サーバ

import socketserver


class MyRequestHandler(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            try:
                data = self.request.recv(1024)    # self.request代表conn,表示连接通道
                print(data.decode("utf-8"))
                if len(data) == 0:
                    break
                self.request.send(data.upper())
            except Exception:
                break
        self.request.close()


server = socketserver.ThreadingTCPServer(("127.0.0.1", 8080), MyRequestHandler)
server.serve_forever()

udpプロトコルで同時機能を実装するためのコード

クライアント

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

while True:
    msg = input("shuru").strip()
    client.sendto(msg.encode("utf-8"), ("127.0.0.1", 8080))
    data, server_addr = client.recvfrom(1024)
    print(data.decode("utf-8"))

サーバ

客户端每发来一条消息,都会起一个线程,会把客户端发来的消息封装到对象里面去,交给一个线程来运行handle方法

import socketserver


class MyRequestHanlder(socketserver.BaseRequestHandler):
    def handle(self):
        data, server = self.request  # data代表客户端发来的数据,server是套接字对象
        server.sendto(data.upper(), self.client_address)  # 服务端给客户端回消息


server = socketserver.ThreadingUDPServer(("127.0.0.1", 8080), MyRequestHanlder)  # 这行代码等同于定义了一个套接字对象,绑定了ip跟端口
server.serve_forever()  # 这行代码代表了循环给客户端收发消息

おすすめ

転載: blog.csdn.net/Yosigo_/article/details/112795174