先讲了五种IO模型

阻塞式IO:  

    readcv系统调用->阻塞->等待系统调用返回

非阻塞式IO

    系统调用->无数据->不阻塞,直接返回->继续调用直到返回成功

这样很浪费CPU

IO复用模型

    调用select->阻塞->等某个数据准备好->再调用readcv系统调用

信号驱动IO模型

    调用Signal,等捕捉某个信号时,才调用recv系统调用

异步IO模型

发起IO,让内核完成读取,读取结束后告诉我们他自己帮我们读完啦,我们可以直接使用了。

比较:

    前四种都是同步IO, 只有异步IO是异步IO

   同步IO中,进程都要调用recv并阻塞,      请求进程会被阻塞直到完成

    异步中,不会阻塞进程,工作由内核完成。



接上一章节的程序

当时那个回射程序存在一个问题: 如果服务器执行关闭, 此时客户进程并不会立即退出,因为它被卡在用户输入那里等待输入,实际上应该马上结束才对。

于是我们引入了select。

select可以帮我们绑定一些描述符(把描述符放入某集合),  然后阻塞。

 当其中某些描述符有了 可读或可写或错误的状态 时,会立即返回。

该函数的参数为   select(要检查的描述符最大值, 读集合,写集合,错误集合, 超时时间)

返回的是已经就绪的描述符数量(即这次返回可能有好几个描述符都可以用了)


改进1:

 根据select原理,我们把stdin描述符,和sock套接字描述符 与select绑定,然后进入select等待。

这时候只要有终端输入,或者收到服务器信息,都可以从select处返回。

 返回后用FD_ISSET去判断是哪个描述符就绪了, 然后进行对应的操作,是Write,还是Puts

 这样子的话,当服务器关闭时,收到的FIN会让select返回,然后readline读到FIN,报错,程序退出。


存在的问题: 当我们连续输入了很多个发送字符串,并且最终按下EOF时, 终端输入就结束了, 收到EOF后,进程会进行报错

,于是结束进程 ,但是!这时候可能路上还有数据在发回来,即还有剩余的数据要接收,不可以马上关闭!要等接收完再关闭。


改进2

当输入EOF后,不执行报错并退出, 而是用shutdown函数去发送FIN,通知服务器我发完啦

然后关闭并清除stdin描述符的绑定。并且设置一个flag

当套接字接收到的数据返回错误时,看一下flag,即判断一下终端输入是否结束了

如果结束了,说明这是正常的程序关闭,于是执行关闭。

如果输入还未结束却接收错误,说明出错了。



#include "unp.h"
#include <time.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( ; ; ){
//当终端还未输入EOF时,设置等待fp描述符可读
if(stdineof == 0){
FD_SET(fileno(fp), &rset);
}
FD_SET(sockfd, &rset);
//取一个最大值+1,用于select的最大描述符检查
maxfdp1 = max(fileno(fp), sockfd) + 1;
//只检测可读, 可写和错误都置为NULL,超时时间也设为NULL
Select(maxfdp1, &rset, NULL, NULL, NULL);

//Select返回,说明有一个条件满足了,检查一下是套接字返回,还是输入返回
if (FD_ISSET(sockfd, &rset)){
if ( (n = Read(sockfd, buf, MAXLINE)) == 0){
if( stdineof == 1) //如果输入也结束了,则退出
return ;
else //输入还未结束,网络却接收出错,则报错
err_quit("str_cli: server terminated prematurely");
}
Writen(fileno(stdout), buf, n);
}

if (FD_ISSET(fileno(fp),&rset)){
if( (n = Read(fileno(fp), buf, MAXLINE))== 0){
stdineof = 1;
Shutdown(sockfd, SHUT_WR); //告诉服务器,我数据发完啦,但我还要接受!
FD_CLR(fileno(fp), &rset); //清除fp
continue; //即不要执行Writen了
}
Writen(sockfd, buf, n);
}

}
}

int main(int argc, char **argv){
int sockfd,i;
struct sockaddr_in servaddr;
if (argc != 2){
err_quit("usage: tcpli<IPaddress>");
}
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);
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd);
exit(0);
}




改进服务器程序, 用select来代替fork

这里的话,就是当监听进程监听到一个新连接,并从select返回时, 我们为该连接新建一个描述符,并绑定如select(这时候要修改一下描述符最大值)

该描述符通过一个数组去确定。

当select返回时,用for循环去判断是哪个描述符可读,并执行对应的读取操作,从缓冲区中获取信息。

select返回的是可就绪的描述符数量,通过这个数量去确定到底要处理几次。



pselect可以避免竞争,时间的精度也更加高,并且可以用指针去确定一个新参数

poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。