深入了解C read/write函数和cat命令实现

1.文件描述符 文件描述符(file descriptor)通常是一个小的非负整数,内核用以 标识一个特定进程正在访问的文件。当内核打开一个现有文件或创建一 个新文件时,它都返回一个文件描述符。在读、写文件时,可以使用这 个文件描述符。

2.标准输入、标准输出和标准错误 按惯例,每当运行一个新程序时,所有的 shell 都为其打开 3 个文 件描述符,即标准输入(standard input)、标准输出(standard output) 以及标准错误(standard error)。如果不做特殊处理,例如就像简单的 命令ls,则这3个描述符都链接向终端。大多数shell都提供一种方法,使 其中任何一个或所有这3个描述符都能重新定向到某个文件,例如: ls > file.list 执行ls命令,其标准输出重新定向到名为file.list的文件。

3.不带缓冲的I/O 函数open、read、write、lseek以及close提供了不带缓冲的I/O。这些 函数都使用文件描述符。

1、read()

函数定义:ssize_t read(int fd, void * buf, size_t count);

函数说明:read()会把参数fd所指的文件传送count 个字节到buf 指针所指的内存中。

返回值:返回值为实际读取到的字节数, 如果返回0, 表示已到达文件尾或是无可读取的数据。若参数count 为0, 则read()不会有作用并返回0。另外,以下情况返回值小于count:
(1)读常规文件时,在读到count个字节之前已到达文件末尾。例如,距文件末尾还有50个字节而请求读100个字节,则read返回50,下次read将返回0。
(2)对于网络套接字接口,返回值可能小于count,但这不是错误,详细解释参考这篇文章https://blog.csdn.net/hhhlizhao/article/details/73912578
注意:read时fd中的数据如果小于要读取的数据,就会引起阻塞。(关于read的阻塞情况网上有朋友有不同意见,笔者查阅资料后作如下补充。)以下情况read不会引起阻塞:

(1)常规文件不会阻塞,不管读到多少数据都会返回;

(2)从终端读不一定阻塞:如果从终端输入的数据没有换行符,调用read读终端设备会阻塞,其他情况下不阻塞;

(3)从网络设备读不一定阻塞:如果网络上没有接收到数据包,调用read会阻塞,除此之外读取的数值小于count也可能不阻塞,原因见上面链接。

由于笔者水平有限,如果文中有谬误之处还恳请诸位指出,以免误导大家。

2、write()
函数定义:ssize_t write (int fd, const void * buf, size_t count);

函数说明:write()会把参数buf所指的内存写入count个字节到所指的文件fd内。

返回值:如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中。

附加说明:

(1)write()函数返回值一般无0,只有当如下情况发生时才会返回0:write(fd, buf, count)中第三参数为0,此时write()什么也不做,只返回0。

(2)write()函数从buf写数据到fd中时,若buf中数据无法一次性读完,那么第二次读buf中数据时,其读位置指针(也就是第二个参数buf)不会自动移动,需要程序员来控制,而不是简单的将buf首地址填入第二参数即可。如可按如下格式实现读位置移动:write(fp, p1+len, (strlen(p1)-len))。 这样write第二次循环时便会从p1+len处写数据到fp, 之后的也一样。由此类推,直至(strlen(p1)-len)变为0。

(3)在write一次可以写的最大数据范围内(貌似是BUFSIZ ,8192),第三参数count大小最好为buf中数据的大小,以免出现错误。(经过笔者再次试验,write一次能够写入的并不只有8192这么多,笔者尝试一次写入81920000,结果也是可以,看来其一次最大写入数据并不是8192,但内核中确实有BUFSIZ这个参数,具体指什么还有待研究)

我们可以写一个简单的文件读写程序,实现从标准输入到标准输出echo(复读机)

#include "../all.h"
#include 
#define BUFFSIZE 4096

int main(int argc, char* argv[]) {
    int n = 0;
    char buf[BUFFSIZE];
    
    printf("This is a simple read/write test\n");

    while((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0) {
        if(write(STDOUT_FILENO, buf, n)!=n) {
            printf("write error\n");
        }
    }

    if(n < 0) {
        printf("read error\n");
    }
    return 0;
}

echo复读机

还可以打开文件,输出到标准输出 实现cat

#include "../all.h"
#include 
#include 
#define BUFFSIZE 4096
int main(int argc, char* argv[]) {
    printf("this is a simple cat, will show the file\n");
    
    if(argc  < 2) {
        printf("argument not enough , at least 2\n");
        exit(-1);
    }

    char buf[BUFFSIZE];
    memset(buf,0,BUFFSIZE);
    int n =0;
    int fd = open(argv[1], O_RDWR|O_CREAT);
    
    while(true) {
        int len = 0;
        while((n= read(fd, buf,BUFFSIZE)) > 0) {
            if(write(STDOUT_FILENO,buf + len,n) !=n) {
                printf("write error\n");
                exit(-1);
            } 
            len+=n;
        }

        break;
    }
    
    return 0;
}


深入了解C read/write函数和cat命令实现_第1张图片
简单的cat

头文件(apue.h中包含了此头文件)及两个常量 STDIN_FILENO和STDOUT_FILENO是POSIX标准的一部分(下一章 将对此做更多的说明)。头文件包含了很多UNIX系统服务的 函数原型。

两个常量STDIN_FILENO和STDOUT_FILENO定义在头 文件中,它们指定了标准输入和标准输出的文件描述符。在POSIX标准 中,它们的值分别是0和1,但是考虑到可读性,我们将使用这些名字来 表示这些常量。

将详细讨论BUFFSIZE常量,说明它的各种不同值将如何影响 程序的效率。但是不管该常量的值如何,此程序总能复制任一UNIX普 通文件。

4.标准I/O
read函数返回读取的字节数,此值用作要写的字节数。当到达输入 文件的尾端时,read返回0,程序停止执行。如果发生了一个读错误, read返回−1。出错时大多数系统函数返回−1。 标准I/O函数为那些不带缓冲的I/O函数提供了一个带缓冲的接 口。使用标准I/O函数无需担心如何选取最佳的缓冲区大小。例如,fgets函数读取一个完整的 行,而read函数读取指定字节数。

我们最熟悉的标准I/O函数是printf。在调用printf的程序中,总是包 含(在本书中,该头文件包含在apue.h中),该头文件包括了 所有标准I/O函数的原型。

你可能感兴趣的:(深入了解C read/write函数和cat命令实现)