相對於select來說,poll 也是在指定時間內論詢一定數量的文件描述符,來測試其中是否有就緒的,不過,poll 提供了一個易用的方法,來實現 i/o 復用。
聲明如下:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
其中,struct pollfd 定義為:
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
fd 為文件描述符,events 告訴poll 監聽fd 上哪些事件,它是一系列事件按位或。revents 由內核修改,來通知應用程序fd 上實際上發生了哪些事件。
nfds 為監聽事件集合fds的大小
timeout 為poll的超時時間,單位毫秒。timeout 為-1時,poll永遠阻塞,直到有事件發生。timeout為0時,poll立即返回。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <errno.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <map>
#include <string>
using namespace std;
#define BUFSIZE 10
#define CLIENTSIZE 100
int createSocket()
{
struct sockaddr_in servaddr;
int listenfd = -1;
if (-1 == (listenfd = socket(PF_INET, SOCK_STREAM, 0)))
{
fprintf(stderr, "socket: %d, %s\n", errno, strerror(errno));
exit(1);
}
int reuseaddr = 1;
if (-1 == setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)))
{
fprintf(stderr, "setsockopt: %d, %s\n", errno, strerror(errno));
exit(1);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = PF_INET;
servaddr.sin_port = htons(8008);
inet_pton(PF_INET, "0.0.0.0", &servaddr.sin_addr);
if (-1 == bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)))
{
fprintf(stderr, "bind: %d, %s\n", errno, strerror(errno));
exit(1);
}
if (-1 == listen(listenfd, 5))
{
fprintf(stderr, "listen: %d, %s\n", errno, strerror(errno));
exit(1);
}
return listenfd;
}
int setnoblock(int fd)
{
int oldopt = fcntl(fd, F_GETFL);
int newopt = oldopt | O_NONBLOCK;
fcntl(fd, F_SETFL, newopt);
return oldopt;
}
int main()
{
struct pollfd fds[CLIENTSIZE];
int listenfd = createSocket();
map<int, string> mapdata;
fds[0].fd = listenfd;
fds[0].events = POLLIN | POLLERR;
fds[0].revents = 0;
int conncount = 0;
while (1)
{
int ret = poll(fds, conncount + 1, -1);
if (ret < 0)
{
fprintf(stderr, "poll: %d, %s\n", errno, strerror(errno));
exit(1);
}
for (int i = 0; i < conncount + 1; i++)
{
// 客戶端關閉,或者錯誤。錯誤的原因是由於客戶端關閉導致的,這里一並處理
if ((fds[i].revents & POLLRDHUP) || (fds[i].revents & POLLERR))
{
int fd = fds[i].fd;
fds[i] = fds[conncount];
i--;
conncount--;
mapdata.erase(fd);
close(fd);
printf("delete connection: %d\n", fd);
}
// 新的連接
else if ((fds[i].fd == listenfd) && (fds[i].revents & POLLIN))
{
struct sockaddr_in client;
socklen_t lenaddr = sizeof(client);
int conn = -1;
if (-1 == (conn = accept(listenfd, (struct sockaddr*)&client, &lenaddr)))
{
fprintf(stderr, "accept: %d, %s\n", errno, strerror(errno));
exit(1);
}
printf("get connection %d from %s:%d\n", conn, inet_ntoa(client.sin_addr), client.sin_port);
conncount++;
setnoblock(conn);
fds[conncount].fd = conn;
fds[conncount].events = POLLIN | POLLRDHUP | POLLERR;
fds[conncount].revents = 0;
}
// 有可讀數據
else if (fds[i].revents & POLLIN)
{
char buf[BUFSIZE] = {0};
int lenrecv = recv(fds[i].fd, buf, BUFSIZE-1, 0);
if (lenrecv > 0)
{
mapdata[fds[i].fd] = buf;
fds[i].events &= (~POLLIN);
fds[i].events |= POLLOUT;
}
else if (lenrecv == 0)
{
printf("------- client %d exit (not print) --------\n", fds[i].fd);
}
else
{
fprintf(stderr, "recv: %d, %s\n", errno, strerror(errno));
exit(1);
}
}
// 可寫數據
else if (fds[i].revents & POLLOUT)
{
if (send(fds[i].fd, mapdata[fds[i].fd].c_str(), mapdata[fds[i].fd].size(), 0) < 0)
{
if (ECONNRESET == errno)
{
continue;
}
fprintf(stderr, "send: %d, %s\n", errno, strerror(errno));
exit(1);
}
fds[i].events &= (~POLLOUT);
fds[i].events |= POLLIN;
}
}
}
}