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

from http://www.cnblogs.com/yuxingfirst/archive/2013/03/08/2950281.html

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

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

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

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

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

int main(int argc, char **argv)
{
        struct sockaddr_in s_addr;
        memset(&s_addr, 0, sizeof(s_addr));
        s_addr.sin_family = AF_INET;
        s_addr.sin_addr.s_addr = inet_addr("remote host");
        s_addr.sin_port = htons(remote port);
        socklen_t addr_len = sizeof(struct sockaddr);
        int c_fd = socket(AF_INET, SOCK_STREAM, 0);
        int ret = connect(c_fd, (struct sockaddr*)&s_addr, addr_len);
        ......
}

  

当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;
}

  

时间: 2025-01-18 16:59:26

非阻塞socket调用connect, epoll和select检查连接情况示例的相关文章

非阻塞socket与epoll

阻塞socket. –阻塞调用是指调用结果返回之前,当前线程会被挂起.函数只有在得到结果之后才会返回. –对于文件操作read,fread函数调用会将线程阻塞. –对于socket,accept与recv.recvfrom函数调用会将线程阻塞. –为了避免整个进程被阻塞后挂起,所以在阻塞模式下,往往需要采用多线程技术. –一个进程中可并发的线程总数是有限的,在处理大量客户端sokcet连接(比如上万个client socket),通过线程并发处理socket并不方便,效率也不高. 非阻塞sock

阻塞、非阻塞、异步、同步以及select/poll和epoll

针对IO,总是涉及到阻塞.非阻塞.异步.同步以及select/poll和epoll的一些描述,那么这些东西到底是什么,有什么差异? 一般来讲一个IO分为两个阶段: 等待数据到达 把数据从内核空间拷贝到用户空间 现在假设一个进程/线程A,试图进行一次IO操作. A发出IO请求,两种情况: 1)立即返回 2)由于数据未准备好,需要等待,让出CPU给别的线程,自己sleep 第一种情况就是非阻塞,A为了知道数据是否准备好,需要不停的询问,而在轮询的空歇期,理论上是可以干点别的活,例如喝喝茶.泡个妞.

客户端非阻塞socket建链流程

TCP协议是面向连接的.可靠的.基于字节流的传输层协议.那使用tcp协议进行通信的两端是如何进行通信的?使用tcp协议进行通信的两端是通过套接字(scoket)来建立连接的.套接字socket主要有两种类型,阻塞和非阻塞.通常为了防止进程阻塞以及避免cpu被长时间占用,客户端和服务端一般都会采用非阻塞socket进行通信,其中Nginx就是一个典型的例子.下面我们就以Nginx的upstream机制所涉及的与后端服务器建链的流程来总结下使用非阻塞socket的客户端建链流程. 先来看下Nginx

非阻塞模式下connect 成功失败判断

将一个socket 设置成阻塞模式和非阻塞模式,使用fcntl方法,即: 设置成非阻塞模式: 先用fcntl的F_GETFL获取flags,用F_SETFL设置flags|O_NONBLOCK; 即: flags = fcntl(sockfd, F_GETFL, 0);                        //获取文件的flags值. fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);   //设置成非阻塞模式: 同时在接收和发送数据时,需要使用MS

JAVA基础知识之网络编程——-基于NIO的非阻塞Socket通信

阻塞IO与非阻塞IO 通常情况下的Socket都是阻塞式的, 程序的输入输出都会让当前线程进入阻塞状态, 因此服务器需要为每一个客户端都创建一个线程. 从JAVA1.4开始引入了NIO API, NIO可以实现非阻塞IO, 这样就可以使用一个线程处理所有的客户请求. 基于NIO的非阻塞Socket通信 服务器将用来监听客户端请求的channel注册到selector上,启动一个线程,使用selector的select()获取求情的客户端的channel数量, 当监听到有客户端请求时,就通过Sel

Linux非阻塞IO(四)非阻塞IO中connect的实现

我们为客户端的编写再做一些工作. 这次我们使用非阻塞IO实现connect函数. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 非阻塞IO有以下用处: 1.将三次握手的处理过程生下来,处理其他事情. 2.使用这个同时建立多个连接. 3.实现超时connect功能,本节实现的connect就可以指定时间,超时后算作错误处理.   在阻塞IO中,调用connect后一般会阻塞,直到确定连接成功或者失败

网络编程中阻塞和非阻塞socket的区别

阻塞socket和非阻塞socket 建立连接阻塞方式下,connect首先发送SYN请求道服务器,当客户端收到服务器返回的SYN的确认时,则connect返回.否则的话一直阻塞.非阻塞方式,connect将启用TCP协议的三次握手,但是connect函数并不等待连接建立好才返回,而是立即返回.返回的错误码为EINPROGRESS,表示正在进行某种过程. 接收连接对于阻塞方式的倾听socket,accept在连接队列中没有建立好的连接时将阻塞,直到有可用的连接,才返回.非阻塞倾听socket,在

linux下异步RPC的阶段性总结-非阻塞SOCKET客户端

尽可能使用非阻塞socket int flags, s;    flags = fcntl (fd, F_GETFL, 0);        if (flags == -1){            close(fd);          return -1;      }          flags |= O_NONBLOCK;        s = fcntl (fd, F_SETFL, flags);        if (s == -1){            close(fd); 

从缓冲上看阻塞与非阻塞socket在发送接收上的区别(转载)

转自:http://blog.chinaunix.net/uid-24517549-id-4044877.html 首先socket在默认情况下是阻塞状态的,这就使得发送以及接收操作处于阻塞的状态,即调用不会立即返回,而是进入睡眠等待操作完成.   一.发送选用send(这里特指TCP)以及sendto(这里特指UDP)来描述 首先需要说明的是,不管阻塞还是非阻塞,在发送时都会将数据从应用缓冲区拷贝到内核缓冲区(SO_RCVBUF选项声明,除非缓冲区大小为0).     在阻塞模式下send操作