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

产生现象的原因在于无法控制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
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TCP协议是一种面向连接的可靠传输协议,它将数据分成一个一个的数据包进行传输。但是,由于网络传输的不确定性,TCP粘包和分包问题就会出现。 1. TCP粘包问题 TCP粘包问题是指发送方将多个数据包合并成一个数据包发送,接收方无法区分多个数据包的边界,从而无法正确处理数据包。造成TCP粘包问题的原因有多种,比如发送方发送的数据包过大、发送速度过快、网络延迟等。 解决方法: (1) 设置消息边界标识符 在发送的消息中添加一个特殊的标识符,如换行符、空格等,用来标识消息的边界。接收方根据标识符来判断消息的边界,将消息分隔成多个数据包。 (2) 定长消息 可以设置一个固定长度的消息,每次发送的数据都是定长的。这样接收方就可以根据固定长度来将消息分隔成多个数据包。 2. TCP分包问题 TCP分包问题是指发送方将一个数据包分成多个数据包发送,接收方接收后需要将多个数据包组合成一个完整的数据包,才能进行处理。造成TCP分包问题的原因有多种,比如发送方发送的数据包过大、网络拥塞等。 解决方法: (1) 设置消息长度 在消息中添加消息长度信息,接收方接收到数据后,根据长度信息将多个数据包组合成一个完整的数据包。 (2) 固定长度消息 发送方每次发送的数据都是固定长度的,接收方根据固定长度来将多个数据包组合成一个完整的数据包。 总之,TCP粘包和分包问题可以通过合理的协议设计和网络优化来解决

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值