非阻塞socket与epoll

阻塞socket。

  –阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。

  –对于文件操作read,fread函数调用会将线程阻塞。

  –对于socket,accept与recv、recvfrom函数调用会将线程阻塞。

  –为了避免整个进程被阻塞后挂起,所以在阻塞模式下,往往需要采用多线程技术。

  –一个进程中可并发的线程总数是有限的,在处理大量客户端sokcet连接(比如上万个client socket),通过线程并发处理socket并不方便,效率也不高。

非阻塞socket。

  –非阻塞调用是指调用立刻返回。

  –在非阻塞模式下,accept与recv、recvfrom函数调用会立刻返回。

  –在nonblocking状态下调用accept函数,如果没有客户端socket连接请求,那么accept函数返回-1,同时errno值为11。

  –在nonblocking状态下调用recv、recvfrom函数,如果没有数据,函数返回-1,同时errno值为11。如果socket已经关闭,函数返回0。

  –在nonblocking状态下对一个已经关闭的socket调用send函数,将引发一个SIGPIPE信号,进程必须捕捉这个信号,因为SIGPIPE系统默认的处理方式是关闭进程。

fcntl函数调用

fcntl函数可以将文件或者socket描述符设置为阻塞或者非阻塞状态

int fcntl(int fd, int cmd, ... /* arg */ );

参数fd为要设置的文件描述符或者socket。

参数cmd, F_GETFL为得到目前状态, F_SETFL为设置状态。

宏定义O_NONBLOCK代表非阻塞,0代表阻塞。

返回值为描述符当前状态。

fcntl函数调用设置非阻塞例子
int opts = fcntl(st, F_GETFL);
if (opts < 0)
{
    printf("fcntl failed %s\n", strerror(errno));
}
opts = opts | O_NONBLOCK;
if (fcntl(st, F_SETFL, opts) < 0)
{
    printf("fcntl failed %s\n", strerror(errno));
}
fcntl函数调用设置阻塞例子
if (fcntl(st, F_SETFL, 0) < 0)
{
    printf("fcntl failed %s\n", strerror(errno));
}

在非阻塞模式下,如何能知道accept与recv有数据返回呢?

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

epoll的系统调用函数:

  –epoll_create

•epoll_create用来创建一个epoll文件描述符。

  –epoll_ctl

•epoll_ctl用来添加/修改/删除需要侦听的文件描述符及其事件。

  –epoll_wait。

•epoll_wait接收发生在被侦听的描述符上的,用户感兴趣的IO事件。

  –epoll文件描述符用完后,需要用close关闭。

  –每次添加/修改/删除文件描述符都需要调用epoll_ctl,所以要尽量少地调用epoll_ctl。

epoll_create:

  –int epoll_create(int size);

  –epoll_create创建一个epoll的句柄。

  –参数size指定epoll所支持的最大句柄数。

  –函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。

  –在用完句柄之后,需要用close()来关闭这个创建出来的epoll句柄。

uepoll_ctl:

  –int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

  –参数epfd是epoll_create()的返回值。

  –参数op表示动作,用三个宏来表示:

•EPOLL_CTL_ADD:注册新的fd到epfd中;

•EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

•EPOLL_CTL_DEL:从epfd中删除一个fd;

  –参数fd是需要监听的socket描述符。

  –参数event通知内核需要监听什么事件。

struct epoll_event结构如下:
typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
};

events定义:

  –EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

  –EPOLLOUT:表示对应的文件描述符可以写;

  –EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

  –EPOLLERR:表示对应的文件描述符发生错误;

  –EPOLLHUP:表示对应的文件描述符被挂断;

  –EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的;

  –EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

关于ET、LT两种工作模式:

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.

  –在LT模式中,内核通知一个文件描述符是否就绪了,然后可以对这个就绪的fd进行IO操作。

  –如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。

ET (edge-triggered)是高速工作方式,只支持no-block socket。

  –在ET模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。

  –ET模式会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。

  –如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知。

ET和LT的区别:

  –LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读,则不断的通知你。

  –ET则只在事件发生之时通知。可以简单理解为LT是水平触发,而ET则为边缘触发。

  –LT模式只要有事件未处理就会触发,而ET则只在高低电平变换时(即状态从1到0或者0到1)触发。

epoll_wait:

  –int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

  –参数epfd是epoll_create()的返回值。

  –参数events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。

  –参数maxevents是当前需要监听的所有socket句柄数。

  –参数timeout是 epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件范围,正整数表示等这么长的时间。

  –一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。

epoll_wait范围之后应该是一个循环,遍历所有的事件。

epoll例子

/*

 *
 *  Created on: 2013年12月27日
 *      Author: zhujy
 */

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

ssize_t socket_recv(int st)
{
    char buf[1024];
    memset(buf, 0, sizeof(buf));
    ssize_t rc = recv(st, buf, sizeof(buf), 0);
    if (rc <= 0)
    {
        printf("recv failed %s\n", strerror(errno));
    } else
    {
        printf("recv %s\n", buf);
        send(st, buf, rc, 0);
    }
    return rc;
}

int socket_accept(int listen_st)
{
    struct sockaddr_in client_addr;
    socklen_t len = sizeof(client_addr);
    memset(&client_addr, 0, sizeof(client_addr));
    int client_st = accept(listen_st, (struct sockaddr *) &client_addr, &len);
    if (client_st < 0)
        printf("accept failed %s\n", strerror(errno));
    else
        printf("accept by %s\n", inet_ntoa(client_addr.sin_addr));
    return client_st;
}

void setnonblocking(int st) //将socket设置为非阻塞
{
    int opts = fcntl(st, F_GETFL);
    if (opts < 0)
    {
        printf("fcntl failed %s\n", strerror(errno));
    }
    opts = opts | O_NONBLOCK;
    if (fcntl(st, F_SETFL, opts) < 0)
    {
        printf("fcntl failed %s\n", strerror(errno));
    }
}

int socket_create(int port)
{
    int st = socket(AF_INET, SOCK_STREAM, 0);
    int on = 1;
    if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
    {
        printf("setsockopt failed %s\n", strerror(errno));
        return 0;
    }
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(st, (struct sockaddr *) &addr, sizeof(addr)) == -1)
    {
        printf("bind port %d failed %s\n", port, strerror(errno));
        return 0;
    }
    if (listen(st, 300) == -1)
    {
        printf("listen failed %s\n", strerror(errno));
        return 0;
    }
    return st;
}

int main(int arg, char *args[])
{
    if (arg < 2)
        return -1;
    int iport = atoi(args[1]);
    int listen_st = socket_create(iport);
    if (listen_st == 0)
        return -1;

    struct epoll_event ev, events[100]; //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
    int epfd = epoll_create(100); //生成用于处理accept的epoll专用的文件描述符
    setnonblocking(listen_st); //把socket设置为非阻塞方式
    ev.data.fd = listen_st; //设置与要处理的事件相关的文件描述符
    ev.events = EPOLLIN | EPOLLERR | EPOLLHUP; //设置要处理的事件类型
    epoll_ctl(epfd, EPOLL_CTL_ADD, listen_st, &ev); //注册epoll事件

    int st = 0;
    while (1)
    {
        int nfds = epoll_wait(epfd, events, 100, -1); //等待epoll事件的发生
        if (nfds == -1)
        {
            printf("epoll_wait failed %s\n", strerror(errno));
            break;
        }

        int i;
        for (i = 0; i < nfds; i++)
        {
            if (events[i].data.fd < 0)
                continue;

            if (events[i].data.fd == listen_st) //监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。
            {
                st = socket_accept(listen_st);
                if (st >= 0)
                {
                    setnonblocking(st);
                    ev.data.fd = st;
                    ev.events = EPOLLIN | EPOLLERR | EPOLLHUP; //设置要处理的事件类型
                    epoll_ctl(epfd, EPOLL_CTL_ADD, st, &ev);
                    continue;
                }
            }
            if (events[i].events & EPOLLIN) //socket收到数据
            {
                st = events[i].data.fd;
                if (socket_recv(st) <= 0)
                {
                    close(st);
                    events[i].data.fd = -1;
                }
            }
            if (events[i].events & EPOLLERR) //socket错误
            {
                close(st);
                events[i].data.fd = -1;
            }

            if (events[i].events & EPOLLHUP) //socket错误
            {
                close(st);
                events[i].data.fd = -1;
            }
        }
    }
    close(epfd);
    return 0;
}

复制去Google翻译翻译结果

时间: 2024-10-26 10:02:35

非阻塞socket与epoll的相关文章

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建链流程

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

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

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

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

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

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

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

Linux - 非阻塞socket编程处理EAGAIN错误

在linux进行非阻塞的socket接收数据时经常出现Resource temporarily unavailable,errno代码为11(EAGAIN),这表明你在非阻塞模式下调用了阻塞操作,在该操作没有完成就返回这个错误,这个错误不会破坏socket的同步,不用管它,下次循环接着recv就可以. 对非阻塞socket而言,EAGAIN不是一种错误.在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK. 另外,如果出现EINTR即errno为4,错误描述Inter

非阻塞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函数. 对于客户端来说,需要打开一个套接字,然后与对端服务器连接,例如:

非阻塞socket学习,select基本用法

server #include <stdio.h> #include <winsock2.h> #include <iostream> #pragma comment(lib, "WS2_32.lib") #define PORT 9999 #define DATA_BUFSIZE 8192 typedef struct _SOCKET_INFORMATION{ CHAR Buffer[DATA_BUFSIZE]; //发送和接收数据的缓冲区 WSA

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

最近在网络上看到一些帖子以及回复,同时又搜索了一些网络上关于阻塞非阻塞区别的描述,发现很多人在描述两者的发送接收时操作返回以及缓冲区处理的区别时有不同程度的误解.所以我想写一篇文章来纠正错误,并作为记录方便查阅,如有转载,注明作者(jwybobo2007)以及出处即可. 首先socket在默认情况下是阻塞状态的(未指异步操作以及其它一些特殊用途下,直接默认为非阻塞),这就使得发送以及接收操作处于阻塞的状态,即调用不会立即返回,而是进入睡眠等待操作完成.下面把讨论点分为发送以及接收.  一.发送选