本章大意
讲解了IO复用的相关知识。所谓IO复用,是指内核一旦发现某个或多个IO条件就绪,就通知相应进程,以便作出及时处理。
细节摘录
1. 五种IO模型:阻塞式IO,非阻塞式IO,IO复用,信号驱动IO,异步IO。
2. select的优势其实在于我们可以等待多个描述符的就绪。
3. 异步IO模型没有阻塞,其他类型或多或少都有阻塞阶段。
4. 描述符集的初始化很重要,别漏。
5. 增大描述符集的唯一方法是先改变FD_SETSIZE的值,然后重新编译内核。
6. 初步的IO复用代码:
8. str_cli函数的再修订版:
11. pselect可以用来防止一种信号永远丢失的情况,参见P143.
吃饭 下午继续
讲解了IO复用的相关知识。所谓IO复用,是指内核一旦发现某个或多个IO条件就绪,就通知相应进程,以便作出及时处理。
细节摘录
1. 五种IO模型:阻塞式IO,非阻塞式IO,IO复用,信号驱动IO,异步IO。
2. select的优势其实在于我们可以等待多个描述符的就绪。
3. 异步IO模型没有阻塞,其他类型或多或少都有阻塞阶段。
4. 描述符集的初始化很重要,别漏。
5. 增大描述符集的唯一方法是先改变FD_SETSIZE的值,然后重新编译内核。
6. 初步的IO复用代码:
#include "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
int maxfdp1; //存放描述符的最大值
fd_set rset; // 描述符集
char sendline[MAXLINE], recvline[MAXLINE]; // 字符缓冲区
FD_ZERO(&rset); // 描述符清零
for ( ; ; ) {
/*
* 打开两个标记位
*/
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
/*
* 获得最大标记位
*/
maxfdp1 = max(fileno(fp), sockfd) + 1;
/*
* 执行IO复用函数
*/
Select(maxfdp1, &rset, NULL, NULL, NULL);
/*
* 如果套接字IO可用
*/
if (FD_ISSET(sockfd, &rset)) {
if (Readline(sockfd, recvline, MAXLINE) == 0)
err_quit("str_cli: server terminated prematurely");
Fputs(recvline, stdout);
}
/*
* 如果IO可用
*/
if (FD_ISSET(fileno(fp), &rset)) {
if (Fgets(sendline, MAXLINE, fp) == NULL)
return;
Writen(sockfd, sendline, strlen(sendline));
}
}
}
7. TCPIP是全双工的,管道,缓冲区容量有限制,这就导致如果上述代码过早地结束,一部分还在线路或者缓冲区上的数据将会被丢失。
8. str_cli函数的再修订版:
/*
* shutdown函数解决了批量处理的问题
* 同时针对缓冲区进行处理避免了一些问题的发生(P136)。
*/
#include "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
int maxfdp1, stdineof;
fd_set rset;
char buf[MAXLINE];
int n;
stdineof = 0;
FD_ZERO(&rset);
for ( ; ; ) {
/* 只有标志为0,我们才去测试标准输入的可读性 */
if (stdineof == 0)
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) // 如果标准输入已经结束后收到终止
return; /* normal termination */
else
err_quit("str_cli: server terminated prematurely");
}
Write(fileno(stdout), buf, n);
}
if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */
if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {
stdineof = 1; // 标记,表示标准输入已经结束了。
Shutdown(sockfd, SHUT_WR); /* send FIN */
FD_CLR(fileno(fp), &rset);
continue;
}
Writen(sockfd, buf, n);
}
}
}
9. str_cli再次修订(使用IO复用替代fork):
/* include fig01 */
#include "unp.h"
int
main(int argc, char **argv)
{
/*
* 前期工作一样,都是SBL。
*/
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE];
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);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
maxfd = listenfd; /* 初始化唯一的监听描述符 */
maxi = -1;
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1; /* 初始化已连接套接字记录数组 */
FD_ZERO(&allset); // 初始化描述符集。
FD_SET(listenfd, &allset);
/* end fig01 */
/* include fig02 */
for ( ; ; ) {
/*
* 启用select监听信号发生
*/
rset = allset; /* structure assignment */
nready = Select(maxfd+1, &rset, NULL, NULL, NULL);
if (FD_ISSET(listenfd, &rset)) { /* 如果监听套接字可读 */
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
#ifdef NOTDEF
printf("new client: %s, port %d\n",
Inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),
ntohs(cliaddr.sin_port));
#endif
for (i = 0; i < FD_SETSIZE; i++)
if (client[i] < 0) {
client[i] = connfd; /* 用记录数组中第一个未被用到的位置记录已连接描述符 */
break;
}
if (i == FD_SETSIZE)
err_quit("too many clients"); /* 太多的描述符 */
FD_SET(connfd, &allset); /* 将连接描述符添加进描述符集 */
if (connfd > maxfd)
maxfd = connfd; /* for select */
if (i > maxi)
maxi = i; /* max index in client[] array */
if (--nready <= 0)
continue; /* no more readable descriptors */
}
for (i = 0; i <= maxi; i++) { /* 接收所有客户的数据 */
if ( (sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset)) {
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
/*4connection closed by client */
Close(sockfd);
/*
* 对记录数组和描述符集作出处理
*/
FD_CLR(sockfd, &allset);
client[i] = -1;
} else
Writen(sockfd, buf, n);
if (--nready <= 0)
break; /* no more readable descriptors */
}
}
}
}
/* end fig02 */
10. 注意上述代码可能会阻塞于某个客户,因此我们需要作出相应的处理。解决思路有:非阻塞IO,多线程,设置超时函数等等。
11. pselect可以用来防止一种信号永远丢失的情况,参见P143.
吃饭 下午继续