产生现象的原因在于无法控制recv/send
先直接上解决代码 , 下面有完整的测试代码 , 完整测试代码基于I/O复用 select c/s 网络模型: 的封装
完整代码太长不建议查看,只要把下面2个方案搞懂就ok;
下面的完整测试代码使用了第2个方案 , 2个方案没有谁好谁不好, 按情况使用;
方式1: 比较简单, 读(写)到指定字节数为止. 就能保证一定能读(写) 到一个完整的包,
所有注意的事项都在注释中
//如果返回0 则对端关闭, 返回的字节数如果==nbytes 则接受到了完整包
//用于读取一个完整的包
long read_msg(SOCKET sock, char * buff, unsigned int nBytes)
{
int len = 0;
unsigned int left = nBytes;
char * pBuff = buff;
while (left > 0)
{
//第3个参数,注意小心,否则容易产生粘包或解包错误
//接受少了就是少包,多了就是粘包,结果就是解包错误
len = recv(sock,pBuff,left, 0);
if (len <= 0){
return 0;
}
left -= len;
pBuff += len;
}
return nBytes - left;
}
//如果返回 -1 则出错了, 如果返回 字节数==nbytes 则发完包
long send_msg(SOCKET sock, char * buff, unsigned int nBytes)
{
unsigned int left = nBytes;
int len = 0;
while (left > 0)
{
//发完一整个包即可,注意第3个参数
len = send(sock, buff, left, 0);
if (len <= 0)
return -1;
left -= len;
buff += len;
}
return left;
}
方式2:
分配2个缓冲区 .
1. buff 用于 recv , 让recv一次性能接受多少算多少,忽略粘包与少包,所有的操作都在msgbuff中进行, buff缓冲区只用与接受数据.
2. msgbuff 用于解决 少包,解包,粘包,先把buff中的数据复制到msgbuff中, 然后对msgbuf进行循环处理数据;
这段代码是从下面的例子中抠出来的代码,如要实际使用修改下即可:
// 接受数据的长度
int len = 0;
//整个包的长度
DWORD total_datalen = 0;
//注意第3个参数, 让 recv 函数一次能收多少算多少
len = recv(hSocket, buff, RECV_BUFFSIZE, 0);
//对端关闭
if (len <= 0){
printf("closed by serv , sock:%ld\n", hSocket);
close(); //自定义函数
return -1;
}
//确保msgbuff 足够存放, 这里我只是简单的返回
//如果要做好可以先处理msgbuff中的剩余数据,然后再次从buff复制数据到msgbuff,再处理数据
if (MSG_BUFFSIZE - lastPos < len){
char * old_msgbuff = msgbuff;
msgbuff_size *= BUFFSIZE_DELTA;
char * new_msgbuff =(char*) realloc(msgbuff, msgbuff_size);
if (!new_msgbuff){
puts("realloc failed ,drop the new msg");
return -1;
}
msgbuff = new_msgbuff;
}
//lastPost 记录msgbuff 下一个可存放的位置
//把所有消息转移到 msgbuff 中进行处理
memcpy(msgbuff + lastPos, buff, len);
lastPos += len;
printf("lastpost : %ld \n", lastPos);
//从msgbuff中处理处理少包,粘包的问题,因此循环
//只要lastpos 大于等于消息头长度, 证明至少收到了消息头
while (lastPos >= headLen){
DataHeader * pHeader = (DataHeader*)msgbuff; //每次从头提取消息,因此可用dequeue来存放消息
//用于确认整个消息长度
total_datalen = pHeader->dataLen + headLen;
printf("got header , total len :%ld , lastpos:%ld\n", total_datalen , lastPos);
//再次判断msgbuff中是否已经有了一整个完整包.
//如果有则处理此消息,如果没有则中断(少包),等待下一次消息.
if (lastPos >= total_datalen){
//处理此消息
onMsgRecved(pHeader->cmd);
//处理完成后,从msgbuff中去除已经处理的消息
memcpy(msgbuff, msgbuff + total_datalen, lastPos - total_datalen);
//修改最后的位置
lastPos -= total_datalen;
printf("after process data , lastpos:%ld\n", lastPos);
}
else{
printf("no msgdata yet!\n");
break;
}
}
下面是完整测试代码:
完整测试代码太长不建议查看 , 且基于select的客户端并不需要这么写;
共享头文件:
#ifndef CMD_HEADER
#define CMD_HEADER
enum CMD{
CMD_LOGIN,
CMD_LOGOUT,
CMD_LOGIN_RESULT,
CMD_LOGOUT_RESULT,
CMD_USER_JOIN,
CMD_ERROR
};
typedef struct _DataHeader{
short dataLen;
short cmd;
} DataHeader, *LPDataHeader;
typedef struct _Login {
DataHeader header;
char uname[32];
char passwd[32];
char data[932]; //用于测试数据
} Login, *LPLogin;
typedef struct _LoginResult{
DataHeader header;
short result;
char testdata[992]; //用于测试数据
}LoginResult, *LPLoginResult;
typedef struct _Logout{
DataHeader header;
char uname[32];
}Logout, *LPLogout;
typedef struct _LogoutResult{
DataHeader header;
short result;
}LogoutResult, *LPLogoutResult;
typedef struct _UserJoin{
DataHeader header;
int sock;
}UserJoin, *LPUserJoin;
#endif
TCPClient封装类
core_client.cpp
#ifndef CORE_CLIENT
#define CORE_CLIENT
#include "../trans.h"
//忽略这些东西即可,这些定义在linux有效
#ifndef _WIN32
#include <unistd.h>
#include <pthread>
#include <arpa/inet.h>
#include <string.h>
#define SOCKET int
#else
#include <process.h>
#include <WinSock2.h>
#include <string.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
#endif
#define RECV_BUFFSIZE (1<<13)
#define MSG_BUFFSIZE (1<<16)
#define BUFFSIZE_DELTA 2
class Tcp_Client{
WSADATA wsadata;
SOCKET hSocket;
SOCKADDR_IN peeraddr;
char * buff;
char * msgbuff;
unsigned int msgbuff_size;
unsigned int las