select模式的IO的事件通知一定是准确的吗?

本文探讨了在网络编程中使用Select模型进行Socket连接检测时遇到的问题。特别是在目标地址无效的情况下,不同平台(如iOS和Ubuntu)上的行为差异及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题:

网络编程中,基于Select对Socket事件的筛选,在连接的地址失效的情况下,可能有不如预期的行为发生。

场景:

这里有一个C++写的SDK,封装了网络通信层,基于Select IO 网络IO模型;同一份代码,移植到到NDK和IOS下,线上反映的问题是,刚建立好TCP连接,然后执行Send操作,立即报EPIPE的错误;

报这个错误的原因解释是:1.尝试在一个未建立连接的socket上send 2.尝试在一个已经断开连接或者关闭写端通道的连接的socket上send;

但是先建立NIO的socket,然后使用Select筛选读写事件来判断TCP连接是否可用,这样的逻辑判断,是连接可用的吗?为什么每次还会Send时报错呢;

奇怪的问题是,这段代码,在Android的NDK下工作是没有问题的;在IOS下与Ubuntu下工作是有问题的;

IOS的测试环境:

 sunwaydeMac-mini:src jiangjim$ uname -a
Darwin sunwaydeMac-mini.local 15.5.0 Darwin Kernel Version 15.5.0: Tue Apr 19 18:36:36 PDT 2016; root:xnu-3248.50.21~8/RELEASE_X86_64 x86_64

Ubuntu的测试环境:

Linux ubuntu 4.2.0-35-generic #40~14.04.1-Ubuntu SMP Fri Mar 18 16:37:35 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

代码:

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <time.h>

inline int SetNoblock(int hSocket)
{
	int32_t flags = 0 ;
	int32_t retval = 0 ;

	flags = fcntl( hSocket , F_GETFL ) ;

	if ( flags > 0 )
	{
		flags |= O_NONBLOCK ;

		if ( fcntl( hSocket, F_SETFL, flags ) >0 )
		{
			retval = 1 ;
		}
	}
	return retval ;
}


inline bool isSocketInPending(int nErr)
{
	return (nErr == EWOULDBLOCK || nErr == EINPROGRESS);
}


void select_once(int fd)
{
	struct timeval tv ;
	tv.tv_sec = 0;
	tv.tv_usec = 1000*1000 ;

	fd_set readset ;
	fd_set sendset ;
	fd_set exptset;
	FD_ZERO( &readset ) ;
	FD_ZERO( &sendset ) ;
	FD_SET( fd, &readset ) ;
	FD_SET( fd, &sendset ) ;	
	FD_SET( fd, &exptset ) ;	
	
	int rc = select( fd+1, &readset, &sendset , &exptset, &tv ) ;
	if (rc < 0)
	{
		printf("select error\n");
		return;
	}
	
	if (rc == 0){
		printf("select timeout\n");
		return;
	}

	if( FD_ISSET( fd, &readset ))	{

		printf("readable\n");
	}
	
	if( FD_ISSET( fd, &sendset)){

		printf("writable\n");
		char buf[11]="";
		int ret=::send( fd,buf,1,0);
		printf("send;ret=%d;%d %s\n",ret,errno,strerror(errno));
		
	}

	if( FD_ISSET( fd, &exptset )){
		printf("exceptional\n");
	}

}


int main()
{
	 int s = socket(AF_INET,SOCK_STREAM, 0);
	 SetNoblock(s);

	const char* ip="172.16.0.41";
	int port=6012;

	struct sockaddr_in      svraddrV4 ;
	svraddrV4.sin_family = AF_INET;	
	svraddrV4.sin_port = htons( port ) ;
	svraddrV4.sin_addr.s_addr = inet_addr( ip) ;
	
	socklen_t socklen = sizeof( svraddrV4 ) ;
	struct sockaddr* connectAddr = (sockaddr *)&svraddrV4 ;	
	int nErr;
	int rc = ::connect( s, connectAddr,  socklen );
	if (rc < 0 )
	{
		
		
		printf("connect;ret=%d;%d %s\n",rc,errno,strerror(errno));
		nErr = errno ; 
		if (! isSocketInPending(nErr))
		{
		        printf("connect error at now\n");	
			return -1;
		}
		printf("connecting...\n");
	}else{
		printf("connect ok at now\n");
	}

	while(1)
	{
		select_once(s);
		sleep(5);
	}

	

	return 0;
}


如上代码,是我从SDK中裁剪出来,怀疑有问题的代码逻辑原型;

测试现象是:

如果连接的地址是IP,PORT,

假设IP是非法IP无可达路由,那么上面代码运行结果是,Select在超时预期里没有事件通知,直到消耗掉超时预期被判断为连接失败。这是符合预期的;


假设IP是可达的,而PORT上并没有服务在监听,那么问题来了,Select会发出事件通知,Readable,Writable,并且取SO_ERROR也为0,表示连接是成功了的;这不符合预期。这导致之后,会被认为连接是成功的,而执行Send操作,从而发生EPIPE错误。

这个代码执行过程与结论,我分别在IOS与Ubuntu下都进行了测试,测试结果不符合预期;而在Android NDK下测试,测试结果却是符合预期。

同一份代码在不同平台的执行,可能预期的结果是不同,这再一次证明了“”除非你测试了所有的分支,否则经验极有可能不可靠。“”


解决

解决了这一问题:我采用了判断连接成功之后,预先验证连接是否真正可以发送数据的行为判断;

发送一个空字节之后,检查返回值是否为-1;这样来规避这个问题。

		char buf[1]="";
		int ret=::send( fd,buf,0,0);
		printf("send;ret=%d;%d %s\n",ret,errno,strerror(errno));








  




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值