天天看點

非阻塞connect的使用筆記

        非阻塞connect的三個用途:

       可以把三路握手疊加在其他處理上; 可以使用這個技術同時建立多個連接配接;  可以給select指定一個時間限制,使得能夠縮短connect的逾時。

        當一個非阻塞的TCP套接字上調用connect時,connec将立即傳回一個EINPROGRESS錯誤,不過已經發起的TCP三路握手繼續進行。接着使用select/poll/epoll等檢測這個連接配接或成功或失敗的已建立條件。

        盡管套接字連接配接是非阻塞的,如果連接配接到的伺服器在同一個主機上,那麼當調用connect時,連接配接通常立刻建立。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>

#define BUFFER_SIZE  1023

int setnonblocking(int fd)
{
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);

    return old_option;
}


int unblock_connect(const char *ip, int port, int time)
{
    int ret = 0;
    struct sockaddr_in serveraddr;
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(port);
    inet_pton(AF_INET, ip, &serveraddr.sin_addr);

    int sockfd = socket( AF_INET, SOCK_STREAM, 0);
    assert( sockfd >= 0 );

    int fdopt = setnonblocking(sockfd);

    ret = connect(sockfd, ( struct sockaddr *)&serveraddr, sizeof(serveraddr));
    if( ret == 0 )    //立即建立了連接配接
    {
	printf("connect with server immediately\n");
        fcntl(sockfd, F_SETFL, fdopt);
        return sockfd;
    }
    else if( errno != EINPROGRESS) // 傳回錯誤不是EINPROGRESS,則無法建立連接配接
    {
        printf("unblock connect not support\n");
	return -1;
    }

 
    //沒有立即建立連接配接,需要對套機字監聽是否可寫
    fd_set writefds;
    struct timeval timeout;

    FD_ZERO( &writefds );
    FD_SET( sockfd, &writefds);

    timeout.tv_sec = time;
    timeout.tv_usec = 0;

    ret = select( sockfd+1, NULL, &writefds, NULL, &timeout );
    if( ret <= 0)  //逾時或者select調用失敗
    {
	printf("connection time out\n");
        close( sockfd );
 
 	return -1;
    }

    if( !FD_ISSET( sockfd, &writefds ) )   //套接字不可寫,則建立連結失敗
    {
	printf("no events on sockfd found\n");
	close( sockfd );

        return -1;
    }

  
    //調用getsockopt檢查套接字上是否存在待處理錯誤
    //如果錯誤碼是0則表示連接配接建立成功,否則連接配接建立失敗
    int error = 0;
    socklen_t length = sizeof( error );
    if( getsockopt( sockfd, SOL_SOCKET, SO_ERROR, &error, &length) < 0 )
    {
	printf("get sockfd option failed\n");
        close( sockfd );

        return -1;
    }
    
    if( error != 0 )
    {
	printf( "connection failed after select with the error: %d \n", error );
	close( sockfd );
 
        return -1;
    }

    printf("connection ready after select with the socket: %d\n", sockfd );
    fcntl( sockfd, F_SETFL, fdopt );

    return sockfd;
 
}


int main(int argc, char **argv)
{
    if( argc <= 2 )
    {
	printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
	return -1;
    }

    const char *ip = argv[1];
    int port = atoi( argv[2] );

    int sockfd = unblock_connect( ip, port, 10 );

    if( sockfd < 0 )
    {
	return 1;
    }

    return 0;
}
           

繼續閱讀