前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >系统编程-文件读写这件小事

系统编程-文件读写这件小事

作者头像
编程珠玑
发布2019-12-26 18:54:18
6220
发布2019-12-26 18:54:18
举报
文章被收录于专栏:编程珠玑

来源:公众号【编程珠玑】

作者:守望先生

ID:shouwangxiansheng

在《系统编程-文件IO》中简单介绍了文件I/O的基本流程,无论选项或者参数多么变化多端,其流程大抵相同,不过是获取文件描述符,用描述符进行操作,关闭描述符,三步而已。那么文件读写又是怎样的流程?需要注意什么?

write/read

在说明这些常见出错之前,就必须先了解其基本用法了。需要注意的是,write/read是不带缓冲的,调用一次,写一次。与fwrite/fread有区别,另外write/read为系统调用,频繁地系统调用将会增加开销,可参考《库函数和系统调用的区别》。

代码语言:javascript
复制
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

参数解释:

  • fd 文件描述符,这个应该不用多做解释
  • buf 要写入的内容,或者读出内容存储的buf,合适的大小非常关键
  • count 读或写的内容大小

这里有两点需要注意一下。

返回值为ssize_t类型,因为它的返回值可以为负,表示出错,有趣的是这样一来使得其能表示的读写字节范围少了近一半。 返回大于0,表示读或写入对应的字节数。对于read,返回0表示到文件结尾。

另外,我们还注意到,write函数的第二个参数由const修饰。为什么要使用const来修饰?

很显然,在写的过程中,write函数不应该对buf的内容进行修改,它仅仅是从buf中读取罢了。这里在编码时常用的设计,如果不希望该函数修改其内容,则加上const限定符。const详细说明参考《const关键字到底该怎么用?》。

那么返回的读写大小,和参数里的count大小有何区别?前者是真实读写的字节数,而后者是期望读写的字节数。举个简单的例子,文件中有16字节内容,而你尝试读64字节,自然最终只会读到16字节。

正常读写

正常读写的例子如下:

代码语言:javascript
复制
//来源:公众号【编程珠玑】
//博客:https://www.yanbinghu.com
//file.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
int main()
{
    char writeBuf[] = "https://www.yanbinghu.com";
    char readBuf[128] = {0};
    /*可读可写,不存在时创建,有内容时截断*/
    int fd = open("test.txt",O_RDWR | O_CREAT | O_TRUNC);
    if(-1 == fd)
    {
        perror("open failed");
        return -1;
    }
    /*写内容*/
    ssize_t wLen = write(fd,writeBuf,sizeof(writeBuf));
    if(wLen < 0)
    {
        perror("write failed");
        close(fd);
        return -1;
    }
    printf("write len:%ld\n",wLen);
    ssize_t rLen = read(fd,readBuf,sizeof(readBuf));
    if(rLen < 0)
    {
        perror("read failed");
        close(fd);
        return -1;
    }
    readBuf[sizeof(readBuf)-1] = 0;
    printf("read content:%s\n",readBuf);
    close(fd);
    return 0;
}

编译运行,然后你就会惊喜地发现,结果并不是如你想地那样:

代码语言:javascript
复制
$ gcc -o writeFile file.c
$ ./writeFile
write len:26
read content:

我们查看文件可以看到内容可能已经写进去了,但是读取出来地内容却是空!

这是为何? 理解这个问题需要理解文件描述符和偏移量。

文件描述符

文件描述符虽然只是一个整型值,但它只是一个索引值,它指向了该进程打开文件的记录表。还记得常说的“一切皆文件”吗?实际上,即使你打开一个TCP链接,都会有一个对应的文件描述符。这个记录表中包含了很多与文件相关地信息,例如文件偏移量,inode,状态标志等等。

而你每一次进行读写,都会影响所谓地文件偏移量。

因此你在第一次进行写之后,文件偏移量类似于下面这样:

那么你进行第一次读的时候,文件偏移已经到文件的末尾了(此时函数返回值为0),所以你肯定读不出任何内容,因此你需要移动偏移指针。

设置偏移量

为了读取写入后的内容,我们必须要设置偏移量,设置成像下面这样:

有人可能会好奇,这最后为什么还有一个\0?很显然,它被自动加上了,具体原因可以参考《NULL,0,'0'你真的分清了吗》。

还有人会问,你怎么看出有一个\0?用od命令看一下就知道了。

代码语言:javascript
复制
$ od -c test.txt
0000000   h   t   t   p   s   :   /   /   w   w   w   .   y   a   n   b
0000020   i   n   g   h   u   .   c   o   m  \0
0000032

现在看到了吧。

为了设置偏移量,我们需要用到函数lseek:

代码语言:javascript
复制
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

成功返回新的文件偏移量,出错返回-1。

有必要对参数进行解释

  • offset 相对于whence的偏移量
  • whence 相对位置

其中whence有三个值

  • SEEK_SET 文件开始处
  • SEEK_CUR 当前位置
  • SEEK_END 文件末尾

举个例子,假设当前offset为-4,whence为SEEK_CUR,那么当写完内容,并设置该选项后的文件偏移位置如下:

注意,offset是可以为负的。

说白了可以设置偏移位置,而设置可以相对三个位置,开头,当前和结尾。

读取写入的内容

好了,为了读取到我们写入的内容,我们已经知道怎么做了,就是设置偏移量在文件开头,即在读之前加上下面的语句:

代码语言:javascript
复制
lseek(fd, 0, SEEK_SET);//注意检查返回值

然后再次编译运行:

代码语言:javascript
复制
write len:26
read content:https://www.yanbinghu.com

如你所愿!

常见报错

使用不当或者出错的时候会有错误信息,这在编码的时候就需要注意检查。

Bad file descriptor

通常使用了一个并不合法的文件描述符,例如,该文件描述符已经关闭。通常你可以通过下面的命令来观察文件描述符的打开情况:

代码语言:javascript
复制
$ ls -al /proc/`pidof procName`/fd/

这里的procName是你正在运行的程序名。

也有可能是你打开模式不对,例如,以只读方式打开,却尝试写。

Interrupted system call

通常是在读写过程中被中断,常见的如对socket进行读写时,链接被意外中断,或者读写时,进程被中断等等。

File exists

通常在你想创建一个文件,但是文件已经存在的情况。

No such file or directory

就如字面意思,通常是文件或者目录不存在,也许你使用了O_CREATE标志,但是如果你的目录不存在,文件也无法创建成功。

还有一种情况是,你已经打开了该文件,程序执行过程中,该文件又被人删除了,删除后又创建了一个文件名一样的文件,这样的情况下,也有可能会提示该错误。

Too many open fileswrite

进程打开的文件过多。一个进程打开的文件数量是有限的,具体可以通过:

代码语言:javascript
复制
$ ulimit -n
65535

至于当前已经打开了多少,可以这样统计:

代码语言:javascript
复制
$ ls -l /proc/`pidof proName`/fd/ |wc -l

proName为你的进程名。

总结

一些常见错误中很多涉及到网络的读写,这里暂时没有提及。

一般情况,不会用同一个文件描述符对文件进行既读又写,一旦出现这样的场景时,需要注意偏移量的设置。虽然本文的I/O函数不带缓冲,但是读写时,选择合适的buf大小也非常关键。

另外编程中也有以下建议:

  • 检查接口的返回值,处理出错场景
  • 对于不期望被修改内容的参数,添加const限定符
  • 善用man手册
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-12-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程珠玑 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • write/read
  • 正常读写
    • 文件描述符
      • 设置偏移量
        • 读取写入的内容
        • 常见报错
          • Bad file descriptor
            • Interrupted system call
              • File exists
                • No such file or directory
                  • Too many open fileswrite
                  • 总结
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档