使用服务器拥有一个管道,用于从客户端接受消息,发送需要服务器转发的消息以及下线通知。服务器需要维护一个列表(使用结构体),记录哪些用户已经连上服务器用于接受消息的管道。当客户端启动,会向服务器发送上线消息,同时将自己的PID发送给server,server会将其添加到列表,以后会转发消息给在列表上的客户端。与此同时,客户端需要创建一个管道,用于接受服务器转发的消息;注意,要将其创建的管道名称告知服务器,一边server打开管道写端,告知管道名称可以在客户端向server发送线上消息时一并发送。当客户端下线时也要告诉server,以便服务器将其从列表删除,这样以后不会在转发消息给它。如果不删,服务器向一个关闭的读端的管道发送消息,会使服务器挂掉(PIPE信号) 注意: 我们假设现在有3个客户端,服务器用于接受消息的管道称为A。由于服务器只拥有一个管道A用于接受从客户端发送的消息,那么所有的客户端都会在管道A的另一端开启写端。也就是说所有客户端的3个写端对服务器的一个读端。 开启服务器和客户端是创建一个共同的管道文件,这个文件在服务器中用于读取数据,在客户端中用来传输客户端上线和客户端自己创建的管道文件 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <dirent.h> #include <unistd.h> #include <sys/times.h> #include <sys/select.h> typedef struct tag { int s_id; // int s_fd; int s_flag; }USR,*pUSR; int main() { /*打开管道*/ int fd_server; fd_server = open(argv[1],O_RDONLY); if(fd_server == -1) { perror("error"); exit(1); } /*初始化server用户列表*/ USR ulist[1024]; memset(ulist,0,sizeof(ulist)); /*定义server用户列表*/ fd_set read_set,ready_set; //ready_set是read_set的备份 FD_ZERO(&read_set); //清空fd_set FD_SET(fd_server,&read_set);//将服务器用于接受消息的管道添加到监听集合中 struct timeval tm; //select 轮询时间 int nret; //记录select返回值 char buf[1024]; while(1) { /*重设select各项参数*/ tm.tv_sec = 0; tm.tv_usec = 1000; ready_set = read_set; nret = select(fd_server + 1,&ready_set,NULL,NULL,&tm); /*在select的轮询时间内,管道阻塞,则nret返回0*/ if(nret == 0) { continue; } if(FD_ISSET(fd_server,&ready_set)) { memset(buf,0,1024); if(0 == read(fd_server,buf,1024)) { continue; }else { if(strncmp(buf,"on",2) == 0) { int pid; char pipename[32] = ""; //存放管道名 sscanf(buf+3,"%d",&pid);//管道名以pid.pipe命名 printf("%d on \n",pid); sprintf(pipename,"%d.fifo",pid); //在用户列表中,找一个没有用到的结构体将其存入 int index; for(index = 0;index < 1024;index++) { if(ulist[index].s_flag == 0) { break; } } if(index == 1024) { printf("full !\n"); } else { ulist[index].s_id = pid; ulist[index].s_fd = open(pipename,O_WRONLY);//打开服务器的写端,用于转发消息给客户端 ulist[index].s_flag = 1; } }else if(strncmp(buf,"off",3) == 0) { int pid; sscanf(buf+4,"%d",&pid); printf("%d off!\n",pid); int index; for(index = 0;index < 1024;index++) { if(ulist[index].s_id == pid) { ulist[index].s_flag = 0; close(ulist[index].s_fd); break; } } }else { int index; for(index = 0;index < 1024;index++) { if(ulist[index].s_flag == 1) { write(ulist[index].s_fd,buf,strlen(buf)); } } } } } } } //客户端 #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdlib.h> #inlcude <string.h> #include <fcntl.h> int main(int argc,char *argv[]) { /*打开上传消息给服务器的管道*/ int fd_send; fd_send = open(argv[1],O_WDONLY); if(fd_send == -1) { peeror("open"); exit(1); } /*注意一定要在向服务器发送上线消息之前创建号客户端自己的管道*/ /*pipename存放客户端自己所创建的管道,命名统一为pid.fifo*/ char pipename[32] = ""; sprintf(pipename,"%d.fifo",getpid()); /*客户端创建接受消息的管道*/ if(-1 == mkfifo(pipename,666)) { perror("mkfifo"); exit(1); } //将上线消息写入管道 char msg[1024] = ""; sprintf(msg,"on %d ! \n",getpid()); write(fd_send,msg,strlen(msg)); /*打开客户端自己的管道*/ int fd_rcv; fd_rcv = open(pipename,O_RDONLY); if(fd_rcv == -1) { perror("open client"); exit(1); } /*子进程用于接受服务器转发的消息*/ if(fork() == 0) { close(fd_send); while(memset(msg,0,1024),read(fd_rcv,msg,1024)>0) { printf("msg>>:"); fflush(stdout);//fflush的使用法 write(1,msg,strlen(msg)); } /*当客户下线,服务器将其从列表中删除,并关闭客户端管道的写端 当服务器关闭该管道的写端,既退出while循环*/ close(fd_rcv); exit(1); } /*父进程用于发送消息*/ close(fd_rcv); while(memset(msg,0,1024),fgets(msg,1024,stdin)!=NULL) { write(fd_send,msg,strlen(msg)); } /*按下ctrl+d退出循环,之后客户端下线*/ memset(msg,0,1024); sprintf(msg,"off %d\n",getpid()); write(fd_send,msg,strlen(msg)); close(fd_send); wait(NULL); } make 1.pepi ./server.exe 1.fifo ./client.exe 1.fifo ./client.exe 1.fifo