非阻塞socket调用connect, epoll和select检查连接情况示例

非阻塞Connect详解
本文详细介绍了Linux下非阻塞connect函数的使用方法及其注意事项,包括如何设置套接字为非阻塞模式、如何判断连接状态及如何使用select和epoll进行事件监听。

我们知道,linux下socket编程有常见的几个系统调用:

对于服务器来说, 有socket(), bind(),listen(), accept(),read(),write()

对于客户端来说,有socket(),connect()

这里主要要讲的是客户端这边的connect函数。

对于客户端来说,需要打开一个套接字,然后与对端服务器连接,例如:

复制代码
 1 int main(int argc, char **argv) 
 2 {
 3         struct sockaddr_in s_addr;
 4         memset(&s_addr, 0, sizeof(s_addr));
 5         s_addr.sin_family = AF_INET;
 6         s_addr.sin_addr.s_addr = inet_addr("remote host");
 7         s_addr.sin_port = htons(remote port);
 8         socklen_t addr_len = sizeof(struct sockaddr);
 9         int c_fd = socket(AF_INET, SOCK_STREAM, 0);
10         int ret = connect(c_fd, (struct sockaddr*)&s_addr, addr_len);                                 
11         ......
12 }
复制代码

 

当connect上对端服务器之后,就可以使用该套接字发送数据了。

我们知道,如果socket为TCP套接字, 则connect函数会激发TCP的三次握手过程,而三次握手是需要一些时间的,内核中对connect的超时限制是75秒,就是说如果超过75秒则connect会由于超时而返回失败。但是如果对端服务器由于某些问题无法连接,那么每一个客户端发起的connect都会要等待75才会返回,因为socket默认是阻塞的。对于一些线上服务来说,假设某些对端服务器出问题了,在这种情况下就有可能引发严重的后果。或者在有些时候,我们不希望在调用connect的时候阻塞住,有一些额外的任务需要处理;

这种场景下,我们就可以将socket设置为非阻塞,如下代码:

int flags = fcntl(c_fd, F_GETFL, 0);
if(flags < 0) {
    return 0;      
}
fcntl(c_fd, F_SETFL, flags | O_NONBLOCK);

当我们将socket设置为NONBLOCK后,在调用connect的时候,如果操作不能马上完成,那connect便会立即返回,此时connect有可能返回-1, 此时需要根据相应的错误码errno,来判断连接是否在继续进行。

当errno=EINPROGRESS时,这种情况是正常的,此时连接在继续进行,但是仍未完成;同时TCP的三路握手操作继续进行;后续只要用select/epoll去注册对应的事件并设置超时时间来判断连接否是连接成功就可以了。

复制代码
int ret = connect(c_fd, (struct sockaddr*)&s_addr, addr_len);
while(ret < 0) {
    if( errno == EINPROGRESS ) {
         break;
    }  else {
         perror("connect fail'\n");
         return 0;
    }
}
复制代码

这个地方,我们很可能会判断如果ret小于0,就直接判断连接失败而返回了,没有根据errno去判断EINPROGRESS这个错误码。这里也是昨天在写份程序的时候遇到的一个坑。

使用非阻塞 connect 需要注意的问题是:
1. 很可能 调用 connect 时会立即建立连接(比如,客户端和服务端在同一台机子上),必须处理这种情况。
2. Posix 定义了两条与 select 和 非阻塞 connect 相关的规定:
1)连接成功建立时,socket 描述字变为可写。(连接建立时,写缓冲区空闲,所以可写)
2)连接建立失败时,socket 描述字既可读又可写。 (由于有未决的错误,从而可读又可写)

不过我同时用epoll也做了实验(connect一个无效端口,errno=110, errmsg=connect refused),当连接失败的时候,会触发epoll的EPOLLERR与EPOLLIN,不会触发EPOLLOUT。

当用select检测连接时,socket既可读又可写,只能在可读的集合通过getsockopt获取错误码。

当用epoll检测连接时,socket既可读又可写,只能在EPOLLERR中通过getsockopt获取错误码。

完整代码如下:

复制代码
/* 
 * File:   main.cpp
 * Created on March 7, 2013, 5:54 PM
 */

#include <cstdlib>
#include <string>
#include <iostream>

#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <error.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>


using namespace std;

struct so {
    int fd;
    string val;
};

int select_version(int *fd) {
    int c_fd = *fd;
    fd_set rset, wset;
    struct timeval tval;
    FD_ZERO(&rset);
    FD_SET(c_fd, &rset);
    wset = rset;
    tval.tv_sec = 0;
    tval.tv_usec = 300 * 1000; //300毫秒
    int ready_n;
    if ((ready_n = select(c_fd + 1, &rset, &wset, NULL, &tval)) == 0) {
        close(c_fd); /* timeout */
        errno = ETIMEDOUT;
        perror("select timeout.\n");
        return (-1);
    }
    if (FD_ISSET(c_fd, &rset)) {
        int error;
        socklen_t len = sizeof (error);
        if (getsockopt(c_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
            cout << "getsockopt error." << endl;
            return -1;
        }
        cout << "in fire." << error << endl;
    }
    if (FD_ISSET(c_fd, &wset)) {
        int error;
        socklen_t len = sizeof (error);
        if (getsockopt(c_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
            cout << "getsockopt error." << endl;
            return -1;
        }
        cout << "out fire." << error << endl;
    }
    return 0;
}

int epoll_version(int *fd) {
    int c_fd = *fd;
    int ep = epoll_create(1024);
    struct epoll_event event;
    event.events = (uint32_t) (EPOLLIN | EPOLLOUT | EPOLLET);
    struct so _data;
    _data.fd = c_fd;
    _data.val = "test";
    event.data.ptr = (void*) &_data;
    epoll_ctl(ep, EPOLL_CTL_ADD, c_fd, &event);
    struct epoll_event eventArr[1000];
    int status, err;
    socklen_t len;
    err = 0;
    len = sizeof (err);
    int n = epoll_wait(ep, eventArr, 20, 300);
    for (int i = 0; i < n; i++) {
        epoll_event ev = eventArr[i];
        int events = ev.events;
        if (events & EPOLLERR) {
            struct so* so_data = (struct so*) ev.data.ptr;
            cout << so_data->val << ",err event fire." << endl;
            status = getsockopt(c_fd, SOL_SOCKET, SO_ERROR, &err, &len);
            cout << status << "," << err << endl;
        }
        if (events & EPOLLIN) {
            struct so* so_data = (struct so*) ev.data.ptr;
            cout << so_data->val << ",in event fire." << endl;
            status = getsockopt(c_fd, SOL_SOCKET, SO_ERROR, &err, &len);
            cout << status << "," << err << endl;
        }
        if (events & EPOLLOUT) {
            struct so* so_data1 = (struct so*) ev.data.ptr;
            cout << so_data1->val << ",out event fire." << endl;
        }
    }

}

int main(int argc, char** argv) {
    string ip = "127.0.0.1";
    int port = 25698;
    int c_fd, flags, ret;
    struct sockaddr_in s_addr;
    memset(&s_addr, 0, sizeof (s_addr));
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(port);
    s_addr.sin_addr.s_addr = inet_addr(ip.c_str());

    if ((c_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("create socket fail.\n");
        exit(0);
    }
    flags = fcntl(c_fd, F_GETFL, 0);
    if (flags < 0) {
        perror("get socket flags fail.\n");
        return -1;
    }

    if (fcntl(c_fd, F_SETFL, flags | O_NONBLOCK) < 0) {
        perror("set socket O_NONBLOCK fail.\n");
        return -1;
    }
    ret = connect(c_fd, (struct sockaddr*) &s_addr, sizeof (struct sockaddr));
    while (ret < 0) {
        if (errno == EINPROGRESS) {
            break;
        } else {
            perror("connect remote server fail.\n");
            printf("%d\n", errno);
            exit(0);
        }
    }
    //select_version(&c_fd);
    epoll_version(&c_fd);
    return 0;
}
复制代码
Socket编程中,阻塞模式非阻塞模式是两种不同的数据传输处理方式,它们在行为、性能适用场景上存在显著差异。 ### 阻塞模式 在阻塞模式下,当调用一个Socket函数(如`recv`、`send`、`accept`)时,程序会等待该操作完成后再继续执行。例如,当调用`recv`函数读取数据时,如果没有数据到达,程序会一直等待,直到数据到来或者发生错误。这种模式简化了编程模型,因为开发者不需要处理异步操作的复杂性[^1]。 ### 非阻塞模式 在非阻塞模式下,Socket函数调用会立即返回,无论操作是否完成。例如,当调用`recv`函数时,如果没有数据可读,函数会立即返回一个错误(通常为`EWOULDBLOCK`或`EAGAIN`),而不是等待。这种方式要求开发者编写更多的逻辑来处理未完成的操作,例如使用循环不断检查Socket状态,或者结合`select`、`poll`、`epoll`等多路复用技术[^1]。 ### 应用场景 #### 阻塞模式的应用场景 1. **简单应用**:对于简单的网络通信,阻塞模式可以减少代码的复杂性,便于开发维护。 2. **单线程服务**:适用于单线程服务器,例如小型的客户端-服务器应用,其中每个连接由一个独立的线程或进程处理。 3. **调试测试**:在调试阶段,阻塞模式有助于观察程序流程,因为每个操作都是同步的。 #### 非阻塞模式的应用场景 1. **高性能服务器**:在需要处理大量并发连接的服务器中,非阻塞模式结合多路复用技术(如`epoll`)可以显著提高性能。 2. **实时系统**:实时系统要求快速响应,非阻塞模式可以避免因等待某个操作完成而影响整体性能。 3. **异步通信**:适用于需要异步处理的场景,例如事件驱动的架构,其中每个事件触发相应的处理逻辑。 ### 示例代码 以下是一个简单的Python示例,展示如何将Socket设置为非阻塞模式: ```python import socket # 创建Socket对象 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置Socket非阻塞模式 sock.setblocking(False) try: # 尝试连接服务器 sock.connect(("example.com", 80)) except BlockingIOError: # 在非阻塞模式下,connect可能会抛出BlockingIOError pass # 使用select检查Socket是否可写 import select ready = select.select([], [sock], [], 10) if ready[1]: print("Socket is ready to send data") ``` ### 总结 阻塞模式非阻塞模式各有优劣,选择哪种模式取决于具体的应用需求。阻塞模式适合简单、低并发的场景,而非阻塞模式则更适合高性能、高并发的服务器应用。理解这两种模式的区别及其适用场景,可以帮助开发者更好地设计优化网络通信。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值