1. TCP粘包问题的模拟实现
1.1 何谓TCP粘包
因为TCP协议是有连接,可靠有序,面向字节流的协议,也正是它面向字节流这个特性,导致存放在接收缓冲区的数据没有明显的界限,TCP的recv函数在接收数据的时候不会识别数据是第一条还是第二条,而是直接根据规定的大小进行读取数据,而我们每次都不知道发送数据方发送的数据大小,因此再读取数据的时候,极有可能读取到不完整的数据,或者说是粘连的数据。举个例子来看:
如果按照我们自己的逻辑,服务端应该给客户端返回两次结果 2、4;但是这里服务端只会给客户端返回一次结果,即1+12+2 = 15。这就与我们的预期不符,因此,这就是TCP的粘包问题。
接下来我们来对其进行模拟实现。
1.2 TCP粘包问题的模拟实现
本次我们使用的是多线程的TCP版本代码,同上篇文章Linux:TCP Socket编程(代码实战)一样,这里我们还是使用封装类的形式实现客户端和服务端之间的通信,为了实现TCP粘包问题,我们在这里规定客户端连续给服务端发送两次数据,然后服务端每次接收数据的时候,直接读取buf所能读取的最大数据。
封装类代码 tcp.hpp
#pragma once
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <string>
#include <iostream>
using namespace std;
class tcp
{
public:
tcp() : sockfd_(-1)
{
}
tcp(int sock) : sockfd_(sock)
{
}
~tcp()
{
close(sockfd_);
}
int createSockfd()
{
sockfd_ = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sockfd_ < 0)
{
cout << "socket failed" << endl;
return -1;
}
return sockfd_;
}
int Bind(string ip = "0.0.0.0",uint16_t port = 18989)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
int ret = bind(sockfd_,
(struct sockaddr *)&addr,sizeof(addr));
if(ret < 0)
{
cout << "bind failed" << endl;
return -1;
}
return ret;
}
int Listen(int backlog = 2)
{
int ret = listen(sockfd_,backlog);
if(ret < 0)
{
cout << "listen failed" << endl;
return -1;
}
return ret;
}
int Accept(struct sockaddr_in* addr,socklen_t* socklen)
{
int new_sockfd = accept(sockfd_,
(struct sockaddr *)addr,socklen);
if(new_sockfd < 0)
{
cout << "accept failed" << endl;
return -1;
}
return new_sockfd;
}
ssize_t Recv(char* buf,size_t len)
{
ssize_t ret = recv(sockfd_,buf,len,0);
if(ret < 0)
{
cout << "recv failed" << endl;
}
return ret;
}
ssize_t Send(char* buf,size_t len)
{
ssize_t ret = send(sockfd_,buf,len,0);
if(ret < 0)
{
cout << "send faild" << endl;
return -1