跳转至内容
  • 版块
  • 最新
  • 标签
  • 热门
  • Online Tools
  • 用户
  • 群组
折叠
品牌标识

D2Learn Forums

  1. 主页
  2. SubForums
  3. 现代C++ | mcpp论坛
  4. Linux系统编程(3)文件读写操作

Linux系统编程(3)文件读写操作

已定时 已固定 已锁定 已移动 现代C++ | mcpp论坛
linux 系统编程
1 帖子 1 发布者 6 浏览
  • 从旧到新
  • 从新到旧
  • 最多赞同
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • 月 离线
    月 离线
    月仁不吃五饼
    编写于 最后由 编辑
    #1

    Linux系统编程(4)文件读写操作

    通过read()读文件

    #include <unistd.h>
    
    ssize_t read(int fd, void *buf, size_t count);
    

    每个参数分别是什么

    参数 1:int fd

    • fd 是文件描述符,一般来自:

      • open() 打开文件

      • 或者标准输入 0(STDIN_FILENO)

    • 内核用 fd 找到你打开的“文件对象 + 当前偏移量”等状态。

    参数 2:void *buf

    • 你提供的缓冲区地址,用来装读出来的数据。

    • 必须保证这块内存可写并且至少有 count 个字节容量。

    • 例如:

    char buf[4096];
    read(fd,buf,sizeof(buf));
    

    参数 3:size_t count

    • 你希望这次最多读取多少字节。

    • 注意:这是“最多”,不是“必须读满”。


    每次调用read()函数,会从fd指向的文件的当前偏移开始读取len字节到buf所指向的内存中。执行成功时,返回写入buf中的字节数;出错时,返回-1,并设置errno值。fd的文件位置指针会向前移动,移动的长度由读取到的字节数决定。如果fd所指向的对象不支持seek操作(比如字符设备文件),则读操作总是从“当前”位置开始。
    基本用法很简单。下面这个例子就是从文件描述符fd所指向的文件中读取数据并保存到word中。读取的字节数即unsigned long类型的大小,在Linux的32位系统上是4字节,在64位系统上是8字节。成功时,返回读取的字节数;出错时,返回-1:**

    usigned long world;
    ssize_t nr
    nr = read(fd,&world,sizeof(world));
    if(nr==-1)......
    

    返回值三种情况(必须背熟)

    情况 A:返回值 > 0

    • 表示实际读取到的字节数,比如返回 1000,就说明 buf[0..999] 有效。

    • 不保证等于 count:读文件也可能因为系统缓冲、信号等原因“读不满”。

    情况 B:返回值 == 0

    • 表示 EOF(文件结束):已经读到文件末尾,再读就没有数据了。

    • 对普通文件,这是“读完”的标志。

    情况 C:返回值 == -1

    • 表示出错,具体原因在全局变量 errno 里。

    • 最常见的你需要认识两个:

      • errno == EINTR:被信号打断,通常重试即可

      • errno == EAGAIN / EWOULDBLOCK:非阻塞读时没数据(普通文件很少遇到)


    读入所有字节

    诚如前面所描述的,由于调用read()会有很多不同情况,如果希望处理所有错误并且真正每次读入len个字节(至少读到EOF),那么之前简单“粗暴”的read()调用并不合理。要实现这一点,需要有个循环和一些条件语句,如下:

    ssize_t ret;//声明返回值
    size_t length;//声明需要读取的字节长度
    while(len>0 && ret=read(fd,buf,length) ){
      if(ret==-1){
       if(error==EINTR) continue;
      perror("read"); 
      break;
    }
      len-=(size_t)ret;
      buf+=ret;
    }
    

    read() 读出来的是原始字节,它只返回“实际读了多少字节”,不会自动在末尾补 '\0'。

    所以:

    • 如果把 read 的结果当作 C 字符串来用,必须自己加终止符,并且要预留一个字节空间。
      实例:
    char buf[1024];
    ssize_t n = read(fd, buf, sizeof(buf) - 1); // 预留 1 字节给 '\0'
    if (n > 0) {
        buf[n] = '\0';  // 手动补 '\0'
        printf("%s", buf);
    }
    

    补充两点常见坑:

    • read 读到的内容里可能本来就包含 '\0'(比如二进制文件),这时即使你手动补了终止符,printf("%s") 也会在中间的 '\0' 提前截断。

    • 如果 n == sizeof(buf)-1,上面这种写法仍然安全,因为你预留了 1 字节。


    阻塞IO(先了解即可)

    有时,开发人员不希望read()调用在没有数据可读时阻塞在那里。相反地,他们希望调用立即返回,表示没有数据可读。这种方式称为非阻塞I/O,它支持应用以非阻塞模式执行I/O操作,因而如果是读取多个文件,以防错过其他文件中的可用数据。
    因此,需要额外检查errno值是否为EAGAIN。正如前面所讨论的,如果文件描述符以非阻塞模式打开(即open()调用中指定参数为O_NONBLOCK,open()调用的参数”),并且没有数据可读,read()调用会返回-1,并设置errno值为EAGAIN,而不是阻塞模式。当以非阻塞模式读文件时,必须检查EAGAIN,否则可能因为丢失数据导致严重错误。你可能会用到如下代码:

     char buf[MAX_SIZE];
     usigned int len;
     start:
     ssize_t ret = read(fd,buf,len);
     if (ret==-1){
    	 if(error==ENTER) goto satrt;
    	else if(error==EAGAIN)....;
    	else...;
     }
    

    怎么打开非阻塞设置

    方法 A:open 时带 O_NONBLOCK

    int fd = open("a.txt",O_RDONLY | O_NONBLOCK); 
    

    方法 B:对已有 fd 用 fcntl 打开非阻塞

    #include <fcntl.h>
    
    int flags = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    

    非阻塞读的正确姿势:配合 select/poll/epoll

    非阻塞不是让你疯狂 while(read...) 空转(那会 CPU 100%),而是:

    1)先等“可读事件”(有数据了再读)
    2)再 read(),并且通常要一直读到 EAGAIN 才停(把缓冲区读空)

    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <stdio.h>
    #include <poll.h>
    
    int main() {
        int fd = STDIN_FILENO;
    
        int flags = fcntl(fd, F_GETFL, 0);
        fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    
        char buf[1024];
    
        while (1) {
            struct pollfd pfd = { .fd = fd, .events = POLLIN };
            int r = poll(&pfd, 1, 5000); // 等 5 秒
            if (r == 0) { puts("timeout..."); continue; }
            if (r < 0) { perror("poll"); break; }
    
            // 可读了:把当前能读的都读出来(直到 EAGAIN)
            while (1) {
                ssize_t n = read(fd, buf, sizeof(buf));
                if (n > 0) {
                    write(STDOUT_FILENO, buf, (size_t)n);
                    continue;
                }
                if (n == 0) return 0; // EOF
                if (errno == EINTR) continue;
                if (errno == EAGAIN || errno == EWOULDBLOCK) break;
                perror("read");
                return 1;
            }
        }
        return 0;
    }
    
    1 条回复 最后回复
    0

    • 登录

    • 没有帐号? 注册

    • 登录或注册以进行搜索。
    d2learn forums Powered by NodeBB
    • 第一个帖子
      最后一个帖子
    0
    • 版块
    • 最新
    • 标签
    • 热门
    • Online Tools
    • 用户
    • 群组