0.select、poll、epoll的比较
1. 5种IO模型
2.文件描述符就绪状态
3. select函数
linux select函数详解详细讲解了select各个参数的意义,以及调用select的流程图
服务端代码:
#include "unp.h"
int
main(int argc, char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE];//最大FD_SETSIZE:支持并发数目1024个连接 存放套接字connfd文件描述符(init -1)
ssize_t n;
fd_set rset, allset;
char buf[MAXLINE];
socklen_t clilen;
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);//uint32_t, 0
servaddr.sin_port = htons(SERV_PORT);//9877
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
maxfd = listenfd; /* initialize 表示读集rset中下标最大的那个文件描述符*/
maxi = -1; /* index into client[] array 表示client中有效的最大下标*/
//client数组初始化-1
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1; /* -1 indicates available entry */
//a.全置为0
FD_ZERO(&allset);
//b.监听套接字放入rset(布置感兴趣事件)
FD_SET(listenfd, &allset);
for ( ; ; ) {
rset = allset; /* 每次都要重新布置 */
nready = Select(maxfd+1, &rset, NULL, NULL, NULL);/*返回就绪描述符数量*/
if (FD_ISSET(listenfd, &rset)) { /* 如果监听套接字可写了,说明有新的连接 */
clilen = sizeof(cliaddr);
//c.
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
for (i = 0; i < FD_SETSIZE; i++) {
if (client[i] < 0) {
client[i] = connfd; /* 在client数组中找到第一个可以用的位置(值为-1),保存下connfd*/
break;
}
}
if (i == FD_SETSIZE) /* 没有可用位置了,出错*/
err_quit("too many clients");
//d.
FD_SET(connfd, &allset); /* 布置感兴趣事件,把连接套接字connfd加入rset */
if (connfd > maxfd)
maxfd = connfd; /* 更新maxfd */
if (i > maxi)
maxi = i; /* 更新maxi */
if (--nready <= 0) /* 除了listenfd之外没有更多的可读文件描述符了*/
continue;
}
for (i = 0; i <= maxi; i++) { /* 对每个客户检查(selest只能这么低效,不能知道是哪个客户套接字可读) */
if ( (sockfd = client[i]) < 0) /*如果为-1,说明已经这个客户已经和服务器关闭连接*/
continue;
if (FD_ISSET(sockfd, &rset)) {
//e.
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {/*该客户关闭连接,则要关闭已连接套接字*/
//f. /*rset那一位清0,client那一位置变-1 */
Close(sockfd);
//g.
FD_CLR(sockfd, &allset);
client[i] = -1;
} else /*正常状态,往服务器写*/
//h.
Writen(sockfd, buf, n);
if (--nready <= 0) /* 除了该客户已连接套接字之外没有更多的可读文件描述符了*/
break;
}
}
}
}
/* end fig02 */
图中的a,b,c…对应写在代码注释了
拒绝服务型攻击
一个恶意的客户连接到服务器,发送一个字节的数据后进入睡眠,服务器会一直阻塞在read上,以至于不能给其他客户提供服务。所以,当一个服务器在处理多个客户时,他绝对不能阻塞于只与单个客户相关的某个函数调用,否则服务器可能被挂起,拒绝为其他客户提供服务。解决方法:
- 使用非阻塞IO
- 创建一个子进程或一个线程来服务每个客户
- 为每个IO操作设置超时时间
客户端代码
主要对str_cli函数的修改,这里用了shutdown函数,替代close
UNIX网络编程——shutdown 与 close 函数 的区别
同时用read而不是readline和fgets(readline和fgets每次只返回第一行,其余的在缓冲区)
#include "unp.h"
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT); //服务端服务端口9877
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr); //命令行传入服务端地址
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd); /* do it all */
exit(0);
}
void
str_cli(FILE *fp, int sockfd)
{
int maxfdp1, stdineof;
fd_set rset;
char buf[MAXLINE];
int n;
stdineof = 0; //标志位
FD_ZERO(&rset);
for ( ; ; ) {
if (stdineof == 0) //标准输入stdin只要没有遇到EOF,就关注stdin的可读性
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdp1 = max(fileno(fp), sockfd) + 1;
Select(maxfdp1, &rset, NULL, NULL, NULL);
if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
if (stdineof == 1) /* 已经在标准输入遇到过EOF了,是正常终止*/
return; /* normal termination */
else /*服务器过早终止*/
err_quit("str_cli: server terminated prematurely");
}
Write(fileno(stdout), buf, n); //正常情况往stdout写
}
if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */
if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {
stdineof = 1; /* stdin遇到EOF*/
Shutdown(sockfd, SHUT_WR); /* send FIN 关闭写一半*/
FD_CLR(fileno(fp), &rset); /* stdin清0 */
continue;
}
Writen(sockfd, buf, n); //正常情况往服务器写
}
}
}
4. poll函数
服务端代码
Linux网络编程——I/O复用之poll函数介绍poll函数
和select非常类似
/* include fig01 */
#include "unp.h"
#include <limits.h> /* for OPEN_MAX */
int
main(int argc, char **argv)
{
int i, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[MAXLINE];
socklen_t clilen;
struct pollfd client[OPEN_MAX];//不同于select,这个client数组的内容是结构体pollfd
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, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
client[0].fd = listenfd;
client[0].events = POLLRDNORM;
for (i = 1; i < OPEN_MAX; i++)
client[i].fd = -1; /* -1 indicates available entry */
maxi = 0; /* max index into client[] array */
for ( ; ; ) {
nready = Poll(client, maxi+1, INFTIM);
if (client[0].revents & POLLRDNORM) { /* => FD_ISSET(listenfd, &rset), new client connection */
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
for (i = 1; i < OPEN_MAX; i++)
if (client[i].fd < 0) {
client[i].fd = connfd; /* save descriptor */
break;
}
if (i == OPEN_MAX)
err_quit("too many clients");
client[i].events = POLLRDNORM;//=> FD_SET(connfd, &allset)
if (i > maxi)
maxi = i; /* max index in client[] array */
if (--nready <= 0)
continue; /* no more readable descriptors */
}
for (i = 1; i <= maxi; i++) { /* check all clients for data */
if ( (sockfd = client[i].fd) < 0)
continue;
if (client[i].revents & (POLLRDNORM | POLLERR)) {// =>FD_ISSET(sockfd, &rset)
if ( (n = read(sockfd, buf, MAXLINE)) < 0) {
if (errno == ECONNRESET) {
/*4connection reset by client */
Close(sockfd);
client[i].fd = -1;
} else
err_sys("read error");
} else if (n == 0) {
/*4connection closed by client */
Close(sockfd);
client[i].fd = -1;
} else
Writen(sockfd, buf, n);
if (--nready <= 0)
break; /* no more readable descriptors */
}
}
}
}
/* end fig02 */