11月 172009
 

libevent のソースコードを参考にしつつ epoll を使ってみたメモ。
ただの興味本位。
動作は、Linux CentOS 5 で確認。
エラー処理とかはやってたりやってなかったり、わりと適当。

server.cpp

/**
 * @file    server.cpp
 * @brief   epoll test server.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/epoll.h>
 
#define LISTEN_PORT (12345) ///< 待ち受けポート
#define MAX_EVENTS (1000) ///< 最大イベント数
#define MAX_BACKLOG (10) ///< リクエスト待機キューの格納数
 
struct epollev
{
    void (*callback)(int, void *); ///< callback function.
    void *arg; ///< user data.
};
 
struct epollop
{
    int epfd; ///< epoll fd.
    struct epoll_event *events; ///< epoll events.
    int num_events; ///< maximum number of events.
    struct epollev *fds; ///< fds
};
static epollop g_epollop;
 
static void callback_accept( int fd, void *arg );
static void callback_client( int fd, void *arg );
 
//! @brief    強制終了
static void server_exit( const char *log )
{
    perror( log );
    exit( EXIT_FAILURE );
}
 
//! @brief    イベント初期化
static void server_init()
{
    int epfd, nfiles = MAX_EVENTS;
 
    if( ( epfd = epoll_create( nfiles ) ) == -1 ){
        server_exit( "epoll_create()" );
    }
    g_epollop.epfd = epfd;
    g_epollop.events = new struct epoll_event[nfiles];
    memset( g_epollop.events, 0, sizeof(struct epoll_event)*nfiles );
    g_epollop.num_events = MAX_EVENTS;
    g_epollop.fds = new epollev[nfiles];
    memset( g_epollop.fds, 0, sizeof(struct epollev)*nfiles );
}
 
//! @brief    イベント追加
static void server_add( int fd, int events, void (*callback)(int, void *), void *arg )
{
    struct epoll_event epev = {0, {0}};
    struct epollev *ev = &g_epollop.fds[fd];
 
    int op = EPOLL_CTL_ADD;
    if( ev->callback != NULL ){
        op = EPOLL_CTL_MOD;
    }
 
    epev.data.fd = fd;
    epev.events = events;
    if( epoll_ctl( g_epollop.epfd, op, fd, &epev ) == -1 ){
        perror( "epoll_ctl()" );
        return;
    }
    ev->callback = callback;
    ev->arg = arg;
}
 
//! @brief    イベント削除
static void server_del( int fd )
{
    struct epoll_event epev = {0, {0}};
    struct epollev *ev = &g_epollop.fds[fd];
    int op;
 
    op = EPOLL_CTL_DEL;
    epev.data.fd = fd;
    memset( ev, 0, sizeof( struct epollev ) );
 
    if( epoll_ctl( g_epollop.epfd, op, fd, &epev ) == -1 ){
        perror( "epoll_ctl()" );
        return;
    }
    close( fd );
}
 
//! @brief    受付
static void server_listen()
{
    int sock;
    struct sockaddr_in saddr;
 
    if( ( sock = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 ){
        server_exit( "socket()" );
    }
 
    memset( &saddr, 0, sizeof( saddr ) );
    saddr.sin_family    = AF_INET;
    saddr.sin_addr.s_addr = htonl( INADDR_ANY );
    saddr.sin_port = htons( LISTEN_PORT );
 
    if( bind( sock, (struct sockaddr *) &saddr, sizeof(saddr) ) != 0 ){
        close( sock );
        server_exit( "bind()" );
    }
 
    if( listen( sock, MAX_BACKLOG ) < 0 ){
        close( sock );
        server_exit( "listen()" );
    }
 
    server_add( sock, EPOLLIN, callback_accept, NULL );
}
 
//! @brief    メインループ
static int server_loop( )
{
    fprintf( stdout, "in server_loop.\n" );
 
    while( true ){
        int i, res;
        int timeout = -1;
        struct epoll_event *events = g_epollop.events;
 
        res = epoll_wait( g_epollop.epfd, events, g_epollop.num_events, timeout );
        if( res == -1 ){
            server_exit( "epoll_wait()" );
        }
 
        for( i = 0; i < res; ++i ){
            int fd = events[i].data.fd;
            struct epollev *ev = &g_epollop.fds[fd];
 
            ev->callback( fd, ev->arg );
        }
    }
    return 0;
}
 
//! @brief    受付コールバック
static void callback_accept( int fd, void *arg )
{
    fprintf( stdout, "in callback_accept. fd:%d\n", fd );
 
    int sock;
    struct sockaddr saddr;
    socklen_t len = sizeof( struct sockaddr_in );
 
    if( ( sock = accept( fd, (struct sockaddr *)&saddr, &len ) ) < 0 ){
        perror( "accept()" );
        return;
    }
 
    int flag = fcntl( sock, F_GETFL, 0 );
    fcntl( sock, F_SETFL, flag | O_NONBLOCK );
 
    fprintf( stdout, "accept. sock:%d\n", sock );
    server_add( sock, EPOLLIN, callback_client, NULL );
}
 
//! @brief    クライアントコールバック
static void callback_client( int fd, void *arg )
{
    fprintf( stdout, "in callback_client. fd:%d\n", fd );
 
    char buff[1024];
    int ret = ::recv( fd, buff, sizeof(buff), 0 );
    if( ret <= 0 ){
        fprintf( stdout, "exit. fd:%d ret:%d\n", fd, ret );
        server_del( fd );
    }
    else{
        fprintf( stdout, "fd:%d -> %s\n", fd, buff );
    }
}
 
int main()
{
    server_init();
    server_listen();
    return server_loop();
}

client.cpp

/**
 * @file    client.cpp
 * @brief   test client.
 */
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
 
#define CONNECT_ADDR "127.0.0.1"
#define CONNECT_PORT (12345)
 
bool setnonblock(int fd)
{
    int flags = fcntl( fd, F_GETFL );
    if( flags < 0 ){
        return false;
    }
 
    if( fcntl( fd, F_SETFL, flags |= O_NONBLOCK ) < 0 ){
        return false;
    }
    return true;
}
 
int main(void)
{
    int ret = 0, sock = 0, clilen = 0;
    struct sockaddr_in cliaddr;
 
    // socket
    if( ( sock = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 ){
        puts( "socket error!!" );
        return 1;
    }
 
    memset( &cliaddr, 0, sizeof( cliaddr ) );
    cliaddr.sin_addr.s_addr = inet_addr( CONNECT_ADDR );
    cliaddr.sin_family    = AF_INET;
    cliaddr.sin_port    = htons( CONNECT_PORT );
 
    clilen = sizeof( cliaddr );
    if( ( ret = connect( sock, (struct sockaddr *)&cliaddr, clilen ) ) < 0 ){
        perror( "connect()" );
        return 1;
    }
    if( !setnonblock( sock ) ){
        perror( "setnonblock()" );
    }
 
    puts( "in client loop." );
    char str[1024];
    char *strpos;
    while( 1 )
    {
        memset( str, 0, sizeof( str ) );
        fgets( str, sizeof(str), stdin );
 
        // \n を除去
        if( ( strpos = strchr( str, '\n' ) ) != NULL ){
            (*strpos) = '\0';
        }
 
        // exit
        if( strncmp( str, "exit", 4 ) == 0 )
            break;
 
        // send
        if( send( sock, str, strlen( str ) + 1, 0 ) < 0 ){
            perror( "send()" );
            break;
        }
    }
 
    // 後始末
    while( 1 ){
        ret = recv( sock, str, sizeof(str), 0 );
        if( ret <= 0 )
            break;
    }
    close( sock );
 
    return 0;
}