IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 计算机网络基础——粘包问题、socketserver模块 -> 正文阅读

[网络协议]计算机网络基础——粘包问题、socketserver模块

粘包

先基于tcp协议写一个远程执行命令的程序:
客户端:

from socket import *

client = socket(AF_INET, SOCK_STREAM)

client.connect(('127.0.0.1', 8080))

while True:
    msg = input('请输入命令:>>>').strip()
    if len(msg) == 0:
        continue
    if msg == 'quit':
        break
    client.send(msg.encode('utf-8'))
    cmd_res = client.recv(1024)
    print(cmd_res.decode('gbk'))

client.close()

服务端:

from socket import *
import subprocess

server = socket(AF_INET, SOCK_STREAM)

server.bind(('127.0.0.1', 8080))

server.listen(5)

# 服务端应该做两件事
# 第一件事:循环地从板连接池中取出链接请求与其建立双向链接,拿到链接对象
while True:
    conn, client_address = server.accept()

# 第二件事:拿到链接对象,与其进行通信循环
while True:
    try:
        cmd = conn.recv(1024)
        if len(cmd) == 0:
            break
        obj = subprocess.Popen(cmd.decode('utf-8'),
                               shell=True,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE
                               )
        stdout_res = obj.stout.read()
        stderr_res = obj.stderr.read()

        conn.send(stdout_res+stderr_res)
    except Exception:
        break

    conn.close()

在上述代码运行过程有可能会出现粘包的现象。

何为粘包现象

TCP协议是面向流的协议,在数据传输过程,数据就像流水一样,从服务端发送至客户端的缓存中,服务端可能会一次性发送大量的数据,而客户端一次只能接收一定数量的数据,就会导致客户端的缓存中还留有服务端一次性发送的数据,还未被接收的数据,然后服务端再发送数据,就导致上次未接收完的数据与新发送的数据首尾相连(TCP协议是面向流的协议,数据都是连在一起的),就导致了粘包现象。

两种情况下会发生粘包。

1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包
2.接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

所以最开始的代码,就会因为客户端发送命令,服务端接收命令,并将命令的执行结果发送给客户端,而有些命令的结果会过长,导致客户端接收不完全,导致数据遗留在了缓存区,形成了粘包现象

解决粘包问题

解决粘包问题思路:
1、先收固定长度的头,解析出数据的描述信息,包括;数据的总大小total_size
2、recv_size=0,循环接收,每接收一次,recv_size+=接收的长度
3、直到recv_size=total_size
客户端:

from socket import *
import struct

client = socket(AF_INET, SOCK_STREAM)

client.connect(('127.0.0.1', 8080))

while True:
    msg = input('请输入命令:>>>').strip()
    if len(msg) == 0:
        continue
    if msg == 'quit':
        break
    header = client.recv(4)
    total_size = struct.unpack('i', header)[0]
    recv_size = 0
    while recv_size < total_size:
        recv_data = client.recv(1024)
        recv_size += len(recv_data)
        print(recv_data.decode('utf-8'))

    client.send(msg.encode('utf-8'))
    cmd_res = client.recv(1024)
    print(cmd_res.decode('utf-8'))

client.close()

服务端:

from socket import *
import subprocess
import struct

server = socket(AF_INET, SOCK_STREAM)

server.bind(('127.0.0.1', 8080))

server.listen(5)

# 服务端应该做两件事
# 第一件事:循环地从板连接池中取出链接请求与其建立双向链接,拿到链接对象
while True:
    conn, client_address = server.accept()

# 第二件事:拿到链接对象,与其进行通信循环
while True:
    try:
        cmd = conn.recv(1024)
        if len(cmd) == 0:
            break
        obj = subprocess.Popen(cmd.decode('utf-8'),
                               shell=True,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE
                               )
        stdout_res = obj.stout.read()
        stderr_res = obj.stderr.read()
        total_size = len(stderr_res) + len(stdout_res)

        # 1、先发头信息(固定长度的bytes):对数据描述信息
        # int->固定长度的bytes
        header = struct('i', total_size)
        conn.send(header)

        # 2、再发送真实的数据
        conn.send(stdout_res)
        conn.send(stderr_res)
    except Exception:
        break

    conn.close()

通常情况下,头部的信息不仅仅是包含数据的大小,而应该会有更多的信息,例如,文件名字等等
解决方法之一:
将头部信息写入字典
优化之后:

客户端:

import json
from socket import *
import struct

client = socket(AF_INET, SOCK_STREAM)

client.connect(('127.0.0.1', 8080))

while True:
    msg = input('请输入命令:>>>').strip()
    if len(msg) == 0:
        continue
    if msg == 'quit':
        break
        # 接收端,先接收4个字节,从中提取接下来要接收的头的长度
    header_size = client.recv(4)
    header_len = struct.unpack('i', header_size)[0]
    # 提取到了头部长度,就可以接收头部信息字典
    json_str_bytes = client.recv(header_len)
    # 将bytes格式转化了json,再转化为utf-8
    json_str = json_str_bytes.decode('utf-8')
    header_dic = json.loads(json_str)
    total_size = header_dic['total_size']
    recv_size = 0
    while recv_size < total_size:
        recv_data = client.recv(1024)
        recv_size += len(recv_data)
        print(recv_data.decode('utf-8'))

    client.send(msg.encode('utf-8'))
    cmd_res = client.recv(1024)
    print(cmd_res.decode('utf-8'))

client.close()

服务端:

from socket import *
import subprocess
import struct
import json

server = socket(AF_INET, SOCK_STREAM)

server.bind(('127.0.0.1', 8080))

server.listen(5)

while True:
    conn, client_address = server.accept()

    while True:
        try:
            cmd = conn.recv(1024)
            if len(cmd) == 0:
                break
            obj = subprocess.Popen(cmd.decode('utf-8'),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE
                                   )
            stdout_res = obj.stdout.read()
            stderr_res = obj.stderr.read()
            total_size = len(stderr_res) + len(stdout_res)

            # 头部信息
            header_dic = {
                'filename': 'a.txt',
                'total_size': 123545687,
                'md5': 'asd789a8s7cv46x57a8w'
            }
            # 将头部信息写成json格式,可以方便其他平台进行接收
            json_str = json.dumps(header_dic)
            json_str_bytes = json_str.encode('utf-8')

            # 将带有头部信息的字典转成固定大小,方便客户端解析,提取头部信息
            header_size = struct.pack('i', len(json_str_bytes))
            conn.send(header_size)

            conn.send(json_str_bytes)

            # 再发送真实的数据
            conn.send(stdout_res)
            conn.send(stderr_res)
        except Exception:
            break

    conn.close()

socketserver模块

基于tcp协议

解决多并发问题:
服务端:


import socketserver
class MyRequestHandle(socketserver.BaseRequestHandler):
    def handle(self):
        print(self.request)  # 如果是tcp协议,相当于self.request=>conn
        print(self.client_address)
        while True:
            try:
                msg = self.request.recv(1024)
                if len(msg) == 0:
                    break
                self.request.send(msg.upper())
            except Exception:
                break
        self.request.close()


# 服务端应该做两件事情
# 第一件事:循环地从半连接池中取出链接请求与其建立双向链接,拿到链接对象

s = socketserver.ThreadingTCPServer(('127.0.0.1', 8099), MyRequestHandle)
s.serve_forever()
# 等同于
# while True:
#   conn,client_address = server.accept()
#   启动一个线程(conn,client_address)

# 第二件事:拿到链接对象,与其进行通信循环====》hanle方法
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8099))

while True:
    msg = input('请输入你的信息:').strip()
    if len(msg) == 0:
        continue
    client.send(msg.encode('utf-8'))

    res = client.recv(1024)
    print(res.decode('utf-8'))

基于udp协议

服务端:

class MyRequestHanlde(socketserver.BaseRequestHandler):
    def handle(self):
        client_data = self.request[0]
        server = self.request[1]
        client_address = self.client_address
        print('客户端发来的数据:%s' % client_data)
        server.sendto(client_data.upper(), client_address)


s = socketserver.ThreadingUDPServer(('127.0.0.1', 8090), MyRequestHanlde)
s.serve_forever()

客户端:

client = socket(AF_INET, SOCK_DGRAM)
client.connect(('127.0.0.1', 8090))

while True:
    msg = input('请输入你的信息:').strip()
    if len(msg) == 0:
        continue
    client.send(msg.encode('utf-8'))

    res = client.recv(1024)
    print(res.decode('utf-8'))
  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-08-07 12:25:58  更:2021-08-07 12:28:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/3 10:44:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码