
//------------------------------1.Unix下可用的5种I/O模型的基本差别----------------------------------
POSIX有定义,同步IO操作(synchronous I/O Operation)导致请求进程阻塞unp和apue阅读顺序,直到I/O操作完成。
异步I/O操作(asynchrounous I/O operation)不引起请求进程阻塞。
根据上述定义,前4种模型都是同步I/O模型,因为其中真正的I/O操作(recvfrom)都将阻塞进程,只有异步模型与POSIX定义的异步I/O相匹配。
//-----------------------------------2.I/O复用使用的场合------------------------------------
1.当客户处理多个叙述符(通常是交互式输入和网络套接字时)必须使用I/O复用。
2.一个客户同时处理多个套接字也是也许的,但非常少见。
3.如果一个TCP服务器既要处理套接字,又要处理已连接套接字,一般就要使用I/O复用。
4.如果一个服务器既要处理TCP又要处理UDP,一般还要使用I/O复用。
5.如果一个服务器要处理多个服务或者多个协议,一般还要使用I/O复用。
//------------------------------------3.select函数----------------------------------------
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);
1.timeval结构
struct timeval {
long tv_sec; // seconds
long tv_usec; // microseconds
};
该参数有三种可能,
1个可能是修改为空指针unp和apue阅读顺序,即仅在有一个描述符准备好I/O时才返回,不然依然期待下去。
2个可能是期待一段固定时间,在有一个描述符准备好I/O时返回,但是不少于由该参数所指向的timeval 结构中指定的秒数和微秒数。
3.不期待,检查描述符后立刻返回,这称为轮询(polling),为此,该参数需要指向一个timeval结构,而且其中的字段都需要是0
前2种的常常有也许进程等待之后被被捕获的信号中断,并从信号处理变量返回。不同系统对被中止的select的能否自动重启不同,因此为了可移植性考虑,如果我们在捕获信号,那么应该做好select返回EINTR错误的准备。
尽管timeval结构允许我们指定一个微妙级的分辨率,但是内核支持的真实分辨率通常粗糙的多,例如许多unix把超时值向上舍入成10ms的倍数,另外还涉及调度的延后,也就是说定时器时间到后,内核还必须花一点时间调度相应进程运行。timeval结构才能表达select不支持的值,比如tv_sec为1亿秒,但是select函数会以EINVAL错误返回。
有些linux版本会设置这个timeval结构,因此从可移植性考虑,应该假定该timeval结构在select返回时未被定义,因而经常调用select之前都得对它进行再次初始化,不过还好,POSIX规定对该结构使用const限定词。
再来看看后面的3个参数readset,writeset,和exceptcet指定我们要让内核测试读,写和异常条件的表述符。目前支持的异常条件有2种:1是某个套接字的带外数据的前往,2是某个已置为分组方式的伪终端存在课从主端读取的控制状况信息。

描述符集的初始化非常重要,因为成为自动变量分配的一个描述符集若没初始化,那么也许出现不可预期的后果。
select函数的后面三个参数readset,writeset,exceptset中,如果我们对某个条件不感兴趣,就可以把它设计为空指针。事实上,如果三个指针都为空,我们就有了一个比unix的sleep函数更为精确的定时器(sleep以秒为最小单位),poll函数也可以类似的功能。
maxfdp1参数指定待检测的表述符的个数,它的值是待检测的表述符+1,描述符0,1,2,...maxfdp1-1均会被测试。
头文件<sys/select.h>中定义的FD_SETSIZE常值是数据类别fd_set中的表述符数目,其值一般是1024,不过极少有程序用到那么多的表述符。maxfdp1参数使得我们计算出所关心的最大描述符,让它+1,并告知内核该值。之所以要有这个参数,纯粹是为了精度的缘故,每个fd_set都有表示长期描述符(典型数量为1024),然而一个普通进程所用数量却少的多,内核正是通过在进程与内核之间不复制描述符集中不必要的部份,从而不测试总为0的这些位来提升效率的。
select函数修改由指针readset,writeset和exceptcet所指向的描述符集,因而这三个参数都是值-结果参数。该变量返回后,我们使用FD_ISSET宏来检测fd_set数据类别中的表述符,描述符集内任何与未就绪描述符对应的位均清成0,为此,每次重心调用select函数时,我们都得重新把所有描述符集内所关心的位均置为1.
该函数的返回值表示跨所有描述符集的已就绪的总位数,如果在任何表述符就绪之前定时器到时,那么返回0.返回-1表示错误(比如被一个所捕获的信号中断)。
//---------------------------------------4.描述符就绪条件------------------------------------
尽管可读性与课写性针对普通文件那样的表述符显而易见,然而针对导致select返回套接字“就绪”的条件我们需要探讨的更明确些。
1.满足以下四个条件的任何一个时,一个套接字准备好读:
a).该套接字接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水位标记的当前大小。对这种的套接字执行读操作不会阻塞并将返回一个大于0的值(也就是返回打算好读入的数据)我们可以使用SO_RCVLOWAT套接字选项设定该套接字的低水位标记。对于TCP和UDP套接字而言,其值默认是1.
b).该连接的读半部关闭(也就是接收了FIN的TCP连接),对这种的套接字的读操作将不阻塞并返回0(也就是返回EOF)
c).该套接字是一个套接字且已完成的连接数不为0.
d).其上有一个套接字错误待处理,对这种的套接字的读操作将不阻塞并返回-1(也就是返回一个错误),同时把errno设置成确却的错误条件。这些待处理的出错(pengding error)也可以通过指定SO_ERROR套接字选项读入getsockopt获取并清除。
2.下列四个条件中的任何一个时,一个套接字准备好写:
a).该套接字发送缓冲区中的可用空间字节数大于等于套接字发送缓冲区低水位标记的当前大小。我们可以使用SO_SNDLOWAT套接字选项来修改该套接字的低水位标记,对于TCP和UDP而言,其默认值是2048.
b).该连接的写半部关闭,对这种的套接字的写操作将形成SIGPIPE信号。
c).使用非阻塞式connect的套接字已创建连接,或者connect已经以成功告终。
d).其上有一个套接字错误待处理,对这种的套接字的写操作将不阻塞并返回-1(也就是返回一个错误),同时把errno设置成确却的错误条件,这些待处理的错误也可以借助指定SO_ERROR选项调用getsockopt获取并消除。
3.如果一个套接字存在带外数据以及仍处于带外标记,那么它有异常条件待处理。
注意,当某个套接字上出现错误时,它将由select标记既可读又可写。
设计出接收低水位标记和发送低水位标记的目的在于:允许应用进程控制在select返回可读或可写条件之前有多少数据可读或有很大空间可用于写。举例来说,如果我们明白即使至少存在64个字节的数据,否则我们的应用进程没有任何有效工作可言,那么可以把接收地水位标记设置为64,以避免超过64个字节准备好读时select唤醒我们。
任何UDP套接字只要发送缓冲区大小小于等于其发送低水位标记就总是可写的,这是因为UDP套接字不需要连接。
通过一个表总结下:
//-----------------------------------5.客户的str_cli函数改进版------------------------------------
早先的哪个版本问题在于:当套接字上出现这种事件时,客户也许阻塞于fgets调用。
新版本改为在数据准本好前阻塞于select调用,而不是具体的套接字I/O函数或者标准I/O函数

#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;
Select(maxfdp1, &rset, NULL, NULL, NULL);
if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
if (Readline(sockfd, recvline, MAXLINE) == 0)
err_quit("str_cli: server terminated prematurely");
Fputs(recvline, stdout);
}
if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */
if (Fgets(sendline, MAXLINE, fp) == NULL)
return; /* all done */
Writen(sockfd, sendline, strlen(sendline));
}
}
}//-----------------------------------6.客户的str_cli函数再次改进版--------------------------------
为什么需要改进呢,问题的原因在于我们对标准输入中的EOF的处理,str_cli函数就此返回到main函数,而main函数随后终止,然而在批量模式下,标准输入中的EOF并不意味着我们同时也完成了从套接字的调用,可能仍有请求在去往服务器的路上,或者仍有应答在返回客户的路上。我们必须的是一种关闭TCP连接中其中一半的方式。也就是说,我们想给服务器发送一个FIN,告诉它我们终于完成了数据发送,但是依然维持套接字描述符打开从而读取。这就得让shutdown函数上场提供它的一部分功能了。
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
ret:若顺利则为0,失败错误则为-1
终止网络联结的一般原则是调用close函数,不过close却有2个限制,都可以用shutdown来防止。
1.close把描述符减1,仅在该计数变为0时才关掉套接字,使用shutdown可以不管引用计数就促使TCP的正常连接终止系列。
2.close终止读和写2个方向的数据传送,就会造成后面讲到的弊端。既然TCP是全双工的,有时候我们应该告知对端我们终于完成了数据发送,即时对端仍有数据要发送给我们。
本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-124075-1.html
内抓汉奸
速率普遍低于15节
雷达探测精度和灵敏度