包裹 read 和 write 系统调用
--------------------------------------------------------------------------------
字节流套接字(TCP套接字)上的 read 和 write 函数所表现的行为不同于通常的
文件 I/O。字节流套接字上调用 read 和 write 输入或者输出的字节数可能比请求
的少,然而这不是出错的状态。这个现象的原因在于内核中用于套接字的缓冲区可能
已到达了极限。此时需要的是调用者再次调用 read 和 write 函数,以输入或输出
剩下的字节数。所以为了防止在我们的应用程序在一次调用 read 和 write 函数时
返回一个异于我们目标的字节数,我们可以为这两个系统调用实现一个包裹函数来
实现我们的目标。
readn 跟 writen 函数均能满足套接字缓冲区大于或小于目标字节的情况。
以下程序来自 UNIX 网络编程
- /*-------------------------------------------------------------------------*/
- //函数原型声明
- ssize_t readn( int filedes, void *buff, size_t nbytes );
- ssize_t writen( int filedes, const void *buff, size_t nbytes );
- 均返回:读或写的字节数,若出错则为-1
- /*--------------------------------------------------------------------------*/
- /* 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;
- /*
- * 需将 void * 型指针转换成 char * 型指针。因为指针必须按所读或缩写的
- * 字节数增长,但是C不允许void 指针如此增长(因为C编译器不知道void指向
- * 的数据类型)
- */
- char *ptr;
- ptr = vptr;
- nleft = n;
- while (nleft > 0) {
- if ( (nread = read(fd, ptr, nleft)) < 0) {
- if (errno == EINTR) /* 系统调用被一个捕获的信号中断 */
- nread = 0; /* and call read() again */
- else
- return(-1);
- } else if (nread == 0)
- 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)) < 0)
- err_sys("readn error");
- return(n);
- }
- /* 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 > 0) {
- if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
- if (nwritten < 0 && errno == EINTR)
- nwritten = 0; /* and call write() again */
- else
- return(-1); /* 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");
- }
下面再贴一个文件复制的经典例子(同样考虑并改善了文章开头提到的情况)。
要仔细理解下面这两个语句。
- while ( bytes_read = read(from_fd,buffer,BUFFER_SIZE) );
- while ( bytes_write = write(to_fd, ptr, bytes_read) );
当一个文件读完或者写完的时候返回的是0,那么就会跳出 while 循环了。
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <errno.h>
- #define BUFFER_SIZE 1024
- int main(int argc,char **argv)
- {
- int from_fd, to_fd;
- int bytes_read, bytes_write;
- char buffer[BUFFER_SIZE];
- char *ptr;
- if ( argc != 3 ) {
- fprintf(stderr,"Usage:%s fromfile tofile\n",argv[0]);
- exit(1);
- }
-
- /* 打开源文件 */
- if( (from_fd = open(argv[1], O_RDONLY)) == -1 ) {
- fprintf(stderr, "Open %s Error:%s\n",argv[1], strerror(errno));
- exit(1);
- }
- /* 创建目的文件 */
- if ( (to_fd=open(argv[2], O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR)) == -1 ) {
- fprintf(stderr, "Open %s Error:%s\n",argv[2], strerror(errno));
- exit(1);
- }
-
- /* 以下代码是一个经典的拷贝文件的代码 */
- while ( bytes_read = read(from_fd,buffer,BUFFER_SIZE) ) {
-
- /* 一个致命的错误发生了 */
- if ( (bytes_read==-1) && (errno!=EINTR) ) break;
- else if ( bytes_read > 0 ) {
- ptr = buffer; /* 指针重新指向缓冲区头部,因为接下来要从缓冲区读数据 */
- while ( bytes_write = write(to_fd, ptr, bytes_read) ) {
- /* 一个致命错误发生了 */
- if ( (bytes_write==-1) && (errno!=EINTR) ) break;
- /* 写完了所有读的字节 */
- else if ( bytes_write == bytes_read ) break;
- /* 只写了一部分,继续写 */
- else if ( bytes_write > 0 ) {
- ptr += bytes_write;
- bytes_read -= bytes_write;
- }
- }
- /* 写的时候发生的致命错误 */
- if ( bytes_write == -1 ) break;
- }
- }
- close(from_fd);
- close(to_fd);
- exit(0);
- }
阅读(2317) | 评论(0) | 转发(0) |