文章一:
今天在开发游戏客户端测试程序时,由于出现很多客户端,经过connect成功后,代码卡在recv系统调用中,后来发现可能是由于socket默认是阻塞模式,所以会令很多客户端
链接处于链接却不能传输数据状态。
后来修改socket为非阻塞模式,但在connect的时候,发现返回值为-1,刚开始以为是connect出现错误,但在服务器上看到了链接是ESTABLISED状态。证明链接是成功的
但为什么会出现返回值是-1呢? 经过查询资料,以及看stevens的APUE,也发现有这么一说。
当connect在非阻塞模式下,会出现返回-1值,错误码是EINPROGRESS,但如何判断connect是联通的呢?stevens书中说明要在connect后,继续判断该socket是否可写?
若可写,则证明链接成功。如何判断可写,有2种方案,一种是select判断是否可写,二用poll模型。
select:
int CheckConnect(int iSocket)
{
fd_set rset;
FD_ZERO(&rset);
FD_SET(iSocket, &rset);
timeval tm;
tm. tv_sec = 0;
tm.tv_usec = 0;
if ( select(iSocket + 1, NULL, &rset, NULL, &tval) <= 0)
{
close(iSocket);
return -1;
}
if (FD_ISSET(iSocket, &rset))
{
int err = -1;
socklen_t len = sizeof(int);
if ( getsockopt(iSocket, SOL_SOCKET, SO_ERROR ,&err, &len) < 0 )
{
close(iSocket);
printf("errno:%d %s\n", errno, strerror(errno));
return -2;
}
if (err)
{
errno = err;
close(iSocket);
return -3;
}
}
return 0;
}
poll:
int CheckConnect(int iSocket) { struct pollfd fd; int ret = 0; socklen_t len = 0; fd.fd = iSocket; fd.events = POLLOUT; while ( poll (&fd, 1, -1) == -1 ) { if( errno != EINTR ){ perror("poll"); return -1; } } len = sizeof(ret); if ( getsockopt (iSocket, SOL_SOCKET, SO_ERROR, &ret, &len) == -1 ) { perror("getsockopt"); return -1; } if(ret != 0) { fprintf (stderr, "socket %d connect failed: %s\n", iSocket, strerror (ret)); return -1; } return 0; }
文章二:
步骤1: 设置非阻塞,启动连接
实现非阻塞 connect ,首先把 sockfd 设置成非阻塞的。这样调用
connect 可以立刻返回,根据返回值和 errno 处理三种情况:
(1) 如果返回 0,表示 connect 成功。
(2) 如果返回值小于 0, errno 为 EINPROGRESS, 表示连接
建立已经启动但是尚未完成。这是期望的结果,不是真正的错误。
(3) 如果返回值小于0,errno 不是 EINPROGRESS,则连接出错了。
步骤2:判断可读和可写
然后把 sockfd 加入 select 的读写监听集合,通过 select 判断 sockfd
是否可写,处理三种情况:
(1) 如果连接建立好了,对方没有数据到达,那么 sockfd 是可写的
(2) 如果在 select 之前,连接就建立好了,而且对方的数据已到达,
那么 sockfd 是可读和可写的。
(3) 如果连接发生错误,sockfd 也是可读和可写的。
判断 connect 是否成功,就得区别 (2) 和 (3),这两种情况下 sockfd 都是
可读和可写的,区分的方法是,调用 getsockopt 检查是否出错。
步骤3:使用 getsockopt 函数检查错误
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len)
在 sockfd 都是可读和可写的情况下,我们使用 getsockopt 来检查连接
是否出错。但这里有一个可移植性的问题。
如果发生错误,getsockopt 源自 Berkeley 的实现将在变量 error 中
返回错误,getsockopt 本身返回0;然而 Solaris 却让 getsockopt 返回 -1,
并把错误保存在 errno 变量中。所以在判断是否有错误的时候,要处理
这两种情况。
代码:
- int conn_nonb(int sockfd, const struct sockaddr_in *saptr, socklen_t salen, int nsec)
- {
- int flags, n, error, code;
- socklen_t len;
- fd_set wset;
- struct timeval tval;
-
- flags = fcntl(sockfd, F_GETFL, 0);
- fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
-
- error = 0;
- if ((n == connect(sockfd, saptr, salen)) == 0) {
- goto done;
- } else if (n < 0 && errno != EINPROGRESS){
- return (-1);
- }
-
-
-
- FD_ZERO(&wset);
- FD_SET(sockfd, &wset);
- tval.tv_sec = nsec;
- tval.tv_usec = 0;
-
- if ((n = select(sockfd+1, NULL, &wset,
- NULL, nsec ? &tval : NULL)) == 0) {
- close(sockfd);
- errno = ETIMEDOUT;
- return (-1);
- }
-
- if (FD_ISSET(sockfd, &wset)) {
- len = sizeof(error);
- code = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
-
-
-
-
- if (code < 0 || error) {
- close(sockfd);
- if (error)
- errno = error;
- return (-1);
- }
- } else {
- fprintf(stderr, "select error: sockfd not set");
- exit(0);
- }
-
- done:
- fcntl(sockfd, F_SETFL, flags);
- return (0);
- }
GNU CLASSPATH的实现修改:
if (EINPROGRESS == errno)
{
fd_set wrfds,rdfds;
FD_ZERO(&wrfds);
FD_SET(fd, &wrfds);
rdfds=wrfds;
ret = cpnio_select (fd + 1, &rdfds, &wrfds, NULL, &timeo);
printf("ret is %d\n",ret);
if (ret == -1)
{
JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
return JNI_ERR;
}
if (ret == 0) /* connect timed out */
{
JCL_ThrowException (env, SOCKET_TIMEOUT_EXCEPTION,
"connect timed out");
printf("time out\n");
return JNI_ERR;
}
if ( FD_ISSET(fd, &rdfds) && FD_ISSET(fd, &wrfds)) {
int ret;
socklen_t len = sizeof (ret);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &ret, &len) < 0) {
printf( "getsockopt error.\n");
return -1;
}
if(ret!=0)
fprintf (stderr, "socket %d connect failed: %s\n",
fd, strerror (ret));
JCL_ThrowException (env, CONNECT_EXCEPTION, strerror (ret));
return JNI_ERR;
}else
return JNI_OK;
}
else if (ECONNREFUSED == errno)
{
JCL_ThrowException (env, CONNECT_EXCEPTION,
strerror (errno));
return JNI_ERR;
}
else
{
JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
return JNI_ERR;
}
------------------------------------------------------------------------------------
宋涛(Tao Song)
烽火通信科技股份有限公司(Fiberhome Telecommunication Technologies Co.,LTD)
业务与终端产出线 (Service and CPE Business Unit) 内核技术开发部
Tel: +86-27-59100833
Mobile: +86-13545021858
Email: tsong@fiberhome.com.cn
http://www.fiberhome.com.cn/