epoll系列系统调用

内核事件表
epoll是Linux特有的I/O复用函数。使用一组函数来完成任务。epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无须像select和poll那样每次调用都要重复传入文件描述符或事件集。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。
#include<sys/epoll.h>
int epoll_create(int size)
size参数现在并不起作用,只是给内核一个提示,告诉它事件表需要多大。该函数返回的文件描述符将用作其他所有epoll系统调用的第一个参数,以指定要访问的内核
事件表。
#include<sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
fd参数是要操作的文件描述符,op参数则指定操作类型。
操作类型有如下3种:
EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL
event参数指定事件,它是epoll_event结构指针类型。epoll_event的定义如下:
struct epoll_event
{
  __uint32_t events;/*epoll事件*/
  epoll_data_t data;/*用户数据*/
};

其中events成员描述事件类型。epoll支持的事件类型和poll基本相同。
表示epoll事件类型的宏是在poll对应的宏上加上E,比如epoll的数据可读事件是EPOLLIN,但epoll有两个额外的事件类型---EPOLLET和EPOLLONESHOT。它们对于epoll的高校运作非常关键,。
typedef union epoll_data_t
{
   void* ptr;
   int fd;
   uint32_t u32;
   uint64_t u64;
}epoll_data_t;
epoll_data_t 是一个联合体,其4个成员中使用最多的是fd,它指定事件所从属的目标文件描述符。ptr成员可用来指定与fd相关的用户数据。但由于epoll_data_t是一个联合体。

epoll_wait函数
epoll系列系统调用的主要接口是epoll_wait函数。
#include<sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event* events,int maxevents,
             int timeout);
epoll_wait函数如果检测到事件,就将所有就绪的时间从内核事件表中复制到它的第二个参数events指向的数组中。这个数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数那样既用于传入用户注册的事件,又用于输出内核检测到的就绪事件。
int ret = poll(fds,MAX_EVENT_NUMBER,-1);
for(int i = 0; i < MAX_EVENT_NUMBER; i++)
{
   if(fds[i].revents & POLLIN)
   {
       int sockfd = fds[i].fd;
   }
};

int ret =  epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);
for(int i = 0; i < ret; i++)
{
   int sockfd = events[i].data.fd;
}

LT和ET模式
epoll对文件描述符的操作有两种模式:LT模式和ET模式。LT模式是默认的工作模式,这种模式下epoll相当于一个效率较高的poll。当往epoll内核事件中注册一个文件描述符上的EPOLLET事件时,epoll将以ET模式来操作该文件描述符。ET模式是epoll的高校工作模式。对于采用LT工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到该事件被处理。而对于采用ET工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再通知这一事件。可见,ET模式在很大程度上降低了同一个epoll是事件被重复触发的次数。因为效率要比LT模式高。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 10

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

void addfd( int epollfd, int fd, bool enable_et )
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN;
    if( enable_et )
    {
        event.events |= EPOLLET;
    }
    epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
    setnonblocking( fd );
}

void lt( epoll_event* events, int number, int epollfd, int listenfd )
{
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd( epollfd, connfd, false );
        }
        else if ( events[i].events & EPOLLIN )
        {
            printf( "event trigger once\n" );
            memset( buf, '\0', BUFFER_SIZE );
            int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
            if( ret <= 0 )
            {
                close( sockfd );
                continue;
            }
            printf( "get %d bytes of content: %s\n", ret, buf );
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}

void et( epoll_event* events, int number, int epollfd, int listenfd )
{
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd( epollfd, connfd, true );
        }
        else if ( events[i].events & EPOLLIN )
        {
            printf( "event trigger once\n" );
            while( 1 )
            {
                memset( buf, '\0', BUFFER_SIZE );
                int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
                if( ret < 0 )
                {
                    if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )
                    {
                        printf( "read later\n" );
                        break;
                    }
                    close( sockfd );
                    break;
                }
                else if( ret == 0 )
                {
                    close( sockfd );
                }
                else
                {
                    printf( "get %d bytes of content: %s\n", ret, buf );
                }
            }
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}

int main( int argc, char* argv[] )
{
    if( argc <= 2 )
    {
        printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi( argv[2] );

    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 listenfd = socket( PF_INET, SOCK_STREAM, 0 );
    assert( listenfd >= 0 );

    ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
    assert( ret != -1 );

    ret = listen( listenfd, 5 );
    assert( ret != -1 );

    epoll_event events[ MAX_EVENT_NUMBER ];
    int epollfd = epoll_create( 5 );
    assert( epollfd != -1 );
    addfd( epollfd, listenfd, true );

    while( 1 )
    {
        int ret = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );
        if ( ret < 0 )
        {
            printf( "epoll failure\n" );
            break;
        }
    
        lt( events, ret, epollfd, listenfd );
        //et( events, ret, epollfd, listenfd );
    }

    close( listenfd );
    return 0;
}


EPOLLONESHOT事件
克服了在并发程序中出现两个线程同时操作一个socket的局面。当一个线程操作某一个socket时,其他线程是不可能有机会操作该socket的。但反过来思考,注册了POLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他线程有机会处理这个socket.
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 1024
struct fds
{
   int epollfd;
   int sockfd;
};

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

void addfd( int epollfd, int fd, bool oneshot )
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    if( oneshot )
    {
        event.events |= EPOLLONESHOT;
    }
    epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
    setnonblocking( fd );
}

void reset_oneshot( int epollfd, int fd )
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
    epoll_ctl( epollfd, EPOLL_CTL_MOD, fd, &event );


void* worker( void* arg )
{
    int sockfd = ( (fds*)arg )->sockfd;
    int epollfd = ( (fds*)arg )->epollfd;
    printf( "start new thread to receive data on fd: %d\n", sockfd );
    char buf[ BUFFER_SIZE ];
    memset( buf, '\0', BUFFER_SIZE );
    while( 1 )
    {
        int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
        if( ret == 0 )
        {
            close( sockfd );
            printf( "foreiner closed the connection\n" );
            break;
        }
        else if( ret < 0 )
        {
            if( errno == EAGAIN )
            {
                reset_oneshot( epollfd, sockfd );
                printf( "read later\n" );
                break;
            }
        }
        else
        {
            printf( "get content: %s\n", buf );
            sleep( 5 );
        }
    }
    printf( "end thread receiving data on fd: %d\n", sockfd );
}

int main( int argc, char* argv[] )
{
    if( argc <= 2 )
    {
        printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi( argv[2] );

    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 listenfd = socket( PF_INET, SOCK_STREAM, 0 );
    assert( listenfd >= 0 );

    ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
    assert( ret != -1 );

    ret = listen( listenfd, 5 );
    assert( ret != -1 );

    epoll_event events[ MAX_EVENT_NUMBER ];
    int epollfd = epoll_create( 5 );
    assert( epollfd != -1 );
    addfd( epollfd, listenfd, false );


    while( 1 )
    {
        int ret = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );
        if ( ret < 0 )
        {
            printf( "epoll failure\n" );
            break;
        }
    
        for ( int i = 0; i < ret; i++ )
        {
            int sockfd = events[i].data.fd;
            if ( sockfd == listenfd )
            {
                struct sockaddr_in client_address;
                socklen_t client_addrlength = sizeof( client_address );
                int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
                addfd( epollfd, connfd, true );
            }
            else if ( events[i].events & EPOLLIN )
            {
                pthread_t thread;
                fds fds_for_new_worker;
                fds_for_new_worker.epollfd = epollfd;
                fds_for_new_worker.sockfd = sockfd;
                pthread_create( &thread, NULL, worker, ( void* )&fds_for_new_worker );
            }
            else
            {
                printf( "something else happened \n" );
            }
        }
    }

    close( listenfd );
    return 0;
}


 

在网络编程中,尤其是在使用 TCP 协议进行通信时,通常会涉及一系列系统调用。这些系统调用构成了客户端与服务器之间通信的基本流程。以下是 TCP 协议下常用的系统调用流程及其作用。 --- ### 一、TCP 服务器端系统调用流程 1. **`socket()`** - **作用**:创建一个 socket,返回一个文件描述符(fd)。 - **示例**: ```c int sockfd = socket(AF_INET, SOCK_STREAM, 0); ``` 2. **`bind()`** - **作用**:将 socket 绑定到一个本地 IP 地址和端口上。 - **示例**: ```c struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8080); server_addr.sin_addr.s_addr = INADDR_ANY; bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); ``` 3. **`listen()`** - **作用**:将 socket 设置为监听状态,准备接受客户端连接。 - **示例**: ```c listen(sockfd, 5); // 5 是连接队列的最大长度 ``` 4. **`accept()`** - **作用**:接受客户端的连接请求,返回一个新的 socket 文件描述符用于与客户端通信。 - **示例**: ```c int client_fd = accept(sockfd, NULL, NULL); ``` 5. **`read()` / `write()`** - **作用**:在客户端和服务器之间进行数据收发。 - **示例**: ```c char buf[1024]; read(client_fd, buf, sizeof(buf)); write(client_fd, buf, strlen(buf)); ``` 6. **`close()`** - **作用**:关闭 socket。 - **示例**: ```c close(client_fd); close(sockfd); ``` --- ### 二、TCP 客户端系统调用流程 1. **`socket()`** - 创建 socket,与服务器端相同。 2. **`connect()`** - **作用**:主动连接服务器。 - **示例**: ```c struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8080); inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); ``` 3. **`read()` / `write()`** - 与服务器端通信。 4. **`close()`** - 关闭 socket。 --- ### 三、UDP 通信系统调用流程 UDP 是无连接协议,流程略有不同: #### 1. 服务器端 - `socket()`:创建 socket。 - `bind()`:绑定地址和端口。 - `recvfrom()` / `sendto()`:接收和发送数据报。 - `close()`:关闭 socket。 #### 2. 客户端 - `socket()`:创建 socket。 - `sendto()` / `recvfrom()`:发送和接收数据报。 - `close()`:关闭 socket。 --- ### 四、系统调用流程图(TCP) ``` 服务器端: socket() → bind() → listen() → accept() → read()/write() → close() 客户端: socket() → connect() → read()/write() → close() ``` --- ### 五、注意事项 1. **错误处理** - 每个系统调用都可能失败,应使用 `perror()` 或 `errno` 检查错误。 2. **非阻塞模式** - 可以通过 `fcntl()` 设置 socket 为非阻塞模式,避免 `read()` 或 `accept()` 阻塞。 3. **多线程/多进程支持** - 服务器可以使用 `fork()` 或线程池处理多个客户端连接。 4. **超时机制** - 可以使用 `select()`、`poll()` 或 `epoll()` 设置超时或监听多个 socket。 --- ### 六、总结 | 系统调用 | 用途 | 适用场景 | |-------------|--------------------------------|----------------------| | `socket()` | 创建 socket | 所有网络通信 | | `bind()` | 绑定地址和端口 | 服务器 | | `listen()` | 设置监听 | 服务器 | | `accept()` | 接受连接 | 服务器 | | `connect()` | 主动连接服务器 | 客户端 | | `read()` | 读取数据 | 通信中 | | `write()` | 发送数据 | 通信中 | | `close()` | 关闭 socket | 通信结束 | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值