本文记录我对read(),write()函数这两个常见调用的再次学习。参考了如下链接:
[socket:阻塞与非阻塞模式下,read和write的返回值 ]
read
阻塞调用:
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#define LEN 128
void set_fl( int fd, int flag ){
int ret;
if( -1 == (ret = fcntl(fd, F_GETFL, 0)) ){
perror( "fcntl()" );
exit( EXIT_FAILURE );
}
ret |= flag;
if( -1 == ( ret = fcntl(fd, F_SETFL, flag) ) ){ // 直接设置会关闭以前的标志位,所以需要先获取当前的标志为,然后修改,再置位
perror( "fcntl()" );
exit(EXIT_FAILURE);
}
}
void set_nonblocking( int fd ) {
set_fl( fd, O_NONBLOCK );
}
int main( void ){
int ret;
char buf[LEN];
int fd = STDIN_FILENO;
int nread = 0;
while(1){
nread = read( fd, buf, LEN-1 );
if( nread > 0 ){ // 读取正常
buf[nread-1] = '\0';
printf( "Input from keyboard: %s\n", buf );
}
else if( 0 == nread ){ // fd关闭,读到FIN。或者EOF
printf( "No more data!\n" );
break;
}
else{
// 读取异常
if( EINTR == errno)//操作被中断,fd正常,可以继续读取
continue;
else{
perror("read()");
exit(EXIT_FAILURE);
}
}
}
exit(EXIT_SUCCESS);
}
非阻塞调用:
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#define LEN 128
void set_fl( int fd, int flag ){
int ret;
if( -1 == (ret = fcntl(fd, F_GETFL, 0)) ){
perror( "fcntl()" );
exit( EXIT_FAILURE );
}
ret |= flag;
if( -1 == ( ret = fcntl(fd, F_SETFL, flag) ) ){ // 直接设置会关闭以前的标志位,所以需要先获取当前的标志为,然后修改,再置位
perror( "fcntl()" );
exit(EXIT_FAILURE);
}
}
void set_nonblocking( int fd ) {
set_fl( fd, O_NONBLOCK );
}
int main( void ){
int ret;
char buf[LEN];
int fd = STDIN_FILENO;
int nread = 0;
set_nonblocking(fd);
while(1){
nread = read( fd, buf, LEN-1 );
if( nread > 0 ){ // 读取正常
buf[nread-1] = '\0';
printf( "Input from keyboard: %s\n", buf );
}
else if( 0 == nread ){ // fd关闭,读到FIN。或者EOF
printf( "No more data!\n" );
break;
}
else{
// 读取异常
if( EINTR == errno)//操作被中断,fd正常,可以继续读取
continue;
else if( EAGAIN == errno || EWOULDBLOCK == errno ){
printf( "No data! sleep for a while.\n" );
sleep(1);
}
else{
perror("read()");
exit(EXIT_FAILURE);
}
}
}
exit(EXIT_SUCCESS);
}
阻塞与非阻塞的区别,看起来是缓冲区为空是否返回。本质我们知道,I/O的第一个阶段是否阻塞。非阻塞模式,内核没有准备好数据就返回了。所以,需要判断,是不是数据没有准备好返回的。
write
select和non-blocking IO联合使用
Under Linux, select() may report a socket file descriptor as “ready for reading”, while nevertheless a subsequent read blocks.
This could for example happen when data has arrived but upon examination has wrong checksum and is discarded. There may be other
circumstances in which a file descriptor is spuriously reported as ready. Thus it may be safer to use O_NONBLOCK on sockets that
should not block.
上面这一段是man手册当中给出的内容,也就是说select通常要和non blocking IO一起使用。原因也很明显,我们之前总结过,多路复用是费阻塞io把轮询的过程帅锅给内核,那么当select,epoll这类调用返回的时候,一定是有socket就绪的。此时,在进行read是没有问题的。但是,man 手册举了个例子是,如果已经有数据就绪,然后select返回。但是,这个数据在传输过程中出了错误,进行丢弃。那么,在之后的读取时便会阻塞。因为,通常的情形,select报告事件发生后,进行读取是一定不会阻塞的。所以,不用将socket设为阻塞状态。但是,存在上面说的特殊的情形。数据就绪,但是有错误,进行丢弃。再读取时会阻塞。那么,此时进程被阻塞之后,无法执行select过程,也就是说,哪怕有别的socket就绪,也无法读取了。因为,不只是监听你一个socket。所以,多路复用的时候一定要小心。一定要和non blocking IO联合使用。