Epoll使用の真髄
33263 ワード
linuxのネットワークプログラミングでは、長い間selectを使用してイベントトリガをしていました.linuxの新しいカーネルでは、epollを置き換えるメカニズムがあります.
selectに比べてepollの最大の利点は、傍受fd数の増加に伴って効率が低下しないことである.カーネル内のselect実装では、ポーリングによって処理されるため、ポーリングのfd数が多ければ多いほど、自然に時間がかかる.さらに、linux/posix_types.hヘッダファイルには、次のような宣言があります.
#define __FD_SETSIZE 1024
selectは最大1024個のfdを同時に傍受することを示し、もちろん、ヘッダファイルを修正してカーネルを再コンパイルすることでこの数を拡大することができるが、これは根本的ではないようだ.
epollのインタフェースは非常に簡単で、全部で3つの関数です.
1. int epoll_create(int size);
epollのハンドルを作成し、sizeはカーネルというリスニングの数が合計でどれだけ大きいかを示すために使用されます.このパラメータはselect()の最初のパラメータとは異なり、最大リスニングfd+1の値を与える.注意すべきは、epollハンドルを作成するとfd値が占有され、linuxで/proc/プロセスid/fd/を表示すると、このfdが表示されるので、epollを使用した後、close()を呼び出して閉じる必要があります.そうしないと、fdが消費される可能性があります.
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epollのイベント登録関数は、select()とは異なり、イベントをリスニングするときにカーネルにどのタイプのイベントをリスニングするかを教えるのではなく、ここでリスニングするイベントタイプを先に登録します.最初のパラメータはepoll_create()の戻り値、2番目のパラメータは動作を表し、3つのマクロで表されます.
EPOLL_CTL_ADD:新しいfdをepfdに登録する.
EPOLL_CTL_MOD:登録されたfdの傍受イベントを変更する.
EPOLL_CTL_DEL:epfdからfdを削除します.
3番目のパラメータは傍受が必要なfdで、4番目のパラメータはカーネルに何を傍受する必要があるかを教えて、struct epoll_イベント構造は次のとおりです.
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:対応するファイル記述子にエラーが発生したことを示す;
EPOLLHOP:対応するファイル記述子が切断されたことを示す;
EPOLLET:EPOLLをエッジトリガ(Edge Triggered)モードに設定します.これは水平トリガ(Level Triggered)に対してです.
EPOLLONESHOT:一度だけイベントをリスニングします.今回のイベントをリスニングした後、このソケットをリスニングし続ける必要がある場合は、このソケットを再びEPOLLキューに追加する必要があります.
3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
イベントの生成を待機します.select()呼び出しと似ています.パラメータeventsは、カーネルからイベントの集合を取得するために使用されます.maxeventsは、カーネルというeventsがどれだけ大きいかを示します.このmaxeventsの値はepoll_の作成より大きくはなりません.create()時のsize、パラメータtimeoutはタイムアウト時間(ミリ秒、0はすぐに戻り、-1は不確定、永久ブロックという説もあります).この関数は、処理するイベントの数を返します.たとえば、0を返すとタイムアウトします.
4.ET、LTの2種類の動作モードについて:
このような結論が得られます.
ETモードは状態が変化した場合にのみ通知されるが、ここでいう状態の変化にはバッファに未処理のデータがあるわけではない.すなわち、ETモードを採用するには、エラーが発生するまでread/writeを続ける必要がある.LTモードは、データがある限り処理がないことを通知し続ける.
では、epollをどのように使うのでしょうか.実はとても簡単です.
ヘッダファイル#includeといくつかの簡単なAPIを含むことで、ネットワークサーバのサポート人数を大幅に向上させることができます.
まずはcreate_epoll(int maxfds)は、maxfdsがepollでサポートされている最大ハンドル数であるepollのハンドルを作成します.この関数は新しいepollハンドルを返し、その後のすべての操作はこのハンドルによって操作されます.使い終わったら、close()で作成したepollハンドルを閉じてください.
その後、あなたのネットワークのメインサイクルでは、フレームごとにepollを呼び出します.wait(int epfd,epoll_event events,int max events,int timeout)は、すべてのネットワークインタフェースをクエリーし、どれが読めるか、どれが書けるかを見ます.基本的な構文は次のとおりです.
nfds = epoll_wait(kdpfd, events, maxevents, -1);
ここでkdpfdはepollであるcreate作成後のハンドル、eventsはepoll_event*のポインタ、epoll_waitという関数の操作に成功した後、epoll_eventsにはすべての読み書きイベントが格納されます.max_eventsは、現在傍受する必要があるすべてのsocketハンドル数です.最後のtimeoutはepoll_waitのタイムアウトは、0の場合はすぐに戻り、-1の場合はずっと待っていて、イベント範囲があるまで、任意の正の整数の場合はこんなに長い時間待っていて、ずっとイベントがなければ、範囲を表します.一般に,ネットワークの主ループが単独のスレッドであれば−1で待つことができ,いくつかの効率を保証することができ,主論理と同じスレッドであれば0で主ループの効率を保証することができる.
epoll_wait範囲の後はループであり、すべてのイベントを利益しなければならない.
ほとんどのepollプログラムでは、次のフレームワークが使用されています.
for( ; ; )
{
nfds = epoll_wait(epfd,events,20,500);
for(i=0;i {
if(events[i].data.fd=listenfd)/新しい接続がある
{
connfd=accept(listenfd,(sockaddr*)&clientaddr,&clilen);//acceptこの接続
ev.data.fd=connfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);//epollのリスニングキューに新しいfdを追加
}
else if(events[i].events&EPOLLIN)/データを受信し、socketを読む
{
n = read(sockfd, line, MAXLINE)) < 0 //読み取り
ev.data.ptr = md; //mdはカスタムタイプ、データの追加
ev.events=EPOLLOUT|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//識別子を修正し、次のサイクルを待つ間にデータを送信し、非同期処理の真髄
}
else if(events[i].events&EPOLLOUT)/送信待ちのデータがあり、socketを書く
{
struct myepoll_data* md = (myepoll_data*)events[i].data.ptr; //データをとる
sockfd = md->fd;
send( sockfd, md->ptr, strlen((char*)md->ptr), 0 ); //データの送信
ev.data.fd=sockfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//識別子を変更し、次のサイクルを待つ間にデータを受信する
}
else
{
//その他の処理
}
}
}
次に、完全なサーバ側の例を示します.
クライアントはこのサーバに直接接続すればいいです.
http://blog.csdn.net/ljx0305/article/details/4065058
selectに比べてepollの最大の利点は、傍受fd数の増加に伴って効率が低下しないことである.カーネル内のselect実装では、ポーリングによって処理されるため、ポーリングのfd数が多ければ多いほど、自然に時間がかかる.さらに、linux/posix_types.hヘッダファイルには、次のような宣言があります.
#define __FD_SETSIZE 1024
selectは最大1024個のfdを同時に傍受することを示し、もちろん、ヘッダファイルを修正してカーネルを再コンパイルすることでこの数を拡大することができるが、これは根本的ではないようだ.
epollのインタフェースは非常に簡単で、全部で3つの関数です.
1. int epoll_create(int size);
epollのハンドルを作成し、sizeはカーネルというリスニングの数が合計でどれだけ大きいかを示すために使用されます.このパラメータはselect()の最初のパラメータとは異なり、最大リスニングfd+1の値を与える.注意すべきは、epollハンドルを作成するとfd値が占有され、linuxで/proc/プロセスid/fd/を表示すると、このfdが表示されるので、epollを使用した後、close()を呼び出して閉じる必要があります.そうしないと、fdが消費される可能性があります.
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epollのイベント登録関数は、select()とは異なり、イベントをリスニングするときにカーネルにどのタイプのイベントをリスニングするかを教えるのではなく、ここでリスニングするイベントタイプを先に登録します.最初のパラメータはepoll_create()の戻り値、2番目のパラメータは動作を表し、3つのマクロで表されます.
EPOLL_CTL_ADD:新しいfdをepfdに登録する.
EPOLL_CTL_MOD:登録されたfdの傍受イベントを変更する.
EPOLL_CTL_DEL:epfdからfdを削除します.
3番目のパラメータは傍受が必要なfdで、4番目のパラメータはカーネルに何を傍受する必要があるかを教えて、struct epoll_イベント構造は次のとおりです.
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:対応するファイル記述子にエラーが発生したことを示す;
EPOLLHOP:対応するファイル記述子が切断されたことを示す;
EPOLLET:EPOLLをエッジトリガ(Edge Triggered)モードに設定します.これは水平トリガ(Level Triggered)に対してです.
EPOLLONESHOT:一度だけイベントをリスニングします.今回のイベントをリスニングした後、このソケットをリスニングし続ける必要がある場合は、このソケットを再びEPOLLキューに追加する必要があります.
3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
イベントの生成を待機します.select()呼び出しと似ています.パラメータeventsは、カーネルからイベントの集合を取得するために使用されます.maxeventsは、カーネルというeventsがどれだけ大きいかを示します.このmaxeventsの値はepoll_の作成より大きくはなりません.create()時のsize、パラメータtimeoutはタイムアウト時間(ミリ秒、0はすぐに戻り、-1は不確定、永久ブロックという説もあります).この関数は、処理するイベントの数を返します.たとえば、0を返すとタイムアウトします.
4.ET、LTの2種類の動作モードについて:
このような結論が得られます.
ETモードは状態が変化した場合にのみ通知されるが、ここでいう状態の変化にはバッファに未処理のデータがあるわけではない.すなわち、ETモードを採用するには、エラーが発生するまでread/writeを続ける必要がある.LTモードは、データがある限り処理がないことを通知し続ける.
では、epollをどのように使うのでしょうか.実はとても簡単です.
ヘッダファイル#include
まずはcreate_epoll(int maxfds)は、maxfdsがepollでサポートされている最大ハンドル数であるepollのハンドルを作成します.この関数は新しいepollハンドルを返し、その後のすべての操作はこのハンドルによって操作されます.使い終わったら、close()で作成したepollハンドルを閉じてください.
その後、あなたのネットワークのメインサイクルでは、フレームごとにepollを呼び出します.wait(int epfd,epoll_event events,int max events,int timeout)は、すべてのネットワークインタフェースをクエリーし、どれが読めるか、どれが書けるかを見ます.基本的な構文は次のとおりです.
nfds = epoll_wait(kdpfd, events, maxevents, -1);
ここでkdpfdはepollであるcreate作成後のハンドル、eventsはepoll_event*のポインタ、epoll_waitという関数の操作に成功した後、epoll_eventsにはすべての読み書きイベントが格納されます.max_eventsは、現在傍受する必要があるすべてのsocketハンドル数です.最後のtimeoutはepoll_waitのタイムアウトは、0の場合はすぐに戻り、-1の場合はずっと待っていて、イベント範囲があるまで、任意の正の整数の場合はこんなに長い時間待っていて、ずっとイベントがなければ、範囲を表します.一般に,ネットワークの主ループが単独のスレッドであれば−1で待つことができ,いくつかの効率を保証することができ,主論理と同じスレッドであれば0で主ループの効率を保証することができる.
epoll_wait範囲の後はループであり、すべてのイベントを利益しなければならない.
ほとんどのepollプログラムでは、次のフレームワークが使用されています.
for( ; ; )
{
nfds = epoll_wait(epfd,events,20,500);
for(i=0;i
if(events[i].data.fd=listenfd)/新しい接続がある
{
connfd=accept(listenfd,(sockaddr*)&clientaddr,&clilen);//acceptこの接続
ev.data.fd=connfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);//epollのリスニングキューに新しいfdを追加
}
else if(events[i].events&EPOLLIN)/データを受信し、socketを読む
{
n = read(sockfd, line, MAXLINE)) < 0 //読み取り
ev.data.ptr = md; //mdはカスタムタイプ、データの追加
ev.events=EPOLLOUT|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//識別子を修正し、次のサイクルを待つ間にデータを送信し、非同期処理の真髄
}
else if(events[i].events&EPOLLOUT)/送信待ちのデータがあり、socketを書く
{
struct myepoll_data* md = (myepoll_data*)events[i].data.ptr; //データをとる
sockfd = md->fd;
send( sockfd, md->ptr, strlen((char*)md->ptr), 0 ); //データの送信
ev.data.fd=sockfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//識別子を変更し、次のサイクルを待つ間にデータを受信する
}
else
{
//その他の処理
}
}
}
次に、完全なサーバ側の例を示します.
#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
using namespace std;
#define MAXLINE 5
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5000
#define INFTIM 1000
void setnonblocking(int sock)
{
int opts;
opts=fcntl(sock,F_GETFL);
if(opts<0)
{
perror("fcntl(sock,GETFL)");
exit(1);
}
opts = opts|O_NONBLOCK;
if(fcntl(sock,F_SETFL,opts)<0)
{
perror("fcntl(sock,SETFL,opts)");
exit(1);
}
}
int main(int argc, char* argv[])
{
int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;
ssize_t n;
char line[MAXLINE];
socklen_t clilen;
if ( 2 == argc )
{
if( (portnumber = atoi(argv[1])) < 0 )
{
fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
return 1;
}
}
else
{
fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
return 1;
}
// epoll_event ,ev ,
struct epoll_event ev,events[20];
// accept epoll
epfd=epoll_create(256);
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
// socket
//setnonblocking(listenfd);
//
ev.data.fd=listenfd;
//
ev.events=EPOLLIN|EPOLLET;
//ev.events=EPOLLIN;
// epoll
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char *local_addr="127.0.0.1";
inet_aton(local_addr,&(serveraddr.sin_addr));//htons(portnumber);
serveraddr.sin_port=htons(portnumber);
bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
listen(listenfd, LISTENQ);
maxi = 0;
for ( ; ; ) {
// epoll
nfds=epoll_wait(epfd,events,20,500);
//
for(i=0;i<nfds;++i)
{
if(events[i].data.fd==listenfd)// SOCKET SOCKET , 。
{
connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
if(connfd<0){
perror("connfd<0");
exit(1);
}
//setnonblocking(connfd);
char *str = inet_ntoa(clientaddr.sin_addr);
cout << "accapt a connection from " << str << endl;
//
ev.data.fd=connfd;
//
ev.events=EPOLLIN|EPOLLET;
//ev.events=EPOLLIN;
// ev
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
}
else if(events[i].events&EPOLLIN)// , , 。
{
cout << "EPOLLIN" << endl;
if ( (sockfd = events[i].data.fd) < 0)
continue;
if ( (n = read(sockfd, line, MAXLINE)) < 0) {
if (errno == ECONNRESET) {
close(sockfd);
events[i].data.fd = -1;
} else
std::cout<<"readline error"<<std::endl;
} else if (n == 0) {
close(sockfd);
events[i].data.fd = -1;
}
line[n] = '/0';
cout << "read " << line << endl;
//
ev.data.fd=sockfd;
//
ev.events=EPOLLOUT|EPOLLET;
// sockfd EPOLLOUT
//epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
}
else if(events[i].events&EPOLLOUT) //
{
sockfd = events[i].data.fd;
write(sockfd, line, n);
//
ev.data.fd=sockfd;
//
ev.events=EPOLLIN|EPOLLET;
// sockfd EPOLIN
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
}
}
}
return 0;
}
クライアントはこのサーバに直接接続すればいいです.
http://blog.csdn.net/ljx0305/article/details/4065058