UNIX网络编程------高级IO函数
一、概述
首先是在I/.O操作上设置超时,三种方法;read和write这两个函数的三个变体:recv和send运行通过第四个参数从进程到内核传递标志:readvhe writev允许指定往其中输入数据或从其中输出数据的缓冲区向量:recvmsg和sendmsg结合了其他I/O函数的所有特性,并具备接受和发送辅助数据的新能力。
二、套接字超时
1、 套接字的I/O操作上设置超时操作;
1) 使用信号处理函数alarm,不过这样会涉及到信号处理函数的问题,同时还有可能会引起程序中其他alarm函数的处理,
2) 使用select函数,在这个函数的最后一个参数中可以设置时间超时,这个函数在上一篇文章中已经讲解。
3) 使用比较新颖的超时套接字选项SO_RCVTIMEO和SO_SENDTIMEO,属于套接字选项中的内容。并非所有实现都支持这两个套接字选项。
以上这三个技术都适用于输入和输出操作(read、write及其注入recvfrom、sendto之类的变体)。但是TCP内置的connect函数超时默认为75s。select可用来在connect函数上设置超时的先决条件是相应的套接字处于非阻塞模式,而上述的两个套接字选项对connect并不适用,前两种技术适用于任何技术,第三个技术适用于套接字描述符。(通过例子细细揣摩其中的意思)。
2、 使用上述三种技术设置超时
1) 使用SIGALRM为connect设置超时
这段代码考虑到了如果在这个函数体内已经存在报警函数的问题,如果存在报警,alarm(sec)返回值为当前报警的剩余时间,否则alarm的返回值为0。同时代码还透漏了一个消息:signal函数的返回值为一个函数指针,就是信号处理函数的函数指针,记录这个函数,如果已经存在报警,那么在函数的最后还需要恢复原来的信号处理函数。
2) 使用selecth函数设置套接字
3) 使用套接字选项设置
三、recv和send函数
#include <sys/socket.h>
Ssize_t recv(int sockfd,void* buff,size_t nbytes,intflags);
Ssize_t send(int sockfd,void *buff.size_t nbytes,iintflags);
这两个函数的前三个参数和read和write的三个参数一样,(都是套接字、缓冲区、缓冲区大小,最后一个参数有一定的讲究,可以简单的设置为0,也可以参考一定的数值设置,其实这个参数就按照默认的0即可,因为其他几种设置没有实际的作用)
Flags参数在设计上存在一个基本问题,它是按值传递的,而不是一个值-结果参数。因此它只能用于从进程向内核传递标志,内核无法向进程传回标志,对于TCP这一点不成问题,以为TCP不需要从内核向进程传递标志。然而随着OSI协议被加到4.3BSD中,却提出了随输入操作向进程返回 MSG_EOR标志的需求。这样最后的决定为保持常用输入函数(recv和recvfrom)的参数不变,而改变recvmsg和sendmsg所用的msghdr结构。这个决定同时意味着如果一个进程需要由内核更新标志,它就必须调用recvmsg,而不是recv或者recvfrom。
四、readv和writev函数
着两个函数允许但是系统调用腐乳到或写出自一个或多个缓冲区。这些操作分别称为分散读和几种写,因为来自读操作的数据被分散到多个应用缓冲区中,而来自多个应用缓冲区的输出数据则被集中提供给单个写操作。
#include <sys/uio.h>
Ssize_t readv(int filedes,const struct iovec *iov,intiovcnt);
Ssize_t writev(int filedes,const struct iovec *iov,intiovcnt);
Readv和writev这两个函数可用于任何描述符,而不仅限于套接字。另外writev是一个原子操作,以为这对于一个基于记录的协议而言,一次writev调用只产生单个UDP数据报。
五、recvmsg和sendmsg函数
这两个函数为最通用的I/O函数。可以使用read、readv、recv和recvfrom调用替换为recvmsg调用,各种输出函数调用可以替换成sendmsg调用。
#include <sys/socket.h>
Ssize_t recvmsg(int sockfd,struct msghdr *msg,int flags);
Ssize_t sendmsg(int sockfd,struct msghdr *msg,int flags);
当然,使用这两个函数的时候需要注意第二个参数,第二个参数相当于原来的字符串缓冲区,这里只是替换为struct msghdr的结构体。
其中结构体中的msg_name用于套接字未链接的场合(比如UDP套接字),他们类似recvfrom和sendto的第五和第六个参数,msg_name指向一个套接字地址结构,调用者在其中存放接受者(对于sendmsg调用)或者发送者(对于recvmsg调用)的协议地址,如果无需指明地址(比如TCP套接字或已链接的UDP套接字),msg_name应为空指针。Msg_iov和msg_iovlen这两个成员指定输入或输出缓冲区数组,类似readv或writev的第二个和第三个参数。
几个常见的I/O函数:
函数 | 任何描述符 | 仅套接字描述符 | 单个读/写缓冲区 | 分散/集中读/写 | 可选标志 | 可选对端地址 | 可选控制信息 |
Read,write | 是 |
| 是 |
|
|
|
|
Readv,writev | 是 |
|
| 是 |
|
|
|
Recv,send |
| 是 | 是 |
| 是 |
|
|
Recvfrom,sendto |
| 是 | 是 |
| 是 | 是 |
|
Recvmsg,sendmsg |
| 是 |
| 是 | 是 | 是 | 是 |
六、排队的数据量
如果我们想要在不真正读取数据的前提下知道一个套接字上已用多少数据排队等着读取。可用三个技术实现
1) 可以使用非阻塞I/O,
2) 如果既想查看数据,又想数据仍然保留在接受队列中以供本进程其他部分稍后读取,那么可以使用MSG_PEEK标志。(需要注意的是:如果使用这个标志来读取套接字上可读数据的大小,在两次调用之间缓冲区可能会增加数据,如果第一次指定使用MSG_PEEK标志,而第二次调用没有指定使用MSG_PEEK标志,那么这两次调用的返回值是一样的,及时在这两次调用之间缓冲区已经增加了数据。)
3) 一些实现支持ioctl的FIONREAD命令。该命令的第三个ioctl参数是指向某个整数的一个指针,内核通过该整数返回的值就是套接字接受队列的当前字节数。
七、套接字和标准I/O
标准I/O函数库可用于套接字,但是必须考虑以下几点:
1) 通过调用fdopen,可以从任何一个描述符创建出一个标准I/O流。类似地,通过调用fileno,可以获取一个给定标准I/O流对应的描述符。
2) 在全双工的I/O上,我们必须在调用一个输出函数之后插入一个fflush、fseek、fsetpos或rewind调用才能接着调用一个输入函数。类似地,调用一个输入函数后必须插入一个fseek、fsetpos或rewind调用才能调用一个输出函数,除非输入函数遇到一个EOF。Fseek、fsetpos和rewind这三个函数的问题是它们都调用lseek,但是lseek用在套接字上会失败。
3) 使用标准I/O的一个简单函数
Void str_echo(int sockfd)
{
Char line[MAXLINE];
FILE*fpin,*fpout;
Fpin=fdopen(sockfd,”r”);
Fpout=fdopen(sockefd,”w”);
While(fgets(line,MAXLINE,fpin)!=EOF)
Fputs(line,fpout);
}
这段程序在运行的时候,只有在所有的输入行结束以后,使用键盘键入ROF结束符后才会有刚才所有输入的字符串。发生这种事情的原因:
我们键入第一行输入文本,它被发送到服务器。
服务器用fgets读入文本,再用fputs回射本行。
服务器的标准I/O流被标准I/O函数库完全缓冲。这意味着该函数库把回射行复制到输出流的标准I/O缓冲区。但是不把该缓冲区中的内容写到描述符,因为该缓冲区未满。
同样的事情发生在第二行第三行输入文本上。
我们键入EOF字符,致使我们的函数调用shutdown,从而发送一个FIN到服务器。
服务器TCP收到这个FIN,它被fgets读入,致使fgets返回一个空指针。
上述函数返回到服务器的main函数,子进程通过调用exit终止。
C库函数exit调用标准I/O清理函数。之前由我们的fputs调用填入输出缓冲区中的未满内容现在被输入。
服务器子进程终止,致使它的已连接套接字被关闭,从而发送一个FIN到客户,完成TCP的四组终止序列。
上述函数收取病输出由服务器回射的三行文本。
上述函数接着在其套接字上收到一个EOF,客户于是终止。
4) 上述现象的原因是由表中I/O函数库自动执行的缓冲之上。标准I/O函数库执行以下三类缓冲。
1) 完全缓冲,缓冲区满,进程显示调用fflush,或者进程调用exit终止自身。标准I/O缓冲区的通常大小为8192字节。
2) 行穿冲,碰到一个换行符,进程调用fflush,或进程调用exit终止自身。
3) 不缓冲,每次调用标准I/O输出函数都发生I/O
其中还有默认的规则
标准错误输出总是不缓冲。
标准输入和标准输出完全缓冲,除非他们指代终端设备。
所有其他I/O流都是完全缓冲,除非他们