欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

总结文件操作函数(二)-C语言

程序员文章站 2022-07-07 10:39:04
...

格式化读写:

#include <stdio.h>

int printf(const char *format, ...);                   //相当于fprintf(stdout,format,…);

int scanf(const char *format, …);

int fprintf(FILE *stream, const char *format, ...);      //中间的參数为写入文件的格式

int fscanf(FILE *stream, const char *format, …);  //中间为从文件里读取的格式

int sprintf(char *str, const char *format, ...);         // eg:sprintf(buf,”the string is;%s”,str);

int sscanf(char *str, const char *format, …);        //从字符串中格式化读取

以f开头的将格式化后的字符串写入到文件流stream中 

以s开头的将格式化后的字符串写入到字符串str

 

内核为每一个进程维护一个已打开文件的记录表,文件描写叙述符是一个较小的正整数(0—1023),它代表记录表的一项,通过文件描写叙述符和一组基于文件描写叙述符的文件操作函数。就能够实现对文件的读、写、创建、删除等操作。经常使用基于文件描写叙述符的函数有open(打开)、creat(创建)、close(关闭)、read(读取)、write(写入)、ftruncate改变文件大小)、lseek(定位)、fsync(同步)、fstat(获取文件状态)、fchmod(权限)、flock(加锁)、fcntl(控制文件属性)、dup(复制)、dup2、select和ioctl。基于文件描写叙述符的文件操作并不是ANSI C的函数。

此类函数打开文件后将文件名称转化为当前最小可用的文件描写叙述符,0,1,2已经被占用!

#include <sys/types.h>   //头文件

#include <sys/stat.h>

#include <fcntl.h> //flag 经常使用的包含O_RDONLY,O_WRONLY,O_CREAT仅仅读仅仅写创建

int open(const char *pathname, int flags); //文件名称  打开方式

int open(const char *pathname, int flags, mode_t mode);//文件名称  打开方式  权限

open(pathname,O_CREAT|O_WRONLY,mode; //创建并读取文件

open()函数出错时返回-1。相关參数例如以下:

flags和mode都是一组掩码的合成值,flags表示打开或创建的方式,mode表示文件的訪问权限。


通过文件描写叙述符读写文件

函数原型为:

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);//文件描写叙述词  缓冲区  长度 数据从文件读到buf

ssize_t write(int fd, const void *buf, size_t count);  //buf中字符串写入文件

fd=1。write表示将文件从标准输出流输出,fd=0,read表示从标准输入流读入到buf

对于read和write函数,出错返回-1,读取完了之后,返回0。 其它情况返回读写的个数(字节数)。

 

获取文件信息函数:

#include <sys/stat.h> 

int stat(const char *file_name, struct stat *buf); //通过文件名称获取文件信息,并保存在buf所指的结构体stat 

int fstat(int fd, struct stat *buf);   //文件描写叙述词   stat结构体指针

返回值:     运行成功则返回0。失败返回-1,错误代码存于errno(须要include <errno.h>

通过man stat查找结构体中包括文件的详细信息

 

select函数。眼下本人多用于函数读写动态监听:

#include <sys/select.h>

#include <sys/time.h>

int select(int maxfd, fd_set *readset,fd_set *writeset, fd_set *exceptionset, const struct timeval * timeout);

返回:就绪描写叙述字的正数目,0——超时,-1——出错

參数解释:

maxfd 最大的文件描写叙述符(其值应该为最大的文件描写叙述符字 + 1

readset: 内核读操作的描写叙述符字集合

writeset:内核写操作的描写叙述符字集合

exceptionset:内核异常操作的描写叙述符字集合

timeout:等待描写叙述符就绪须要多少时间。NULL代表永远等下去,一个固定值代表等待固定时间,0代表根本不等待,检查描写叙述字之后马上返回

当中readset、writeset、exceptionset都是fd_set集合。该集合的相关操作例如以下:

void FD_ZERO(fd_set *fdset);  /* 将全部fd清零 */

void FD_SET(int fd, fd_set *fdset);  /* 添加一个fd */放入监听集合

void FD_CLR(int fd, fd_set *fdset);  /* 删除一个fd */

int FD_ISSET(int fd, fd_set *fdset);  /* 推断一个fd是否为1 */

一般来说,在使用select函数之前,首先要使用FD_ZERO和FD_SET来初始化文件描写叙述符集,在使用select函数时,会依据设置时间測试set集合中各个描写叙述符的变化,某个描写叙述符发生变化,会将描写叙述符变为1。set变化说明当前并未堵塞。

可循环使用FD_ISSET測试描写叙述符集,測试描写叙述符是否为1。这样就比較好理解了。在运行完对相关文件描写叙述符之后。使用FD_CLR来清除描写叙述符集。

另外,select函数中的timeout是一个struct timeval类型的指针,该结构体例如以下:

struct timeval

{

long tv_sec; /* second */   //

long tv_usec;  /* microsecond */ //微秒

};

 

将文件名称描写叙述符转化为文件指针(多用于不可用fopen打开的管道):

fdopen函数  相关函数:fopen。open,fclose 

#include<stdio.h> 

FILE * fdopen(int fildes,const char * mode); 

函数说明:fdopen取一个现存的文件描写叙述符(我们可能从 o p e n , d u p , d u p 2 , f c n t l或p i p e函数得到此文件描写叙述符)并使一个标准的I / O流与该描写叙述符相结合。

此函数经常使用于由创建管道和网络通信通道函数获得的描写叙述符。

由于这些特殊类型的文件不能用标准I/O fopen函数打开,首先必须先调用设备专用函数以获得一个文件描写叙述符,然后用f d o p e n使一个标准I / O流与该描写叙述符相结合。   

fdopen()会将參数fildes 文件描写叙述词,转换为相应的文件指针后返回。

參数mode 字符串   则代表着文件指针的流形态。此形态必须和原先文件描写叙述词读写模式同样。

   mode有下列几种形态字符串:   r 打开仅仅读文件。该文件必须存在。

   w 打开仅仅写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。      a 以附加的方式打开仅仅写文件。若文件不存在。则会建立该文件。假设文件存在,写入的数据会被加到文件尾。即文件原先的内容会被保留。

   

对于f d o p e n。t y p e參数的意义则稍有差别。由于该描写叙述符已被打开,所以 f d o p e n为写打开并不截断该文件。(比如,若该描写叙述符原来是由 o p e n函数打开的,该文件那时已经存在,则其O _ T R U N C标志将决定是否截短该文件。

f d o p e n函数不能截短它为写而打开的任一文件。 )另外,标准I / O加入方式也不能用于创建该文件(由于如若一个描写叙述符引用一个文件。则该文件一定已经存在)。 



以下贴一下自己联系函数用的代码。第一个是模仿linux ls -l 功能的函数:

//File Name: myls.c
#include <sys/stat.h>
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
void mode_to_str(mode_t md,char *str);
void format(char *time);
int main(int argc,char *argv[])
{
	char *time;

	char str_mode[11];
	DIR *Pdir;
	struct stat mystat;
	struct dirent *myent;
	if(argc==1)
	{
		Pdir=opendir(".");
	}
	else
	{
		Pdir=opendir(argv[1]);
	}
	if(Pdir==NULL)
	{
		perror("open fail:");
		exit(-1);
	}
	printf("no,mode,uid,gid,");
	while((myent = readdir(Pdir))!=NULL)
	{
		memset(&mystat,0,sizeof(mystat));
		stat(myent->d_name,&mystat);
		memset(str_mode,0,11);
		mode_to_str(mystat.st_mode,str_mode);
		time=ctime(&mystat.st_atime);
		time=format(time);
	//	printf("%s\t",myent->d_name);
	//printf("size=%d,type=%c,name=%s\n",myent->d_reclen,myent->d_type,myent->d_name);
		printf("%10s. %2d %8d %8d %5d %s %s\n",str_mode,mystat.st_nlink,getpwuid(mystat.st_uid)->pw_name,getgrgid(mystat.st_gid)->gr_name,mystat.st_size,time,myent->d_name);

	}
	return 0;
}

void mode_to_str(mode_t md,char *str)
{
	strcpy(str,"----------");
	if(S_ISDIR(md))
	{
		str[0]='d';
	}
	if(md & S_IRUSR)
	{
		str[1]='r';
	}
	if(md & S_IWUSR)
	{
		str[2]='w';
	}
	if(md & S_IXUSR)
	{
		str[3]='x';
	}
	if(md & S_IRGRP)
	{
		str[4]='r';
	}
	if(md & S_IWGRP)
	{
		str[5]='w';
	}
	if(md & S_IXGRP)
	{
		str[6]='x';
	}
	if(md & S_IROTH)
	{
		str[7]='r';
	}
	if(md & S_IWOTH)
	{
		str[8]='w';
	}
	if(md & S_IXOTH)
	{
		str[9]='x';
	}
}
char *format(char *time)
{
	char *p;
	while((*time)!=' ')
	{
		time++;
	}
	p=time;
	while((*p)!=':')
	{
		p++;
	}
	p=p+3;
	(*p)='\0';
	return time;
}

像文件操作有标准io流一样,管道也支持文件流模式。

用来创建连接到还有一进程的管道,是通过函数popen和pclose。

函数原型:

#include <stdio.h>

FILE* popen(const char* command, const char* open_mode);

int pclose(FILE* fp);

函数popen():同意一个程序将还有一个程序作为新进程来启动,并能够传递数据给它或者通过它接收数据。

command字符串是要执行的程序名。

open_mode必须是“r”或“w”。

假设open_mode是“r”,被调用程序的输出就能够被调用程序使用。调用程序利用popen函数返回的FILE*文件流指针,就能够通过经常使用的stdio库函数(如fread)来读取被调用程序的输出。假设open_mode是“w”,调用程序就能够用fwrite向被调用程序发送数据,而被调用程序能够在自己的标准输入上读取这些数据。

函数pclose():用popen启动的进程结束时。我们能够用pclose函数关闭与之关联的文件流。

Example1:从标准管道流中读取  打印/etc/profile的内容
	#include <stdio.h>
	int main()
	{
		FILE* fp = popen("cat /etc/profile", "r");
char buf[512] = {0};
while(fgets(buf, sizeof(buf), fp))
{
   			puts(buf);
}
pclose(fp);
return 0;
	}

Example2:写到标准管道流   统计buf单词数
#include<stdio.h>
int main()
{
	char buf[]={"aaa  bbb  ccc  ddd  eee  fff  ggg  hhh"};
	FILE *fp = popen("wc -w", "w");
	fwrite(buf, sizeof(buf), 1, fp);
	pclose(fp);
	return 0;
}

管道函数原型:

#include <unistd.h> 

int pipe(int fds[2]);

管道在程序中用一对文件描写叙述符表示,当中一个文件描写叙述符有可读属性,一个有可写的属性。

fds[0]是读,fds[1]是写。

函数pipe用于创建一个无名管道。假设成功,fds[0]存放可读的文件描写叙述符,fds[1]存放可写文件描写叙述符,而且函数返回0。否则返回-1。

通过调用pipe获取这对打开的文件描写叙述符后。一个进程就能够从fds[0]中读数据。而还有一个进程就能够往fds[1]中写数据。当然两进程间必须有继承关系,才干继承这对打开的文件描写叙述符。

管道不象真正的物理文件,不是持久的,即两进程终止后,管道也自己主动消失了。

演示样例:创建父子进程,创建无名管道,父写子读
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{
int fds[2] = {0};
pipe(fds);
	char szBuf[32] = {'\0'};
	if(fork() == 0){	//表示子进程
		close(fds[1]);	//子进程关闭写
sleep(2);		//确保父进程有时间关闭读,而且往管道中写内容
		if(read(fds[0], szBuf, sizeof(szBuf)) > 0)
			puts(buf);
		close(fds[0]);	//子关闭读
		exit(0);
	}else{  			//表示父进程
		close(fds[0]);	//父关闭读
		write(fds[1],  "hello", 6);
		waitpid(-1, NULL, 0);		//等子关闭读
		//write(fds[1], "world",6);	//此时将会出现“断开的管道”由于子的读已经关闭了
		close(fds[1]);	//父关闭写
		exit(0);
	}
	return 0;
}

使用管道的双人聊天程序:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>
void my_handler(int num)
{
	wait(NULL);
	exit(0);
}
int main(int argc,char *argv[])
{
	int fd_w,fd_r;
	pid_t pid;
	char buf[1024];
//	int *stat=NULL;
	fd_w=open("1to2.fifo",O_WRONLY);
	fd_r=open("2to1.fifo",O_RDONLY);
	if(pid=(fork())>0)//father send
	{
		close(fd_r);

		signal(17,my_handler);//17信号来处理自己;
		FILE *fd=fdopen(fd_w,"w");
		if(fd==NULL)
		{
			perror("1to2 write failed!\n");
		}
		while(memset(buf,0,1024),fgets(buf,1024,stdin)!=NULL)
		{
			fprintf(fd,"trudream:%s",buf);//格式化输入
			fflush(fd);
		}
		fprintf(fd,"bye\n");
		kill(pid,2);//杀儿子
		close(fd_w);
		while(1);
		//	waitpid(-1,NULL,WNOHANG);
		//	wait(NULL);
	}
	else
	{
		close(fd_w);
		while(memset(buf,0,1024),read(fd_r,buf,1024)>0)
		{
			write(1,buf,strlen(buf));
			while((strncmp(buf,"bye",3)==0))
			{
				exit(0);
			}
		}
		close(fd_r);
	}
	return 0;
	
}

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>
void handler(int num)
{
	wait(NULL);
	exit(0);
}
int main(int argc,char *argv[])
{
	int fd_w,fd_r;
	pid_t pid;
	char buf[1024];
	fd_r=open("1to2.fifo",O_RDONLY);
	fd_w=open("2to1.fifo",O_WRONLY);
	if((pid=fork())>0)//father receive
	{
		close(fd_w);
		signal(17,handler);
		while(memset(buf,0,1024),read(fd_r,buf,1024)>0)
		{
			write(1,buf,strlen(buf));
			if((strncmp(buf,"bye",3))==0)
			{
				break;
			}
		}
		close(fd_r);
		kill(pid,2);
		while(1);
	//	waitpid(-1,NULL,WNOHANG);
//		wait(NULL);
	}
	else
	{
		close(fd_r);
		FILE *fd=fdopen(fd_w,"w");
		if(fd==NULL)
		{
			perror("2to1 write failed !\n");
		}
		while(memset(buf,0,1024),fgets(buf,1024,stdin)!=NULL)
		{
			fprintf(fd,"麻麻的微笑:%s",buf);//格式化输入
			fflush(fd);
		}
		fprintf(fd,"bye\n");
		close(fd_w);
	}
	return 0;
}

多人客户server模式聊天:

server端设计:永久server通道(server端仅仅从通道读)>创建set集>server通道描写叙述符增加set,用于推断是否上线#上线则读取进程号。得到相应通道名,获取读写双通道文件描写叙述符#clientport已满强制关闭进程。删除通道>增加set监听变化>循环FD_ISSET()

每一个读管道的变化。若变化则读取管道信息

>推断管道前三个字符是不是bye结束字符串,是,下线操作(关闭进程,关闭读写管道。清-1client列表)

>不是,将管道信息发送到其它管道中(监听client中每个非-1的,进行发送)


//File Name: sever.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <sys/time.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#define NUM 500
typedef struct tag
{
	int s_read;
	int s_write;
}CLIENT_NODE;
int main(int argc,char *argv[])
{
	if(argc!=2)
	{
		printf("No Pipename!\n");
		exit(-1);
	}
	
	int client_pid; //客户端进程id
	int fd_read,fd_write;//记录管道文件描写叙述符
	char buf[1024];
	char fifo_write[128];//这里写端是客户读端,  用于接受客户端创建的管道文件名称
	char fifo_read[128];//读端是客户写端
	CLIENT_NODE client_infor[NUM];//用于放置每一个聊天客户端读写管道
	int fd_sever;//管道名
	memset(client_infor,-1,sizeof(client_infor));
	fd_sever=open(argv[1],O_RDONLY);//仅仅读方式开发server管道
	fd_set read_set,ready_set;//select集
	FD_ZERO(&read_set);
	FD_SET(fd_sever,&read_set);//增加集合
	struct timeval tm;
	while(1)
	{
		tm.tv_sec=0;
		tm.tv_usec=1000;//1微妙
		ready_set=read_set;
		select(1024,&ready_set,NULL,NULL,&tm);//轮询
		if(FD_ISSET(fd_sever,&ready_set))//有人连接server。上线
		{
			memset(buf,0,1024);
			if(read(fd_sever,buf,1024)==0)
			{
				continue;
			}
			client_pid= atoi(buf);  //获取进程号
			printf("client %d on!\n",client_pid);
			sprintf(fifo_read,"%d_write.fifo",client_pid);
			sprintf(fifo_write,"%d_read.fifo",client_pid);
			fd_write=open(fifo_write,O_WRONLY);
			fd_read=open(fifo_read,O_RDONLY);//>>???

??

??? int index; for(index=0;index<NUM;index++) { if(client_infor[index].s_read==-1)//有空地方 { break; } } if(index<NUM)//放到连接列表 { client_infor[index].s_read=fd_read; client_infor[index].s_write=fd_write; } else//client full 强制关闭客户端 { kill(client_pid,0); close(fd_read); close(fd_write); unlink(fifo_write); unlink(fifo_read); } FD_SET(fd_read,&read_set);//上线增加列表 //客户端上线请求结束 } int index; for(index=0;index<NUM;index++) { if(FD_ISSET(client_infor[index].s_read,&ready_set)) { char recvbuf[1024]=""; read(client_infor[index].s_read,recvbuf,1024); if(strncmp(recvbuf,"bye",3)==0)//下线处理 { printf("a client off!\n"); close(client_infor[index].s_read); close(client_infor[index].s_write);//关描写叙述符 FD_CLR(client_infor[index].s_read,&read_set);//从列表中删去 memset(client_infor+index,-1,sizeof(CLIENT_NODE));//重置 } else//非下线信息 { int send_index; for(send_index=0;send_index<NUM;send_index++)//看谁在线 { if(client_infor[index].s_write!=-1) { write(client_infor[send_index].s_write,recvbuf,strlen(recvbuf)); } } } } } } return 0; }

client设计:永久server管道(client仅仅向此通道写入)>读写双通道(使用唯一的进程ID创建两通道)>向server管道写入唯一进程ID >open打开双通道转化为进程描写叙述符>fork()创建父子进程>父进程负责写信息(用于杀死子进程kill(),unlink()删除通道文件。使用signal(),回收子进程,exit()自杀)


//File Name: myclient.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
void handler(int num)
{
	wait(NULL);
	exit(-1);
}
int main(int argc,char *argv[])
{
	if(argc!=2)
	{
		printf("Not Input PipeName!\n");
		exit(-1);
	}
	int fd_sever;//server管道标识符
	int fd_read,fd_write;//读写管道标识符
	char buf[1024];
	char fifo_write[128]="";
	char fifo_read[128]="";
	int pid;
	fd_sever=open(argv[1],O_WRONLY);//向server管道写入信息
	sprintf(fifo_read,"%d_read.fifo",getpid());
	sprintf(fifo_write,"%d_write.fifo",getpid());
	mkfifo(fifo_read,0666);
	mkfifo(fifo_write,0666);
	FILE *fp=fdopen(fd_sever,"w");
	if(fp==NULL)
	{
		perror("sever link failed!\n");
		exit(-1);
	}
	fprintf(fp,"%d",getpid());//提示server上线,并通过进程号告诉server自己的读写管道
	fflush(fp);
	fd_read=open(fifo_read,O_RDONLY);
	fd_write=open(fifo_write,O_WRONLY);//文件
	printf("%d begin talk!",getpid());
	if((pid=fork())>0)//父进程负责写
	{
		close(fd_read);
		signal(17,handler);
		//格式化输入
		FILE *fdw=fdopen(fd_write,"w");
		if(fdw==NULL)
		{
			printf("the fd_write is failed\n");
			exit(-1);
		}
		while(memset(buf,0,1024),fgets(buf,1024,stdin)!=NULL)
		{
			fprintf(fdw,"from %d :%s",getpid(),buf);
			fflush(fdw);
		}
		fprintf(fdw,"%s","bye");
		fflush(fdw);
		printf("kill child!\n");
		close(fd_write);
		kill(pid,9);
		unlink(fifo_read);
		unlink(fifo_write);
		while(1);
	}
	else
	{
		close(fd_write);
		while(memset(buf,0,1024),read(fd_read,buf,1024)>0)
		{
			fflush(stdout);
			write(1,buf,strlen(buf));
			fflush(stdout);
		}
		close(fd_read);
	}
	return 0;
}