说明:本文适用于Windows平台,后面追加一篇适用于Linux平台的
转自:http://www.cnblogs.com/shiqgfmj/archive/2010/12/29/1919434.html
非阻塞的socket客户端connect()困扰了一下午。因为是非阻塞,不管是否连接上都直接返回,所以就无法知道什么时候连接上。
一开始想到网上找资料,可惜不多。于是MSDN,总算找到一句话。
With a nonblocking socket, the connection attempt cannot be completed immediately. In this case, this function will return SOCKET_ERROR and WSAGetLastError will resturn WSAEWOULDBLOCK. The following list shows the three scenarios that are possible in this case:
Use the select function to determine the completion of the connection request by checking to see if the socket is writeable.
If the application is using WSAEventSelect to indicate interest in connection events, then the associated event object will be signaled indicating that the connect operation is complete (successfully or not).
非阻塞socket连接请求不会立即完成。此时,函数返回SOCKET_ERROR且WSAGetLastERROR()返回WSAEWOULDBLOCK。下面展示了此情况的2种场景:
1、使用select函数检查这个socket是否可写确定连接请求是否完成;
2、如果程序使用WSAEventSelect表明关注连接事件,那么被关联的事件对象将被置信号表示连接操作完成(成功或失败)
小代码如下:
client.c
- FD_ZERO(&wtfds);
- _timout.tv_sec = 3;
- _timout.tv_usec = 0;
- msock = socket(AF_INET, SOCK_STREAM, 0);
- toAddr.sin_family = AF_INET;
- toAddr.sin_port = htons(7000);
- toAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.87");
- if (ioctlsocket(msock, FIONBIO, &iTemp) < 0)
- {
- perror("ioctlsocket error\n");
- }
- FD_SET(msock, &wtfds);
- iRet = connect(msock, (SOCKADDR*)&toAddr, sizeof(toAddr));
- if (!iRet)
- {
- printf("connect immediately\n");
- char sndMsg[100] = {0};
- sprintf(sndMsg, "hello world");
- if (SOCKET_ERROR == send(msock, sndMsg, strlen(sndMsg), 0))
- {
- perror("send error");
- return 1;
- }
- printf("send success\n");
- }
- else if (iRet<0 && WSAGetLastError()==WSAEWOULDBLOCK)
- {
- int iRet1 = select(0, NULL, &wtfds, NULL, &_timout);
- if (iRet1 < 0)
- {
- perror("connect error\n");
- return 1;
- }
- else if (!iRet1)
- {
- perror("timeout error\n");
- return 1;
- }
- else
- {
- if (FD_ISSET(msock, &wtfds))
- {
- printf("connect success\n");
- char sndMsg[100] = {0};
- sprintf(sndMsg, "hello world");
- if (SOCKET_ERROR == send(msock, sndMsg, strlen(sndMsg), 0))
- {
- perror("send error");
- return 1;
- }
- printf("send success\n");
- }
- }
- }
server.c
- msock = socket(AF_INET, SOCK_STREAM, 0);
- ioctlsocket(msock, FIONBIO, (unsigned long*)&iNonbk);
- FD_SET(msock, &rdfds);
- cltAddr.sin_family = AF_INET;
- cltAddr.sin_port = htons(7000);
- cltAddr.sin_addr.S_un.S_addr = INADDR_ANY;
- if (SOCKET_ERROR == bind(msock, (SOCKADDR*)&cltAddr, sizeof(cltAddr)))
- {
- perror("bind error");
- return 1;
- }
- if (SOCKET_ERROR == listen(msock, 5))
- {
- perror("listen error");
- return 1;
- }
- _tim = time(NULL);
- ltime = localtime(&_tim);
- printf("%d:%d:%d\n", ltime->tm_hour, ltime->tm_min, ltime->tm_sec);
- iRet = select(0, &rdfds, NULL, NULL, &_timval);
- if (iRet < 0)
- {
- perror("accept error");
- return 1;
- }
- else if(iRet == 0)
- {
- perror("timeout error");
- return 1;
- }
- else
- {
- if (FD_ISSET(msock, &rdfds))
- printf("msock is in rdfds\n");
- else
- return 1;
- SOCKET clsock = accept(msock, (SOCKADDR*)&cltAddr, &iLen);
- if (clsock == INVALID_SOCKET)
- {
- printf("clsock is not a valid socket\n");
- return 1;
- }
- if (FD_ISSET(clsock, &rdfds))
- printf("clsock is in rdfds\n");
- else
- printf("clsock is not in rdfds\n");
- _tim = time(NULL);
- ltime = localtime(&_tim);
- printf("%d:%d:%d\n", ltime->tm_hour, ltime->tm_min, ltime->tm_sec);
- char recvMsg[100] = {0};
- int iSize = recv(clsock, recvMsg, 100, 0);
- printf("from client %s:%s,%d\n", inet_ntoa(cltAddr.sin_addr),recvMsg,WSAGetLastError());
- }
下文适用于Linux平台
转自:http://kenby.iteye.com/blog/1183579
步骤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);
- }
- /* Do whatever we want while the connect is taking place */
- 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); /* timeout */
- errno = ETIMEDOUT;
- return (-1);
- }
- if (FD_ISSET(sockfd, &wset)) {
- len = sizeof(error);
- code = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
- /* 如果发生错误,Solaris实现的getsockopt返回-1,
- * 把pending error设置给errno. Berkeley实现的
- * getsockopt返回0, pending error返回给error.
- * 我们需要处理这两种情况 */
- 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); /* restore file status flags */
- return (0);
- }
关于ioctlsocket()
(摘自:http://www.cnblogs.com/endeavour/archive/2011/11/18/2253529.html)
#include <winsock.h>
This function controls the I/O mode of a socket.
int ioctlsocket(SOCKET s, long cmd,u_long FAR* argp);
Parameters
s
[in] Descriptor identifying a socket.
cmd
[in] Command to perform on socket s.
argp
[in, out] Pointer to a parameter for cmd.
Return Value:
If no error occurs, this function returns zero. If an error occurs, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError.The following table shows a list of possible error codes.
u_long mode = 0;
ioctlsocket(s,FIONBIO,&mode);
控制为阻塞方式。
u_long mode = 1;
ioctlsocket(s,FIONBIO,&mode);
控制为非阻塞方式。
本函数可用于任一状态的任一套接口。它用于获取与套接口相关的操作参数,而与具体协议或通讯子系统无关。支持下列命令:
FIONBIO:允许或禁止套接口s的非阻塞模式。argp指向一个无符号长整型。如允许非阻塞模式则非零,如禁止非阻塞模式则为零。当创建一个套接口时,它就处于阻塞模式(也就是说非阻塞模式被禁止)。这与BSD套接口是一致的。WSAAsynSelect()函数将套接口自动设置为非阻塞模式。如果已对一个套接口进行了WSAAsynSelect() 操作,则任何用ioctlsocket()来把套接口重新设置成阻塞模式的试图将以WSAEINVAL失败。为了把套接口重新设置成阻塞模式,应用程序必须首先用WSAAsynSelect()调用(IEvent参数置为0)来禁至WSAAsynSelect()。
FIONREAD:确定套接口s自动读入的数据量。argp指向一个无符号长整型,其中存有ioctlsocket()的返回值。如果s是SOCKET_STREAM类型,则FIONREAD返回在一次recv()中所接收的所有数据量。这通常与套接口中排队的数据总量相同。如果S是SOCK_DGRAM 型,则FIONREAD返回套接口上排队的第一个数据报大小。
SIOCATMARK:确实是否所有的带外数据都已被读入。这个命令仅适用于SOCK_STREAM类型的套接口,且该套接口已被设置为可以在线接收带外数据(SO_OOBINLINE)。如无带外数据等待读入,则该操作返回TRUE真。否则的话返回FALSE假,下一个recv()或recvfrom()操作将检索“标记”前一些或所有数据。应用程序可用SIOCATMARK操作来确定是否有数据剩下。如果在“紧急”(带外)数据前有常规数据,则按序接收这些数据(请注意,recv()和recvfrom()操作不会在一次调用中混淆常规数据与带外数据)。argp指向一个BOOL型数,ioctlsocket()在其中存入返回值。