1.文件IO
文件IO:是通过系统调用实现的,只要调用文件IO的函数接口就会从用户空间进入到内核空间,系统调用的效率比较低,因为没有缓冲区的概念。文件IO常见的接口open,read,write,close,lseek等
1.1什么是文件描述符
文件描述符:文件描述符本身是一个大于等于0的整数,使用open打开文件之后返回文件描述符,以后在操作文件的时候通过这个文件描述符完成。在一个正在执行的程序中文件描述的范围是0-1023(1024个),可以通过ulimit -a查看限制,这个限制值是可以修改的(ulimit -n 2048)。文件描述符的分配原则是最小未使用。在一个正在执行的程序中默认已经有三个文件描述符了
ulimit -a
open files (-n) 1024
0:标准输入文件描述符
1:标准输出文件描述符
2:标准错误文件描述符
#include <head.h>
int main(int argc,const char * argv[])
{
//在标准IO的文件指针结构体中其实默认包含文件描述符_fileno
printf("%d\n",stdin->_fileno); //标准输入文件描述符 0
printf("%d\n",stdout->_fileno); //标准输出文件描述符 1
printf("%d\n",stderr->_fileno); //标准出错文件描述符 2
return 0;
}
1.2open函数的使用
#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. 读写的方式打开文件
O_APPEND 以追加的方式打开文件
O_CREAT 创建文件,必须填写第三个参数,第三个参数代表创建这个文件的权限
O_TRUNC 清空文件
O_EXCL 确保文件存在,跟O_CREAT结合使用,如果文件存在就返回EEXIST已存在错误,
如果文件不存在正常创建
@mode:只有在第二个flags参数写O_CREAT的时候才需要填充,第三个参数代表创建文件的权限
mode & (~umask) //umask的值0002 umask 0000
~umask=0664 ~umask=0666
文件的最大权限是0666 mode & 0664 mode & 0666
返回值:成功返回文件描述符,失败返回-1置位错误码
标准IO | 文件IO | 含义 |
---|---|---|
“r” | O_RDONLY | 只读的方式打开文件 |
“r+” | O_RDWR | 以读写的方式打开文件 |
“w” | O_WRONLY|O_CREAT|O_TRUNC,0664 | 以只写的方式打开文件,如果文件存在就清空,如果文件不存在就创建 |
“w+” | O_RDWR|O_CREAT|O_TRUNC,0664 | 以读写的方式打开文件,如果文件存在就清空,如果文件不存在就创建 |
“a” | O_WRONLY|O_APPEND|O_CREAT,0664 | 以追加(结尾写)的方式打开文件,如果文件不存在就创建文件 |
“a+” | O_RDWR|O_APPEND|O_CREAT,0664 | 以追加(结尾写,开头读)的方式打开文件,如果文件不存在就创建文件。 |
1.2.1open函数的使用(w+)
head.h
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#define PRINT_ERR(msg) do{ \
perror(msg);\
return -1;\
}while(0)
#endif
#include <head.h>
int main(int argc,const char * argv[])
{
int fd;
fd = open("hello.c",O_RDWR|O_CREAT|O_TRUNC,0666);
if(fd == -1)
PRINT_ERR("open error");
return 0;
}
1.2.2open函数的使用(O_EXCL)
#include <head.h>
int main(int argc, const char *argv[])
{
int fd;
//读写的方式打开文件,如果文件不存在就创建文件
//如果文件存在就报文件已存在的错误EEXIST
fd = open("hello.c", O_RDWR | O_CREAT | O_EXCL, 0666);
if (fd == -1)
{
if (errno == EEXIST)
{
//文件已经存在,不需要创建,直接打开
printf("文件已经存在,不需要创建,直接打开\n");
fd = open("hello.c", O_RDWR, 0666);
}
else
{
PRINT_ERR("open error");
}
}
return 0;
}
1.3close函数的使用
#include <unistd.h>
int close(int fd);
功能:关闭文件
参数:
@fd:文件描述符
返回值:成功返回0,失败返回-1置位错误码
#include <head.h>
int main(int argc, const char *argv[])
{
int fd;
//读写的方式打开文件,如果文件不存在就创建文件
//如果文件存在就报文件已存在的错误EEXIST
fd = open("hello.c", O_RDWR | O_CREAT | O_EXCL, 0666);
if (fd == -1)
{
if (errno == EEXIST)
{
//文件已经存在,不需要创建,直接打开
printf("文件已经存在,不需要创建,直接打开\n");
fd = open("hello.c", O_RDWR, 0666);
}
else
{
PRINT_ERR("open error");
}
}
printf("fd = %d\n",fd); //这里的fd是3
//每一个正在执行的程序都有0-1023这1024个文件描述符
//关闭这个程序中的文件描述符,不影响其他程序的文件描述符。
close(fd);
return 0;
}
1.4read函数的使用
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:从文件中读取数据到buf中
参数:
@fd:文件描述符
@buf:保存读取到的数据的首地址
@count:读取的大小(单位是字节)
返回值:成功返回读取到的字符的个数,0表示读取到了结尾
失败返回-1,置位错误码
1.5write函数的使用
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:将buf中的数据写入到文件中
参数:
@fd:文件描述符
@buf:写数据的首地址
@count:写的大小(单位字节)
返回值:成功返回写入字符的个数,失败返回-1置位错误码
1.5.1使用read/write实现文件的拷贝
#include <head.h>
int main(int argc,const char * argv[])
{
int sfd,dfd;
char buf[128];
int ret;
if(argc != 3){
//如果错误,向2标准出错文件描述符中写入错误的信息
write(2,"input error,try again\n",strlen("input error,try again\n"));
write(2,"usage:./a.out srcfile destfile\n",strlen("usage:./a.out srcfile destfile\n"));
return -1;
}
if((sfd = open(argv[1],O_RDONLY))==-1)
PRINT_ERR("open src error");
if((dfd = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0664))==-1)
PRINT_ERR("open src error");
//当read读取到数据大于0的时候循环写,如果小于等于0的时候
//要退出循环
while((ret = read(sfd,buf,sizeof(buf)))>0){
write(dfd,buf,ret);
}
close(sfd);
close(dfd);
return 0;
}
2.文件IO中lseek函数
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
功能:修改文件中光标的位置
参数:
@fd:文件描述符
@offset:偏移
>0向后偏移
=0不偏移
<0向前偏移
@whence:从哪开始偏移
SEEK_SET:从开头开始
SEEK_CUR:从当前位置
SEEK_END:从结尾开始
返回值:成功返回光标当前位置到开头的字节数
失败返回(off_t)-1置位错误码 lseek=fseek+ftell
#include <head.h>
int main(int argc,const char * argv[])
{
int fd;
if((fd = open("./milaoshu.bmp",O_RDONLY))==-1)
PRINT_ERR("open error");
//将光标定位到文件的结尾,返回结尾到开头的位置
//这个结果就是图片大小
printf("img size = %ld\n",lseek(fd,0,SEEK_END));
close(fd);
return 0;
}
练习:使用文件IO对图片打马赛克
#include <head.h>
#include <stdlib.h>
typedef struct{
unsigned int img_size; //图片的大小
unsigned int img_width;//图片的宽
unsigned int img_high; //图片的高
unsigned short img_bitcount; //一个像素点占用的bit(24bit)
}image_info_t;
typedef struct{
unsigned char b;
unsigned char g;
unsigned char r;
}point_t;
void show_image_info(image_info_t *info)
{
printf("size = %d,width = %d,high = %d,bitcount = %d\n",
info->img_size,info->img_width,info->img_high,info->img_bitcount);
}
void get_image_info(int fd,image_info_t *info)
{
lseek(fd,2,SEEK_SET);
read(fd,&info->img_size,4);
lseek(fd,18,SEEK_SET);
read(fd,&info->img_width,4);
read(fd,&info->img_high,4);
lseek(fd,2,SEEK_CUR);
read(fd,&info->img_bitcount,2);
}
void copy_image_file(int sfd,int dfd)
{
int ret;
char buf[1024] = {0};
while((ret = read(sfd,buf,sizeof(buf)))>0){
write(dfd,buf,ret);
}
return;
}
void set_image_mosaic(int fd,image_info_t *info,int x,int y)
{
int i,j,k,w;
point_t color = {0,0,0xff};
char *buffer = (char *)malloc((info->img_width)*(info->img_high)*3);
//1.将图像读取回来
lseek(fd,54,SEEK_SET);
read(fd,buffer,(info->img_size-54));
//2.修改
//i:整体的高/10
//j:整体的宽除以10
//k:块的高
//w:块的宽
for(i=0;i<info->img_high/y;i++){
for(j=0;j<info->img_width/x;j++){
//读取小方块中最左上角的像素点
color = *(point_t *)(buffer+(j*3*x)+(i*y*info->img_width*3));
for(k=0;k<y;k++){
for(w=0;w<x;w++){
*(point_t*)(buffer+w*3+(k*info->img_width*3)+
(j*3*x)+(i*y*info->img_width*3)) = color;
}
}
}
}
//3.重新将图像写回去
lseek(fd,54,SEEK_SET);
write(fd,buffer,(info->img_size-54));
free(buffer);
}
int main(int argc, char const *argv[])
{
int sfd,dfd;
int size;
image_info_t info;
char new_name[20] = {0};
if(argc != 2){
fprintf(stderr,"input error,try again\n");
fprintf(stderr,"usage:./a.out xxxx.bmp\n");
return -1;
}
//1.打开文件并拷贝文件 milaoshu.bmp
if((sfd = open(argv[1],O_RDONLY))==-1)
PRINT_ERR("open error");
//构造一个新图片的字符串 new_milaoshu.bmp
snprintf(new_name,sizeof(new_name),"new_%s",argv[1]);
//打开新图片,如果不存在就创建,如果存在就清空
if((dfd = open(new_name,O_RDWR|O_CREAT|O_TRUNC,0664))==-1)
PRINT_ERR("open error");
//图片的拷贝,将milaoshu.bmp-->new_milaoshu.bmp
copy_image_file(sfd,dfd);
//2.获取图片前54个字节中有用的信息
get_image_info(dfd,&info);
show_image_info(&info);
//3.尝试打马赛克
//10,10:代表的是打马赛克每个小方块的大小
//10*3= 30
//10 = 10行
set_image_mosaic(dfd,&info,10,10);
//4.关闭源文件和目标文件
close(sfd);
close(dfd);
return 0;
}
3.文件描述符拷贝
3.1文件描述符的直接赋值
使用变量直接赋值方式拷贝文件描述符,其实文件描述符并没有得到拷贝,只是在程序中多了一个变量,这两个变量对应的是同一个文件描述符。通过文件描述符操作文件两者是相互影响的。
#include <head.h>
int main(int argc,const char * argv[])
{
int fd,fd1;
char buf[10];
if((fd = open("./hello.txt",O_RDWR|O_APPEND|O_CREAT,0664))==-1)
PRINT_ERR("open error");
write(fd,"helloworld\n",strlen("helloworld\n"));
//文件描述符分配的规则是最小为使用原则所以fd是3
//fd1也是3
fd1=fd;
write(fd1,"12345678\n",strlen("12345678\n"));
printf("fd = %d,fd1 = %d\n",fd,fd1);
lseek(fd,3,SEEK_SET);
//因为fd1和fd对应的都是3这个整数值,所以通过fd修改的
//光标的位置一定会影响到fd1
read(fd1,buf,sizeof(buf));
printf("buf = %s\n",buf);
if(close(fd))
PRINT_ERR("close fd error");
if(close(fd1)) //这次关闭文件描述符会出错,因为在程序中只有一个文件描述符是3
PRINT_ERR("close fd1 error");
return 0;
}
3.2文件描述符拷贝之dup函数
int dup(int oldfd);
功能:拷贝oldfd得到一个新的fd
参数:
@oldfd:旧的文件描述符
返回值:成功返回新的文件描述符,失败返回-1置位错误码
/*
dup()系统调用创建文件描述符oldfd的副本,使用编号最低的未使用文件描述符作为新描述符。
成功返回后,旧的和新的文件描述符可以互换使用。它们引用相同的打开文件描述(参见open(2)),
因此共享文件偏移量和文件状态标志;例如,如果通过在其中一个文件描述符上使用lseek(2)修改
文件偏移量,那么另一个文件描述符的偏移量也会改变。这两个文件描述符不共享文件描述符标
志(close-on-exec标志)。close-on-exec标志(FD_CLOEXEC;请参阅fcntl(2)),以了解重复描述
符是关闭的。
*/
#include <head.h>
int main(int argc,const char * argv[])
{
int fd,fd1;
char wbuf[10]="hello123\n";
char rbuf[10];
if((fd = open("./hello.txt",O_RDWR|O_TRUNC|O_CREAT,0664))==-1)
PRINT_ERR("open error");
printf("fd = %d\n",fd);
//dup函数会拷贝生成一个新的文件描述符,
//新旧文件描述符都可以同时操作同一个文件
//这两个文件描述符共用同一个光标,lseek修改其中一个光标的位置
//另外一个文件描述符对应的光标也会该表
//新拷贝出来的文件描述符和旧的文件描述符数值是不同的
fd1=dup(fd);
printf("fd1 = %d\n",fd1);
write(fd,wbuf,strlen(wbuf));
lseek(fd,0,SEEK_SET);
read(fd1,rbuf,sizeof(rbuf));
printf("rbuf = %s\n",rbuf);
if(close(fd))
PRINT_ERR("close fd error");
if(close(fd1))
PRINT_ERR("close fd1 error");
return 0;
}
3.3使用open打开多次同一个文件
使用open没打开一次文件,就会产生一个文件描述符,这些文件描述符之间没有任何的关系,也不共用光标,所以对一个文件描述光标的修改,不会影响另外一个。
#include <head.h>
int main(int argc,const char * argv[])
{
int fd,fd1;
char wbuf[10]="hello123\n";
char rbuf[10];
if((fd = open("./hello.txt",O_RDWR))==-1)
PRINT_ERR("open error");
if((fd1 = open("./hello.txt",O_RDWR))==-1)
PRINT_ERR("open error");
lseek(fd,11,SEEK_SET);
read(fd,rbuf,sizeof(rbuf));
printf("rbuf = %s\n",rbuf);
read(fd1,rbuf,sizeof(rbuf));
printf("rbuf = %s\n",rbuf);
if(close(fd))
PRINT_ERR("close fd error");
if(close(fd1))
PRINT_ERR("close fd1 error");
return 0;
}
3.4文件描述符拷贝之dup2函数
int dup2(int oldfd, int newfd);
功能:将oldfd文件描述符拷贝成新的文件描述符,没有使用最小未分配原则,
如果newfd之前是打开的,就关闭这个newfd。
参数:
@oldfd:旧的文件描述符
@newfd:新的文件描述符
返回值:成功返回新的文件描述符,失败返回-1置位错误码
#include <head.h>
int main(int argc,const char * argv[])
{
int fd;
char buffer[] = "helloworld\n";
if((fd = open("./hello.txt",O_RDWR))==-1)
PRINT_ERR("open error");
//功能:将文件描述符2拷贝成fd,fd是3,由于fd对应之前
//有打开的文件,他会将这个文件关闭,以后使用文件描述符2和3
//都是向标准错误中输出
// printf("fd = %d\n",fd);
// dup2(2,fd);
// write(fd,buffer,strlen(buffer));
// write(2,"1234567\n",strlen("1234567\n"));
// printf("fd = %d\n",fd);
//将fd拷贝成0 1 2,会先关闭0 1 2这三个文件描述符
//以后在向0 1 2 中输入信息,就相当于向文件中输入信息
//如下文件描述符拷贝的意思是:比如在开发服务器的时候
//服务器的执行会脱离某一个终端,以后服务器的输入,输出,错误
//这些信息都会被放入到这个open打开的文件中(日志文件)
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
printf("i am test dup2 func .....\n");
fflush(stdout);
//lseek(fd,0,SEEK_SET);
// 0 1 2 fd这个4个文件描述符都是在向文件中读写内容,共用同一个光标
write(2,"i am test 123456789\n",strlen("i am test 123456789\n"));
return 0;
}
4.文件状态获取(stat)
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
功能:获取文件的属性信息
参数:
@pathname:文件的路径和文件的名
@statbuf:获取到的属性信息的结构体
struct stat {
dev_t st_dev; //磁盘的设备号 (设备号是操作系统识别硬件设备的编号)
ino_t st_ino; //inode(ls -i) 文件系统识别文件的唯一编号
mode_t st_mode; //文件的类型和文件的权限 (man 7 inode)
//文件类型的获取方式
S_IFMT 0170000 //文件类型的掩码
//if((st_mode & S_IFMT)== S_IFREG) <==> S_ISREG(st_mode) 就是普通文件
S_IFSOCK 0140000 socket //S_ISSOCK(m)
S_IFLNK 0120000 symbolic link //S_ISLNK(m)
S_IFREG 0100000 regular file //S_ISREG(m)
S_IFBLK 0060000 block device // S_ISBLK(m)
S_IFDIR 0040000 directory // S_ISDIR(m)
S_IFCHR 0020000 character device //S_ISCHR(m)
S_IFIFO 0010000 FIFO //S_ISFIFO(m)
//文件权限的获取方式
st_mode & 0777 ====>获取到的结果就是文件的权限
nlink_t st_nlink; //文件的硬链接数
uid_t st_uid; //用户的id
gid_t st_gid; //组的id号
dev_t st_rdev; //如果是设备文件,显示的是文件的设备号 /dev/input/mouse0
13<<20|32;
off_t st_size; //文件的大小,单位是字节
blksize_t st_blksize; //每一个block的大小 (512字节)
blkcnt_t st_blocks; //block的个数
struct timespec st_atim; //最后一次访问的时间
struct timespec st_mtim; //最后一次修改的时间
struct timespec st_ctim; //最后一次文件状态改变的时间
};
返回值:成功返回0,失败返回-1置位错误码
4.1获取文件的属性信息实例
#include <head.h>
int main(int argc,const char * argv[])
{
struct stat st;
if(stat("./hello.txt",&st)==-1)
PRINT_ERR("get file stat error");
printf("type = %#o,mode = %#o,size=%ld,inode=%ld\n",
st.st_mode&S_IFMT,st.st_mode& 0777,st.st_size,st.st_ino);
return 0;
}
4.2获取文件状态的练习
要求:请向程序输入一个文件名,将这个文件的类型(文件,目录…),权限,文件的inode号等信息输出
#include <head.h>
int main(int argc, const char *argv[])
{
struct stat st;
if (argc != 2)
{
fprintf(stderr, "input error,tryagain\n");
fprintf(stderr, "usage:./a.out pathname\n");
return -1;
}
if (stat(argv[1], &st) == -1)
PRINT_ERR("get file stat error");
printf("%#o\n",st.st_mode & S_IFMT);
switch (st.st_mode & S_IFMT)
{
case 0140000:
printf("这是套接字文件\n");
break;
case 0120000:
printf("软连接文件\n");
break;
case 0100000:
printf("这是普通文件\n");
break;
case 0060000:
printf("这是块设备文件\n");
break;
case 0040000:
printf("这是目录文件\n");
break;
case 0020000:
printf("这是字符设备文件\n");
break;
case 0010000:
printf("这是管道文件\n");
break;
default:
printf("文件类型错误...\n");
return -1;
}
printf("mode = %#o,size=%ld,inode=%ld\n",
st.st_mode & 0777, st.st_size, st.st_ino);
return 0;
}
5.对目录的操作
5.1打开目录(opendir)
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
功能:打开一个目录
参数:
@name:目录名
返回值:成功返回目录的指针,失败返回NULL置位错误码
5.2读取目录下的内容(readdir)
struct dirent *readdir(DIR *dirp);
功能:读取目录下的内容
参数:
@dirp:目录指针
返回值:成功返回结构体指针,失败返回或者到目录结尾返回NULL,通过errno来区分
struct dirent {
ino_t d_ino; //inode号
off_t d_off; //偏移
unsigned short d_reclen; //结构体大小
unsigned char d_type; //文件的类型
char d_name[256]; //文件名
};
5.3关闭目录(closedir)
int closedir(DIR *dirp);
功能:关闭目录
参数:
@dirp:目录指针
返回值:成功返回0,失败返回-1置位错误码
5.4目录操作的实例
#include <head.h>
#include <dirent.h>
int main(int argc,const char * argv[])
{
DIR* dir;
struct dirent *dt;
if((dir = opendir("./"))==NULL)
PRINT_ERR("opendir error");
errno=0;
while((dt=readdir(dir))!=NULL){
printf("name=%-15s,type=%#o,struct_size=%d,inode=%ld\n",
dt->d_name,dt->d_type,dt->d_reclen,dt->d_ino);
}
if(errno){
PRINT_ERR("read dir error");
}
closedir(dir);
return 0;
}
5.5编写类似下载文件的实例
在程序中指定一个服务的目录,在程序执行的时候输入文件名,如果文件存在打印这个文件的信息,如果文件不存在打印文件不存在(重新输入)。
//文件的类型
char d_name[256]; //文件名
};
## 5.3关闭目录(closedir)
```c
int closedir(DIR *dirp);
功能:关闭目录
参数:
@dirp:目录指针
返回值:成功返回0,失败返回-1置位错误码
5.4目录操作的实例
#include <head.h>
#include <dirent.h>
int main(int argc,const char * argv[])
{
DIR* dir;
struct dirent *dt;
if((dir = opendir("./"))==NULL)
PRINT_ERR("opendir error");
errno=0;
while((dt=readdir(dir))!=NULL){
printf("name=%-15s,type=%#o,struct_size=%d,inode=%ld\n",
dt->d_name,dt->d_type,dt->d_reclen,dt->d_ino);
}
if(errno){
PRINT_ERR("read dir error");
}
closedir(dir);
return 0;
}
5.5编写类似下载文件的实例
在程序中指定一个服务的目录,在程序执行的时候输入文件名,如果文件存在打印这个文件的信息,如果文件不存在打印文件不存在(重新输入)。