实现即时聊天和聊天的有序退出。在发送聊天消息需要发生自己的本地日历时间。
一、 核心知识点拆解1. 命名管道FIFO的阻塞对齐特性概念命名管道是一种半双工的进程间通信IPC机制在磁盘上有实体文件但内容在内核缓冲区中。特性关键命名管道的open操作默认是阻塞的。当一个进程以只读 (O_RDONLY)方式打开管道时它会死等另一个进程以只写 (O_WRONLY)方式打开同一个管道。必须读写两端都打开open函数才会同时返回。应用在本题中p1和p2互为读写端。两边命令都敲下回车后管道的读写对齐程序才能同时冲破open关卡。2. I/O 多路复用机制select与fd_set为什么需要传统的read会卡死程序。为了同时监听“键盘输入”和“管道来信”必须使用多路复用。fd_set文件描述符集合底层是一个二进制位图Bitmap。每个比特位代表一个文件描述符。1表示监听0表示不监听。核心函数/宏流水线FD_ZERO(rdset);清空清单。FD_SET(fd, rdset);因—— 每次select前把需要监听的通道键盘、管道登记在清单里。select(...)过程—— 把清单交给内核进程睡觉等待。内核会修改清单把没动静的通道抹成0。FD_ISSET(fd, rdset);果——select返回后判断哪个通道活下来了即有数据到了就去处理谁。3. 标准输出的行缓冲机制Line Buffering原理Linux 系统的标准输出终端屏幕默认是行缓冲的。printf的数据不会立刻上屏而是存在内存缓冲区中。刷新条件只有遇到真正的换行符\n、调用fflush、或者程序正常退出时缓冲区才会被冲刷到屏幕上。易错点写成正斜杠/n会被当成普通字符导致数据被“憋”在缓冲区里造成程序“死锁/卡死”的假象。二、 聊天程序运行的底层闭环原理握手阶段阻塞对齐p1打开管道 1 读、管道 2 写p2打开管道 1 写、管道 2 读。双方参数与代码严格互补四端全部对齐后同时冲出open触发各自缓冲区里的printf(success\n)屏幕上同时亮起success。监听阶段多路复用循环进入while(1)双方把自己的键盘0和各自的管道读端fdr打包进rdset交给select托管。通信与时间戳打包一旦敲击键盘select唤醒程序利用time()和ctime()获取当前系统时间通过sprintf()将时间戳与聊天内容在内存中拼接打包成一个完整的字符串最后用write()一次性发送保证了数据的完整性。优雅退出逻辑EOF 传导当 A 端按下Ctrl D其键盘read返回0触发 EOFA 端跳出循环并关闭写端。B 端的管道读端收到通知read返回0B 端得知对方已离开也打印退出整个通信完美释放。代码演示如下p1.c#includefunc.h 2 #includetime.h 3 #define MAXLINE 256 4 5 int main(int argc, char *argv[]){ 6 //命令格式./p1 1.pipe 2.pipe 7 ARGS_CHECK(argc, 3); 8 9 int fdw open(argv[1],O_WRONLY);//打开1管道的写端 10 ERROR_CHECK(fdw, -1, open read); 11 12 int fdr open(argv[2], O_RDONLY);//打开2管道的读端 13 ERROR_CHECK(fdr, -1, open write); 14 15 printf(success\n); 16 17 char buf[4096]; 18 fd_set rdset;// 声明文件描述符可读集合将待执行的文件描述符存入集合 19 while(1){ 20 FD_ZERO(rdset); //重置集合 select会修改rdset每次循环必须清空 21 FD_SET(STDIN_FILENO, rdset); // 将标准输入加入集合监控键盘输入 22 FD_SET(fdr,rdset); //将管道读端加入集合监控管道读端 23 24 select(fdr1, rdset, NULL, NULL,NULL); 25 //只监听可读无写/异常监听无超时设置如果没有没有数据就永久堵塞 26 //接受信息 27 if(FD_ISSET(fdr, rdset)){ 28 memset(buf, 0, sizeof(buf));//清空缓冲区避免旧数据残留 29 ssize_t sret read(fdr, buf, sizeof(buf));//读取数据 30 31 //如果read返回0则管道所有写端关闭 32 if(sret 0){ 33 break; 34 } 35 printf(buf %s\n, buf); 36 } //发送信息 39 if(FD_ISSET(STDIN_FILENO,rdset)){ 40 memset(buf, 0, sizeof(buf)); 41 ssize_t sret read(STDIN_FILENO, buf, sizeof(buf)); 42 43 if(sret 0){ 44 break; 45 } 46 //发送消息时间 47 time_t now time(NULL);//获取时间 48 char *p ctime(now);//转化为日历时间 49 char newbuf[8192] {0}; 50 sprintf(newbuf, %s %s,p,buf);//拼接字符串并存入newbuf数组 51 52 write(fdw, newbuf,strlen(newbuf));//将newbuf写入管道 53 54 55 } 56 } 57 close(fdr); 58 close(fdw); 59 return 0; 60 }p2.c#includefunc.h 2 #includetime.h 3 4 5 int main(int argc, char *argv[]){ 6 ARGS_CHECK(argc, 3); 7 8 int fdr open(argv[1], O_RDONLY); 9 ERROR_CHECK(fdr, -1, open write); 10 11 int fdw open(argv[2], O_WRONLY); 12 ERROR_CHECK(fdw, -1, open read); 13 14 printf(success\n); 15 16 char buf[4096]; 17 fd_set rdset; 18 while(1){ 19 FD_ZERO(rdset); 20 FD_SET(STDIN_FILENO, rdset); 21 FD_SET(fdr, rdset); 22 23 select(fdr1, rdset, NULL, NULL, NULL); 24 //接收信息 25 if(FD_ISSET(fdr, rdset)){ 26 memset(buf, 0,sizeof(buf)); 27 ssize_t sret read(fdr, buf,sizeof(buf)); 28 29 if(sret 0){ 30 break; 31 } 32 printf(buf %s\n,buf); 33 } 34 35 if(FD_ISSET(STDIN_FILENO, rdset)){ 36 memset(buf, 0,sizeof(buf)); 37 ssize_t sert read(STDIN_FILENO, buf, sizeof(buf)); if(sert 0)break; 40 41 //发送时间 42 time_t now time(NULL); 43 char *p ctime(now); 44 char newbuf[8192] {0}; 45 sprintf(newbuf, %s %s,p,buf); 46 write(fdw, newbuf, strlen(newbuf)); 47 48 } 49 } 50 close(fdr); 51 close(fdw); 52 53 54 return 0; 55 }