tcp非阻塞connect实现

引言:
客户端connect接口, 代表着三次握手, 默认为阻塞模式, 在握手期间, 客户端程序只能处于等待状态, 不能处理其他的业务,
其次,如果握手失败, 还会等connect计时器时间, 大约是在75s左右。

一、阻塞connect存在的几大问题。

1、阻塞期间不能处理其他业务。
2、如果连接失败, 等待的时间过长。

二、实现非阻塞connect的方法

1、将创建sockfd设置为阻塞模式

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

2、connect连接

  • 此时连接会出现error错误【EINPROGRESS】, 这种错误发生在非阻塞socket调用connect中,连接没有立即建立。
  • 程序不会处于阻塞状态,可以执行别的代码,并且三次握手仍然处理中。

3、调用select监控文件描述符(别的也行, 不过就一个fd,不值得epoll)

  • 虽然说已经执行别的代码, 但三次握手仍然处理, 返回的信息会在socfd文件描述中显示。
  • 采用select监控sockfd。 填充可写事件数组

4、分析select监控信息

  • 如果返回值<=0, 监控失败或者三次握手失败, 直接退出。
  • 采用FD_ISSET()判断sockfd是否处在返回的可写事件数组中。 如果不是,证明不是sockfd就绪, 连接失败。

5、采用getsockopt()读取错误码并清除socket错误信息

函数说明:getsockopt()会将参数s 所指定的socket 状态返回.

  • 如果返回值为0,代表连接成功, 后续要把sockfd重新设置为阻塞模式
  • 返回值不为代表连接错误,退出

三、代码测试

#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>

#include "tcpser.hpp"

const int BUFFER_SIZE = 1023;

//设置fd为非阻塞类型
int setnonblockint(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 address;
    address.sin_family = AF_INET;
    address.sin_port = htons(port);
    address.sin_addr.s_addr = inet_addr(ip);
    
    /*创建socket监听套接字*/
    int sockfd = socket(PF_INET, SOCK_STREAM, 0);
    /*设置为非阻塞的模式*/
    int fdopt = setnonblockint(sockfd);
    /*连接服务器*/
    ret = connect(sockfd, (struct sockaddr*)&address, sizeof(address));
    if(ret == 0) {
        /*连接成功,一般设置为非阻塞是不会连接成功的, 这里如果是同一台主机会连接成功*/
        printf("connect with server immediately\n");
        fcntl(sockfd, F_SETFL, fdopt);                  //重新设置为阻塞
        return sockfd;
    }
    else if(errno != EINPROGRESS) {
        /*连接没有立即建立, 然会的错误码*/
        printf("unblock connect not support\n");
    }

    /*正常逻辑*/
    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) {
        /*select超时或者出错, 立即返回*/
        printf("connection timeout\n");
        close(sockfd);
        return -1;
    }

    if(!FD_ISSET(sockfd, &writefds)) {
        /*不是sockfd就绪*/
        printf("no events on sockfd found\n");
        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 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 successfully!\n");
    /*重新设置阻塞模式*/
    fcntl(sockfd, F_SETFL, fdopt);
    return sockfd;
}

int main(int argc, char* argv[]) {
    
    if(argc <= 2) {
        printf("IP : Port, Please input!\n");
        return 1;
    }

    const char* ip = argv[1];
    int port = atoi(argv[2]);
    
    int sockfd = unblock_connect(ip, port, 10);
    if(sockfd < 0) { return 1;  }
    
    Tcpser tp;
    tp.SetSockfd(sockfd);

    while(1) {
        //客户端
        /*发送数据*/
        std::string str_msg;
        printf("client says: ");
        std::getline(std::cin, str_msg);

        tp.Send(str_msg);

        str_msg.clear();
        tp.Recv(str_msg);
        printf("server respond: %s\n", str_msg.c_str());

        sleep(1);
    } 
    
    close(sockfd);
    return 0;
}
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
非阻塞式接收函数是指在没有数据到达时,该函数不会一直等待数据到达,而是立即返回,以便程序能够执行其他任务。在TCP编程中,常用的非阻塞式接收函数是recv()函数。 使用非阻塞式接收函数需要先将socket设置为非阻塞模式,可以通过fcntl或者ioctl函数设置。然后,在调用recv()函数时,如果没有数据到达,该函数会立即返回,并且返回值为-1,同时设置errno为EAGAIN或EWOULDBLOCK。如果有数据到达,recv()函数会返回接收到的字节数。 下面是一个使用非阻塞式接收函数的示例代码: ```c #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 将socket设置为非阻塞模式 int flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(8888); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); char buf[1024]; int n = 0; while (1) { n = recv(sockfd, buf, sizeof(buf), 0); if (n == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { // 没有数据到达,可以执行其他任务 // ... } else { // 接收错误,关闭socket并退出循环 close(sockfd); break; } } else if (n == 0) { // 对方关闭连接,关闭socket并退出循环 close(sockfd); break; } else { // 接收到数据,处理数据 // ... } } return 0; } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值