下面是read和write的函数原型
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count); //count通常取sizeof(buf)
返回值:若成功,返回读取的字节数(不一定是count),出错返回-1并设置errno,如果在调read之前已到达文件末尾,则这次read返回0。文件的当前读写位置向后移。
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
返回值:若成功,返回写入的字节数(不一定是count),出错返回-1并设置errno。
当read普通的文件如磁盘文件时,返回值通常=count,若<count,通常是到达文件尾了,下次read一般会返回0。所以一般会使用如下语句:
if(n=read( fd, buf, count)>0)
write( fd, buf, n);
但是读管道、FIFO、终端和socket时,很可能<count,即使没有到达文件尾返回值也通常这样,这时应该继续读。
当write普通的文件如磁盘文件时,返回值通常=count,若<count,通常是出现了问题,所以一般会使用如下语句:
if(write( fd, buf, count)!=count)
err_sys("write error");
但是write管道、FIFO、终端和socket时,很可能<count,这时候并没有出错,应该继续写余下的数据。(通常只有对非阻塞描述符,或者阻塞描述符但捕捉到一个信号时,才发生这种write的中途返回)
write将buf中的count数量字节内容写入文件描述符fd引用的打开文件中,成功时返回写的字节数,失败时返回-1。 并设置errno变量. 在网络程序中,当我们向套接字文件描述符写时有两种可能.
1)write的返回值大于0,表示写了部分或者是全部的数据.
2)返回的值小于0,此时出现了错误.我们要根据错误类型来处理.
如果错误为EINTR表示在写的时候出现了中断错误.
如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接).
于是对管道、FIFO、终端和socket等文件,我们自定义了下面的函数,直至读写了指定的n字节数据才返回。
版本1
#include"apue.h" ssize_t readn(int fd,void *ptr,size_t n) { size_t nleft; ssize_t nread; nleft = n; while (nleft> 0) { if ((nread= read(fd, ptr, nleft))< 0) { //当read返回-1, if (nleft== n) return(-1); /*若第一次read时出错, readn 返回 -1 */ else break; /*若读了一些数据后出错, readn返回已经读的字节数,而不出错返回 */ } elseif (nread == 0){ //当read返回0时,不再读下去,readn返回已经读的字节数 break;/* EOF */ } nleft -= nread; //当read返回值>0但不等于n时,继续读 ptr += nread; } return(n- nleft);/* return >= 0 */ } ssize_t writen(int fd,const void *ptr, size_t n) size_t nleft; ssize_t nwritten; nleft = n; while (nleft 0){ if ((nwritten= write(fd, ptr, nleft))< 0) { //当write返回-1, if (nleft== n) /*若第一次write时出错, writen 返回 -1 */ return(-1); else break; /*若写了一些数据后出错, writen返回已经读的字节数,而不出错返回 */ } elseif (nwritten== 0){ //当write返回0时,不再写下去,writen返回已经读的字节数 break; } nleft -= nwritten; //当write返回值>0但不等于n时,继续写 ptr += nwritten; } return(n- nleft);/* return >= 0 */ } |
上述版本在read write出错后,若已经读写了一些数据,则返回读写的字节量,而非出错返回。但在read write出错后没有检查errno判断是否因为被信号中断而出错。
版本2
#include "unp.h" ssize_t readn(int fd,void *vptr,size_t n) { size_t nleft; ssize_t nread; char *ptr; ptr = vptr; nleft = n; while (nleft> 0) { if ((nread = read(fd, ptr, nleft))< 0) { //当read返回-1, if (errno== EINTR) nread = 0; /*若出错是因为被信号中断,继续读*/ else return(-1); //若不是因为被信号中断而出错,则不再读readn返回-1 } else if (nread == 0) //当read返回0时,不再读下去,readn返回已经读的字节数 break; /* EOF */ nleft -= nread; //当read返回值>0但不等于n时,继续读 ptr += nread; } return(n- nleft); /* return >= 0 */ } ssize_t writen(int fd,const void *vptr, size_t n) { size_t nleft; ssize_t nwritten; const char *ptr; ptr = vptr; nleft = n; while (nleft> 0) { if ((nwritten =write(fd, ptr, nleft))<= 0){ //当write出错返回-1或者返回0时, if (nwritten< 0 &&errno == EINTR) nwritten = 0; /*若出错返回-1是因为被信号中断,继续写 */ else return(-1); //若不是因为被信号中断而出错返回-1或者 返回0时,则不再写writen返回-1 } nleft -= nwritten; //当write返回值>0但不等于n时,继续写 ptr += nwritten; } return(n); } |
上述版本在read write出错后,不返回读写的字节量,而是出错返回-1。但在read write出错后检查errno判断是否因为被信号中断而出错,若是因为被信号中断,继续读写。
在将数据写到像socket这样的文件类型时,就可以调用writen,但通常只调用read来接收来自这些设备的数据,只有当事先就知道接收数据的数据量时,才调用readn。
again:
while ((n = read(sockfd, buf, MAXLINE))> 0) //调用read
writen(sockfd, buf, n); //调用writen
if (n < 0 &&errno == EINTR)
goto again;
else if(n < 0)
err_sys("str_echo: read error");
}