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

news/2024/4/17 17:06:16

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() {// 创建socketint 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;}// 监听socketif (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);}}}// 关闭socketclose(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;//清空定长str1str1 = str1.substr(7);//清空所有//str1.clear();//str1 = "";std::cout << "str1: " << str1 << std::endl;return 0;
}

输出:

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

https://www.xjx100.cn/news/3090447.html

相关文章

LeetCode 0053. 最大子数组和:DP 或 递归(线段树入门题?)

【LetMeFly】53.最大子数组和&#xff1a;DP 或 递归 力扣题目链接&#xff1a;https://leetcode.cn/problems/maximum-subarray/ 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最…

以“防方视角”观Shiro反序列化漏洞

为方便您的阅读&#xff0c;可点击下方蓝色字体&#xff0c;进行跳转↓↓↓ 01 案例概述02 攻击路径03 防方思路 01 案例概述 这篇文章来自微信公众号“潇湘信安”&#xff0c;记录的某师傅如何发现、利用Shiro反序列化漏洞&#xff0c;又是怎样绕过火绒安全防护实现文件落地、…

开发仿抖音APP遇到的问题和解决方案

uni-app如何引入阿里矢量库图标/uniapp 中引入 iconfont 文件报错文件查找失败 uni-app如何引入阿里矢量库图标 - 知乎 uniapp 中引入 iconfont 文件报错文件查找失败&#xff1a;‘./iconfont.woff?t1673007495384‘ at App.vue:6_宝马金鞍901的博客-CSDN博客 将课件中的cs…

ubuntu linux C/C++环境搭建

目录 前言 1.1 vim安装与配置 ​编辑 1.2 vim配置 1.3 gcc g编译器的安装 与gdb调试器的安装 1.4 写个C/C程序测试一下 1.6 vscode安装 1.7 vscode插件下载​编辑 前言 在开始C之前&#xff0c;我们需要搭建好C的开发环境&#xff0c;我这里使用的操作系统是ubuntu Linux&a…

自然语言处理:Transformer与GPT

Transformer和GPT&#xff08;Generative Pre-trained Transformer&#xff09;是深度学习和自然语言处理&#xff08;NLP&#xff09;领域的两个重要概念&#xff0c;它们之间存在密切的关系但也有明显的不同。 1 基本概念 1.1 Transformer基本概念 Transformer是一种深度学…

流体的压力

压力是流体力学中很重要的物理量&#xff0c;国际标准单位为 Pa&#xff08;帕斯卡&#xff09;&#xff0c;其他常用单位包括 MPa&#xff08;兆帕&#xff09;、atm&#xff08;标准大气压&#xff09;、Torr&#xff08;托&#xff09; 等。 在流体内部&#xff0c;压力是标…

王者荣耀游戏

游戏运行如下&#xff1a; sxt Background package sxt;import java.awt.*; //背景类 public class Background extends GameObject{public Background(GameFrame gameFrame) {super(gameFrame);}Image bg Toolkit.getDefaultToolkit().getImage("C:\\Users\\24465\\D…

SVG圆形 <circle>的示例代码

本专栏是汇集了一些HTML常常被遗忘的知识&#xff0c;这里算是温故而知新&#xff0c;往往这些零碎的知识点&#xff0c;在你开发中能起到炸惊效果。我们每个人都没有过目不忘&#xff0c;过久不忘的本事&#xff0c;就让这一点点知识慢慢渗透你的脑海。 本专栏的风格是力求简洁…