程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> 關於C >> 總結文件操作函數(二)

總結文件操作函數(二)

編輯:關於C

格式化讀寫:

#include

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 //頭文件

#include

#include //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

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

int stat(const char *file_name, struct stat *buf); //通過文件名獲取文件信息,並保存在buf所指的結構體stat中

int fstat(int fd, struct stat *buf); //文件描述詞 stat結構體指針

返回值: 執行成功則返回0,失敗返回-1,錯誤代碼存於errno(需要include

通過man stat查找結構體中包含文件的具體信息

select函數,目前本人多用於函數讀寫動態監聽:

#include

#include

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

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
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

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 
	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
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

int pipe(int fds[2]);

管道在程序中用一對文件描述符表示,其中一個文件描述符有可讀屬性,一個有可寫的屬性。fds[0]是讀,fds[1]是寫。

函數pipe用於創建一個無名管道,如果成功,fds[0]存放可讀的文件描述符,fds[1]存放可寫文件描述符,並且函數返回0,否則返回-1。

通過調用pipe獲取這對打開的文件描述符後,一個進程就可以從fds[0]中讀數據,而另一個進程就可以往fds[1]中寫數據。當然兩進程間必須有繼承關系,才能繼承這對打開的文件描述符。

管道不象真正的物理文件,不是持久的,即兩進程終止後,管道也自動消失了。

示例:創建父子進程,創建無名管道,父寫子讀
#include 
#include 
#include 
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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
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;
}

多人客戶服務器模式聊天:

服務器端設計:永久服務器通道(服務器端只從通道讀)>創建set集>服務器通道描述符加入set,用於判斷是否上線#上線則讀取進程號,得到對應通道名,獲取讀寫雙通道文件描述符#客戶端端口已滿強制關閉進程,刪除通道>加入set監聽變化>循環FD_ISSET()

每個讀管道的變化,若變化則讀取管道信息

>判斷管道前三個字符是不是bye結束字符串,是,下線操作(關閉進程,關閉讀寫管道,清-1客戶端列表)

>不是,將管道信息發送到其他管道中(監聽客戶端中每一個非-1的,進行發送)


//File Name: sever.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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);//只讀方式開發服務器管道
	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))//有人連接服務器,上線
		{
			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

客戶端設計:永久服務器管道(客戶端只向此通道寫入)>讀寫雙通道(使用唯一的進程ID創建兩通道)>向服務器管道寫入唯一進程ID >open打開雙通道轉化為進程描述符>fork()創建父子進程>父進程負責寫信息(用於殺死子進程kill(),unlink()刪除通道文件,使用signal(),回收子進程,exit()自殺)


//File Name: myclient.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
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;//服務器管道標識符
	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);//向服務器管道寫入信息
	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());//提示服務器上線,並通過進程號告訴服務器自己的讀寫管道
	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;
}



  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved