Unix网络篇(3)poll模型突破fd上限

上一篇文章中说道select还存在两个缺陷。其一,fd_set被设置为上限为1024的数组,这就限制了fd的个数;其二,select函数的“传入参数”又被当成了“传出参数”,所以每次都需要重新设置参数。

这些问题,在poll中都已经被解决了。

1 服务端

poll主要解决了select的上述两个问题。首先看一下,它的声明:

int poll(struct pollfd *, unsigned int nfds, int timeout)

1.1 解决的第一个问题:fd上限问题

poll函数使用第二个参数nfds来指定fd上限的个数,也就是用户可以自定义个数了。

1.2 解决的第二个问题:区分开入参和出参问题

可以看到poll的第一个参数为struct pollfd*,如果展开来看,它包括以下三部分:fd、入参events和出参revents

struct pollfd {
int fd;
short events;
short revents;
};

怎么说呢,就是你可以通过events设置对某个fd你要监听的事件。那么,这个事件是否发生了,会通过revents反馈出来。而不会改变events的值。所以,就区分开了,参数问题。

1.3 以下是poll_server.c代码

这里需要说明一下,Stevens说:“polltimeout参数可以被设置为三个值:INFTIM永远等待,0立即返回,>0等待指定的毫秒数。” 同时他也提到了POSIX规范要求INFTIM被定义在<poll.h>中,但是很多系统把它定义在了<sys/stropts.h>中。实际上,我都尝试了,并没有找到该定义。

于是,我自己定义了它:const int INFTIM = -1;

#include <sys/socket.h> 
#include <netinet/in.h> //sockaddr_in
#include <unistd.h> //fork,read,write
#include <stdlib.h> //exit(0)
#include <errno.h>
#include <string.h> //bzero
#include <poll.h> //poll
#include <limits.h> //OPEN_MAX
#include <stdio.h>

const int SERV_PORT = 9527;
const int LISTENQ = 5;
const int MAXBUFSIZE = 1024;

const int INFTIM = -1; //据说INFTIM被定义在<poll.h>中,但是没找到,<sys/stropts.h>中也没有

int main(int argc, char* argv[]) {
int i, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[MAXBUFSIZE];
socklen_t clilen;
struct pollfd client[OPEN_MAX]; //#define OPEN_MAX 10240 (Mac OS X)
struct sockaddr_in cliaddr, servaddr;

listenfd = socket(AF_INET, SOCK_STREAM, 0);

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);

if ( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0 )
exit_msg("bind error");

listen(listenfd, LISTENQ);

client[0].fd = listenfd;
client[0].events = POLLRDNORM; //(优先级)普通数据可读
for (i = 1; i < OPEN_MAX; ++i)
client[i].fd = -1;
maxi = 0;
for ( ; ; ) {
nready = poll(client, maxi + 1, INFTIM);
if (client[0].revents & POLLRDNORM) {
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);

for (i = 1; i < OPEN_MAX; ++i) {
if (client[i].fd < 0) {
client[i].fd = connfd;
break;
}
}
if (i == OPEN_MAX)
exit_msg("too many clients");
client[i].events = POLLRDNORM;
if (i > maxi) maxi = i;
if (--nready <= 0) continue;
}
for (i = 1; i <= maxi; ++i) {
if ( (sockfd = client[i].fd) < 0 )
continue;
if (client[i].revents & (POLLRDNORM | POLLERR)) {
if ( (n = read(sockfd, buf, MAXBUFSIZE)) < 0 ) {
if (errno == ECONNRESET) {
close(sockfd);
client[i].fd = -1;
}
else {
exit_msg("read error");
}
}
else if (n == 0) {
close(sockfd);
client[i].fd = -1;
}
else {
writen(sockfd, buf, n);//DIY: writen = write + while(参见Unix网络篇第一篇文章)
}
if (--nready <= 0)
break;
}
}
}

return 0;
}

2 客户端

客户端和上几篇几乎差不多,都是处理了两个fdsockfdfilefd

再次重声一遍:writen = write + while函数为自定义函数。它是为了避免缓冲区达到极限,write一次不能写入全部数据的问题。

#include <sys/socket.h> 
#include <netinet/in.h> //sockaddr_in
#include <arpa/inet.h> //inet_pon
#include <unistd.h> //fork,read,write
#include <stdlib.h> //exit(0)
#include <errno.h>
#include <string.h> //bzero
#include <stdio.h>
#include <poll.h>

const int SERV_PORT = 9527;
const int LISTENQ = 5;
const int MAXBUFSIZE = 1024;

const int INFTIM = -1;

void str_cli(FILE* fp, int sockfd) {
struct pollfd client[2];
char buf[MAXBUFSIZE];
int n;

client[0].fd = sockfd;
client[0].events = POLLRDNORM; //(优先级)普通数据可读
client[1].fd = fileno(fp);
client[1].events = POLLRDNORM;

for ( ; ; ) {
poll(client, 2, INFTIM);
if (client[0].revents & (POLLRDNORM | POLLERR)) {
if ( (n = read(sockfd, buf, MAXBUFSIZE)) == 0 )
exit_msg("str_cli:server terminated prematurely");
write(fileno(stdout), buf, n); //stdout使用write即可,不必writen
}
if (client[1].revents & (POLLRDNORM | POLLERR)) {
if ( (n = read(fileno(fp), buf, MAXBUFSIZE)) == 0 ) {
shutdown(sockfd, SHUT_WR); //FIN
continue;
}
writen(sockfd, buf, n); //DIY: 参见《Unix网络篇(1)一个典型的TCP Socket通信例子》
}
}
}

int main(int argc, char* argv[]) {
int sockfd;
struct sockaddr_in servaddr;

if (argc != 2)
exit_msg("argv != 2");

sockfd = socket(AF_INET, SOCK_STREAM, 0);

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
exit_msg("conn err");//警告: connect请勿捕获EINTR

str_cli(stdin, sockfd);

return 0;
}

References:
[1] W.Richard Stevens,《Unix网络编程 卷1:套接字联网API(第3版)》