您的位置:首页 > 其它

connect nonblock mode

2016-09-24 19:09 423 查看
非阻塞connect(non-block mode connect)
套接字执行I/O操作有阻塞和非阻塞两种模式。在阻塞模式下,在I/O操作完成前,执行操作的函数一直等候而不会立即返回,该函数所在的线程会阻塞在这里。相反,在非阻塞模式下,套接字函数会立即返回,而不管I/O是否完成,该函数所在的线程会继续运行。
客户端调用connect()发起对服务端的socket连接,如果客户端的socket描述符为阻塞模式,则connect()会阻塞到连接建立成功或连接建立超时(linux内核中对connect的超时时间限制是75s, Soliris 9是几分钟,因此通常认为是75s到几分钟不等)。如果为非阻塞模式,则调用connect()后函数立即返回,如果连接不能马上建立成功(返回-1),则errno设置为EINPROGRESS,此时TCP三次握手仍在继续。此时可以调用select()检测非阻塞connect是否完成。select指定的超时时间可以比connect的超时时间短,因此可以防止连接线程长时间阻塞在connect处。
select判断规则:
   1)如果select()返回0表示在select()超时,超时时间内未能成功建立连接,也可以再次执行select()进行检测,如若多次超时,需返回超时错误给用户。
   2)如果select()返回大于0的值,则说明检测到可读或可写的套接字描述符。源自 Berkeley 的实现有两条与 select 和非阻塞 I/O 相关的规则:
        A) 当连接建立成功时,套接口描述符变成 可写(连接建立时,写缓冲区空闲,所以可写)
        B) 当连接建立出错时,套接口描述符变成 既可读又可写(由于有未决的错误,从而可读又可写)
        因此,当发现套接口描述符可读或可写时,可进一步判断是连接成功还是出错。这里必须将B)和另外一种连接正常的情况区分开,就是连接建立好了之后,服务器端发送了数据给客户端,此时select同样会返回非阻塞socket描述符既可读又可写。
        □对于Unix环境,可通过调用getsockopt来检测描述符集合是连接成功还是出错(此为《Unix Network Programming》一书中提供的方法,该方法在Linux环境上测试,发现是无效的):
               A)如果连接建立是成功的,则通过getsockopt(sockfd,SOL_SOCKET,SO_ERROR,(char *)&error,&len) 获取的error 值将是0
               B)如果建立连接时遇到错误,则errno 的值是连接错误所对应的errno值,比如ECONNREFUSED,ETIMEDOUT 等
        □一种更有效的判断方法,经测试验证,在Linux环境下是有效的:
       
再次调用connect,相应返回失败,如果错误errno是EISCONN,表示socket连接已经建立,否则认为连接失败。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include<sys/ioctl.h>

//inet_addr()
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PEER_IP "192.254.1.1"
#define PEER_PORT 7008
int main(int argc, char **argv)
{
int ret = 0;
int sock_fd;
int flags;
struct sockaddr_in addr;

/* obtain a socket */
sock_fd = socket(AF_INET, SOCK_STREAM, 0);

/* set non-blocking mode on socket*/
#if 1
flags = fcntl(sock_fd, F_GETFL, 0);
fcntl(sock_fd, F_SETFL, flags|O_NONBLOCK);
#else
int imode = 1;
ioctl(sock_fd, FIONBIO, &imode);
#endif

/* connect to server */
addr.sin_family = AF_INET;
addr.sin_port = htons(PEER_PORT);
addr.sin_addr.s_addr = inet_addr(PEER_IP);
int res = connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
if (0 == res)
{
printf("socket connect succeed immediately.\n");
ret = 0;
}
else
{
printf("get the connect result by select().\n");
if (errno == EINPROGRESS)
{
int times = 0;
while (times++ < 5)
{
fd_set rfds, wfds;
struct timeval tv;

printf("errno = %d\n", errno);
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_SET(sock_fd, &rfds);
FD_SET(sock_fd, &wfds);

/* set select() time out */
tv.tv_sec = 10;
tv.tv_usec = 0;
int selres = select(sock_fd + 1, &rfds, &wfds, NULL, &tv);
switch (selres)
{
case -1:
printf("select error\n");
ret = -1;
break;
case 0:
printf("select time out\n");
ret = -1;
break;
default:
if (FD_ISSET(sock_fd, &rfds) || FD_ISSET(sock_fd, &wfds))
{
#if 0 // not useable in linux environment, suggested in <<Unix network programming>>
int errinfo, errlen;
if (-1 == getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &errinfo, &errlen))
{
printf("getsockopt return -1.\n");
ret = -1;
break;
}
else if (0 != errinfo)
{
printf("getsockopt return errinfo = %d.\n", errinfo);
ret = -1;
break;
}

ret = 0;
printf("connect ok?\n");
#else
#if 1
connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
int err = errno;
if (err == EISCONN)
{
printf("connect finished 111.\n");
ret = 0;
}
else
{
printf("connect failed. errno = %d\n", errno);
printf("FD_ISSET(sock_fd, &rfds): %d\n FD_ISSET(sock_fd, &wfds): %d\n", FD_ISSET(sock_fd, &rfds) , FD_ISSET(sock_fd, &wfds));
ret = errno;
perror("connect failed");
}
#else
char buff[2];
if (read(sock_fd, buff, 0) < 0)
{
printf("connect failed. errno = %d\n", errno);
ret = errno;
}
else
{
printf("connect finished.\n");
ret = 0;
}
#endif
#endif
}
else
{
printf("haha\n");
}
}

if (-1 != selres && (ret != 0))
{
printf("check connect result again... %d\n", times);
continue;
}
else
{
break;
}
}
}
else
{
printf("connect to host %s:%d failed.\n", PEER_IP, PEER_PORT);
ret = errno;
}
}
if (0 == ret)
{
send(sock_fd, "12345", sizeof("12345"), 0);
}
else
{
printf("connect to host %s:%d failed.\n", PEER_IP, PEER_PORT);
}

close(sock_fd);
return ret;
}

Non-blocking sockets have a similar effect on the accept() API. When you call accept(), and there isn't already a client connecting to you, it will return 'Operation Would Block', to tell you that it can't complete the accept() without
waiting...

The connect() API is a little different. If you try to call connect() in non-blocking mode, and the API can't connect instantly, it will return the error code for 'Operation In Progress'.
When you call connect() again, later, it may tell you 'Operation Already In Progress' to let you know that it's still trying to connect, or it may give you a successful return code, telling you that the connect has
been made.


Going back to the "web browser" example, if you put the socket that was connecting to the web server into non-blocking mode, you could then call connect(), print a message saying "connecting to host www.floofy.com..." then maybe
do something else, and them come back to connect() again. If connect() works the second time, you might print "Host contacted, waiting for reply..." and then start calling send() and recv(). If the connect() is still pending, you might check
to see if the user has pressed a "abort" button, and if so, call close() to stop trying to connect.

Non-blocking sockets can also be used in conjunction with the select() API. In fact, if you reach a point where you actually WANT to wait for data on a socket that was previously marked as "non-blocking", you could simulate a blocking
recv() just by calling select() first, followed by recv().

The "non-blocking" mode is set by changing one of the socket's "flags". The flags are a series of bits, each one representing a different capability of the socket. So, to turn on non-blocking mode requires three steps:

Call the fcntl() API to retrieve the socket descriptor's current flag settings into a local variable.

In our local variable, set the O_NONBLOCK (non-blocking) flag on. (being careful, of course, not to tamper with the other flags)

Call the fcntl() API to set the flags for the descriptor to the value in our local variable.

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: