Bootstrap

C语言文件操作

一 缓冲文件系统

  缓冲文件系统的特点是:在内存开辟一个“缓冲区”,为程序中的每一个文件使用,当执行读文件的操作时,从磁盘文件将数据先读入内存缓冲区”,装满后再从内存“缓冲区”依此读入接收的变量。执行写文件的操作时,先将数据写入内存“缓冲区”,待内存“缓冲区”装满后再写入文件。由此可以看出,内存 “缓冲区”的大小,影响着实际操作外存的次数,内存“缓冲区”越大,则操作外存的次数就少,执行速度就快、效率高。一般来说,文件“缓冲区”的大小随机器而定。fopenfclosefreadfwritefgetcfgetsfputcfputsfreopenfseekftellrewind等。

二 非缓冲文件系统

  非缓冲文件系统是借助文件结构体指针来对文件进行管理,通过文件指针来对文件进行访问,既可以读写字符、字符串、格式化数据,也可以读写二进制数据。非缓冲文件系统依赖于操作系统,通过操作系统的功能对文件进行读写,是系统级的输入输出,它不设文件结构体指针,只能读写二进制文件,但效率高、速度快,由于ANSI标准不再包括非缓冲文件系统,因此建议大家最好不要选择它。openclosereadwritegetcgetcharputcputchar 等。

三 fopen与open的区别

1 来源不同:

  open 是unix系统调用函数(包括Linux),返回的是文件描述符,它是文件描述符表里的索引。
  fopen 是ANSIC标准中的C语言库函数,在不同的系统中应该调不同的内核api,返回的是一个指向文件结构的指针。

2 移植性:

  从来源看,fopen 是C标准函数,因此拥有良好的移植性,而 openunix 系统调用,移植性有限,如windows下相似的功能使用api函数CreatFile。

3 使用范围

  open 返回文件描述符,而文件描述符是 unnix 系统下的重要概念,unix 下的一切设备都是文件的形式操作,如网络套接字、硬件设备等、当然包括操作普通正规文件(Regular File)
Fopen是从来操纵普通正规文件(Regular File)的

4 文件IO层次

  如果从文件IO的角度来看,open 属于低级IO函数,fopen 属于高级IO函数,低级和高级的简单区分标准是:谁离系统内核更近,低级文件IO运行在内核态、高级文件IO运行在用户态。
  open 是系统调用,返回的是文件句柄,文件的句柄是文件在文件描述副表里的索引,fopen 是ANSIC标准中的C的库函数,在不同的系统中应该调用不同的内核api,返回的是一个指向文件结构的指针。后者是在前者的基础上扩充而来的,在大多数情况下,用后者。fopen 可移植,open 不能。

5 缓冲区

  open 没缓冲区,fopen 有缓冲区。
  缓冲文件系统:fopen 是在缓冲区中进行文件操作的。
  非缓冲文件系统:open 是通过系统调用,在内核中进行文件操作的。
  fopenopen 最主要的区别是 fopen 在用户态下就有了缓存,在进行 readwrite 的时候减少了用户态和内核态的切换,而 open 则每次都需要进行内核态和用户态的切换;表现为,如果顺序访问文件,fopen系列的函数要比直接调用open系列快;如果随机访问文件open要比fopen快。fopen 使用了 FILE 这个结构才保存缓冲数据。open 没有缓存机制,每次读操作都直接从文件系统中获取数据,FILE 这个结构的定义,FILE 包含了一个 open 返回回来的 handle

四 open

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char * pathname, int flags);
int open(const char * pathname, int flags, mode_t mode);
  • pathname 是要打开的文件的路径名,可以是相对路径或绝对路径。
  • flags 是打开文件的标志,用于指定文件的打开方式和操作选项。常用的标志包括:
    • O_RDONLY:以只读方式打开文件;
    • O_WRONLY:以只写方式打开文件;
    • O_RDWR:以读写方式打开文件。上述三种标标是互斥的,也就是不可同时使用,但可与下列的标标利用 OR(I) 运算符组合;
    • O_CREAT:如果文件不存在则创建文件;
    • O_EXCL:如果 O_CREAT 也被设置,此指令会去检査文件是否存在。文件若不存在则建立该文件,否则将导致打开文件错误。此外, 若 O_CREATO_EXCL 同时设置,并且欲打开的文件为符号连接,则会打开文件失败。
    • O_NOCTTY:如果欲打开的文件为终端机设备时,则不会将该终端机当成进程控制终端机;
    • O_TRUNC:如果文件存在则截断文件内容;
    • O_APPEND: 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面;
    • O_NONBLOCK: 以不可阻断的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中;
    • O_NDELAY: 同 O_NONBLOCK
    • O_SYNC: 以同步的方式打开文件;
    • O_NOFOLLOW: 如果参数 pathname 所指的文件为一符号连接,则会令打开文件失败;
    • O_DIRECTORY:如果参数 pathname 所指的文件并非为一目录,则会令打开文件失败;

注:此为Linux2.2 以后特有的旗标,以避免一些系统安全问题.

  • mode 是一个权限参数,用于指定新创建文件的访问权限,只有在使用 O_CREAT 标志创建文件时才有效。此外真正建文件时的权限会受到umask 值所影响,因此该文件权限应该为(mode-umaks)。
    • S_IRWXU 00700 权限,代表该文件所有者具有可读、可写及可执行的权限。
    • S_IRUSRS_IREAD,00400 权限,代表该文件所有者具有可读取的权限。
    • S_IWUSRS_IWRITE,00200 权限,代表该文件所有者具有可写入的权限。
    • S_IXUSRS_IEXEC,00100 权限,代表该文件所有者具有可执行的权限。
    • S_IRWXG 00070 权限,代表该文件用户组具有可读、可写及可执行的权限。
    • S_IRGRP 00040 权限,代表该文件用户组具有可读的权限。
    • S_IWGRP 00020 权限,代表该文件用户组具有可写入的权限。
    • S_IXGRP 00010 权限,代表该文件用户组具有可执行的权限。
    • S_IRWXO 00007 权限,代表其他用户具有可读、可写及可执行的权限。
    • S_IROTH 00004 权限,代表其他用户具有可读的权限。
    • S_IWOTH 00002 权限, 代表其他用户具有可写入的权限。
    • S_IXOTH 00001 权限,代表其他用户具有可执行的权限。
  • 返回值:若所有欲核查的权限都通过了检查则返回0值,表示成功,只要有一个权限被禁止则返回-1
  • 错误代码:
    • EEXIST 参数 pathname 所指的文件已存在,却使用了 O_CREATO_EXCL 标志。
    • EACCESS 参数 pathname 所指的文件不符合所要求测试的权限。EROFS 欲测试写入权限的文件存在于只读文件系统内,
    • EFAULT 参数pathname 指针超出可存取内存空间。
    • EINVAL 参数mode 不正确。
    • ENAMETOOLONG 参数 pathname 太长。
    • ENOTDIR 参数pathname 不是日录
    • ENOMEM 核心内存不足
    • ELOOP 参数pathname 有过多符号连接问题
    • EIO I/O 存取错误,

五 stat()和fstat()函数

头文件:

#include <sys/stat.h>
#include <unistd.h>

  定义函数:int stat(const char * file_name, struct stat *buf);
  函数说明:stat()用来将参数 file_name 所指的文件状态,复制到参数buf所指的结构体stat中。
  定义函数:int fstat(int fildes, struct stat *buf);
  函数说明:fstat() 用来将参数 fildes 所指的文件状态,复制到参数 buf 所指的结构中(struct stat)。 Fstat()与stat()作用完全相同,不同处在于传入的参数为已打开的文件描述词,详细内容请参考stat()。
返回值:执行成功则返回0,失败返回-1,错误代码存于errno
  错误代码:

ENOENT 参数file_name指定的文件不存在
ENOTDIR 路径中的目录存在但却非真正的目录
ELOOP 欲打开的文件有过多符号连接问题,上限为16符号连接
EFAULT 参数buf为无效指针,指向无法存在的内存空间
EACCESS 存取文件时被拒绝
ENOMEM 核心内存不足
ENAMETOOLONG 参数file_name的路径名称太长

struct stat {
    dev_t         st_dev;       //文件的设备编号
    ino_t         st_ino;       //节点
    mode_t        st_mode;      //文件的类型和存取的权限
    nlink_t       st_nlink;     //连到该文件的硬连接数目,刚建立的文件值为1
    uid_t         st_uid;       //用户ID
    gid_t         st_gid;       //组ID
    dev_t         st_rdev;      //(设备类型)若此文件为设备文件,则为其设备编号
    off_t         st_size;      //文件字节数(文件大小)
    unsigned long st_blksize;   //块大小(文件系统的I/O 缓冲区大小)
    unsigned long st_blocks;    //块数
    time_t        st_atime;     //最后一次访问时间
    time_t        st_mtime;     //最后一次修改时间
    time_t        st_ctime;     //最后一次改变时间(指属性)
};

  先前所描述的st_mode 则定义了下列数种情况:

S_IFMT   0170000    文件类型的位遮罩
S_IFSOCK 0140000    scoket
S_IFLNK 0120000     符号连接
S_IFREG 0100000     一般文件
S_IFBLK 0060000     区块装置
S_IFDIR 0040000     目录
S_IFCHR 0020000     字符装置
S_IFIFO 0010000     先进先出
S_ISUID 04000     文件的(set user-id on execution)位
S_ISGID 02000     文件的(set group-id on execution)位
S_ISVTX 01000     文件的sticky位
S_IRUSR(S_IREAD) 00400     文件所有者具可读取权限
S_IWUSR(S_IWRITE)00200     文件所有者具可写入权限
S_IXUSR(S_IEXEC) 00100     文件所有者具可执行权限
S_IRGRP 00040             用户组具可读取权限
S_IWGRP 00020             用户组具可写入权限
S_IXGRP 00010             用户组具可执行权限
S_IROTH 00004             其他用户具可读取权限
S_IWOTH 00002             其他用户具可写入权限
S_IXOTH 00001             其他用户具可执行权限

  上述的文件类型在POSIX中定义了检查这些类型的宏定义:

S_ISLNK(st_mode):是否是一个连接.
S_ISREG(st_mode):是否是一个常规文件.
S_ISDIR(st_mode):是否是一个目录
S_ISCHR(st_mode):是否是一个字符设备.
S_ISBLK(st_mode):是否是一个块设备
S_ISFIFO(st_mode):是否 是一个FIFO文件.
S_ISSOCK(st_mode):是否是一个SOCKET文件

  若一目录具有 sticky(S_ISVTX),则表示在此目录下的文件只能被该文件所有者、此目录所有者或 root 来删除或改名。

struct statfs {
    long    f_type;          //文件系统类型
    long    f_bsize;         //块大小
    long    f_blocks;        //块多少
    long    f_bfree;         //空闲的块
    long    f_bavail;        //可用块
    long    f_files;         //总文件节点
    long    f_ffree;         //空闲文件节点
    fsid_t f_fsid;           //文件系统id
    long    f_namelen;       //文件名的最大长度
    long    f_spare[6];      //spare for later
};

六 文件读写步骤

  1、创建文件描述符,open() 函数;
  2、定义struct stat buf
  3、获取文件状态,fstat()函数,stat()函数;
  4、读写操作。

七 非阻塞读取

  对于文件的阻塞模式还是非阻塞模式:
  方法1、open 时,使用 O_NONBLOCK
  方法2、fcntl 设置,使用 F_SETFL,flags|O_NONBLOCK

八 在文件中间添加内容

#include <stdio.h>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
  int fd = open("ttt", O_RDWR);
  if(fd == -1)
  {
     printf("open faild!\n");
  }
  
  struct stat sb;
  fstat(fd, &sb);
  char buff[200] = "\0";
  lseek(fd, 4, SEEK_SET);
  int i = read(fd, buff, sb.st_size - 4);
  cout << buff << endl;   
    
  lseek(fd, 4, SEEK_SET);
  write(fd, "wdxwan!!!", 9);
  write(fd, buff, i);
  read(fd, buff, sb.st_size + 9 + i);
  cout << buff << endl;   
  close(fd);
  return 0;
 } 

九 文件特定内容的替换

#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <sys/stat.h>

#define MAXSIZE 50000

char* substr(const char*str, unsigned long start, unsigned long end);
void replace(const char *path, const char *ori, const char *dst);

int main(int argc, char** argv)
{
   replace(argv[1], " )", ")");
   replace(argv[1], "( ", "(");
   replace(argv[1], "[ ", "[");
   replace(argv[1], " ]", "]");
   replace(argv[1], " }", "}");
   replace(argv[1], "{ ", "{");
   return 0;
}

char* substr(const char*str, unsigned long start, unsigned long end)
{
   unsigned long n = end - start;
   static char stbuf[20];
   strncpy(stbuf, str + start, n);
   stbuf[n] = 0;
   return stbuf;
}

void replace(const char *path, const char *ori, const char *dst)
{
   int fd = open(path, O_RDWR);
   if(fd == -1)
   {
      printf("open file faild!\n");
   }
   struct stat sb;
   fstat(fd, &sb);

   unsigned char *start = (unsigned char *)mmap(NULL, sb.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
   if(start == MAP_FAILED)
   {
      printf("映射失败,文件过大或者没有权限");
      return;
   }

   unsigned char buf[MAXSIZE];
   unsigned long i = 0;

   unsigned long dSize = sb.st_size;

   unsigned char buffer2[MAXSIZE];
   memccpy(buffer2, start, sizeof(unsigned char), sb.st_size);

   while (i < dSize)
   {
      if(0 == strcmp(ori, substr((char *)buffer2, i, i + strlen(ori))))
      {
         lseek(fd, i + strlen(ori), SEEK_SET);
         int m = read(fd, buf, dSize - i - strlen(ori));

         lseek(fd, i + strlen(dst), SEEK_SET);
         ftruncate(fd, i + strlen(ori));
         write(fd, buf, m);

         lseek(fd, i, SEEK_SET);
         write(fd, dst, strlen(dst));

         dSize = dSize - strlen(ori) + strlen(dst);
         lseek(fd, 0, SEEK_SET);
         memset(buffer2, 0, MAXSIZE);
         read(fd, buffer2, dSize);

      }
      ++i;
   }
   close(fd);
   munmap(start, sb.st_size); /* 解除映射 */
}

十 fcntl函数

  fcntl(File Control)函数是一个 POSIX 标准定义的系统调用,用于对文件描述符进行各种控制操作。这个函数通常用于实现对文件描述符的非阻塞设置、文件锁、获取或修改文件描述符的属性等操作。在网络编程中,它常常用于设置套接字为非阻塞模式。
  以下是 fcntl 函数的基本原型:

#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
  • fd: 需要进行操作的文件描述符。
  • cmd: 需要执行的操作命令,可以是 F_GETFL(获取文件状态标志)、F_SETFL(设置文件状态标志)等。
    • F_DUPFD:复制文件描述符,返回一个新的文件描述符。
    • F_GETFD:获取文件描述符标志。
    • F_SETFD:设置文件描述符标志。
    • F_GETFL:获取文件状态标志。
    • F_SETFL:设置文件状态标志。
    • F_GETLK:获取记录锁的信息。
    • F_SETLK:设置或释放记录锁。
    • F_SETLKW:阻塞地设置或释放记录锁。
  • arg: 根据不同的命令可能需要的参数。

  示例用法:

  1. 获取和设置文件状态标志:
// 获取文件状态标志
int flags = fcntl(fd, F_GETFL);

// 设置文件状态标志为非阻塞模式
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
  1. 复制文件描述符:
int new_fd = fcntl(old_fd, F_DUPFD, 0);
  1. 获取和设置文件描述符标志:
// 获取文件描述符标志
int fd_flags = fcntl(fd, F_GETFD);

// 设置文件描述符标志
fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC);

1 设置文件锁

  在 linux 操作系统中,当多个进程同时操作同一个文件时,该文件的最后状态取决于写该文件的最后一个进程。但是对于有些应用程序,如数据库,进程有时需要确保它在一个时刻只能被一个进程/线程写,这时候就要用到文件锁。文件锁也被称为记录锁(record lock)
  文件锁的作用:当一个进程正在读写文件的某一区域时,其他进程就不能对文件的这个区域进行修改操作。
  提供文件锁操作的函数有两个:flock()fcntl(),其中,flock() 函数是对文件锁操作的早期版本,它只能对整个文件加锁,不能对文件中的某一个区域加锁。fcntl() 函数是在 flock() 函数的基础上构造出来的,它允许对文件中任意字节区域加锁,短至一个字节,长至整个文件。
  在这种情况下,fcntl 函数的原型如下所示:

int fcntl(int fd, int cmd, struct flock *lock);     

  flock 结构体定义如下:

struct flock {
	short	l_type;
	short	l_whence;
	off_t	l_start;
	off_t	l_len;
	pid_t l_pid;
};
  • l_type:锁类型,F_RDLCK(共享读锁)、F_WRLCK(独占性写锁)或 F_UNLCK(解锁)
  • l_pid:持有锁的进程ID。(仅由于 cmd = F_GETLK 时返回)
  • l_len:区域字节长度。
  • l_start:参数 l_start 的含义跟参数 l_whence 的值有关
  • l_whence = SEEK_SET 时,则将文件的偏移量设置为文件开始处 l_start 个字节,参数 l_start 必须为非负数。
  • l_whence = SEEK_CUR 时,则将文件的偏移量设置为 当前文件的偏移量 + l_start 个字节,参数 l_start 可以为正数,也可以为负数,只要最终得到的文件偏移量不会小于文件的起始位置(字节0)即可。
  • l_whence = SEEK_END 时,则将文件的偏移量设置为 文件长度 + l_start 个字节,参数 l_start 可以为正数,也可以为负数,只要最终得到的文件偏移量不会小于文件的起始位置(字节0)即可。
  • l_startl_whencel_len 这三个参数一起指定了待加锁的字节范围。如果 l_len = 0,则表示锁的范围会被扩展到最大的可能偏移量。这意味着对从由 l_start 和 l_whence 确定的起始位置开始,不管向该文件中追加写了多少数据,它们都处于锁的范围内。
  • 为了对整个文件加锁,可以设置 l_start = 0, l_whence = SEEK_SET, l_len = 0;
  • 锁可以在当前文件尾端处开始或越过文件尾端处开始,但不能在文件起始位置之前开始。
  • 一般来说,应用程序应该只对所需的最小字节范围进行加锁,这样其他进程就能够同时对同一个文件的不同区域进行加锁,进而取得更大的并发性。
    (1)cmd = F_GETLK 时,检测能否获取 lock 指针指定的文件区域的锁(lock指针指向的结构中的 l_type 字段的值必须是 F_RDLCK 或 F_WRLCK)。如果允许加锁(即在指定的文件区域上不存在不兼容的锁),那么 l_type 字段会返回 F_UNLCK,剩余的字段保持不变。如果在指定的文件区域上存在不兼容的锁(即不能加锁),那么 lock 会返回不兼容的锁的所有相关信息(如果有多把不兼容的锁的话,会返回其中一把不兼容的锁的所有相关信息,但不确定会返回哪一把) 。
    (2)cmd = F_SETLK 时,给 fd 引用的文件加锁(lock 指针指定的文件区域的锁)。如果另一个进程持有了一把待加锁的区域中任意部分上的不兼容的锁,fcntl() 就会失败并返回 -1,并设置 errno 为 EAGAIN,有的系统也可能设置 errno 为 EACCES。
    (3)cmd = F_SETLKW 时,这个命令参数是 F_SETLK 命令参数的阻塞版本(命令名中的 W 表示等待 wait)。如果另一个进程持有了一把待加锁的区域中任意部分上的不兼容的锁,那么调用进程会设置为休眠。如果请求创建的锁已经可用,或者休眠由信号中断,该进程将会被唤醒。
      需要注意的是,用 F_GETLK 测试能否建立一把锁,然后用 F_SETLK 或 F_SETLKW 企图建立那把锁,这两者不是一个原子操作,因此不能保证在这两次 fcntl() 调用之间会不会有另一个进程插入并建立了一把相同的锁。如果不希望在等待锁变成可用时产生阻塞,就必须处理 F_SETLK 返回的可能出错。
      例子
      以下程序功能:用 fcntl() 函数对一个对文件进行读写时,先加锁,为了测试需要,读写完后不解锁,需要输入 u 命令进行解锁。
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/stat.h>
 
#define BUF_SIZE 4096
 
/*
is_errno: 是否要打印系统的出错信息,0:否 1:是
is_exit: 打印出错信息后,是否立马终止进程,0:否 1:是
*/
void err_msg(const char is_errno, const char is_exit, const char *format, ...);
void close_fd(const int fd, char is_exit);
void cmd_r(const int fd, char* const ibuf, const char *filename);
void cmd_w(const int fd, char* const ibuf, const char *filename);
 
int main(int argc, char *argv[])
{
    if (argc != 2)
        err_msg(0, 1, "%s filename", argv[0]);
 
    const char *filename = argv[1];
    int fd = open(argv[1], O_RDWR | O_APPEND);
    if (fd == -1)
        err_msg(1, 1, "open error");
    
    printf("open %s success\n", filename);
 
    char ibuf[BUF_SIZE] = ""; // stdin buf
    
    while (1) {
        printf("please input r or w or quit\n", filename);
        fgets(ibuf, BUF_SIZE, stdin); // 从 stdin 中读入一行
        if (ibuf[strlen(ibuf) - 1] == '\n') 
            ibuf[strlen(ibuf) - 1] = 0;
 
        if (strcmp(ibuf, "r") == 0) {
            cmd_r(fd, ibuf, filename);
        } else if (strcmp(ibuf, "w") == 0) {
            cmd_w(fd, ibuf, filename);
        } else if (strcmp(ibuf, "quit") == 0) {
            break;
        }
    }
 
    close_fd(fd, 1);
 
    return 0;
}
 
void err_msg(const char is_errno, const char is_exit, const char *format, ...) {
    char ibuf[BUF_SIZE] = "";
    va_list ap;
    va_start(ap, format);
    vsnprintf(ibuf, BUF_SIZE - 1, format, ap); // 因为最后一个字节放换行符'\n',所以 BUF_SIZE-1
    if (is_errno)
        snprintf(ibuf + strlen(ibuf), BUF_SIZE - strlen(ibuf) - 1, ": %s", strerror(errno));
    strcat(ibuf, "\n");
    fflush(stdout); // 刷新stdout
    fputs(ibuf, stderr); // 错误信息写到stderr
    fflush(NULL); // 参数为 NULL, fflush() 会刷新所有 stdio
    va_end(ap);
 
    if (is_exit)
        exit(EXIT_FAILURE);
}
 
void close_fd(const int fd, char is_exit) { // is_exit: 0 (false),1(true)
    if (-1 == close(fd)) {
        perror("close error");
        
        if (is_exit)
            exit(EXIT_FAILURE);
    }
}
 
void cmd_r(const int fd, char* const ibuf, const char *filename)
{
    struct flock lock;
    /* 对整个文件加锁 */
    lock.l_start = 0;
    lock.l_whence = SEEK_SET;
    lock.l_len = 0; 
    lock.l_type = F_RDLCK;
    int res = fcntl(fd, F_GETLK, &lock);
    printf("res = %d\n", res);
    if (lock.l_type == F_UNLCK) {
        lock.l_type = F_RDLCK;
        if (fcntl(fd, F_SETLK, &lock) == -1) {
            err_msg(0, 0, "lock %s fail, please try again later", filename);
        } else {
            lseek(fd, 0, SEEK_SET); // 将文件偏移量设置到文件开始处
 
            printf("******* 文件内容 *******\n");
            ssize_t nread = 0;
            char rbuf[BUF_SIZE] = "";
            while ((nread = read(fd, rbuf, BUF_SIZE)) > 0) {
                if (write(STDOUT_FILENO, rbuf, nread) != nread)
                    err_msg(1, 1, "write error");
            }
            printf("******* 文件内容 *******\n");
            
            while (1)
            {
                printf("%s 已经被该进程共享写锁锁住,请输入 u 进行解锁\n", filename);
                fgets(ibuf, BUF_SIZE, stdin);
                if (ibuf[strlen(ibuf) - 1] == '\n')
                    ibuf[strlen(ibuf) - 1] = 0;
                if (strcmp(ibuf, "u") == 0) {
                    lock.l_type = F_UNLCK;
                    if (fcntl(fd, F_SETLK, &lock) == -1) {
                        err_msg(1, 0, "unlock %s fail, please try again later.", filename);
                    } else {
                        break;
                    }
                }
            }
        }
    } else {
        err_msg(0, 0, "getlock %s fail, please try again later", filename);
    }
}
 
void cmd_w(const int fd, char* const ibuf, const char *filename) {
    struct flock lock;
    /* 对整个文件加锁 */
    lock.l_start = 0;
    lock.l_whence = SEEK_SET;
    lock.l_len = 0; 
    lock.l_type = F_WRLCK;
    int res = fcntl(fd, F_GETLK, &lock);
    printf("res = %d\n", res);
    if (lock.l_type == F_UNLCK) {
        lock.l_type = F_WRLCK;
        if (fcntl(fd, F_SETLK, &lock) == -1) {
            err_msg(0, 0, "lock %s fail, please try again later", filename);
        } else {
            lseek(fd, 0, SEEK_END); // 将文件偏移量设置到文件末尾
            
            char buf[] = "wrlck\n";
            size_t len = strlen(buf);
            if (write(fd, buf, len) != len)
                err_msg(1, 1, "write error");
            
            buf[strlen(buf) - 1] = 0;
            printf("%s 已写入文件\n", buf);
            
            while (1)
            {
                printf("%s 已经被该进程独占写锁锁住,请输入 u 进行解锁\n", filename);
                fgets(ibuf, BUF_SIZE, stdin);
                if (ibuf[strlen(ibuf) - 1] == '\n')
                    ibuf[strlen(ibuf) - 1] = 0;
                if (strcmp(ibuf, "u") == 0) {
                    lock.l_type = F_UNLCK;
                    if (fcntl(fd, F_SETLK, &lock) == -1) {
                        err_msg(1, 0, "unlock %s fail, please try again later.", filename);
                    } else {
                        break;
                    }
                }
            }
        }
    } else {
        err_msg(0, 0, "getlock %s fail, please try again later", filename);
    }
}

十一 windows句柄操作

1 CreateFile

  CreateFile 函数是 Windows API 中用于创建文件、目录、管道、控制台输入/输出缓冲区或远程 IO 设备的函数。它位于 <windows.h> 头文件中,函数原型如下:

HANDLE CreateFile(
  LPCTSTR lpFileName,
  DWORD dwDesiredAccess,
  DWORD dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD dwCreationDisposition,
  DWORD dwFlagsAndAttributes,
  HANDLE hTemplateFile
);

  CreateFile 函数的参数说明如下:

  • lpFileName:指定文件名的字符串指针。
  • dwDesiredAccess:指定文件的访问模式。可以是下列值之一:
    • GENERIC_READ:可以读取文件。
    • GENERIC_WRITE:可以写入文件。
    • GENERIC_EXECUTE:可以执行文件。
  • dwShareMode:指定文件的共享模式。可以是下列值之一:
    • FILE_SHARE_READ:允许其他进程读取文件。
    • FILE_SHARE_WRITE:允许其他进程写入文件。
    • FILE_SHARE_DELETE:允许其他进程删除文件。
  • lpSecurityAttributes:指定文件的安全属性。如果设为 NULL,则使用默认安全描述符。
  • dwCreationDisposition:指定如何创建文件,对于文件以外的设备,此参数通常设置为OPEN_EXISTING,如果指定的文件或设备不存在,则函数失败并且最后一个错误代码设置为 ERROR_FILE_NOT_FOUND (2)。可以是下列值之一:
    • CREATE_NEW:如果文件不存在,则创建新文件,如文件存在则会出错,返回 ERROR_FILE_EXISTS 错误。
    • CREATE_ALWAYS:总是创建一个新文件。
    • TRUNCATE_EXISTING:如果文件存在,则截断文件,将现有文件缩短为零长度;否则,返回ERROR_FILE_NOT_FOUND 错误。
    • FILE_SHARE_READ:启用对文件或设备的后续打开操作以请求读取访问权限。
    • OPEN_EXISTING:打开一个已经存在的设备,如文件不存在则创建它。
  • dwFlagsAndAttributes:指定文件的属性。可以是下列值之一:
    • FILE_ATTRIBUTE_NORMAL:普通文件。
    • FILE_ATTRIBUTE_READONLY:只读文件。
    • FILE_ATTRIBUTE_HIDDEN:隐藏文件。
    • FILE_ATTRIBUTE_SYSTEM:系统文件。
  • hTemplateFile:指定用于创建文件的模板文件句柄。如果没有模板文件,则设为 NULL。

  如果 CreateFile 函数调用成功,则返回一个文件句柄;如果调用失败,则返回一个特殊的值 INVALID_HANDLE_VALUE,并可以使用 GetLastError 函数获取错误代码。
  示例:

#include <windows.h>
#include <iostream>
int main() {
  // 指定文件名和访问模式
  HANDLE hFile = CreateFile(L"test.txt",
                             GENERIC_WRITE,
                             0,  // 不共享
                             NULL,  // 默认安全描述符
                             CREATE_ALWAYS,  // 如果文件存在,则覆盖
                             FILE_ATTRIBUTE_NORMAL,  // 文件属性
                             NULL);  // 没有模板文件
  if (hFile == INVALID_HANDLE_VALUE) {
    std::cerr << "Error creating file: " << GetLastError() << std::endl;
    return 1;
  }
  std::cout << "File created successfully." << std::endl;
  // 关闭文件句柄
  CloseHandle(hFile);
  return 0;
}

2 清空文件

SetFilePointer(h_Log, 0, FILE_BEGIN);
SetEndOfFile(h_Log);

3 打开文件

CreateFile函数提供了两种模式来实现“如果不存在则创建,‌如果存在则打开”的功能,‌分别是CREATE_ALWAYS和OPEN_ALWAYS。‌

CREATE_ALWAYS:‌如果指定的文件已经存在,‌则打开该文件;‌如果文件不存在,‌则创建一个新文件。‌这个选项允许在文件已经存在的情况下打开它,‌同时也在文件不存在时创建新文件。‌如果文件已经存在,‌CREATE_ALWAYS会覆盖原有的文件内容。‌
OPEN_ALWAYS:‌如果文件存在,‌则打开该文件;‌如果文件不存在,‌则创建它。‌这个选项与CREATE_ALWAYS类似,‌但它在文件不存在时创建新文件的行为与CREATE_NEW不同,‌因为OPEN_ALWAYS在文件不存在时会创建它,‌而CREATE_NEW在文件不存在时不会做任何操作。‌

;