• nonblock connect


    阻塞 connect

    客户端调用 connect 发起对服务端的 socket 连接,调用 connect 函数将激发 tcp 三次握手过程.如果客户端的 socket 描述符为阻塞模式(默认),则 connect 会阻塞到连接建立成功或连接超时(linux内核中对 connect 的超时时间限制是 75s). 在某些情况下我们并不希望 connect 阻塞这么久,如在做端口扫描时. 这个问题可以通过非阻塞 connect + select(或者 poll/epoll) 设置超时解决

    非阻塞 connect

    如果为非阻塞模式,则调用 connect 后函数立即返回. 如果连接不能马上建立成功返回-1. 当 errno 被设置为 EINPROGRESS 时,表示连接建立未完成但仍在继续(tcp 三次握手仍在继续)。此时可以调用 select,epoll 等函数监听这个连接失败的 socket 上的可写事件.当 select, poll 等函数返回后再用 getsockopt 来读取错误码并清除该 socket 上的错误.如果错误码是 0,表示连接建立,否则连接失败

    #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 <unistd.h>
    #include <sys/ioctl.h>
    #include <string.h>
    
    #define BUFFER_SIZE 1023
    
    
    // 设置文件描述符 fd 为非阻塞
    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;
    }
    
    
    // 超时连接函数, 参数分别是服务器 ip, 端口号和超时时间(毫秒)
    // 成功返回已经处于连接状态的 socket, 失败返回 -1
    int unblock_connect(const char *ip, int port, int time) {
        int ret = 0;
        struct sockaddr_in address;
        bzero(&address, sizeof(address));
        address.sin_family = AF_INET;
        inet_pton(AF_INET, ip, &address.sin_addr);
        address.sin_port = htons(port);
    
        int sockfd = socket(PF_INET, SOCK_STREAM, 0);
    
        int fdopt = setnonblocking(sockfd);
    
        ret = connect(sockfd, (struct sockaddr*)&address, sizeof(address));
        
        // 如果连接成功, 则恢复 sockfd 的属性并立即返回
        if(ret == 0) {
            printf("connect with server immediatelyn");
            fcntl(sockfd, F_SETFL, fdopt);
            return sockfd;
    
        } else if (errno != EINPROGRESS) {
            // 如果连接没有建立, 则只有当 errno 是 EINPROGRESS 时才表示连接还在进行
            printf("unblock connect not supportn");
            return -1;
        }
    
        fd_set readfds;
        fd_set writefds;
        struct timeval timeout;
    
        FD_ZERO(&readfds);
        FD_SET(sockfd, &writefds);
    
        timeout.tv_sec = time;
        timeout.tv_usec = 0;
    
        ret = select(sockfd + 1, NULL, &writefds, NULL, &timeout);
    
        // 超时或者出错, 立即返回
        if(ret <= 0) {
            printf("connection time outn");
            close(sockfd);
            return -1;
        }
    
        if(!FD_ISSET(sockfd, &writefds)) {
            printf("no events on sockfd foundn");
            close(sockfd);
            return -1;
        }
    
        int error = 0;
        socklen_t length = sizeof(error);
        // 调用 getsockopt 来获取并清除 sockfd 上的错误
        if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &length) < 0) {
            printf("get socket option failedn");
            close(sockfd);
            return -1;
        }
    
        // 错误号不为 0 表示连接出错
        if(error != 0) {
            printf("connection failed after select with the error: %sn", strerror(error));
            close(sockfd);
            return -1;
        }
        
        // 连接成功
        printf("connection ready after select with the socket: %dn", sockfd);
        // 将 sockfd 设置回原来的属性即阻塞
        fcntl(sockfd, F_SETFL, fdopt);
        return sockfd;
    
    }
    
    
    int main(int argc, char const *argv[]) {
        if(argc <= 2) {
            printf("usage: %s ip_address port_numbern", basename(argv[0]));
            return 1;
        }
    
        for(int i = 1; i + 1 <= argc; i += 2) {
            const char *ip = argv[i];
            int port = atoi(argv[i + 1]);
    
            int sockfd = unblock_connect(ip, port, 10);
    
            if(sockfd < 0) {
                return 1;
            }
    
            close(sockfd);
    
        }
    
        return 0;
    }
  • 相关阅读:
    8、【C++基础】内存管理
    7、【C++基础】内联函数、友元函数
    5、【C++基础】强制类型转换
    4、【C++基础】引用和指针
    3、【C++基础】基本的输入输出
    2、【C++基础】命名空间
    1、【C++基础】bool数据类型
    13、【C语言基础】预处理器、头文件
    6、git常用命令总结
    5、git标签管理
  • 原文地址:https://www.cnblogs.com/dream397/p/14772644.html
Copyright © 2020-2023  润新知