多路IO复用模型之poll
多路IO复用模型之select
多路IO复用模型之epoll
poll和select一样,都可用于执行多路复用IO。
poll函数原型
int poll(struct pollfd * fds,
unsigned int nfds,
int timeout);
这里还是用一张图来说明各个参数的作用
当poll()返回-1时,errno的值为下列中的一个:
- EBADF:一个或多个结构体中指定的文件描述符无效;
- EFAULTfds:指针指向的地址超出进程的地址空间;
- EINTR:请求的事件之前产生一个信号,调用可以重新发起;
- EINVALnfds:参数超出PLIMIT_NOFILE值;
- ENOMEM:可用内存不足,无法完成请求。
poll实战
server.cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#define PORT 6666
#define MAXLINE 1024
#define LISTENQ 5
#define OPEN_MAX 1000
#define INFTIM -1
int bind_and_listen(){
//创建套接字,进行绑定和监听
int serverfd; //监听socket:serverfd
struct sockaddr_in my_addr; //本机的ip地址信息
if ((serverfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
return -1;
}
printf("socket ok \n");
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(PORT);
my_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(my_addr.sin_zero), 0);
if (bind(serverfd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == -1) {
perror("bind");
return -2;
}
printf("bind ok \n");
if (listen(serverfd, LISTENQ) == -1) {
perror("listen");
return -3;
}
printf("listen ok \n");
return serverfd;
}
void do_poll(int listenfd){
//IO多路复用POLL
int connfd,sockfd;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen;
struct pollfd clientfds[OPEN_MAX];
int maxi;
int i;
int nready;
clientfds[0].fd = listenfd; //添加监听描述符
clientfds[0].events = POLLIN;
for (i = 1; i < OPEN_MAX; i++) {
clientfds[i].fd = -1; //初始化客户连接描述符
}
maxi = 0;
while (1) {
nready = poll(clientfds, maxi+1, INFTIM); // 获取可用描述符的个数
if (nready == -1) {
perror("poll error :");
exit(1);
}
if (clientfds[0].revents & POLLIN) {
cliaddrlen = sizeof(cliaddr);
if ((connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddrlen)) == -1) {
if (errno == EINTR) {
continue;
}
else{
perror("accept error:");
exit(1);
}
}
fprintf(stdout, "accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);
for (i = 1; i < OPEN_MAX; i++) {
//将新的连接描述符加入到数组中
if (clientfds[i].fd < 0) {
clientfds[i].fd = connfd;
break;
}
}
if (i == OPEN_MAX) {
fprintf(stderr, "too many clients. \n");
exit(1);
}
clientfds[i].events = POLLIN; //将新的描述符添加到读描述符的集合中
maxi = (i > maxi ? i : maxi); //记录连接套接字的个数
if (--nready <= 0) {
continue;
}
}
char buf[MAXLINE]; //处理多个连接上客户端发来的包
memset(buf, 0, MAXLINE);
int readlen = 0;
for (i = 1; i <= maxi; i++) {
if (clientfds[i].fd < 0) {
continue;
}
if (clientfds[i].revents & POLLIN) {
readlen = read(clientfds[i].fd, buf, MAXLINE); //接收客户端发送的信息
if (readlen == 0) {
close(clientfds[i].fd);
clientfds[i].fd = -1;
continue;
}
write(STDOUT_FILENO, buf, readlen);
// write(clientfds[i].fd, buf, readlen); //向客户端发送buf
}
}
}
}
int main(){
int listenfd = bind_and_listen();
if (listenfd < 0) {
return 0;
}
do_poll(listenfd);
return 0;
}
client.cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <strings.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#define DEFAULT_PORT 6666
#define MAXLINE 1024
#define max(a,b) (a > b) ? a : b
static void handle_connection(int sockfd);
int main(){
int connfd = 0;
int cLen = 0;
struct sockaddr_in client;
client.sin_family = AF_INET;
client.sin_port = htons(DEFAULT_PORT);
client.sin_addr.s_addr = inet_addr("172.22.0.191"); //这里填服务器的ip地址
connfd = socket(AF_INET,SOCK_STREAM, 0);
if (connfd < 0) {
perror("socket");
return -1;
}
if (connect(connfd, (struct sockaddr*)&client, sizeof(client)) < 0) {
perror("connect");
return -1;
}
handle_connection(connfd); //处理连接描述符
return 0;
}
static void handle_connection(int sockfd){
char sendline[MAXLINE],recvline[MAXLINE];
int maxfdp,stdineof;
struct pollfd pfds[2];
int n;
pfds[0].fd = sockfd; //添加连接描述符
pfds[0].events = POLLIN;
pfds[1].fd = STDIN_FILENO; //添加标准输入描述符
pfds[1].events = POLLIN;
while (1) {
poll(pfds, 2, -1);
if (pfds[0].revents & POLLIN) {
n = read(sockfd, recvline, MAXLINE);
if (n == 0) {
fprintf(stderr, "Client: server is colosed . \n");
close(sockfd);
}
write(STDOUT_FILENO, recvline, n);
}
if (pfds[1].revents & POLLIN) {
n = read(STDIN_FILENO, sendline, MAXLINE);
if (n == 0) {
shutdown(sockfd, SHUT_WR);
continue;
}
write(sockfd, sendline, n);
}
}
}
可以看出,poll也可以让服务器具备同时处理多个客户端请求的能力。
总结
-
poll()和select()相比,它没有了最大连接数的限制(select在32位linux系统中最大连接数为1024个),可以创建特定大小的数组来保存监控的描述符,且不受文件描述符值大小的影响;
-
poll()不要求在计算最大文件描述符时+1的操作;
-
poll()在应付大数目的文件描述符的时候更快,因为select()需要内核检查大量的描述符对应的fd_set中的每一个比特位,比较费时;
-
select()对所监控的fd_set在函数返回后会发生变化,在下次进入select()的时候需要重新初始化要监控的fd_set,poll()函数将监控的输入和输出事件分开,允许被监控的文件数组被复用而不需要重新初始化;
-
select()的可移植性优于poll(),在某些unix系统上不支持poll();
-
select()对于超时值提供了更好的精度。