寫在轉載之前的:
在nginx源碼src/event/ngx_event.h中涉及了好幾種網絡模型:
windows select
windows IOCP
select
poll
epoll
devpoll
kqueue
eventport
廢棄的:
glibc aio
rtsig
可以說不同的系統,使用的網絡模型不盡相同,是時候對它們總結一下了。
以下是轉載內容:
這裏介紹兩種機制,它們跟select和poll這兩個函數具備類似的特性。
/dev/poll接口
Solaris上名爲/dev/poll的特殊文件提供了一個可擴展的輪詢大量描述符的方法。select和poll存在的一個問題是,每次調用它們都得傳遞待查詢的文件描述符。輪詢設備能在調用之間維持狀態,因此輪詢進程可以預先設置好待查詢描述符的列表,然後進入一個循環等待事件發生,每次循環回來時不必再次設置該列表。
打開/dev/poll之後,輪詢進程必須先初始化一個pollfd結構(即poll函數使用的結構,不過本機制不使用其中的revents成員)數組,再調用write往/dev/poll設備上寫這個結構數組以把它傳遞給內核,然後執行DP_POLL命令阻塞自身以等待事件發生。
傳遞給ioctl調用的結構如下:
struct dvpoll {
struct pollfd *dp_fds;
int dp_nfds;
int dp_timeout;
};
其中dp_fds成員指向一個緩衝區,供ioctl在返回時存放一個pollfd結構數組。dp_nfds成員指定該緩衝區。ioctl調用將一直阻塞到任何一個被輪詢描述符上發生所關心的事件,或者流逝時間超過經由dp_timeout成員指定的毫秒數爲止。dp_timeout指定爲0將導致ioctl立即返回,從而提供了使用本接口的非阻塞手段。dp_timeout指定爲-1表示沒有超時設置。
以下利用/dev/poll機制對函數str_cli進行改編。
#include "unp.h"
#include
void
str_cli(FILE *fp, int sockfd)
{
int stdineof;
char buf[MAXLINE];
int n;
int wfd;
struct pollfd pollfd[2];
struct dvpoll dopoll;
int i;
int result;
wfd = open("/dev/poll", O_RDWR, 0);
pollfd[0].fd = fileno(fp);
pollfd[0].events = POLLIN;
pollfd[0].revents = 0;
pollfd[1].fd = sockfd;
pollfd[1].events = POLLIN;
pollfd[1].revents = 0;
write(wfd, pollfd, sizeof(struct pollfd) * 2);
stdineof = 0;
for ( ; ; ) {
dopoll.dp_timeout = -1;
dopoll.dp_nfds = 2;
dopoll.dp_fds = pollfd;
result = ioctl(wfd, DP_POLL, &dopoll);
for (i = 0; i < result; i++) {
if (dopoll.dp_fds[i].fd == sockfd) {
if ( (n = read(sockfd, buf, MAXLINE)) == 0) {
if (stdineof == 1)
return;
else
err_quit("str_cli: server terminated prematurely");
}
write(fileno(stdout), buf, n);
}
else {
if ((n = read(fileno(fp), buf, MAXLINE)) == 0) {
stdineof = 1;
shutdown(sockfd, SHUT_WR);
continue;
}
write(sockfd, buf, n);
}
}
}
}
kqueue接口
FreeBSD引入了kqueue接口。本接口允許進程向內核註冊描述所關注kqueue事件的事件過濾器。事件除了與select所關注類似的文件I/O和超時外,還有異步I/O、文件修改通知(例如文件被刪除或修改時發出的通知)、進程跟蹤(例如進程調用exit或fork時發出的通知)和信號處理。kqueue接口有如下兩個函數和一個宏。
#include
#include
#include
int kqueue(void);
int kevent(int kq,const struct kevent *changelist, int nchanges,
struct kevent *eventlist, int nevents,
const struct timespec *timeout);
void EV_SET(struct kevent *kev,uintptr_t ident, short filter,
u_short flags, u_int fflags, intptr_t data, void *udata);
kqueue函數返回一個新的kqueue描述符,用於後續的kevent調用中。kevent函數既用於註冊所關注的事件,也用於確定是否有所關注的事件發生。changelist和nchanges這兩個參數給出對所關注事件做出的更改,若無更改則分別取值NULL和0。如果nchanges不爲0,kevent函數就執行changelist數組中所請求的每個事件過濾器更改。其條件已經觸發的任何事件(包括剛在changelist中增設的那些事件)由kevent函數通過eventlist參數返回,它指向一個由nevents個元素構成的kevent結構數組。kevent函數在eventlist中返回的事件數目作爲函數返回值返回,0表示超時。超時通過timeout參數設置,其處理類似select:NULL阻塞進程,非0值timespec指定明確的超時值,0值timespec執行非阻塞事件檢查。注意,kevent使用的timespec結構不同於select使用的timeval結構,前者的分辨率爲納秒,後者的分辨率爲微秒。
kevent結構在頭文件中定義:
struct kevent {
uintptr_t ident;
short filter;
u_short flags;
u_int fflags
intptr_t data;
void *udata;
};
其中的flags成員在調用時指定過濾器更改行爲,在返回時額外給出條件,如下圖所示。
filter成員指定的過濾器類型如下圖所示。
以下利用kqueue機制對函數str_cli進行改編。
void
str_cli(FILE *fp, int sockfd)
{
int kq, i, n, nev, stdineof = 0, isfile;
char buf[MAXLINE];
struct kevent kev[2];
struct timespec ts;
struct stat st;
isfile = ((fstat(fileno(fp), &st) == 0) &&
(st.st_mode & S_IFMT) == S_IFREG);
EV_SET(&kev[0], fileno(fp), EVFILT_READ, EV_ADD, 0, 0, NULL);
EV_SET(&kev[1], sockfd, EVFILT_READ, EV_ADD, 0, 0, NULL);
kq = kqueue();
ts.tv_sec = ts.tv_nsec = 0;
kevent(kq, kev, 2, NULL, 0, &ts);
for ( ; ; ) {
nev = kevent(kq, NULL, 0, kev, 2, NULL);
for (i = 0; i < nev; i++) {
if (kev[i].ident == sockfd) {
if ( (n = read(sockfd, buf, MAXLINE)) == 0) {
if (stdineof == 1)
return;
else
err_quit("str_cli: server terminated prematurely");
}
write(fileno(stdout), buf, n);
}
if (kev[i].ident == fileno(fp)) {
n = read(fileno(fp), buf, MAXLINE);
if (n > 0)
write(sockfd, buf, n);
if (n == 0 || (isfile && n == kev[i].data)) {
stdineof = 1;
shutdown(sockfd, SHUT_WR);
kev[i].flags = EV_DELETE;
kevent(kq, &kev[i], 1, NULL, 0, &ts);
continue;
}
}
}
}
}