C++多进程英文单词统计
/* * * Author : shenghan , UESTC * * Time : 2012-11-02 * * Function : A word frequency statistics tool, usage wordscan scan_dir results * */ #include <iostream> #include <fstream> #include <vector> #include <map> #include <string> #include <cstring> #include <cstdlib> #include <fcntl.h> #include <unistd.h> #include <dirent.h> #include <sys/stat.h> #include <errno.h> #include <sys/wait.h> #include <sys/ipc.h> #include <sys/msg.h> using namespace std; DIR * rootdir; //需要扫描的目录 #define SIZE 50 //单词的大小限制为50B #define B_SIZE 4096 //每次从文件中 void readProcess(char * start_dir); //读进程执行的函数 void statisticProcess(); //单词统计进程执行的函数 pid_t r_wait(int * stat_loc); //回收所有的子进程 void doReadFile(char * filename); //读进程中真正执行读文件的函数 void recordResult(); //记录统计结果到文件中 bool isAlphabet(char alphabet); //判断是否为ASCII英文字符 void getNextWord(char * b_buffer, int * bindex, char * result); //从读取的数据块中获取一个英文单词 struct my_msg //消息队列结构体 { int msg_type; //消息类型 char msg_text[SIZE]; //消息内容 }; typedef struct my_msg message; int msgqueueid; //消息队列ID ofstream ofile; //记录统计结构的文件 bool isHalfWord = false; //判断获取的单词是否为半个单词 char prehalfword[SIZE]; //存放获取的上半个单词 map<string,int> statisticMap; //用于保存单词和单词数的Map(STL) int main(int argc, char ** argv) { struct dirent * dp; struct stat stat_buffer; vector<pid_t> readProID; //保存读进程的进程ID //pipe pid_t pid; if(argc != 3) { cerr<<"Usage wordscan <scan_dir> <resultfilename>"<<endl; _exit(EXIT_FAILURE); } ofile.open(argv[2]); if(!ofile) { cerr<<"open result file error ! "<<endl; _exit(EXIT_FAILURE); } //cout<<"root dir :"<<argv[1]<<endl; if((rootdir = opendir(argv[1])) == NULL) { cerr<<"open dir "<<argv[1]<<" failed ! "<<endl; _exit(EXIT_FAILURE); } if((msgqueueid = msgget((key_t)12345,0666 | IPC_CREAT)) == -1) //创建并获取消息队列 { cerr<<"error in create message queue ."<<endl; _exit(EXIT_FAILURE); } if((pid = fork()) == -1) //创建单词统计进程 { cerr<<"fork error !"<<endl; _exit(EXIT_FAILURE); } else if(pid == 0) { statisticProcess(); //单词统计进程执行的函数 } else { chdir(argv[1]); while((dp = readdir(rootdir)) != NULL) //遍历指定的扫描根目录 { // cout<<"cur : "<<dp->d_name<<endl; if(dp->d_name[0] == '.') //忽略本目录‘.’、父目录‘..’和所有隐藏的目录及文件 continue; if((stat(dp->d_name,&stat_buffer)) == -1) //获取扫描到的当前的文件或目录的属性 { cout<<"error in function :stat , in scan "<<argv[1]<<endl; _exit(EXIT_FAILURE); } if(S_ISDIR(stat_buffer.st_mode)) //如果是目录 { //cout<<"a subdir "<<endl; if((pid = fork()) == -1) //对指定的根目录下的每一个子目录创建一个进程 { cerr<<"fork error !"<<endl; _exit(EXIT_FAILURE); } else if(pid == 0){ break; } else { readProID.push_back(pid);//将文件读取京城的进程好保存到vector中 } } else if(S_ISREG(stat_buffer.st_mode)) //如果根目录中有普通文件,则直接读取普通文件 { //cout<<"a regular file "<<endl; doReadFile(dp->d_name); } } if(pid == 0) { readProcess(dp->d_name); //读进程执行的函数 } else { vector<pid_t>::iterator iter; for(iter = readProID.begin();iter != readProID.end();iter ++) { waitpid(*iter,NULL,0); //wait all the readProcess to end; } message endmsg; endmsg.msg_type = 2; strcpy(endmsg.msg_text,"end"); if((msgsnd(msgqueueid,(void *)&endmsg,SIZE,0)) == -1) //待所有的读进程都执行完毕后,发送一个结束消息到消息队列,结束消息的消息类型为2 { cerr<<"message send error !"<<endl; _exit(EXIT_FAILURE); } while(r_wait(NULL) > 0);//wait for all the subprocess. if(msgctl(msgqueueid,IPC_RMID,NULL) == -1)//删除消息队列 { cerr<<"msgctl(IPC_RMID) failed !"<<endl; _exit(EXIT_FAILURE); } closedir(rootdir); ofile.close(); cout<<"OK !"<<endl; return 0; } } } pid_t r_wait(int * stat_loc) { int revalue; while(((revalue = wait(stat_loc)) == -1) && (errno == EINTR)); return revalue; } void statisticProcess() { cout<<"statisticing ..."<<endl; message currentmsg; //bool RUN = true; if((msgrcv(msgqueueid,(void *)¤tmsg,SIZE,0,0)) == -1)//从消息队列获取第一个消息 { cerr<<"message receive error !"<<endl; _exit(EXIT_FAILURE); //RUN = false; } map<string,int>::iterator iter; //遍历Map(STL)的跌代器 int tempnum = 0; while(currentmsg.msg_type != 2) //如果接受的消息不是结束类型的消息,则继续循环执行一下代码 { /* *在Map(STL)中查找是否有当前接受的单词,如果有的话,则将单词数加1,如果没有的话,则将该单词插入到Map(STL)中,并设置单词数为1 */ if((iter = statisticMap.find(currentmsg.msg_text)) != statisticMap.end()) { tempnum = (*iter).second; tempnum ++; statisticMap[currentmsg.msg_text] = tempnum; } else { statisticMap[currentmsg.msg_text] = 1; } //cout<<currentmsg.msg_text<<" type :"<<currentmsg.msg_type<<endl; if((msgrcv(msgqueueid,(void *)¤tmsg,SIZE,0,0)) == -1)//从消息队列中接受下一个消息 { cerr<<"message receive error !"<<endl; _exit(EXIT_FAILURE); // RUN = false; } } recordResult(); } void recordResult() { map<string,int>::iterator iter; cout<<"writing result to file ..."<<endl; for(iter = statisticMap.begin();iter != statisticMap.end();iter++) //遍历Map(STL),并将其中的单词统计数据保存到文件中 { ofile<<(*iter).first<<" "<<(*iter).second<<endl;//记录的格式为<单词> <单数数> } cout<<"finished writing !"<<endl; } void doReadFile(char * filename) { char b_buffer[B_SIZE+1]; //b_buffer[B_SIZE]='\0' //cout<<"begin read ! "<<endl; cout<<"-> "<<endl; int fd; int rbytenum; message currentmsg; char curreadword[SIZE]; int begin_index = 0; fd = open(filename,O_RDONLY); if(fd == -1) { cerr<<"open file "<<filename<<" error !"<<endl; _exit(EXIT_FAILURE); } do { rbytenum = read(fd,b_buffer,B_SIZE);//从文件中读取B_SIZE大小的数据块,并保存到b_buffer中 b_buffer[rbytenum]='\0';//在读取的数据快结尾插上一个结束符 //printf("%s\n\n\n\n",b_buffer ); begin_index = 0; while(begin_index != rbytenum) //提取上面获取的数据块中的所有单词 { getNextWord(b_buffer,&begin_index,curreadword); //在b_buffer中,从begin_index处开始提取一个单词,并保存到curreadword中 if(isHalfWord) //去过获取的是半个单词的话,说明当前数据块已经遍历到结尾,跳出本次循环,并从新读取一个数据块 break; if(curreadword[0] != '\0')//如果当前提取的单词不为空 { currentmsg.msg_type = 1; strcpy(currentmsg.msg_text,curreadword); if((msgsnd(msgqueueid,(void *)¤tmsg,SIZE,0)) == -1) //将当前提取的单词发送到消息队列中 { cerr<<"message send error !"<<endl; _exit(EXIT_FAILURE); } } } }while(rbytenum == B_SIZE);//一直读取文件中的内容,直到结束 //cout<<"read finished !"<<endl; close(fd); } void readProcess(char * start_dir) { //cout<<"pid : "<<getpid()<<endl; //cout<<" a dir : "<<start_dir<<endl; DIR * curdir; struct dirent * curdp; struct stat curstat_buffer; if((curdir = opendir(start_dir)) == NULL) { cerr<<"open dir "<<start_dir<<" failed ! "<<endl; _exit(EXIT_FAILURE); } chdir(start_dir); //切换当前的工作目录 char * current_dir = get_current_dir_name();//获取当前工作目录,并保存,方便最后返回到该目录 //cout<<"cur dir "<<current_dir<<endl; while((curdp = readdir(curdir)) != NULL) //遍历当前子目录中的文件 { // cout<<"cur :"<<curdp->d_name<<endl; if(curdp->d_name[0] == '.') //忽略本目录‘.’、父目录‘..’和所有隐藏的目录及文件 continue; if((stat(curdp->d_name,&curstat_buffer)) == -1) //获取扫描到的当前的文件或目录的属性 { cout<<"error in funtion stat in scan "<<start_dir<<endl; _exit(EXIT_FAILURE); } if(S_ISDIR(curstat_buffer.st_mode)) //如果扫描到目录,则递归遍历该子目录 { readProcess(curdp->d_name); chdir(current_dir); //因为在递归遍历子目录的过程中改变了当前工作目录,所有要根据前面保存的工作目录返回到递归子目录之前的工作目录 } else if(S_ISREG(curstat_buffer.st_mode))//如果扫描到普通文件则直接读取该文件中的单词 { doReadFile(curdp->d_name); } } //cout<<"cur dir : "<<get_current_dir_name()<<" finished"<<endl; } bool isAlphabet(char ch) { if((ch>=65&&ch<=90)||(ch>=97&&ch<=122)) //判断是否为ASCII字符 return true; else return false; } void getNextWord(char * buffer, int * bindex,char * result) { char ch; bool begin = true; int index = 0;//用于存放提取的单词的索引 result[index] = '\0'; if(isHalfWord)//如果上次提取的是半个单词,则继续从新获得的数据块中提取下一半单词 { strcpy(result,prehalfword); isHalfWord = false; if((buffer[*bindex] == ' '|| buffer[*bindex] == '\n' || buffer[*bindex] == '\r')) //如果新获取的数据块的第一个字节是非显示字符,则表明上次获取的半个单词其实是一个整的单词 { return; } else index = strlen(prehalfword);//如果第一个字节是显示字符,则继续获取下一半单词 } while(buffer[*bindex] != '\0') //如果当前数据块没有到达结尾 { ch = buffer[(*bindex)++];//获取当前数据块的索引所指定的一个字符,并使数据块的索引加1 if((ch == ' ' || ch =='\n' || ch == '\r') && begin) //如果获取的是非显示字符,并且还未获取单词的第一个字符,则继续获取数据块的下一个字符 continue; /* *如果获取的是非显示字符,并且已经获取长度大于0的单词,则说明已经获取的单词字符构成一个整的单词,跳出循环,并返回以获取的单词 */ else if((ch == ' ' || ch == '\n' || ch == '\r') && !begin) break; else { if(isAlphabet(ch))//如果该显示字符是ASCII字符,则记录该字符为当前单词的一部分 { result[index++] = ch; if(begin) begin = false; if(index == (SIZE -1)) //如果当前单词的长度超过指定的单词长度SIZE,则忽略该长单词的其余部分 { while(buffer[*bindex] != '\0') { ch = buffer[(*bindex)++] ; if(ch == ' ' || ch == '\n' || ch == '\r') break; } //ignore other char break; } } else { if(ch == ',' || ch =='.')//如果该显示字符是','或'.'则说明,该字符也是一个单词分割符,已经获取的单词符号可以组成一个整的单词 { break; } else { //如果该显示字符不是单词分割符,则忽略以后的所有字符,直到遇到一个单词分隔符 while(buffer[*bindex] != '\0') { ch = buffer[(*bindex)++] ; if(ch == ' ' || ch == '\n' || ch == '\r'||ch == ','|| ch == '.') break; } //从新初始化各个变量 begin = true; index = 0; result[0] = '\0'; continue; } } } } result[index] = '\0';//将向单词字符插入一个结束符 ch = buffer[*bindex -1]; if((buffer[*bindex] == '\0') && ch !=' '&&ch != '\n'&&ch != '\r'&&ch!=','&&ch!='.') { //如果该数据块已经扫描完成,并且最后一个字符不是单词分隔符,则已经获取的单词很可能是一半个单词 isHalfWord = true; } }