UNP卷一学习笔记:高级I/O函数
2016-03-27 19:49
232 查看
UNP卷一中所列的高级I/O函数有5组:
1.read & write
2.recvfrom & sendto
sockfd:套接字。
nbytes:读写字节数。
buff:读入或写出缓冲区。
flags:指明recvfrom或sendto的具体操作方式。
from:发送方源地址;to:接收方目的地址。
addrlen:from或to的地址长度
3.recv & send
注意:即使设置了MSG_WAITALL等待读入nbytes所有字节数,但是遇到捕获信号,连接中止,套接字错误,recv仍可能返回比所请求nbytes要少的字节数。
4.readv & writev
readv被称作分散读,因为输入数据被分散到多个应用缓冲区中,writev被称作集中写,因为来自多个应用缓冲区的输出数据被集中供给单个写操作。
5.recvmsg & sendmsg
[align=justify]注意:recvmsg使用msg->msg_flags,flags参数会被复制到msg_flags上,并由内核使用其值驱动接收处理过程;sendmsg忽略msg->msg_flags,而直接使用flags参数驱动发送处理过程。[/align]
5组I/O函数的比较:
a.recv&send和readv&writev是read&write的升级版,recv&send通过标志flags提供了更多的读写操作,readv&writev因为针对多个输入或输出缓冲区进行读写,因此效率更高。
b.一般来讲,读写字节流数据多采用read&write和recv&send,读写数据报数据多采用recvfrom&sendto,而readv&writev可以适用于字节流和数据报,recvmsg&sendmsg是最通用的一组I/O函数,换句话说,其他4组函数几乎都可以转换成recvmsg&sendmsg。
c.除了read&write和readv&writev适用于任何描述符外,其他3组函数仅适用于套接字描述符。
对于套接字而言,我们希望不要长期阻塞在某个调用上,因此需要对其设置超时控制,对于I/O函数有3种方法设置超时:
(1)调用alarm,它在指定超时期满产生SIGALRM信号。
return;
}
上述程序通过调用alarm(nsec)在设置SIGALRM发生在该语句执行后nsec秒,这样在recvfrom阻塞期间,若alarm时间到,内核就会发送SIGALRM信号给该进程,进程捕获到该信号后,会调用alarm_process函数处理信号,同时中断recvfrom的调用,从而将其解阻塞。alarm方法虽然简单,但是有一个缺陷,如果你不能合理使用,就无法达到超时的效果。
将上面的alarm_test1变为以下代码:
我们以nsec=1传入函数,这样alarm(1)就会等1s后,让内核产生SIGALRM信号给进程。现在的问题是:如果recvfrom没有阻塞,而是在数毫秒内返回一定字节大小的数据报,这样程序就会转到sleep(1),程序休眠一秒钟,这一秒钟时间内,内核产生了SIGALRM信号并被进程捕获,但是信号处理函数alarm_process仅仅返回void,什么都没做,处理完信号后的进程可能执行到了printf语句,之后又循环到recvfrom语句,这下好了,如果此时recvfrom阻塞,alarm(1)就没有任何超时作用了,因为已经超时过一次了,此时程序又无法返回到之前的alarm(1)语句重新设置超时时间。
总结起来就是:在使用alarm方法设置超时时,要确保在捕获SIGALRM信号的时候,进程阻塞在你所期望的I/O函数上。
有三种方法可以实现:
a.用pselect阻塞和解阻塞信号
b.使用sigsetjmp和siglongjmp
c.使用从信号处理函数到主控函数的IPC
(2)在select中设置超时
之前的笔记中提到过select的用法
方法简单,就是通过设置timeval来设置超时时间。当然,既然select可以,epoll系列函数肯定也行咯。
(3)为套接字设置SO_RCVTIMEO和SO_SNDTIMEO套接字选项
举例来说:
三种设置超时方法的比较:
a.select/epoll和SO_RCVTIMEO&SO_SNDTIMEO方法使用更为简单,alarm方法使用更加复杂,上面也说了如果不合理使用是达不到效果的。
b.alarm和select/epoll要求我们在对套接字的每个读写操作前做好相应的铺垫工作,而SO_X套接字选项只需一次性设置好读写超时。
c.alarm和select/epoll不仅适用5组高级I/O函数的超时设置,对于connect和accept之类的函数也同样适用;SO_X套接字选项仅仅适用于对套接字的读写操作设置超时,并不适用connect和accept之流。
1.read & write
#include<sys/socket.h> ssize_t read(int fd,void* buff,size_t nbytes); //成功返回从fd读取的字节数,失败返回-1 ssize_t write(int fd,const void* buff,size_t nbytes); //成功返回写入fd中的字节数,失败返回-1
2.recvfrom & sendto
#include<sys/socket.h> ssize_t recvfrom(int sockfd,void* buff,size_t nbytes,int flags,\ struct sockaddr* from,socklen_t* addrlen); //成功返回从sockfd读得的字节数,出错返回-1 ssize_t sendto(int sockfd,const void* buff,size_t nbytes,int flages,\ const struct sockaddr*to ,socklen_t addrlen); //成功返回写入sockfd的字节数,出错返回-1
sockfd:套接字。
nbytes:读写字节数。
buff:读入或写出缓冲区。
flags:指明recvfrom或sendto的具体操作方式。
from:发送方源地址;to:接收方目的地址。
addrlen:from或to的地址长度
3.recv & send
#include<sys/socket.h> ssize_t recv(int sockfd,void* buff,size_t nbytes,int flags); ssize_t send(int sockfd,const void* buff,size_t nbytes,int flags); //成功返回读入或写出的字节数,若出错则返回-1
flags | 说明 | recv | send |
MSG_DONTROUTE | 绕过路由表查询 | no | yes |
MSG_DONTWAIT | 临时将recv或send设置成非阻塞 | yes | yes |
MSG_OOB | 发送或接收带外数据 | yes | yes |
MSG_PEEK | 查看已可读取数据 | yes | no |
MSG_WAITALL | 保证读入请求数目为nbytes的字节数 | yes | no |
4.readv & writev
#include<sys/uio.h> ssize_t readv(int fd,const struct iovec* iov,int iovcnt); ssize_t writev(int fd,const struct iovec* iov,int iovcnt); //成功返回读入或写出的字节数,失败返回-1 struct iovec{ void *iov_base; //缓冲区起始地址 size_t iov_len; //缓冲区大小 }
readv被称作分散读,因为输入数据被分散到多个应用缓冲区中,writev被称作集中写,因为来自多个应用缓冲区的输出数据被集中供给单个写操作。
5.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); //返回:若成功则为读入或写出的字节数,若出错则为-1。 struct msghdr{ void *msg_name; //协议地址 socklen_t msg_namelen; //协议地址大小 struct iovec *msg_iov; //多个输入或输出缓冲区 int msg_iovlen; //msg_iov中缓冲区的数量 void *msg_control; //辅助数据(控制信息) socklen_t msg_controllen; //辅助数据长度 int msg_flags; //recvmsg返回的消息标志位 }
[align=justify]注意:recvmsg使用msg->msg_flags,flags参数会被复制到msg_flags上,并由内核使用其值驱动接收处理过程;sendmsg忽略msg->msg_flags,而直接使用flags参数驱动发送处理过程。[/align]
5组I/O函数的比较:
a.recv&send和readv&writev是read&write的升级版,recv&send通过标志flags提供了更多的读写操作,readv&writev因为针对多个输入或输出缓冲区进行读写,因此效率更高。
b.一般来讲,读写字节流数据多采用read&write和recv&send,读写数据报数据多采用recvfrom&sendto,而readv&writev可以适用于字节流和数据报,recvmsg&sendmsg是最通用的一组I/O函数,换句话说,其他4组函数几乎都可以转换成recvmsg&sendmsg。
c.除了read&write和readv&writev适用于任何描述符外,其他3组函数仅适用于套接字描述符。
对于套接字而言,我们希望不要长期阻塞在某个调用上,因此需要对其设置超时控制,对于I/O函数有3种方法设置超时:
(1)调用alarm,它在指定超时期满产生SIGALRM信号。
static void alarm_process(int); //信号处理函数<pre name="code" class="cpp">int alarm_test1(int nsec) { Sigfunc* sigfunc; int n; sigfunc=Signal(SIGALRM,alarm_process); if(alarm(nsec)!=0) { err_msg("alarm was already set"); } if((n=recvfrom(.....))<0) { ..... } alarm(0); //关闭alarm Signal(SIGALRM,sigfunc); return n; }static void alarm_process(int){
return;
}
上述程序通过调用alarm(nsec)在设置SIGALRM发生在该语句执行后nsec秒,这样在recvfrom阻塞期间,若alarm时间到,内核就会发送SIGALRM信号给该进程,进程捕获到该信号后,会调用alarm_process函数处理信号,同时中断recvfrom的调用,从而将其解阻塞。alarm方法虽然简单,但是有一个缺陷,如果你不能合理使用,就无法达到超时的效果。
将上面的alarm_test1变为以下代码:
int alarm_test2(int nsec) { Sigfunc* sigfunc; int n; sigfunc=Signal(SIGALRM,alarm_process); while(Fgets(....)!=NULL){ if(alarm(nsec)!=0) { err_msg("alarm was already set"); } for(;;){ if((n=recvfrom(.....))<0) { if(errno==EINTR) break; else err_sys("recvfrom error"); } else{ sleep(1); printf("may catch signal here\n"); } } } return n; }
我们以nsec=1传入函数,这样alarm(1)就会等1s后,让内核产生SIGALRM信号给进程。现在的问题是:如果recvfrom没有阻塞,而是在数毫秒内返回一定字节大小的数据报,这样程序就会转到sleep(1),程序休眠一秒钟,这一秒钟时间内,内核产生了SIGALRM信号并被进程捕获,但是信号处理函数alarm_process仅仅返回void,什么都没做,处理完信号后的进程可能执行到了printf语句,之后又循环到recvfrom语句,这下好了,如果此时recvfrom阻塞,alarm(1)就没有任何超时作用了,因为已经超时过一次了,此时程序又无法返回到之前的alarm(1)语句重新设置超时时间。
总结起来就是:在使用alarm方法设置超时时,要确保在捕获SIGALRM信号的时候,进程阻塞在你所期望的I/O函数上。
有三种方法可以实现:
a.用pselect阻塞和解阻塞信号
b.使用sigsetjmp和siglongjmp
c.使用从信号处理函数到主控函数的IPC
(2)在select中设置超时
之前的笔记中提到过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); //返回:若有就绪描述符则为其数目,若超时则为0,若出错则为-1 struct timeval{ long tv_sec; //秒 long tv_usec; //微妙 }
方法简单,就是通过设置timeval来设置超时时间。当然,既然select可以,epoll系列函数肯定也行咯。
(3)为套接字设置SO_RCVTIMEO和SO_SNDTIMEO套接字选项
举例来说:
int sockfd; struct timeval tv; setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizoef(tv)); //为sockfd上所有读操作设置超时时间tv setsockopt(sockfd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizoef(tv)); //为sockfd上所有写操作设置超时时间tv
三种设置超时方法的比较:
a.select/epoll和SO_RCVTIMEO&SO_SNDTIMEO方法使用更为简单,alarm方法使用更加复杂,上面也说了如果不合理使用是达不到效果的。
b.alarm和select/epoll要求我们在对套接字的每个读写操作前做好相应的铺垫工作,而SO_X套接字选项只需一次性设置好读写超时。
c.alarm和select/epoll不仅适用5组高级I/O函数的超时设置,对于connect和accept之类的函数也同样适用;SO_X套接字选项仅仅适用于对套接字的读写操作设置超时,并不适用connect和accept之流。
相关文章推荐
- 73. Set Matrix Zeroes
- Swift里自定义一个文字在左、图片在右的,标题按钮
- Leetcode 1. Two Sum(python)
- Problem C
- 一种编码而已
- CSS代码重构与优化之路
- CodeForces 653 A. Bear and Three Balls——(IndiaHacks 2016 - Online Edition (Div. 1 + Div. 2))
- uva-10487 - Closest Sums
- BZOJ3876支线剧情
- 第四节课作业
- mybatis第4天
- shell命令二
- hdu2689树状数组
- MySQL学习(二)
- Listener监听器
- 基于UDP/IP 协议的Socket程序
- ViewPagerIndicator
- 初学visual studio 2013 遇到的几个问题
- 我的Python学习之路之基本语法-函数
- ZMY_分页加载