当前位置: 首页 > article >正文

c++处理tcp粘包问题以及substr方法

c++处理tcp粘包问题以及substr方法

  • 1.粘包原因
  • 2.tcp基础
    • 三次握手
    • 四次挥手
    • 长连接和和短连接
  • 3.解决方式
    • 1.定长消息:
    • 2.分隔符消息:
  • 4.substr方法

1.粘包原因

在TCP通信中,粘包是指发送方在发送数据时,多个小的数据包被合并成一个大的数据包,或者接收方在接收数据时,一个大的数据包被拆分成多个小的数据包。这种情况可能会导致接收方无法正确解析数据,从而造成数据处理错误。

2.tcp基础

TCP是一种面向连接的协议,它提供可靠的、有序的、基于字节流的数据传输。TCP建立连接的过程包括“三次握手”,即客户端发送连接请求,服务器回应确认,最后客户端再次回应确认。连接建立后,TCP通过使用滑动窗口、序列号和确认机制来保证数据的顺序和完整性。TCP还支持流量控制和拥塞控制机制,以保证网络的可靠性和稳定性。因此,TCP适用于需要可靠传输的应用,如网页浏览、文件传输、电子邮件等。

三次握手

1.第一次握手:客户端发送连接请求报文段(SYN)到服务器,进入SYN_SENT状态。
2.第二次握手:服务器收到请求后,回复一个确认报文段(SYN+ACK)以及自己的连接请求报文段(SYN),进入SYN_RCVD状态。
3.第三次握手:客户端收到确认后,再发送一个确认报文段(ACK),双方进入Established状态,连接建立。
三次握手的主要目的是双方确认彼此的发送和接收能力正常,并且同步初始序列号。

四次挥手

1.第一次挥手:发起关闭的一方发送一个FIN报文段给对方,进入FIN_WAIT_1状态。
2.第二次挥手:对方收到FIN后,回复一个确认报文段(ACK),进入CLOSE_WAIT状态。
3.第三次挥手:当对方不再需要发送数据时,会发送一个FIN报文段给发起关闭的一方,进入LAST_ACK状态。
4.第四次挥手:发起关闭的一方收到对方的FIN后,发送一个确认报文段(ACK),进入TIME_WAIT状态。等待2MSL时间后,进入CLOSED状态。
四次挥手的主要目的是确保双方都能够完成未发送完的数据的传输,并且结束连接。

长连接和和短连接

  • 长连接:指在一个TCP连接中可以传输多个数据包,在处理完一个请求后不会立即断开连接,而是保持连接状态,等待后续的请求。长连接可以减少连接建立和断开的开销,适用于频繁的数据交换场景,如网页浏览、移动App等。
  • 短连接:指每次请求都要建立一个新的TCP连接,在请求处理完毕后立即断开连接。短连接适用于一次性传输少量数据的场景,如DNS查询、文件下载等。
    选择长连接还是短连接取决于具体的应用场景和性能要求。

3.解决方式

解决TCP粘包问题有多种方法,下面介绍两种常用的方式:

1.定长消息:

发送方在发送数据时,将每个数据包的长度固定为一个固定值。接收方在接收数据时,根据固定的长度对数据进行拆分。这种方式简单直接,但是对于不同长度的数据包处理不便。

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
const int MESSAGE_LENGTH = 10;  // 定义消息长度为10
int main() {
// 创建socket
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == -1) {
std::cerr << "Failed to create socket" << std::endl;
return -1;
}
// 设置socket地址
sockaddr_in serverAddress{};
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(8080);
serverAddress.sin_addr.s_addr = INADDR_ANY;

// 绑定socket地址
if (bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) {
    std::cerr << "Failed to bind socket" << std::endl;
    close(serverSocket);
    return -1;
}

// 监听socket
if (listen(serverSocket, SOMAXCONN) == -1) {
    std::cerr << "Failed to listen on socket" << std::endl;
    close(serverSocket);
    return -1;
}

while (true) {
    // 接受新的连接
    sockaddr_in clientAddress{};
    socklen_t clientAddressSize = sizeof(clientAddress);
    int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientAddressSize);
    if (clientSocket == -1) {
        std::cerr << "Failed to accept connection" << std::endl;
        close(serverSocket);
        return -1;
    }

    std::cout << "New connection accepted" << std::endl;

    // 接收数据
    char buffer[MESSAGE_LENGTH];
    std::string receivedData;

    while (true) {
        memset(buffer, 0, sizeof(buffer));
        ssize_t bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
        if (bytesRead == -1) {
            std::cerr << "Failed to receive data" << std::endl;
            close(clientSocket);
            break;
        }
        if (bytesRead == 0) {
            std::cout << "Connection closed" << std::endl;
            close(clientSocket);
            break;
        }

        receivedData += buffer;

        // 检查是否有完整的消息
        while (receivedData.length() >= MESSAGE_LENGTH) {
            std::string message = receivedData.substr(0, MESSAGE_LENGTH);
            std::cout << "Received message: " << message << std::endl;

            // 处理消息

            // 移除已处理的消息
            receivedData = receivedData.substr(MESSAGE_LENGTH);
        }
    }
}

// 关闭socket
close(serverSocket);

return 0;
}

2.分隔符消息:

发送方在发送数据时,在每个数据包的末尾添加一个特定的分隔符,例如换行符或特殊字符。接收方在接收数据时,根据分隔符将数据包拆分成多个小的数据包。这种方式对于不同长度的数据包处理更加灵活,但需要注意选择合适的分隔符,以避免与数据内容冲突。

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstdlib>
#include <cstring>

const char DELIMITER = '\n';

int main() {
    // 创建socket
    int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket == -1) {
        std::cerr << "Failed to create socket" << std::endl;
        return -1;
    }

    // 设置socket地址
    sockaddr_in serverAddress{};
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(8080);
    serverAddress.sin_addr.s_addr = INADDR_ANY;

    // 绑定socket地址
    if (bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) {
        std::cerr << "Failed to bind socket" << std::endl;
        close(serverSocket);
        return -1;
    }

    // 监听socket
    if (listen(serverSocket, SOMAXCONN) == -1) {
        std::cerr << "Failed to listen on socket" << std::endl;
        close(serverSocket);
        return -1;
    }

    while (true) {
        // 接受新的连接
        sockaddr_in clientAddress{};
        socklen_t clientAddressSize = sizeof(clientAddress);
        int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientAddressSize);
        if (clientSocket == -1) {
            std::cerr << "Failed to accept connection" << std::endl;
            close(serverSocket);
            return -1;
        }

        std::cout << "New connection accepted" << std::endl;

        // 接收数据
        char buffer[1024];
        std::string receivedData;

        while (true) {
            memset(buffer, 0, sizeof(buffer));
            ssize_t bytesRead = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);
            if (bytesRead == -1) {
                std::cerr << "Failed to receive data" << std::endl;
                close(clientSocket);
                break;
            }
            if (bytesRead == 0) {
                std::cout << "Connection closed" << std::endl;
                close(clientSocket);
                break;
            }

            receivedData += buffer;

            // 检查是否有完整的消息
            size_t delimiterPos = receivedData.find(DELIMITER);
            while (delimiterPos != std::string::npos) {
                std::string message = receivedData.substr(0, delimiterPos);
                std::cout << "Received message: " << message << std::endl;

                // 处理消息

                // 移除已处理的消息
                receivedData = receivedData.substr(delimiterPos + 1);

                // 继续查找下一个分隔符
                delimiterPos = receivedData.find(DELIMITER);
            }
        }
    }

    // 关闭socket
    close(serverSocket);

    return 0;
}

4.substr方法

C++ 标准库中的一个字符串处理函数,用于从一个字符串中提取子字符串。
substr 函数的语法如下:

string substr(size_t pos = 0, size_t len = npos) const;

其中,pos 参数表示要提取的子字符串的起始位置,len 参数表示要提取的子字符串的长度。如果不指定 len 参数,则默认提取从 pos 位置到字符串末尾的所有字符。
substr 函数返回一个新的字符串,包含了从原始字符串中提取的子字符串。
下面是一个简单的示例,演示了如何使用 substr 函数:

#include <iostream>
#include <string>

int main() {
    std::string str1 = "Hello, World!";

    // 提取从位置 7 开始的子字符串
    std::string substr1 = str1.substr(7);
    std::cout << "Substring 1: " << substr1 << std::endl;

    // 提取从位置 0 开始,长度为 5 的子字符串
    std::string substr2 = str1.substr(0, 5);
    std::cout << "Substring 2: " << substr2 << std::endl;
    std::cout << sizeof(str1) << std::endl;


    //清空定长str1
    str1 = str1.substr(7);
    //清空所有
    //str1.clear();
    //str1 = "";

    std::cout << "str1: " << str1 << std::endl;



    return 0;
}

输出:

Substring 1: World!
Substring 2: Hello
str1:World!

http://www.kler.cn/a/135937.html

相关文章:

  • 如何在 Ubuntu 16.04 上设置 NFS 挂载
  • 番外:MySQL的一些事务处理
  • Java反序列化之CommonsCollections2链的学习
  • go语言 分布式一致
  • 深入理解Java虚拟机:你真的了解JVM吗?
  • ESLint 使用教程(四):ESLint 有哪些执行时机?
  • vue3使用element-plus
  • 拼图小游戏
  • 轻松实现公网访问本地内网搭建的WBO白板【内网穿透】
  • Labview中for循环“无法终止”问题?即使添加了条线接线端,达到终止条件后,仍在持续运行?
  • PostgreSQL 难搞的事系列 --- vacuum 的由来与PG16的命令的改进 (1)
  • 基于LLM+场景识别+词槽实体抽取实现多轮问答
  • 进程管理(三)
  • POS系统完整体系的介绍 Pos终端主密钥MK、DUKPT、PEK、DEK、MEK、TUSN的含义 ---安全行业基础篇7
  • CICD 持续集成与持续交付——git
  • 安全项目简介
  • 中间件安全:Apache 目录穿透.(CVE-2021-41773)
  • java源码-工程讲解
  • 三十分钟学会zookeeper
  • 软件测试:测试分类
  • 【FFmpeg实战】ffmpeg播放器-音视频解码流程
  • Python如何调用ixchariot进行吞吐量测试
  • mysql数据模型
  • hypermesh学习总结(一)
  • UE 视差材质 学习笔记
  • AIX 系统基线安全加固操作