tcp 粘包 解包 少包 两种解决方式

本文介绍了TCP通信中出现粘包、少包、解包现象的原因,并提供了两种解决策略。策略一通过读取指定字节数确保解包正确;策略二利用两个缓冲区,一个接收数据,另一个处理解包问题。这两种方法在不同场景下各有适用。文章附带了基于I/O复用select模型的测试代码片段。
摘要由CSDN通过智能技术生成

产生现象的原因在于无法控制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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值