在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粘包问题有多种方法,下面介绍两种常用的方式:
发送方在发送数据时,将每个数据包的长度固定为一个固定值。接收方在接收数据时,根据固定的长度对数据进行拆分。这种方式简单直接,但是对于不同长度的数据包处理不便。
#include
#include
#include
#include
#include
#include
#include
#include
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;
}
发送方在发送数据时,在每个数据包的末尾添加一个特定的分隔符,例如换行符或特殊字符。接收方在接收数据时,根据分隔符将数据包拆分成多个小的数据包。这种方式对于不同长度的数据包处理更加灵活,但需要注意选择合适的分隔符,以避免与数据内容冲突。
#include
#include
#include
#include
#include
#include
#include
#include
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;
}
C++ 标准库中的一个字符串处理函数,用于从一个字符串中提取子字符串。
substr 函数的语法如下:
string substr(size_t pos = 0, size_t len = npos) const;
其中,pos 参数表示要提取的子字符串的起始位置,len 参数表示要提取的子字符串的长度。如果不指定 len 参数,则默认提取从 pos 位置到字符串末尾的所有字符。
substr 函数返回一个新的字符串,包含了从原始字符串中提取的子字符串。
下面是一个简单的示例,演示了如何使用 substr 函数:
#include
#include
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!