本文学习相关资料:
C/C++ socket编程教程
环境:vs2015
源码:本文代码
前面学到了TCP怎么循环发包,但是TCP连接的话会出现一个问题粘包。
TCP连接接收到的数据并不是马上读取到内存里面的,而是放在缓冲区,让后调用recv函数来从缓冲区读取数据。
当然缓冲区是有大小限制
这时候就可能会出现粘包了。
1、假如客户端发送的数据很少,但次数多;服务端一次读取得多,就会将多次发送的内容全部读到一起。
2、假如一次客户端发送的数据很多,服务端一次没有读取完,那么就会还有剩下的数据在缓冲区;这时客户端第二次发送数据过来,和前一次读剩下的数据一起放。这时服务端又来读取数据了,就会出现了第一次读剩下的数据和第二次的部分数据被服务端一起读取。
来看看怎么实现这样的情况
情况1:
服务端
int maxlen = 200;
//接受客户端的连接
SOCKET client = accept(servSock, (sockaddr*)&clntAddr, &nSize);while (1) {//通过sleep来让客户端信息全部发送到缓冲区,让服务器能够一次读取完Sleep(1000);//接受到信息int len = recv(client, buf, maxlen, 0);std::string s(buf);if (s.compare("exit") == 0) {std::cout << "接收到关闭信息,关闭服务器" << std::endl;break;}std::cout << s << " " << len << std::endl;}closesocket(client);
客户端
int num = 5;
connect(client, (sockaddr*)&servAddr, sizeof(sockaddr));std::string s("1234");
for (int i = 0; i < num; ++i)std::cout << send(client, s.c_str(), s.size(), 0) << std::endl;//注意不要把字符串最后面那个'\0'也发送了
send(client, "\0", 1, 0);
//让服务端先读取完前面的内容在发送结束
Sleep(4000);
s = "exit";
std::cout << send(client, s.c_str(), s.size() + 1, 0) << std::endl;closesocket(client);
情况2:
服务端
int maxlen = 5; //注意这里不同了,表示服务端一次读取得少
//接受客户端的连接
SOCKET client = accept(servSock, (sockaddr*)&clntAddr, &nSize);while (1) {//通过sleep来让客户端信息全部发送到缓冲区,让服务器能够一次读取完//Sleep(1000); 睡眠也注释了//接受到信息int len = recv(client, buf, maxlen, 0);std::string s(buf);if (s.compare("exit") == 0) {std::cout << "接收到关闭信息,关闭服务器" << std::endl;break;}std::cout << s << " " << len << std::endl;}closesocket(client);
客户端
int num = 5;
connect(client, (sockaddr*)&servAddr, sizeof(sockaddr));std::string s("12345679"); //这里不同了,表示客户端一次发送得多
for (int i = 0; i < num; ++i)std::cout << send(client, s.c_str(), s.size(), 0) << std::endl;//注意不要把字符串最后面那个'\0'也发送了
send(client, "\0", 1, 0);
//让服务端先读取完前面的内容在发送结束,不然会读到最后面那个'\0',和exit拼在了一起,就不会结束了
Sleep(4000);
s = "exit";
std::cout << send(client, s.c_str(), s.size() + 1, 0) << std::endl;closesocket(client);
怎么解决粘包呢?
1、约定好每次读取的数据长度,每次发送的数据长度,保证一次读完
2、约定好每段发送数据的结束符,当读到这个结束符的时候表明读取完了第一次发送的数据,结束符后面的内容属于第二次发送的数据。
方法一比较简单,就只是改个数值就可以了
来看看方法二要怎么做:
客户端
int num = 5;
connect(client, (sockaddr*)&servAddr, sizeof(sockaddr));std::string s("12345679A"); //这里确定每次发送的数据长度为10字节,A表示数据结束
for (int i = 0; i < num; ++i)std::cout << send(client, s.c_str(), s.size(), 0) << std::endl;
Sleep(4000);
s = "exit";
std::cout << send(client, s.c_str(), s.size() + 1, 0) << std::endl;closesocket(client);
服务端
SOCKET client = accept(servSock, (sockaddr*)&clntAddr, &nSize);
char temp[maxlen];
memset(temp, 0, sizeof(temp));
//通过sleep来让客户端信息全部发送到缓冲区,让服务器能够一次读取完
Sleep(1000);//表示读取缓冲区的内容的下标
int vBufSite = 0;
int len = recv(client, buf, 7, 0); //一次读7个字节,肯定是要读两次的
while (1) {int vTempSite = 0;bool vRead = true;while (vRead) {if (vBufSite >=len) { //看看前一次读取的内容是不是包含了两次发送的内容len = recv(client, buf, 7, 0);vBufSite = 0;}while(vBufSite < len ) { //看看是不是包含了两次发送的内容if (buf[vBufSite] == 0) {vRead = false;temp[vTempSite] = 0;++vBufSite;break;}else if (buf[vBufSite] != 'A') { //读到分隔符就结束temp[vTempSite++] = buf[vBufSite];++vBufSite;}else {vRead = false;temp[vTempSite] = 0;++vBufSite;break;}}}std::string s(temp);if (s.compare("exit") == 0) {std::cout << "接收到关闭信息,关闭服务器" << std::endl;break;}std::cout << s << std::endl;temp[0] = 0;
}
closesocket(client);