connect nonblock mode

非阻塞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:

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

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

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



<think>嗯,用户问的是C++ socket客户端如何判断与服务器已经连接。我需要先回忆一下socket编程的基本步骤。客户端通常通过connect函数来连接服务器,但如何确认连接是否成功呢? 首先,connect函数的返回值是关键。如果返回0,应该表示成功,否则失败。但可能要考虑不同平台的情况,比如Windows和Linux的错误码不同。比如在Linux,错误码可能是EINPROGRESS,特别是在非阻塞模式下,这时候可能需要检查socket是否可写,或者使用select、poll等函数来检测连接状态。 然后想到,除了检查connect的返回值,还需要处理阻塞和非阻塞模式的区别。阻塞模式下,connect会一直等到有结果,返回0就是成功,负值则失败,可以通过errno或WSAGetLastError获取具体错误。而非阻塞模式下,connect可能立即返回EINPROGRESS或类似的错误,这时候需要用select等函数来等待连接完成,然后检查socket是否可写,再通过getsockopt获取SO_ERROR确认是否有错误。 另外,异步操作中可能需要更复杂的处理,比如使用事件驱动或完成端口。但用户可能只需要基础的方法,所以应该重点放在同步阻塞和非阻塞两种情况。 还有,是否要提到心跳包或者应用层确认?比如即使TCP连接成功,应用层可能还需要进一步的验证,但这可能超出问题范围。用户的问题可能更关注TCP层的连接确认,所以暂时不考虑应用层协议。 总结步骤:调用connect,检查返回值,处理不同错误码,非阻塞模式下的处理,以及可能的后续确认。可能需要给出示例代码,比如连接后检查返回值,然后使用getsockopt来确保没有未处理的错误。同时要注意跨平台的问题,比如Windows的WSA相关函数和Unix的区别。 还要注意,有时候防火墙或网络问题可能导致connect返回成功但实际上无法通信,但这种情况可能属于更高层次的错误处理,用户可能只关心连接阶段的确认。所以主要回答如何在调用connect后判断连接是否真正建立。</think>在 C++ 中,客户端通过 Socket 判断是否成功连接服务器,主要依赖于 `connect()` 函数的返回值和错误处理机制。以下是核心判断方法及注意事项: --- ### **1. 阻塞模式下判断连接** ```cpp int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建 TCP Socket struct sockaddr_in server_addr = {/* 填充服务器地址和端口 */}; // 尝试连接 int result = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); // 判断结果 if (result == 0) { // 返回 0:连接成功(仅说明 TCP 握手完成) } else { // 返回 -1:连接失败,需通过 errno(Linux)或 WSAGetLastError()(Windows)获取错误码 #ifdef _WIN32 int error = WSAGetLastError(); if (error == WSAECONNREFUSED) { /* 服务器拒绝 */ } #else if (errno == ECONNREFUSED) { /* 服务器拒绝 */ } else if (errno == ETIMEDOUT) { /* 超时 */ } #endif } ``` --- ### **2. 非阻塞模式下判断连接** 当 Socket 设置为非阻塞模式时,`connect()` 可能立即返回 `-1`,此时需检查错误码是否为 `EINPROGRESS`(Linux)或 `WSAEWOULDBLOCK`(Windows),然后通过 `select()`/`poll()` 等待连接完成: ```cpp // 设置为非阻塞模式 #ifdef _WIN32 unsigned long mode = 1; ioctlsocket(sockfd, FIONBIO, &mode); #else int flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); #endif int result = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); if (result == 0) { // 极少数情况下直接成功 } else { #ifdef _WIN32 if (WSAGetLastError() != WSAEWOULDBLOCK) { /* 处理错误 */ } #else if (errno != EINPROGRESS) { /* 处理错误 */ } #endif // 使用 select/poll 等待可写事件(表示连接完成) fd_set writefds; FD_ZERO(&writefds); FD_SET(sockfd, &writefds); timeval timeout = {/* 设置超时 */}; int sel = select(sockfd + 1, NULL, &writefds, NULL, &timeout); if (sel > 0 && FD_ISSET(sockfd, &writefds)) { // 连接完成,检查是否有错误 int error = 0; socklen_t len = sizeof(error); getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len); if (error == 0)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值