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;
}
实质上就是数组处理。