Linux下實現I/O多路複用的系統調用主要有select、poll和epoll。
這裏主要剖析poll。
1>fds參數是一個pollfd結構類型的數組,它指定所有我們感興趣的文件描述符上發生的可讀、可寫和異常事件,pollfd結構體的定義如下:
其中,fd成員指定文件描述符;events成員告訴poll監聽fd上的哪些事情,它是一系列事件的按位或;revents成員則由內核修改,以通知應用程序fd上實際發生了哪些事件。poll支持的事件類型有POLLIN、POLLOUT、POLLPRI等。
2>nfds參數指定被監聽的事件集合fds的大小。類型是:typedef unsigned long int nfds_t;
3>timeout參數指定poll的超時值,單位是毫秒。當timeout爲-1時,poll調用將永遠阻塞,知道某個事件發生;當timeout爲0時,poll調用將立即返回。
poll系統調用的返回值的含義與select相同。
poll是一個系統調用,系統主要做這幾件事:
1.將用戶傳入的pollfd數組拷貝到內核空間因爲拷貝操作與數組長度相關。時間上這是一個O(n)操作。
2.查詢每個文件描述符對應設備的狀態,如果該設備尚未就緒,則在該設備的等待隊列中加入一項並繼續查詢下一設備的狀態,查詢完所有設備後沒有一個就緒,這時則需要掛起當前進程等待,直到設備就緒或者超時。
3.將獲得的數據傳到用戶空間並執行釋放內存和剝離等待隊列等善後工作。
代碼實現:
/*************************************************************************
> File Name: poll.c
> Author: ZX
> Mail: [email protected]
> Created Time: Fri 17 Mar 2017 10:37:42 PM PDT
************************************************************************/
#include <stdio.h>
#include <poll.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <assert.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <errno.h>
#define _SIZE_ 1024
int startup(const char* _ip, int _port)
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
perror("socket");
return 1;
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_addr.s_addr = inet_addr(_ip);
local.sin_port = htons(_port);
socklen_t len = sizeof(local);
if(bind(sock, (struct sockaddr*)&local, len) < 0)
{
perror("bind");
return 2;
}
if(listen(sock, 5) < 0)
{
perror("listen");
return 3;
}
return sock;
}
static void Init_fd(struct pollfd* pfd, int len)
{
assert(pfd);
int i = 0;
for(; i<len; i++)
{
pfd[i].fd = -1;
pfd[i].events = 0;
pfd[i].revents = 0;
}
}
static int Add_fd(struct pollfd* pfd, int len, int add_fd)
{
assert(pfd);
int i = 0;
for(; i<len; i++)
{
if(pfd[i].fd == -1)
{
pfd[i].fd = add_fd;
return i;
}
}
return -1;
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
printf("Usage: %s [local_ip] [local_port]", argv[0]);
return 4;
}
int listen_sock = startup(argv[1], atoi(argv[2]));
struct pollfd pfd[_SIZE_];
Init_fd(pfd, _SIZE_);
int index = Add_fd(pfd, _SIZE_, listen_sock);
if(index != -1)
{
pfd[index].events = POLLIN;
}
int timeout = 2000;
char* buf[1024];
while(1)
{
switch(poll(pfd, _SIZE_, timeout))
{
case 0:
printf("timeout...\n");
break;
case -1:
perror("poll");
// exit(1);
break;
default:
{
int i = 0;
printf("default..\n");
for(; i<_SIZE_; i++)
{
int fd = pfd[i].fd;
printf("fd: %d\n",fd);
sleep(1);
if(fd == listen_sock && (pfd[i].fd & POLLIN))
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
if(new_sock < 0)
{
perror("accept");
// exit(5);
return 5;
}
else if(new_sock > 0)
{
printf("get a new client# ip: %s\n", inet_ntoa(peer.sin_addr));
int newfd = Add_fd(pfd, _SIZE_, new_sock);
printf("newfd: %d\n",newfd);
if(newfd != -1)
{
pfd[newfd].events = POLLIN;
}
else if(newfd == _SIZE_)
{
printf("newfd == _SIZE_\n");
close(newfd);
}
}
}
else if(fd != -1 && fd != listen_sock && (pfd[i].events & POLLIN))
{
printf("read:\n");
char buf[1024];
memset(buf, '\0', sizeof(buf));
ssize_t _s = read(fd, buf, sizeof(buf)-1);
if(_s > 0)
{
buf[_s] = 0;
printf("buf: %s\n", buf);
}
else if(_s == 0)
{
printf("client is quit!\n");
close(pfd[i].fd);
}
else
{
perror("read");
continue;
}
}//else if
}
}
}
}
return 0;
}
實質上就是數組處理。