33 粘包问题 socketserver简介

粘包问题 socketserver简介

1 案例:远程控制服务端

server.py

import socket
import subprocess

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8080))
s.listen(5)

while True:
    conn, addr = s.accept()
    while True:
        try:
            recv_data = conn.recv(1024)
            if len(recv_data) == 0:
                break
            cmd = recv_data.decode('utf-8')
            print(f'>>>{cmd}')
            obj = subprocess.Popen(
                cmd,
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            stdout_res = obj.stdout.read()
            stderr_res = obj.stderr.read()
            conn.send(stdout_res)
            conn.send(stderr_res)
        except Exception:
            break
    conn.close()
s.close()

client.py

import socket
import struct

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

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

    recv_data = s.recv(1024)
    print(recv_data.decode('gbk'))

s.close()

遇到的问题:假设客户端发送的命令在服务端产生的返回值数据量很大,客户端接收来自服务端的数据时可能会出现粘包问题。

2 粘包问题

2.1 介绍

粘包问题

  1. TCP协议属于面向流的协议,数据像水流一样混在一起,接收方不能判断每次发送数据的界限;
  2. 接收方不能保证每次接收数据是完整的,可能有残留,残留数据会暂存于缓存中,和下一次接收的数据混在一起。

解决粘包问题
主要在接收端做处理

  1. 获取发送数据的总量;
  2. 循环接收数据,通过数据总量判断是否接收完全;
  3. 数据接收完全后,跳出循环。

TCP协议下的传输使用了Nagle算法进行优化,发送端将多次发送的间隔短且数据量小的数据合并成一个大的数据块,进行封包发送,这样做接收端就难以分辨出实际的每次发送的数据了。因此,面向流(TCP协议)的通信中不存在数据边界。
UDP协议是面向消息的协议,接收方每次接收的数据是有边界的,假设接收端没有接收完全,残留的数据会被抛弃,不会影响下一次接收的数据,因此UDP协议下的通信不会出现粘包现象。UDP协议稳定传输的数据量上限是512个字节。

server.py

import socket
import subprocess
import struct

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8080))
s.listen(5)

while True:
    conn, addr = s.accept()
    while True:
        try:
            recv_data = conn.recv(1024)
            if len(recv_data) == 0:
                break
            cmd = recv_data.decode('utf-8')

            obj = subprocess.Popen(
                cmd,
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            stdout_res = obj.stdout.read()
            stderr_res = obj.stderr.read()

            send_total_size = len(stdout_res) + len(stderr_res)
            # 1. 发送固定长度的头信息,这里长度是4Bytes
            # 头信息的内容是发送的数据总量
            # pack的i模式将整形数据打包成长度为4的Bytes类型数据
            send_head = struct.pack('i', send_total_size)
            conn.send(send_head)

            # 2. 发送实际数据
            conn.send(stdout_res)
            conn.send(stderr_res)
        except Exception:
            break
    conn.close()
s.close()

client.py

import socket
import struct

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

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

    # 1. 先接受固定长度的头信息,这里固定长度为4Bytes
    recv_head = s.recv(4)
    # 2. 从头信息中获取接收数据的总量
    recv_total_size = struct.unpack('i', recv_head)[0]

    recv_size = 0
    while recv_size < recv_total_size:
        recv_data = s.recv(1024)
        recv_size += len(recv_data)
        print(recv_data.decode('gbk'), end='')
    else:
        print()

s.close()

3 socketserver简介

3.1 TCP协议

server.py

import socketserver

class CustomRequestHandler(socketserver.BaseRequestHandler):
    def handle(self):
        # 如果是tcp协议,self.request => conn连接对象
        print(self.client_address)  # 客户端ip地址和端口号
        # 通信循环
        while True:
            try:
                msg = self.request.recv(1024)
                if len(msg) == 0: break
                self.request.send(msg.upper())
            except Exception:
                break
        self.request.close()

# 服务端应该做两件事
# 1. 不停地从半连接池中获取连接请求,并与其建立双向连接通路,获取连接对象
s = socketserver.ThreadingTCPServer(('127.0.0.1', 8889), CustomRequestHandler)
s.serve_forever()

# 2. 通过连接与客户端进行通信
# 调用自定义类CustomRequestHandle中的方法handle
3.2 UDP协议

server.py

import socketserver

class CustomRequestHandler(socketserver.BaseRequestHandler):
    def handle(self):
        recv_data = self.request[0]
        print('客户端发送的数据为%s' % recv_data)

        server = self.request[1]
        client_address = self.client_address
        server.sendto(recv_data.upper(), client_address)

s = socketserver.ThreadingUDPServer(("127.0.0.1", 8888), CustomRequestHandler)
s.serve_forever()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值