原文出处:http://blog.chinaunix.net/space.php?uid=20558494&do=blog&id=2803003
read函数是Linux下不带缓存的文件I/O操作函数之一,所谓的不带缓存是指一个函数只调用系统中的一个函数。另外还有open、write、lseek、close,它们虽然不是ANSI C的组成部分,但是POSIX的组成部分。

在对read的使用过程中,发现对其返回值的处理比较重要,这里做一下总结。

read函数原型:
ssize_t read(int fd,void *buf,size_t count)

函数返回值分为下面几种情况:
1、如果读取成功,则返回实际读到的字节数。这里又有两种情况:一是如果在读完count要求字节之前已经到达文件的末尾,那么实际返回的字节数将 小于count值,但是仍然大于0;二是在读完count要求字节之前,仍然没有到达文件的末尾,这是实际返回的字节数等于要求的count值。
2、如果读取时已经到达文件的末尾,则返回0。
3、如果出错,则返回-1。
这样也就是说分为>0 <0 =0三种情况进行讨论。在有的时候,<0 =0可以合为一种情况进行处理。这要根据程序的需要进行处理。
实例分析:

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MAXSIZE 35
int main(void)
{
int i,j,fd,size,len;
char *buf="Hello!I`m writing to this file!";
char buf_r[MAXSIZE];
len=strlen(buf);
//open
if((fd=open("/tmp/hello.c",O_CREAT | O_TRUNC | O_RDWR,))<) {
perror("open:");
exit();
}
else
printf("Open file:hello.c %d\n",fd);
//write
if((size=write(fd,buf,len))<){
perror("write:");
exit();
}
else
printf("Write:%s\n\n\n",buf);
//test-read
printf("Now test starts...\n\n");
for(i=;i<;i++){
lseek(fd,,SEEK_SET);
for(j=;j<MAXSIZE;j++)
buf_r[j]=;
if((size=read(fd,buf_r,MAXSIZE-i))<){
perror("read:");
exit();
}
else {
buf_r[MAXSIZE-i]='\0';
printf("string-len=%d,count=%d,size=%d\n",len,MAXSIZE-i,size);
printf("read from file:%s \n",buf_r);
}
}
printf("\n\nNow test stops...\n");
//close
if(close(fd)<){
perror("close:");
exit();
}
else
printf("close hello.c\n");
exit();
}

-------------------------------

结果如下:

-------------------------------

[armlinux@lqm test-read]$ ./write
Open file:hello.c 3
Write:Hello!I`m writing to this file!

Now test starts...

string-len=31,count=35,size=31
read from file:Hello!I`m writing to this file!
string-len=31,count=34,size=31
read from file:Hello!I`m writing to this file!
string-len=31,count=33,size=31
read from file:Hello!I`m writing to this file!
string-len=31,count=32,size=31
read from file:Hello!I`m writing to this file!
string-len=31,count=31,size=31
read from file:Hello!I`m writing to this file!
string-len=31,count=30,size=30
read from file:Hello!I`m writing to this file
string-len=31,count=29,size=29
read from file:Hello!I`m writing to this fil
string-len=31,count=28,size=28
read from file:Hello!I`m writing to this fi
string-len=31,count=27,size=27
read from file:Hello!I`m writing to this f
string-len=31,count=26,size=26
read from file:Hello!I`m writing to this 
string-len=31,count=25,size=25
read from file:Hello!I`m writing to this
string-len=31,count=24,size=24
read from file:Hello!I`m writing to thi
string-len=31,count=23,size=23
read from file:Hello!I`m writing to th
string-len=31,count=22,size=22
read from file:Hello!I`m writing to t
string-len=31,count=21,size=21
read from file:Hello!I`m writing to 
string-len=31,count=20,size=20
read from file:Hello!I`m writing to
string-len=31,count=19,size=19
read from file:Hello!I`m writing t
string-len=31,count=18,size=18
read from file:Hello!I`m writing 
string-len=31,count=17,size=17
read from file:Hello!I`m writing
string-len=31,count=16,size=16
read from file:Hello!I`m writin

Now test stops...
close hello.c

-------------------------------

现象:

测试部分中,string-len是测试文件内容的长度,count是要求读取的字节数,size是实际读取的字节数。可以观察出,开始 count>string-len,所以虽然读取成功,但是返回的实际字节数要小于要求的字节数。从count=string-len之后,实际返 回的字节数等于要求的字节数。

问题分析:

1、每次执行read函数之前,保证指定好起始位置,并且对buf初始化。

如果将

lseek(fd,0,SEEK_SET);
                for(j=0;j<MAXSIZE;j++)
                        buf_r[j]=0;

移到for循环外,那么只能保证i=0时完成了上述工作。这是已经读到文件的末尾,所以后面的size应该全部为零。因为没有对buf_r初始化,所以读取内容没有变化。具体结果如下:

-------------------------------

[armlinux@lqm test-read]$ ./write
Open file:hello.c 3
Write:Hello!I`m writing to this file!

Now test starts...

string-len=31,count=35,size=31
read from file:Hello!I`m writing to this file!
string-len=31,count=34,size=0
read from file:Hello!I`m writing to this file!
string-len=31,count=33,size=0
read from file:Hello!I`m writing to this file!
...

...
Now test stops...
close hello.c

-------------------------------

2、对于一个数组,总是要自动分配一个\0作为结束,所以实际有效的buf长度就成为buf_r-1了。在本例中,倘若把MAXSIZE设为31,即等于string-len的长度。那么当你读取31个字符时,\0就没有了地方。如果把buf_r[MAXSIZE-i]='\0';去掉,那么在显示后面就会出现乱码现象。

-------------------------------

[armlinux@lqm test-read]$ ./write
Open file:hello.c 3
Write:Hello!I`m writing to this file!

Now test starts...

string-len=31,count=31,size=31
read from file:Hello!I`m writing to this file?B
B

參考:http://blog.sina.com.cn/s/blog_5328beed0100zbob.html

http://blog.csdn.net/hjhcs121/article/details/7460738

linux网络编程 readn,writen函数:

/* include readn */
#include "unp.h" ssize_t /* Read "n" bytes from a descriptor. */
readn(int fd, void *vptr, size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr; ptr = vptr;
nleft = n;
while (nleft > ) {
if ( (nread = read(fd, ptr, nleft)) < ) {
if (errno == EINTR)
nread = ; /* and call read() again */
else
return(-);
} else if (nread == )
break; /* EOF */ nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}
/* end readn */ ssize_t
Readn(int fd, void *ptr, size_t nbytes)
{
ssize_t n; if ( (n = readn(fd, ptr, nbytes)) < )
err_sys("readn error");
return(n);
}

writen:

/* include writen */
#include "unp.h" ssize_t /* Write "n" bytes to a descriptor. */
writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr; ptr = vptr;
nleft = n;
while (nleft > ) {
if ( (nwritten = write(fd, ptr, nleft)) <= ) {
if (nwritten < && errno == EINTR)
nwritten = ; /* and call write() again */
else
return(-); /* error */
} nleft -= nwritten;
ptr += nwritten;
}
return(n);
}
/* end writen */ void
Writen(int fd, void *ptr, size_t nbytes)
{
if (writen(fd, ptr, nbytes) != nbytes)
err_sys("writen error");
}

readline函数,从一个描述字读文本行,一次读一个字节(test/readline1.c]

/* include readline */
#include "unp.h" /* PAINFULLY SLOW VERSION -- example only */
ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr; ptr = vptr;
for (n = ; n < maxlen; n++) {
again:
if ( (rc = read(fd, &c, 1)) == ) {
*ptr++ = c;
if (c == '\n')
break; /* newline is stored, like fgets() */
} else if (rc == ) {
*ptr = ;
return(n - ); /* EOF, n - 1 bytes were read */
} else {
if (errno == EINTR)
goto again;
return(-); /* error, errno set by read() */
}
} *ptr = ; /* null terminate like fgets() */
return(n);
}  
/* end readline */ ssize_t
Readline(int fd, void *ptr, size_t maxlen)
{
ssize_t n; if ( (n = readline(fd, ptr, maxlen)) < )
err_sys("readline error");
return(n);
}

当面临从套接口读入文本行的需求时,改用标准库IO函数库(stdio)相当诱人。不过却会引发许多问题。究其原因在于stdio缓冲区的状态是不可见的。

基于文本行的网络协议非常之多,譬如http,ftp等。因此正对文本行操作的这一需求一再被提出。不过我们的建议是依照缓冲区而不是文本行的要求来考虑编程,编写从缓冲区中读取数据的代码,当期待一个文本行时,查看缓冲区中是否含有那一行。

/* include readline */
#include "unp.h" static int read_cnt;
static char *read_ptr;
static char read_buf[MAXLINE]; static ssize_t
my_read(int fd, char *ptr)
{ if (read_cnt <= ) {
again:
if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < ) {
if (errno == EINTR)
goto again;
return(-);
} else if (read_cnt == )
return();
read_ptr = read_buf;
} read_cnt--;
*ptr = *read_ptr++;
return();
} ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr; ptr = vptr;
for (n = ; n < maxlen; n++) {
if ( (rc = my_read(fd, &c)) == ) {
*ptr++ = c;
if (c == '\n')
break; /* newline is stored, like fgets() */
} else if (rc == ) {
*ptr = ;
return(n - ); /* EOF, n - 1 bytes were read */
} else
return(-); /* error, errno set by read() */
} *ptr = ; /* null terminate like fgets() */
return(n);
} ssize_t
readlinebuf(void **vptrptr)
{
if (read_cnt)
*vptrptr = read_ptr;
return(read_cnt);
}
/* end readline */ ssize_t
Readline(int fd, void *ptr, size_t maxlen)
{
ssize_t n; if ( (n = readline(fd, ptr, maxlen)) < )
err_sys("readline error");
return(n);
}

内部函数my_read最多每次读MAXLINE个字符,然后每次返回一个字符。

函数readline本身的唯一变化是调用函数my_read而不是read。

readlinebuf这个新的函数能够展露内部缓冲区状态,以便于调用者查看当前文本行之后是否有新的数据已收到。

-----------------------------------

fread就是通过read来实现的,fread是C语言的库,而read是系统调用
但是差别在read每次读的数据是调用者要求的大小,比如调用要求读取10个字节数据,read就会读10个字节数据到数组中,而fread不一样,为了加快读的速度,fread每次都会读比要求更多的数据,然后放到缓冲区中,这样下次再读数据只需要到缓冲区中去取就可以了。

fread每次会读取一个缓冲区大小的数据,32位下一般是4096个字节,相当于调用了read(fd,buf,4096)

比如需要读取512个字节数据,分4次读取,调用read就是:
for(i=0; i<4; ++i)
read(fd,buf,128)
一共有4次系统调用

而fread一次就读取了4096字节放到缓冲区了,所以省事了

 
 
比如用fgetc读一个字节,fgetc有可能从内核中预读1024个字节到I/O缓冲区中,再返回第一个字节,这时该文件在内核中记录的读写位置是1024,而在FILE结构体中记录的读写位置是1。注意返回值类型是ssize_t,表示有符号的size_t,这样既可以返回正的字节数、0(表示到达文件末尾)也可以返回负值-1(表示出错)。read函数返回时,返回值说明了buf中前多少个字节是刚读上来的。有些情况下,实际读到的字节数(返回值)会小于请求读的字节数count,例如:

  • 读常规文件时,在读到count个字节之前已到达文件末尾。例如,距文件末尾还有30个字节而请求读100个字节,则read返回30,下次read将返回0。

  • 从终端设备读,通常以行为单位,读到换行符就返回了。(输入we,显示读入是3个字节,可以试验下)

  • 从网络读,根据不同的传输层协议和内核缓存机制,返回值可能小于请求的字节数,后面socket编程部分会详细讲解。

write函数向打开的设备或文件中写数据。

#include <unistd.h>  ssize_t write(int fd, const void *buf, size_t count); 返回值:成功返回写入的字节数,出错返回-1并设置errno

写常规文件时,write的返回值通常等于请求写的字节数count,而向终端设备或网络写则不一定。

读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。

 
 
现在明确一下阻塞(Block)这个概念。当进程调用一个阻塞的系统函数时,该进程被置于睡眠(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包,或者调用sleep指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运行(Running)状态,在Linux内核中,处于运行状态的进程分为两种情况:
  • 正在被调度执行。CPU处于该进程的上下文环境中,程序计数器(eip)里保存着该进程的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,正在读写该进程的地址空间。

  • 就绪状态。该进程不需要等待什么事件发生,随时都可以执行,但CPU暂时还在执行另一个进程,所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,同时要兼顾用户体验,不能让和用户交互的进程响应太慢。

下面这个小程序从终端读数据再写回终端。

例 28.2. 阻塞读终端
下面这个小程序从终端读数据再写回终端。

例 28.2. 阻塞读终端
#include <unistd.h>
#include <stdlib.h> int main(void)
{
char buf[10];
int n;
n = read(STDIN_FILENO, buf, 10);
if (n < 0) {
perror("read STDIN_FILENO");
exit(1);
}
write(STDOUT_FILENO, buf, n);
return 0;
}
执行结果如下:
$ ./a.out  hello(回车) hello $ ./a.out  hello world(回车) hello worl$ d bash: d: command not found
 

第一次执行a.out的结果很正常,而第二次执行的过程有点特殊,现在分析一下:

  1. Shell进程创建a.out进程,a.out进程开始执行,而Shell进程睡眠等待a.out进程退出。

  2. a.out调用read时睡眠等待,直到终端设备输入了换行符才从read返回,read只读走10个字符,剩下的字符仍然保存在内核的终端设备输入缓冲区中。

  3. a.out进程打印并退出,这时Shell进程恢复运行,Shell继续从终端读取用户输入的命令,于是读走了终端设备输入缓冲区中剩下的字符d和换行符,把它当成一条命令解释执行,结果发现执行不了,没有d这个命令。

如果在open一个设备时指定了O_NONBLOCK标志,read/write就不会阻塞。以read为例,如果设备暂时没有数据可读就返回-1,同时置errnoEWOULDBLOCK(或者EAGAIN,这两个宏定义的值相同),表示本来应该阻塞在这里(would block,虚拟语气),事实上并没有阻塞而是直接返回错误,调用者应该试着再读一次(again)。这种行为方式称为轮询(Poll),调用者只是查询一下,而不是阻塞在这里死等,这样可以同时监视多个设备:

while(1) {  非阻塞read(设备1);  if(设备1有数据到达)   处理数据;  非阻塞read(设备2);  if(设备2有数据到达)   处理数据;  ... }

如果read(设备1)是阻塞的,那么只要设备1没有数据到达就会一直阻塞在设备1的read调用上,即使设备2有数据到达也不能处理,使用非阻塞I/O就可以避免设备2得不到及时处理。

非阻塞I/O有一个缺点,如果所有设备都一直没有数据到达,调用者需要反复查询做无用功,如果阻塞在那里,操作系统可以调度别的进程执行,就不会做无用功了。在使用非阻塞I/O时,通常不会在一个while循环中一直不停地查询(这称为Tight Loop),而是每延迟等待一会儿来查询一下,以免做太多无用功,在延迟等待的时候可以调度其它进程执行。
while(1) {  非阻塞read(设备1);  if(设备1有数据到达)   处理数据;  非阻塞read(设备2);  if(设备2有数据到达)   处理数据;  ...  sleep(n); }
这样做的问题是,设备1有数据到达时可能不能及时处理,最长需延迟n秒才能处理,而且反复查询还是做了很多无用功。以后要学习的select(2)函数可以阻塞地同时监视多个设备,还可以设定阻塞等待的超时时间,从而圆满地解决了这个问题。
以下是一个非阻塞I/O的例子。目前我们学过的可能引起阻塞的设备只有终端,所以我们用终端来做这个实验。程序开始执行时在0、1、2文件描述符上自动打开的文件就是终端,但是没有O_NONBLOCK标志。所以就像例 28.2 “阻塞读终端”一样,读标准输入是阻塞的。我们可以重新打开一遍设备文件/dev/tty(表示当前终端),在打开时指定O_NONBLOCK标志。
O_NONBLOCK 以不可阻断的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中。
例 28.3. 非阻塞读终端
从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

#define MSG_TRY "try again\n"

int main(void)
{
char buf[10];
int fd, n;
fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
if(fd<0) {
perror("open /dev/tty");
exit(1);
}
tryagain:
n = read(fd, buf, 10);
if (n < 0) {
if (errno == EAGAIN) {
sleep(1);
write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
goto tryagain;

perror("read /dev/tty");
exit(1);
}
write(STDOUT_FILENO, buf, n);
close(fd);
return 0;
}

直到按下回车把之前的输入输出(最多10个),然后停止。

以下是用非阻塞I/O实现等待超时的例子。既保证了超时退出的逻辑又保证了有数据到达时处理延迟较小。

例 28.4. 非阻塞读终端和等待超时

read:既可以返回正的字节数、0(表示到达文件末尾)也可以返回负值-1(表示出错)

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h> #define MSG_TRY "try again\n"
#define MSG_TIMEOUT "timeout\n" int main(void)
{
char buf[10];
int fd, n, i;
fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
if(fd<0) {
perror("open /dev/tty");
exit(1);
}
for(i=0; i<5; i++) {
n = read(fd, buf, 10);
if(n>=0)
break;
if(errno!=EAGAIN) {
perror("read /dev/tty");
exit(1);
}
sleep(1);
write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
}
if(i==5)
write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));
else
write(STDOUT_FILENO, buf, n);
close(fd);
return 0;
}
参考:
 

linux read和write函数的更多相关文章

  1. Linux下利用ioctl函数获取网卡信息

    linux下的ioctl函数原型如下: #include <sys/ioctl.h> int ioctl(int handle, int cmd, [int *argc, int argv ...

  2. module_init宏解析 linux驱动的入口函数module_init的加载和释放

    linux驱动的入口函数module_init的加载和释放 http://blog.csdn.net/zhandoushi1982/article/details/4927579 void free_ ...

  3. 【C/C++】Linux下使用system()函数一定要谨慎

    [C/C++]Linux下使用system()函数一定要谨慎 http://my.oschina.net/renhc/blog/53580 曾经的曾经,被system()函数折磨过,之所以这样,是因为 ...

  4. linux C之access函数 (20

    http://blog.sina.com.cn/s/blog_6a1837e90100uh5d.html linux C之access函数 (20access():判断是否具有存取文件的权限 相关函数 ...

  5. linux c语言 select函数用法

    linux c语言 select函数用法 表头文件 #i nclude<sys/time.h> #i nclude<sys/types.h> #i nclude<unis ...

  6. c/c++ linux 进程 fork wait函数

    linux 进程 fork wait函数 fork:创建子进程 wait:父进程等待子进程结束,并销毁子进程,如果父进程不调用wait函数,子进程就会一直留在linux内核中,变成了僵尸进程. for ...

  7. Linux编程之fork函数

    在Linux中,fork函数的功能就是在一个进程中创建一个新的进程,当前调用fork函数的进程就是产生的新进程的父进程,新进程在以下也称为子进程.在新进程生成之后就会在系统中开始执行. 函数原型:pi ...

  8. linux i2c 的通信函数i2c_transfer在什么情况下出现错误

    问题: linux i2c 的通信函数i2c_transfer在什么情况下出现错误描述: linux i2c设备驱动 本人在写i2c设备驱动的时候使用i2c transfer函数进行通信的时候无法进行 ...

  9. Linux下系统时间函数、DST等相关问题总结(转)

    Linux下系统时间函数.DST等相关问题总结 下面这个结构体存储了跟时区相关的位移量(offset)以及是否存在DST等信息,根据所在的时区信息,很容易找到系统时间与UTC时间之间的时区偏移,另外根 ...

  10. 关于linux中的system函数

    Linux下使用system()函数一定要谨慎 https://blog.csdn.net/senen_wakk/article/details/51496322 system()正确应用 https ...

随机推荐

  1. 增强学习————K-摇臂赌博机

    探索与利用增强学习任务的最终奖赏是在多步动作之后才能观察到,于是我们先考虑最简单的情形:最大化单步奖赏,即仅考虑一步操作.不过,就算这样,强化学习仍与监督学习有显著不同,因为机器要通过尝试来发现各个动 ...

  2. 一个关于ExtJS4具体控件的详细教程

    发现一遍介绍ExtJS控件介绍的比较好的系列文章,在此做总结 ExtJs4 笔记(1) ExtJs大比拼JQuery:Dom文档操作 ExtJs4 笔记(2) ExtJs对js基本语法扩展支持 Ext ...

  3. apple 官方文档 Push Notification Programming

    iOS Developer LibraryDeveloper Search Local and Push Notification Programming Guide PDF Table of Con ...

  4. NF3 里面的z cull reverse reload

    nf3 siggraph2011的 分享 里面有谈对csm的优化. 1.mask white red 2. HI Z 这俩我都懂 3.reverse depth buffer这实在不明白, 为什么会有 ...

  5. 6 高级IO函数

    6.1 pipe函数 pipe函数创建一个管道,用于实现进程间通信 #include<unistd.h> ]); 参数包含两个文件描述符fd[0]和fd[1],往fd[1]写入的数据可以从 ...

  6. spring security 构造函数初始化bean思路

    采用有参数的构造方法来解决注入你要的属性例如:public MyInvocationSecurityMetadataSource(RoleService roleService) { this.rol ...

  7. C:\Windows\system32\config\systemprofile\AppData\Local\Microsoft\Team Foundation\4.0\Cache\VersionControl.config is not valid and cannot be loaded.

    Recently, we experienced a strange problem with TFS 2010. We spent a few days before we figured it o ...

  8. UVA 11610 Reverse Prime (数论+树状数组+二分,难题)

    参考链接http://blog.csdn.net/acm_cxlove/article/details/8264290http://blog.csdn.net/w00w12l/article/deta ...

  9. POJ 1274

    #include<iostream> #include<stdio.h> #include <string.h> #include <vector> # ...

  10. Jquery 中map和each的区别

    <script type="text/javascript"> $(function () { var json = {"Name":"L ...