1. 程式人生 > 實用技巧 >24、解決粘包問題的方法、UDP套接字簡單示例、多併發問題

24、解決粘包問題的方法、UDP套接字簡單示例、多併發問題

一、解決粘包問題的方法(簡單版)

  • 服務端
from socket import *
from subprocess import PIPE, Popen
import struct

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8000))
server.listen(5)

while True:
    conn, client_addr = server.accept()
    print(client_addr)

    while True:  # 通訊迴圈
        try:
            cmd = conn.recv(8096)
            if len(cmd) == 0:  # 針對於linux系統
                break
            obj = Popen(cmd.decode('utf-8'),
                        shell=True,
                        stderr=PIPE,
                        stdout=PIPE, )
            res1 = obj.stdout.read()
            res2 = obj.stderr.read()
            total_size = len(res1) + len(res2)

            # 先把資料的長度給發過去,長度為4
            header = struct.pack('i', total_size)
            conn.send(header)

            # 再發送真正的資料
            conn.send(res2)  # 利用TCP協議的特性
            conn.send(res1)  # nagle演算法規定,TCP協議會將資料量較小、
            # 時間間隔短的資料合併為一條傳送給客戶端
        except Exception:
            break
    conn.close()  # 異常斷開後回收資源
  • 客戶端
from socket import *
from subprocess import PIPE, Popen
import struct

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8000))

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

    # 先接收資料的長度
    header = client.recv(4)
    total_size = struct.unpack('i', header)[0]  # 提取出位元組長度

    # 接收真正的資料
    recv_size = 0
    res = b''
    while recv_size < total_size:
        data = client.recv(1024)
        recv_size += len(data)
        res += data
    print(res.decode('gbk'))

二、解決粘包問題的方法(優化版)

  • 服務端
import json
from socket import *
import struct
from subprocess import PIPE, Popen

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8000))
server.listen(5)

while True:  # 連結迴圈
    conn, client_addr = server.accept()
    print(client_addr)

    while True:  # 通訊迴圈
        try:
            cmd = conn.recv(8096)
            if len(cmd) == 0:
                break
            obj = Popen(cmd.decode('utf-8'),
                        shell=True,
                        stdout=PIPE,
                        stderr=PIPE)
            res1 = obj.stdout.read()
            res2 = obj.stderr.read()

            header_dic = {
                'filename': 'a.txt',
                'total_size': len(res1) + len(res2),
                'md5': 'qwe165qwqwe65456qw5'
            }
            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(res1)
            conn.send(res2)
        except Exception:
            break
    conn.close()  # 關閉視窗來回收資源

  • 客戶端
from socket import *
import struct
import json

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8000))

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

    # 先接收四個位元組,提取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_json)
    total_size = header_dic['total_size']

    # 再接收真正的資料
    recv_size = 0
    res = b''
    while recv_size < total_size:
        data = client.recv(1024)
        recv_size += len(data)
        res += data

    print(res.decode('gbk'))

三、基於UDP套接字的編寫

  • 服務端
import socket

server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 造手機
server.bind(('127.0.0.1', 8000))  # 需要繫結,不許要監聽
while True:  # 通訊迴圈
    data, client_addr = server.recvfrom(1024)
    # 解壓賦值(文字,傳送端的埠號)
    print(data)
    server.sendto(data.upper(), client_addr)
  • 客戶端
from socket import *

client = socket(AF_INET, SOCK_DGRAM)  # 造手機
while True:  # 輸入迴圈
    msg = input('輸入:').strip()
    client.sendto(msg.encode('utf8'), ('127.0.0.1', 8000))
    # sendto(傳送內容二進位制,接收埠)
    data, client_addr = client.recvfrom(1024)
    # 解壓賦值(接收的文字,埠)
    print(data.decode('utf-8'))
  • UDP協議一般不會用於大資料的傳輸
  • UDP套接字雖然沒有粘包的問題,但是不能代替TCP套接字,因為UDP協議有一個缺陷,如果資料傳送的途中,資料丟失,則資料就丟失了,而TCP協議則不會有這種缺陷,因此一般的UDP套接字使用者無關緊要的資料傳送,例如QQ、微信聊天等.

四、socketserver模組

  • 基本使用框架
import socketserver


class MyHandler(socketserver.BaseRequestHandler):
    # 通訊迴圈
    def handle(self):  # 呼叫的話以上不變
        # data = self.request.recv(1024)  # self.recv(1024)
        # print(data)
        # self.request.send(data.upper())

        if __name__ == '__main__':
            server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyHandler, bind_and_activate=True)
            server.serve_forever()

  • 多併發——服務端
# 同一時刻有多個人在接聽
import socketserver
import json
from subprocess import Popen, PIPE
import struct


class MyHandler(socketserver.BaseRequestHandler):
    # 通訊迴圈
    def handle(self):  # 呼叫的話以上不變
        # data = self.request.recv(1024)  # self.recv(1024)
        # print(data)
        # self.request.send(data.upper())
        try:
            cmd = self.request.recv(8096)
            obj = Popen(cmd.decode('utf-8'),
                        shell=True,
                        stdout=PIPE,
                        stderr=PIPE,
                        )

            res1 = obj.stdout.read()
            res2 = obj.stderr.read()

            header_dic = {
                'filename': "a.txt",
                'total_size': len(res1) + len(res2),
                'md5': '123dfsfsaf123213'
            }

            header_json = json.dumps(header_dic)
            header_bytes = header_json.encode('utf-8')

            # 先發4個位元組
            self.request.send(struct.pack('i', len(header_bytes)))
            # 再發報頭字典
            self.request.send(header_bytes)

            # 最後傳送真正的資料
            self.request.send(res1)
            self.request.send(res2)
        except Exception:
            self.request.close()


# 使用socketserver的連線迴圈(併發),但是使用了自己的通訊迴圈

if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyHandler, bind_and_activate=True)
    server.serve_forever()

# 呼叫的話只需要將conn改成self.request即可

  • 多併發——客戶端1
from socket import *
import struct
import json

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

while True:
    cmd = input(">>>: ").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:
        data = client.recv(1024)
        recv_size += len(data)
        res += data

    print(res.decode('gbk'))
  • 多併發——客戶端2
from socket import *
import struct
import json

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

while True:
    cmd = input(">>>: ").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:
        data = client.recv(1024)
        recv_size += len(data)
        res += data

    print(res.decode('gbk'))