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
    评论
在C语言中,可以通过以下几种方式来解决粘包问题: 1. 定长消息:发送方将每个消息固定为相同的长度,接收方按照固定长度进行接收和解析。这样可以确保每个消息的长度固定,不会发生粘包问题。但是,如果消息长度较短,会造成数据的浪费;如果消息长度较长,会增加传输延迟。 2. 分隔符:发送方在消息之间插入特定的分隔符,接收方根据分隔符来切割消息并进行处理。常见的分隔符有换行符、空格、特殊字符等。这种方式可以灵活地处理不同长度的消息,但是需要确保分隔符在消息内容中不会出现。 3. 消息头部长度:发送方在每个消息前添加一个固定长度的头部,头部中包含了消息的长度信息。接收方首先读取头部,根据长度信息读取相应长度的数据进行处理。这种方式可以精确地控制每个消息的长度,并且不需要特定的分隔符,但是需要在发送和接收时进行长度转换和处理。 4. 使用特定协议:设计一个特定的协议来处理粘包问题,将消息的长度、类型等信息封装到协议中。发送方按照协议进行数据打包和发送,接收方按照协议解析和处理数据。这种方式可以根据实际需求设计更复杂的协议,以满足不同的业务需求。 需要注意的是,解决粘包问题不仅仅是在发送方进行处理,接收方同样需要进行相应的处理。双方需要约定好消息的格式和处理方式,以确保数据的正确传输。 另外,如果使用TCP协议进行通信,TCP本身提供了可靠的数据传输机制,会自动将数据分割成合适的大小进行传输,因此在TCP中粘包问题相对较少。但是在一些特殊情况下(如高并发、大数据量等),仍然可能发生粘包问题,因此上述解决方案仍然适用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值