问题描述
北京项目通过调用我们提供的库libsigxt.a与加密机通信,c/s架构,客户端启用多个线程,每个线程流程有以下三步,连接加密机,签名,关闭链接。在正常运行一段时间后会出现不能连接加密机服务问题。
连接服务器代码如下:
setnonblocking(sk_fd);
ret = connect(sk_fd, (struct sockaddr *)&sa_serv, server_len);
P_DEBUG("main connect ret:%d\n",ret);
if(ret < 0){
if(errno != EINPROGRESS){
P_DEBUG("mainip connect error ret.\n");
}else{
tv.tv_sec = timeout;
tv.tv_usec = 0;
fd_set wset;
FD_ZERO(&wset);
FD_SET(sk_fd,&wset);
ret = select(sk_fd+1,NULL,&wset,NULL,&tv);
printf("main select ret is %d\n",ret);
if(ret<0){
P_DEBUG("mainip select error,can not connect to server.\n");
}else if(ret == 0){
P_DEBUG("mainip connect timeout.\n");
}else{
if(FD_ISSET(sk_fd,&wset)){
P_DEBUG("mainip connect successful.\n");
setblocking(sk_fd);
}else{
P_DEBUG("not fd_isset(). not connected.\n");
goto quit;
}
}
}
}
先设置非阻塞模式,然后通过connect连接,当返回0时,表示连接成功;当返回-1,并且errno 为 EINPROGRESS表示正在连接过程中,通过select监听wset,当select返回小于0时,表示连接失败;当select返回0时,表示超时;当返回大于0时,表示连接成功,重新再设置成阻塞模式。
根据现场日志打印,当select返回非1的正数,则监听不到可写事件,连接失败,发送数据也失败。
原因查找定位
通过网上搜索发现,1024限定的不只是监听的个数,还是文件描述符的最大值。FD_SETSIZE限制了文件描述符的个数。
但是根据fd_set存储文件描述符的原理,FD_SETSIZE限制的应该是文件描述符的最大值,当然限制了最大值也就限制了个数。
下面一段摘自man select中的原话
"Executing FD_CLR() or FD_SET() with a value of fd that is negative or is equal to or larger than FD_SETSIZE will result in undefined behavior."
再查下现场打开的描述符个数,的确大于1024,造成select返回的结果不可预期。
解决方案
既然select使用除了问题,那就换成epoll来监听,修改代码如下:
setnonblocking(sk_fd);
ret = connect(sk_fd, (struct sockaddr *)&sa_serv, server_len);
if(ret == 0){
goto connect_success;
}
if(ret<0 && errno != EINPROGRESS){
P_DEBUG("back connect failed.\n");
goto connect_failure;
}
if((epfd = epoll_create(1))<0){
P_DEBUG("main epoll create error.\n");
goto connect_failure;
}
memset(&ev, 0, sizeof ev);
ev.events = EPOLLOUT;
ev.data.fd = sk_fd;
if(epoll_ctl(epfd, EPOLL_CTL_ADD, sk_fd, &ev)<0){
P_DEBUG("main epoll_ctl error.\n");
goto connect_failure;
}
n = epoll_wait(epfd,events,1,itimeout);
P_DEBUG("back epoll_wait n is %d\n",n);
if(n<=0 || events[0].events & EPOLLERR){
P_DEBUG("back connect failure.\n");
goto connect_failure;
}
connect_success:
P_DEBUG("connect success.\n");
setblocking(sk_fd);
......
connect_failure:
if(sk_fd>0)
close(sk_fd);
sk_fd = -1;
if(epfd>0)
close(epfd);
return -1;
}
经测试发现,没有再出现连接失败问题。
结论:建议linux下弃用select。