epoll学習、epollとselect、poolの違い



linuxネットワークのプログラミングでは、長い間selectでイベントトリガをしていました.linuxの新しいカーネルの中で、それを置き換えるメカニズムがあります.epollです.
selectと比較する、epollの最大の利点は、傍受fd数の増加に伴って効率が低下しないことである.カーネルにおけるselect実装では、ポーリングによって処理するため、ポーリングされたfdデータが多ければ多いほど、自然に時間がかかる.
epollのインタフェースの3つの関数
1) int epoll_create(int size);
epollハンドルを作成し、sizeはカーネルという傍受データの合計サイズを教えます.
なお、epollハンドルを作成するとfd値が占有され、epollを使用した後、closeを呼び出して閉じる必要があり、そうしないとfdが消費する可能性がある.
2) int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
epoll登録関数、最初のパラメータはepoll_create()の戻り値.
2番目のパラメータは動作を表し、3つのマクロで表す
EPOLL_CTL_ADD:新しいfdをepfdに登録する
EPOLL_CTL_MOD:登録されたfdのリスニングイベントを変更する
EPOLL_CTL_DEL:epfdからfdを削除する
3番目のパラメータは傍受が必要なfdです
4つ目のパラメータは、カーネルが何を監視する必要があるかを示すことです.
struct_イベント構造は次のとおりです.
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は、対応するファイル記述子にエラーが発生したことを示す
EPOLLHCUP:対応するファイル記述子が切断されたことを示す
EPOLLET:EPOLLをエッジトリガモード(Edge Triggered)とする.これは水平トリガ(Level Triggered)に対して
EPOLLONSHOT:一度だけ傍受し、今回のイベントを傍受した後、このソケットを引き続き傍受する必要がある場合は、再びこのソケットをWPOLLキューに追加する必要があります
 
3) int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
パラメータeventsは、カーネルからイベントの集合を取得するために使用されます.maxeventsは、カーネルというeventsがどれだけ大きいかを示します.このmaxeventsの値はepoll_の作成より大きくはなりません.create()時のsize,パラメータtimeoutはタイムアウト時間(ミリ秒)であり,0は直ちに戻り,-1は不確定であり,永久閉塞という説もある.この関数は、処理するイベントの数を返し、0を返すとタイムアウトを示す.
 
ET.LTの2つの動作モードについて
ETモードは、状態送信が変化する場合にのみ通知され、ここで、状態の変化とは、バッファに未処理のデータがある、すなわち、含まれない.ETモードを採用するには、エラーが発生するまでread/writeを継続する必要がある.なぜETモードで一部のデータを受信しただけで通知が得られないのかを反映する人が多いのは、そのためであることが多い.
LTモードは、データが処理されていない限り通知し続ける.
 
epollモデル
まずは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範囲の後はループであり、すべてのイベントを利益しなければならない.
 
 
 
#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;

int main(int argc,char *argv[])
{
	int maxi,listenfd,connfd,sockfd,epfd,nfds;
	ssize_t n;
	char line[100];

	listenfd = socket(AF_INET,SOCK_STREAM,0);

	//  epoll_event     ,ev      ,            
	struct epoll_event ev,events[20];
	epfd = epoll_create(256);
	ev.date.fd = listenfd;
	ev.events = EPOLLIN|EPOLLET;
	epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev); //  epoll  

	struct sockaddr_in serveraddr;
	bzero(&serveraddr,sizeof(serveraddr));
	char *local_addr = "127.0.0.1";
	inet_aton(local_addr,&(serveraddr.sin_addr));
	serveraddr.sin_port=htons(8888);
	bind(listenfd,(sockaddr*)&serveraddr,sizeof(serveraddr));
	listen(listenfd,LISTENQ);
	maxi=0;

	for(;;)
	{
		//  epoll    
		nfds = epoll_wait(epfd,events,20,500);

		//         
		for(int i = 0;i <nfds;i++)
		{
			if(events[i].data.fd == listenfd) //        SOCKET         socket  ,     
			{
				struct sockaddr_in clientaddr;
				socketlen_t clilen;
				connfd = accept(listenfd,(sockaddr *)&clientaddr,&clilen);
				char *str = inet_ntoa(clientaddr.sin_addr);
				cout <<"accept a connection from "<<str<<endll;
				
				ev.data.fd = connfd;
				ev.events = EPOLLIN|EPOLLET;
				epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);

			}
			else if(events[i].events & EPOLLIN) //         ,      ,      
			{
				sockfd = events[i].data.fd;
				n = read(sockfd,line,100);
				line[n] = '\0';
				cout <<"read msg :"<<line<<endl;

				ev.data.fd = sockfd;
				ev.events = EPOLLOUT|EPOLLEN;
				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;
				epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
			}

		}
	}

	return 0;


}

 
 
epollとselect,pollの違い
1.プロセスが多数のsocket記述子を開くことをサポートする(FD)
selectが最も耐えられないのは、プロセスが開いているFDには一定の制限があり、FD_SETSIZE設定、デフォルトは2048です.サポートが必要な数万以上の接続数のIMサーバにとっては、明らかに少なすぎます.この場合、マクロを変更してカーネルを再コンパイルすることができますが、データはネットワーク効率の低下をもたらすことを指摘しています.
2.IO効率はFD数の増加に伴って線形に低下しない
従来のselect/pollのもう一つの致命的な弱点は、大きなsocketセットを持っていることですが、ネットワークが遅延しているため、任意の時間に一部のsocketだけが「アクティブ」ですが、select/pollは呼び出すたびにすべてのセットを線形にスキャンし、効率が線形に低下します.しかし、epollにはこの問題は存在せず、カーネル実装では、各fd上のcallback関数に基づいてepollが実装されるため、「アクティブ」なsocketのみが動作します.
epollはイベントベースの準備完了通知方式を採用している.select/pollでは、プロセスは、一定のメソッドを呼び出した後にのみ、カーネルが監視されているすべてのファイル記述子をスキャンし、epollは事前にepoll_を通過します.ctl()はファイル記述子を登録し、あるファイル記述子に基づいて準備が整うと、カーネルはcallbackのようなコールバックメカニズムを採用し、プロセスがepoll_を呼び出すと、このファイル記述子を迅速にアクティブにします.wait()の場合に通知されます.
3.mmapを使用してカーネルとユーザ空間のメッセージングを加速する
selectでもpollでもepollでもカーネルがFDメッセージをユーザ空間に通知する必要があり、不要なメモリコピーをどのように回避するかが重要である点で、epollはカーネルがユーザ空間mmapと同じメモリで実現される
4.カーネル微調整
この点はepollの長所ではなく、Linuxプラットフォーム全体の長所である.