格式化读写:
#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; }
多人客户服务器模式聊天:
服务器端设计:永久服务器通道(服务器端只从通道读)>创建set集>服务器通道描述符加入set,用于判断是否上线#上线则读取进程号,得到对应通道名,获取读写双通道文件描述符#客户端端口已满强制关闭进程,删除通道>加入set监听变化>循环FD_ISSET()
每个读管道的变化,若变化则读取管道信息
>判断管道前三个字符是不是bye结束字符串,是,下线操作(关闭进程,关闭读写管道,清-1客户端列表)
>不是,将管道信息发送到其他管道中(监听客户端中每一个非-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);//只读方式开发服务器管道 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<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; }
客户端设计:永久服务器管道(客户端只向此通道写入)>读写双通道(使用唯一的进程ID创建两通道)>向服务器管道写入唯一进程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;//服务器管道标识符 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; }