Linux网络编程之poll多路转接服务器
poll是对select的改进,但是它是个半成品,相对select提升不大,最终版本是epoll。
一、pool函数简介
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:监听的文件描述符【数组】
struct pollfd {
int fd:待监听的文件描述符
short events:待监听的文件描述符对应的监听事件,取值:POLLIN、POLLOUT、POLLERR
short revnets: 传入时,给0。如果满足对应事件的话,返回非0 --> POLLIN、POLLOUT、POLLERR
}
nfds: 监听数组的,实际有效监听个数。
timeout:> 0:超时时长。单位:毫秒。
-1: 阻塞等待
0: 不阻塞
返回值:返回满足对应监听事件的文件描述符 总个数。
优点:
自带数组结构。可以将 监听事件集合 和 返回事件集合 分离。
拓展监听上限。超出 1024限制。
缺点:
不能跨平台,只能Linux。
无法直接定位满足监听事件的文件描述符,编码难度较大。
pool使用注意事项:
二、基于poll的多路转接服务器
服务端:
/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
#define OPEN_MAX 1024
int main(int argc, char *argv[])
{
int i, j, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[MAXLINE], str[INET_ADDRSTRLEN];
socklen_t clilen;
struct pollfd client[OPEN_MAX];
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);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
Listen(listenfd, 20);
client[0].fd = listenfd;
/* listenfd监听普通读事件 */
client[0].events = POLLRDNORM;
for (i = 1; i < OPEN_MAX; i++)
/* 用-1初始化client[]里剩下元素 */
client[i].fd = -1;
/* client[]数组有效元素中最大元素下标 */
maxi = 0;
for ( ; ; ) {
/* 阻塞 */
nready = poll(client, maxi+1, -1);
/* 有客户端链接请求 */
if (client[0].revents & POLLRDNORM) {
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 1; i < OPEN_MAX; i++) {
if (client[i].fd < 0) {
/* 找到client[]中空闲的位置,存放accept返回的connfd */
client[i].fd = connfd;
break;
}
}
if (i == OPEN_MAX)
perr_exit("too many clients");
/* 设置刚刚返回的connfd,监控读事件 */
client[i].events = POLLRDNORM;
if (i > maxi)
/* 更新client[]中最大元素下标 */
maxi = i;
/* 没有更多就绪事件时,继续回到poll阻塞 */
if (--nready <= 0)
continue;
}
/* 检测client[] */
for (i = 1; i <= maxi; i++) {
if ((sockfd = client[i].fd) < 0)
continue;
if (client[i].revents & (POLLRDNORM | POLLERR)) {
if ((n = Read(sockfd, buf, MAXLINE)) < 0) {
/* 当收到 RST标志时 */
if (errno == ECONNRESET) {
/* connection reset by client */
printf("client[%d] aborted connection\n", i);
Close(sockfd);
client[i].fd = -1;
} else {
perr_exit("read error");
}
} else if (n == 0) {
/* connection closed by client */
printf("client[%d] closed connection\n", i);
Close(sockfd);
client[i].fd = -1;
} else {
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
Writen(sockfd, buf, n);
}
if (--nready <= 0)
/* no more readable descriptors */
break;
}
}
}
return 0;
}
客户端:
/* client.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
if (argc != 2) {
printf("Enter: ./client server_IP\n");
exit(1);
}
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
printf("------------connect ok----------------\n");
while (fgets(buf, MAXLINE, stdin) != NULL) {
Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break;
}
else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0;
}