首页 > 其他分享 >04. 粘包问题

04. 粘包问题

时间:2023-10-29 18:36:31浏览次数:34  
标签:socket 04 tcp 粘包 问题 header connection server 客户端

一、什么是粘包

  TCP 是流式协议,数据向水流一样粘在一起,没有任何边界区分。如果接收数据没收干净,数据会有残留,就会和下一次的结果混淆在一起,就会出现粘包问题。粘包 指的是发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。

  TCP 默认使用 Nagle 算法(主要作用:减少网络中报文段的数量),而 Nagle 算法主要做两件事:①只有上一个分组得到确认,才会发送下一个分组;②收集多个小分组,在一个确认到来时一起发送;因此,Nagle 算法造成了发送方可能会出现粘包问题。

  tcp_server.py 内容如下

from socket import socket
from socket import AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR

HOST = "127.0.0.1"
PORT = 8080
ADDRESS = (HOST, PORT)

tcp_server = socket(AF_INET, SOCK_STREAM)                           # 创建服务器套接字
tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)                  # 解决端口占用问题
tcp_server.bind(ADDRESS)                                            # 套接字与地址绑定
tcp_server.listen(5)                                                # 监听连接,最大挂起连接数为5

print("waiting for connection...")
connection, address = tcp_server.accept()                           # 接收客户端连接
print("...connection from: ", address)

data = connection.recv(1024)                                        # 服务端接收消息,单次最大接收为1024个字节
print("收到客户端数据:%s" % data.decode("utf-8"))

connection.close()                                                  # 关闭连接
tcp_server.close()                                                  # 关闭服务器套接字

  tcp_client.py 内容如下:

from socket import socket
from socket import AF_INET, SOCK_STREAM

HOST = "127.0.0.1"
PORT = 8080
ADDRESS = (HOST, PORT)

tcp_client = socket(AF_INET, SOCK_STREAM)                   # 创建客户端套接字
tcp_client.connect(ADDRESS)                                 # 尝试连接服务器

tcp_client.send("hello ".encode("utf-8"))                   # 客户端发送数据
tcp_client.send("world!".encode("utf-8"))                   # 客户端发送数据

tcp_client.close()                                          # 关闭客户端套接字

二、TCP服务器

import subprocess
import struct
import json

from socket import socket
from socket import AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR

HOST = "127.0.0.1"
PORT = 8080
ADDRESS = (HOST, PORT)

tcp_server = socket(AF_INET, SOCK_STREAM)                           # 创建服务器套接字
tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)                  # 解决端口占用问题
tcp_server.bind(ADDRESS)                                            # 套接字与地址绑定
tcp_server.listen(5)                                                # 监听连接,最大挂起连接数为5

while True:                                                         # 监听连接
    print("waiting for connection...")
    connection, address = tcp_server.accept()                       # 接收客户端连接
    print("...connection from: ", address)

    while True:                                                     # 通信循环
        try:
            # 1、接收客户端发送的命令
            cmd = connection.recv(1024)                             # 服务端接收命令,单次最大接收为1024个字节
            # 在Linux系统中,一旦data收到空,意味着是一种异常的行为:客户端非法断开连接
            if not cmd:                                             # 适用于Linux系统
                break

            # 2、执行系统命令,拿到结果
            obj = subprocess.Popen(cmd.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            stdout = obj.stdout.read()                              # 命令正确的返回结果
            stderr = obj.stderr.read()                              # 命令错误的返回结果

            # 3、把命令的结果返回给客户端
            # 第一步:制作固定长度的包头
            header_dict = {
                "total_size": len(stdout) + len(stderr)
            }

            header_json = json.dumps(header_dict)
            header_bytes = header_json.encode("utf-8")

            # 第二步:先发送报头的长度
            connection.send(struct.pack("i",len(header_bytes)))

            # 第三步:把报头(固定长度)发送给客户端
            connection.send(header_bytes)

            # 第四步:在发送真实的数据
            connection.send(stdout)
            connection.send(stderr)
        except ConnectionResetError:                                # 适用于Windows系统
            break

    connection.close()                                              # 连接

tcp_server.close()                                                  # 关闭服务器套接字

三、TCP客户端

import struct
import json

from socket import socket
from socket import AF_INET, SOCK_STREAM

HOST = "127.0.0.1"
PORT = 8080
ADDRESS = (HOST, PORT)


tcp_client = socket(AF_INET, SOCK_STREAM)                       # 创建客户端套接字
tcp_client.connect(ADDRESS)                                     # 尝试连接服务器

while True:                                                     # 通信循环
    # 1、向服务器发送命令
    data = input(">>>: ").strip()
    if not data:                                                # 按空格、回车等键结束连接
        break
    tcp_client.send(data.encode("utf-8"))                       # 客户端发送数据
  
    # 2、拿到命令的结果,并打印
    # 第一步:先接收报头的长度
    obj = tcp_client.recv(4)
    header_size = struct.unpack("i", obj)[0]

    # 第二步:再接收报头
    header_bytes = tcp_client.recv(header_size)

    # 第三步:从报头中解析出对真实数据的描述信息(数据的长度)
    header_json = header_bytes.decode("utf-8")
    header_dict = json.loads(header_json)
    total_size = header_dict["total_size"]

    # 第四步:接收真实的数据
    recv_size = 0
    recv_data = b""
    while recv_size < total_size:
        data = tcp_client.recv(1024)
        recv_data += data
        recv_size += len(data)

    # Windows默认的编码格式为gbk,Linux和MacOS默认的编码格式为utf-8
    print(recv_data.decode("gbk"))

tcp_client.close()                                               # 关闭客户端套接字

四、执行TCP服务器和客户端

  如果先运行客户端,那么将无法进行任何连接,因为没有服务器等待接受请求。服务器可以视为一个被动伙伴,因为必须首先建立自己,然后被动的等待连接。另一方面,客户端是一个主动的合作伙伴,因为它主动发起一个连接。换句话说,首先启动服务器(在任何客户端试图连接之前)。

  在开发中,创建这种 “友好的” 退出方式的一种方法就是,将服务器的 while 循环放在一个 try-except 语句中的 except 子句中,并监控 EOFError 或 KeyboardInterrupt 异常,这样你就可以在 except 或 finally 子句中关闭服务器的套接字。

标签:socket,04,tcp,粘包,问题,header,connection,server,客户端
From: https://www.cnblogs.com/kurome/p/17796171.html

相关文章

  • 解决使用 OkHttp 库出现 java.lang.NoSuchMethodError: okhttp3.internal.platform.Pl
    报错:Exceptioninthread"main"java.lang.NoSuchMethodError:okhttp3.internal.platform.Platform.log(ILjava/lang/String;Ljava/lang/Throwable;)Vatokhttp3.logging.HttpLoggingInterceptor$Logger.lambda$static$0(HttpLoggingInterceptor.java:112)......
  • java 数组常见问题
    当访问了数组中不存在的索引,就会引发索引越界异常。索引越界异常原因:访问了不存在的索引避免:索引的范围最小索引:0最大索引:4(数组的长度-1)......
  • Spring Boot环境下自定义shiro过滤器会过滤所有的url的问题
    问题起因:在Shiro配置类中定义如下:@BeanpublicShiroFilterFactoryBeanshiroFilterFactoryBean(ShiroAuthFiltershiroAuthFilter,SecurityManagersecurityManager){ShiroFilterFactoryBeanfactoryBean=newShiroFilterFactoryBean();Map<String,......
  • 常见问题解决 --- 安卓12关闭phantom processes killer杀后台功能
    1.adb连接成功后,执行adbdevices2.执行adbshell3.执行device_configset_sync_disabled_for_testspersistentdevice_configputactivity_managermax_phantom_processes2147483647settingsputglobalsettings_enable_monitor_phantom_procsfalse......
  • 【C++】继承 ⑬ ( 虚继承原理 | 虚继承解决继承二义性问题 | 二义性产生的原因分析 )
    文章目录一、虚继承原理1、虚继承解决继承二义性问题2、二义性产生的原因分析3、虚继承原理二、代码示例-虚继承原理1、完整代码示例2、执行结果一、虚继承原理1、虚继承解决继承二义性问题继承的二义性:如果一个子类(派生类)继承多个父类(基类),这些父类都继......
  • RC2104-WS_EX_COMPOSITED-WS_EX_NOINHERITLAYOUT
    VS编译出现RC2104,或者打开资源出现错误提示未定义#include"afxres.h"....2TEXTINCLUDEBEGIN"#include""afxres.h""\r\n""\0"END修改为#include<windows.h>#include"afxres.h"...2TEXTINCLUDE......
  • 常见问题解决 --- adb连接失败
    可能原因有,手机问题,电脑问题,线材问题。手机问题有:没有开启adb调试usb连接模式不是文件传输模式电脑问题有:adb驱动安装版本不匹配adb没有正确安装安卓驱动没有安装线材质量不好,断开 ......
  • VMware VCSA 5480 后台登录提示无法登陆问题解决
     通过控制台登入启用shell使用service-control--status--all查看applmgmt服务状态(显示已停止) 使用service-control--startapplmgmt启动服务 回车后会自动退出命令行模式 此时回到浏览器新建标签页重新登录5480端口成功    使用官网说明使用SingleS......
  • 谈谈 Angular 的升级问题
    我们知道咱们目前在用的ComposableStorefront,基于SAPSpartacus这个开源项目,在5.0的时候,Spartacus成为SAP正式产品之一,更名为ComposableStorefront.咱们今天谈论的是ComposableStorefront的升级问题,首先就要明确为什么我们要进行升级。Spartacus基于一系列的前端......
  • 【问题复盘】在Ubuntu 20.04下安装OFED驱动
    复盘:在Ubuntu20.04下安装OFED驱动起因最近收到两台服务器,都搭载了ConnectX-5EX网卡。由于供应商预装了Ubuntu20.04操作系统,而我们的后端代码也是基于Ubuntu开发的,因此需要在Ubuntu上安装ConnectX-5网卡的驱动。问题尽管供应商已经预装了驱动,但运行我们的服务时出现以下错误......