C++网络编程Socket TCP阻塞模式客户端发送数据后,获取服务端返回的数据大小(缓冲区内的长度)

前言

最近研究网络编程,研究未知字节长度下TCP客户端接收服务端发来的消息。
我们在使用socket接收消息时候,会定义一个定长的char数组,长度1024或更多,通常1024足够接收所有,但也不一定,比如发送一本小说那么长的字符串,大于1024太正常。
通常,解决方式是在数据头部写上数据大小,客户端拿到数据大小后确定长度,这也是普遍的做法。
本人最近在研究用socket连接redis数据库,redis发送过来的包没有长度,人家的协议硬性规定,咱也不可能去改,只能针对这种不知道发送过来的数据有多大的情况,另行他法。

注:本文环境为windows,对于linux和mac,方法不变,代码改成相应的版本即可。

尝试一:失败

第一种尝试,改为非阻塞模式,while循环不断向redis服务器recv消息。检查recv函数的返回值、
每循环一次,recv返回值为-1,表示接受失败,进一步判断错误码。
根据资料,在recv错误码为WSAEWOULDBLOCK时,为等待操作完成。

char buff[1024];
std::string res;
while(true){
	int val = recv(sockSrv, buff, 1024, 0);
	if(val < 0){
		int err = WSAGetLastError()
		if(err = WSAEWOULDBLOCK){
			continue;
		}else{
			break;
		};
	}
	res += buff;
}

尝试后,发现,while进入了死循环。WSAEWOULDBLOCK几乎永远会满足,打断点调试,也是如此。
遂放弃。
可能是我代码有问题,理解有问题,如果这个思路可行,求大佬指点。

尝试二:失败

查阅资料,找到第二个方法,在阻塞模式下,通过 ioctlsocket 函数的 FIONBIO 来获取缓冲区可读的大小。

send(.....)	// 发送数据,这步不细写了
unsigned long len;
ioctlsocket(socketSer, FIONBIO, &len);
std::cout << len << std::endl;

代码简单,输出len的值为0,代表缓冲区内没有可以读的数据,但显然redis那边不可能不给我返回东西。
思考:对面明明返回了,缓冲区内却没有数据,是为何?
可能性一:没返回——不可能,redis不可能不返回数据。
可能性二:代码错误——不可能,n存在值了,n如果没有值,n自身也没初始化,cout打印时定会报错。
可能性三:send没完成——不可能,没完成的话就阻塞了,不会进行到ioctlsocket函数
可能性四:redis(服务端)并没有把数据发到缓冲区。
大侦探福尔摩斯说过,排除所有不可能情况,剩下的再不可能,也是事实的真相。
本人的分析:send数据给redis服务端后,send确实是send完毕了,然后执行 ioctlsocket 函数,但我们在执行 ioctlsocket 来获取缓冲区内可读数据大小时,会发生这几种情况:
1、redis那边可能并没有接收完数据,redis不接收完数据,也不会去处理数据并把结果send出来到缓冲区,ioctlsocket 函数自然返回缓冲区长度为0。
2、send数据后,redis那边在ioctlsocket执行之前瞬间接收到了数据,但还没处理完,ioctlsocket就执行了,此时缓冲区里也不可能有数据。
3、send数据后,redis在ioctlsocket执行之前就收到了数据并完成了处理,但还没来得及send到缓冲区。

总之,就是redis那边发送数据并发送完成没有ioctlsocket执行地快。拼速度拼不过人家,ioctlsocket读到的长度自然是0,延伸到其他客户端也是一样,客户端瞬间发送完数据后立马ioctlsocket去拿长度,服务端还没来得及发送数据。

尝试三:成功一半

有了分析,那么想解决办法。
查阅资料,先看看能不能获取到socket是否可读。
发现,阻塞状态下貌似不能(可能是我太菜了,没找到方法)。
那么能不能等服务端发送完再去执行 ioctlsocket 去获取缓冲区长度。
服务端显然不可能告诉我们这些。
那么我们就多执行几次 ioctlsocket 直到获取到的长度不为0。

unsigned long n = 0;
while(n == 0){
	ioctlsocket(sockSer, FIONBIO, &n);
}
std::cout << n << endl;

运行,输出。
成功,确实获取到了缓冲区内的长度。
这种方法有个弊端,一直去执行while循环,对于redis这种必然存在返回值的服务端还好,while不会持续多长时间,但while循环时,碰巧连接出现了问题,服务端关了或者发生了什么什么时,while就进入了无限的死循环。并且while循环实在是太占CPU资源。

尝试四:成功

那么再思考,什么情况下表示客户端发来消息可以读呢?
其实多么显而易见,可以读的时候缓冲区可以读
这不是废话么。
实践是检验真理的唯一标准,你能不能读,我去读一下不就得了!!
我们send完消息之后,去尝试用recv读一个字节,直到能读之前,recv一直阻塞,发现读到一个字节之后,我们去执行ioctlsocket获取缓冲区内剩余的可读字节的长度,此时获取到的长度n为真实值减1。

send(.......)
// 开始尝试读一个字节
std::string res;
char buff[1];
recv(sockSer, buff, 1, 0);
// 读到一个字节,执行ioctlsocket函数
unsigned long n;
ioctlsocket(sockSer, FIONBIO, &n);
std::cout << n << std::endl;
// 这个是时候拿到了缓冲区内剩余字节的数量,然后便用这个数去读剩下的全部
char* buff_2 = new char[n + 1];	// 创建的数组要比目标数组多1为,最后一个字节为\0,否则会出现乱码
recv(sockSer, buff_2, n, 0);
// 最后别忘了要跟第一个字节拼一下
buff_2[n] = '\0';	// 把字节数组最后一位变为\0,防止乱码
res += buff + buff_2;
std::cout << res << std::endl;
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值