经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C 语言 » 查看文章
C语言实现四窗口聊天
来源:jb51  时间:2021/6/15 9:23:27  对本文有异议

C语言实现四窗口聊天,供大家参考,具体内容如下

为了练习前段时间学习的共享内存、管道、消息队列等进程同步机制,做了一个聊天小项目。

项目描述:

有4个进程,A进程和B进程负责通信,从标准输入读到的字符串通过管道发给对方,A1和B1进程负责显示,其中:

  • A进程和B进程通过管道通信,A进程和A1进程通过共享内存通信,B进程和B1进程通过消息队列通信;
  • A进程从标准输入读到的字符串后,放到管道和共享内存里,从管道中读到的字符串放到共享内存里,B进程从管道中拿到A放的字符串,A1进程到共享内存中拿到字符串,打印到屏幕上;
  • B进程从标准输入读到的字符串发给A进程,同时通过消息队列发给B1进程,B1进程从消息队列中读出消息,打印到屏幕上;
  • 退出时,在A和B任意一个进程中输入 Crtl+c 或者 Ctrl+\ ,四个进程会删除所有管道、共享内存、消息队列等资源,然后有序退出。

操作系统:Ubuntu20.4

语言:c

编译器:gcc

func.h

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <sys/types.h>
  5. #include <unistd.h>
  6. #include <time.h>
  7. #include <sys/mman.h>
  8. #include <fcntl.h>
  9. #include <sys/select.h>
  10. #include <sys/shm.h>
  11. #include <sys/sem.h>
  12. #include <sys/msg.h>
  13. #include <signal.h>
  14.  
  15. #define ARGS_CHECK(argc, num){if(argc!=num){fprintf(stderr,"args error!\n"); return -1;}}
  16.  
  17. #define ERROR_CHECK(ret,num,msg){if(ret == num){perror(msg); return -1;}}

a.c

  1. //========= A窗口 ===========
  2. //1.从标准输入读取数据,通过有名管道发送给B窗口
  3. //2.接收从B窗口发送过来的数据
  4. //3.通过共享内存和信号量,将从B来的数据发送给A1
  5. //===========================
  6.  
  7. #include <func.h>
  8.  
  9. int chatA(int shmid, int semid, char *p);
  10. int sndToA1(int semid, int shmid, char *p, char *msg);//把要打印的消息发送给A1
  11. void closeAll();//把关闭消息发送出去,关闭共享内存和信号量集
  12. void sigFunc(int signum);//新的2号和3号信号处理函数,如果在A窗口发生2号和3号信号就调用close函数
  13.  
  14. //全局变量,后面捕获到退出信号回收资源时使用
  15. int semid;
  16. int shmid;//共享内存
  17. char *p;
  18.  
  19. int fdWrite;
  20. int fdRead;
  21.  
  22. int main()
  23. {
  24. //1.创建信号量集,如果有新消息就往共享内存中写,类似生产者
  25. semid = semget(2000, 1, IPC_CREAT|0666);
  26. ERROR_CHECK(semid, -1, "A semget");
  27. shmid = shmget(1000, 4096, IPC_CREAT|0666);//创建一个共享内存
  28. ERROR_CHECK(shmid, -1, "shmget");
  29. p = (char *)shmat(shmid, NULL, 0);
  30.  
  31. signal(SIGINT, sigFunc);
  32. signal(SIGQUIT, sigFunc);
  33.  
  34. int ret = chatA(shmid, semid, p);
  35. ERROR_CHECK(ret, -1, "run A");//检查是否成功打开通信窗口
  36. return 0;
  37. }
  38.  
  39. int chatA(int shmid, int semid, char *p){
  40. //成功运行返回1,否则返回-1
  41. fdRead = open("1.pipe", O_RDONLY);//以只读模式打开管道1
  42. ERROR_CHECK(fdRead, -1, "open fdRead");//检查是否成功打开
  43. fdWrite = open("2.pipe", O_WRONLY);//以只写模式打开管道2
  44. ERROR_CHECK(fdWrite, -1, "open fdWrite");//检查
  45. setbuf(stdin, NULL);
  46. puts("=========== A ===========");
  47. char buf[512] = {0};
  48. fd_set rdset;//设置一个信箱,用来监控有没有读取到信息
  49. while(1){
  50. struct timeval timeout;//设置超时
  51. timeout.tv_sec = 5;
  52. timeout.tv_usec = 15000000;//超过5秒没有接收到信息就是超时
  53. FD_ZERO(&rdset);//初始化集合,清空信箱
  54. FD_SET(fdRead, &rdset);//将要监听的管道1注册到集合中
  55. FD_SET(STDIN_FILENO, &rdset);//将要监听的标准输入注册到集合中
  56. int tret = select(fdRead + 1,&rdset,NULL,NULL,&timeout);//调用select进行监听
  57. if(tret == 0){
  58. puts("time out!");
  59. }
  60. //select阻塞进程,任意一个FD就绪,解除阻塞
  61. //解除阻塞,检查是谁就绪
  62. if(FD_ISSET(fdRead, &rdset)){
  63. //如果是管道就绪,读取管道中的内容,发送给A1
  64. memset(buf, 0, sizeof(buf));//清空buf中的内容,用来接收管道中的信息
  65. int ret = read(fdRead, buf, 1024);//将管道中的信息读取出来
  66. if(ret == 0){
  67. //如果另一端对管道的写先关闭了,退出聊天
  68. sigFunc(2);
  69. break;
  70. }
  71. //获取从B来的消息的类型
  72. int type = 0;
  73. sscanf(buf, "%*d %d", &type);//读取消息的类别,1类为正常,2类为关闭所有窗口
  74. int snd_ret = 0;
  75. switch (type){
  76. case 1:
  77. //如果是1号信息,通过共享内存直接把消息发送给A1
  78. snd_ret = sndToA1(shmid, semid, p, buf);
  79. ERROR_CHECK(snd_ret, -1, "sndToA1");
  80. break;
  81. case 2:
  82. //=====如果是从B发过来的2号信息,关闭所有窗口=====
  83. //向A1发送一个空的2号信号,让A1自己退出,然后自己再退出
  84. sigFunc(2);
  85. exit(0);
  86. }
  87. }
  88. if(FD_ISSET(STDIN_FILENO, &rdset)){
  89. //如果标准输入准备就绪,读取标准输入区的数据,标记为3号信号,发送给A1和B
  90. time_t localtm;
  91. time(&localtm);//获取当前时间
  92. localtm += 8*3600;
  93. memset(buf, 0, sizeof(buf));//清空buf
  94. int ret = read(STDIN_FILENO, buf, 1024);//读取数据
  95. if(ret == 0){
  96. //如果在标准输入中读到了终止符,退出聊天窗口
  97. puts("I quite.");
  98. break;
  99. }
  100. char sstoA1[1024] = {0};//用来拼接数据,发送给A1的数据
  101. char sstoB[1024] = {0};//用来拼接数据,发送给B的数据
  102. sprintf(sstoA1, "%ld %d %s", localtm, 3, buf); //标注为三号信号发送给A1
  103. sprintf(sstoB, "%ld %d %s", localtm, 1, buf); //标注为1号信号发送给B
  104. sndToA1(shmid, semid, p, sstoA1);//发送给A1
  105. write(fdWrite, sstoB, sizeof(sstoB));//通过管道发送给B
  106. }
  107. }
  108. close(fdRead);
  109. close(fdWrite);
  110. return 1;//程序成功运行结束,返回1
  111. }
  112.  
  113. int sndToA1(int shmid, int semid, char *p, char *msg){
  114. //使用共享内存和信号量给A1传递信息
  115. //信号量集的操作,如果有新消息就往共享内存中写,类似生产者
  116. struct sembuf V;
  117. V.sem_num = 0;
  118. V.sem_op = +1;
  119. V.sem_flg = SEM_UNDO;
  120. semop(semid, &V, 1);
  121. /* int shmid = shmget(1000, 4096, IPC_CREAT|0666);//创建一个共享内存 */
  122. ERROR_CHECK(shmid, -1, "shmget");
  123. /* char *p = (char *)shmat(shmid, NULL, 0); */
  124. memcpy(p, msg, strlen(msg));//向共享内存中写信息
  125. return 1;
  126. }
  127.  
  128. void closeAll(){
  129. //根据共享内存和信号量级的标识符,关闭并删除它们
  130. write(fdWrite, "0 2 0", 5);//通过管道发送给B
  131. shmdt(p);
  132. shmctl(shmid, IPC_RMID, NULL);
  133. semctl(semid, IPC_RMID, 0);
  134. close(fdWrite);
  135. close(fdRead);
  136. exit(0);
  137. }
  138.  
  139. void sigFunc(int signum){
  140. printf("Bye Bye.\n");
  141. //捕捉2号和3号信号,发送关闭信息给A1,然后调用closeAll
  142. sndToA1(shmid, semid, p, "0 2 0");//发送给A1
  143. usleep(500);
  144. closeAll();
  145. }

b.c

  1. //========= B窗口 ===========
  2. //1.从标准输入读取数据,通过有名管道发送给A窗口
  3. //2.接收从A窗口发送过来的数据
  4. //3.通过共享内存和信号量,将从A来的数据发送给B1
  5. //===========================
  6.  
  7. #include <func.h>
  8.  
  9. //自定义一个消息结构体,用来和B1传递数据
  10. typedef struct myMsg{
  11. long mtype;
  12. char mtext[512];
  13. }myMsg_t;
  14.  
  15. int chatB(char *pipe1, char *pipe2);
  16. int sndToB1(int msqid, char *msg);//把从A来的消息发送给B1
  17. void closeAll();
  18. void sigFunc(int signum);
  19.  
  20. //全局变量,后面回收资源时要用到
  21. int msqid;
  22. int fdWrite;
  23. int fdRead;
  24.  
  25. int main()
  26. {
  27. msqid = msgget(3000, IPC_CREAT|0666);
  28. ERROR_CHECK(msqid, -1, "B msgget");
  29.  
  30. //注册新的信号处理函数
  31. signal(SIGINT, sigFunc);
  32. signal(SIGQUIT, sigFunc);
  33.  
  34. int ret = chatB("./1.pipe", "./2.pipe");
  35. ERROR_CHECK(ret, -1, "run B");
  36. return 0;
  37. }
  38.  
  39. int chatB(char *pipe1, char *pipe2){
  40. //通信窗口2,读管道2中的信息,向管道1写信息
  41. fdWrite = open(pipe1, O_WRONLY);
  42. ERROR_CHECK(fdWrite, -1, "open pipe1");
  43. fdRead = open(pipe2, O_RDONLY);
  44. ERROR_CHECK(fdRead, -1, "open pipe2");
  45. setbuf(stdin, NULL);
  46. puts("============ B ============");
  47. char buf[512] = {0};
  48. fd_set rdset;//设置集合,用来监听
  49. while(1){
  50. //利用集合设置阻塞
  51. struct timeval timeout;
  52. timeout.tv_sec = 5;
  53. timeout.tv_usec = 15000000;
  54. FD_ZERO(&rdset);//初始化集合
  55. FD_SET(fdRead, &rdset);
  56. FD_SET(STDIN_FILENO, &rdset);
  57. int tret = select(fdRead+1,&rdset,NULL,NULL,&timeout);
  58. if(tret == 0){
  59. puts("time out!");
  60. }
  61.  
  62. //集合中有就绪的,检查是谁就绪,并进行相应的操作
  63. if(FD_ISSET(fdRead, &rdset)){
  64. //如果是管道就绪,读取数据并发送给B1
  65. memset(buf, 0, sizeof(buf));
  66. int ret = read(fdRead, buf, 1024);
  67. if(ret == 0){
  68. sigFunc(2);
  69. break;
  70. }
  71. int type = 0;//用来存储消息的类型
  72. sscanf(buf, "%*d %d", &type);//从消息中获取类型信息
  73.  
  74. //如果是2号信息,关闭所有窗口
  75. //向B1发送关闭信号,然后回收消息队列,再自己结束
  76. if(type == 2){
  77. sigFunc(2);
  78. exit(0);
  79. }
  80.  
  81. //如果是其他有效信息,发送给B1
  82. int snd_ret = sndToB1(msqid, buf);
  83. ERROR_CHECK(snd_ret, -1, "B sndToB1");
  84. }
  85. if(FD_ISSET(STDIN_FILENO, &rdset)){
  86. //如果是标准输入区就绪,读取数据,分别发给A和B1
  87. time_t localtm;
  88. time(&localtm);//获取当前时间
  89. localtm += 8*3600;
  90. memset(buf, 0, sizeof(buf));
  91. int ret = read(STDIN_FILENO, buf, 1024);
  92. if(ret == 0){
  93. puts("I quite.");
  94. break;
  95. }
  96. //按照协议拼接数据并发送出去
  97. char sstoA[1024] = {0};//发送给A的数据
  98. sprintf(sstoA, "%ld %d %s", localtm, 1, buf);
  99. write(fdWrite, sstoA, sizeof(sstoA));
  100.  
  101. char sstoB1[1024] = {0};//发送给B1的数据标注为3号
  102. sprintf(sstoB1, "%ld %d %s", localtm, 3, buf);
  103. sndToB1(msqid, sstoB1);
  104. }
  105. }
  106. close(fdRead);
  107. close(fdWrite);
  108. return 1;//程序成功运行结束,返回1
  109. }
  110.  
  111. int sndToB1(int msqid, char *msg){
  112. //通过消息队列,把数据发送给B1
  113. myMsg_t msgtoB1;//创建一个消息结构体
  114. msgtoB1.mtype = 1;
  115. memset(msgtoB1.mtext, 0, sizeof(msgtoB1.mtext));
  116. memcpy(msgtoB1.mtext, msg, strlen(msg));
  117. msgsnd(msqid, &msgtoB1, strlen(msg), 0);
  118. return 1;
  119. }
  120.  
  121. void closeAll(){
  122. msgctl(msqid, IPC_RMID, 0);//删除消息队列
  123. close(fdWrite);//关闭管道
  124. close(fdRead);
  125. exit(0);
  126. }
  127.  
  128. void sigFunc(int signum){
  129. printf("Bye Bye.\n");
  130. //通过消息队列,把关闭信息发送给B1,然后删除消息队列,然后自己退出
  131. sndToB1(msqid, "0 2 0");//发送给B1关闭信号
  132. write(fdWrite, "0 2 0", 5);//发送给A关闭信号
  133. usleep(500);//睡一下,等B1先关闭
  134. //捕获2号和3号信号,调用closeAll函数
  135. closeAll();
  136. }

a1.c

  1. //========== A1 ==========
  2. //1.从共享内存中读取消息
  3. //2.打印
  4.  
  5. int display();
  6.  
  7. #include <func.h>
  8.  
  9. int main()
  10. {
  11. int ret = display();
  12. ERROR_CHECK(ret, -1, "A1 display");
  13. return 0;
  14. }
  15.  
  16. int display(){
  17. //1.从共享内存中读取数据
  18. //没有消息就等待,有消息就读取,使用信号量集,类似消费者
  19. //1.1 创建一个信号量集,如果共享内存中有数据就读取,如果共享内存中没有数据就阻塞
  20. int semid = semget(2000, 1, IPC_CREAT|0666);
  21. ERROR_CHECK(semid, -1, "A1 semget");
  22. semctl(semid, 0, SETVAL, 0);//信号量初始值设为0
  23. //设置信号量,测试并取资源,类似消费者操作
  24. struct sembuf P;
  25. P.sem_num = 0;
  26. P.sem_op = -1;
  27. P.sem_flg = SEM_UNDO;
  28. printf("=========== A1 ===========\n");
  29. while(1){
  30. semop(semid, &P, 1);//P操作,测试并取资源
  31. int shmid = shmget(1000, 4096, IPC_CREAT|0666);
  32. ERROR_CHECK(shmid, -1, "A1 shmget");
  33. char *p = (char *)shmat(shmid, NULL, 0);//连接共享内存
  34.  
  35. int type = 0;
  36. sscanf(p, "%*d %d", &type);//获取消息的属性,然后根据协议执行相应的操作
  37. switch (type){
  38. case 1:
  39. //从B来的消息
  40. printf("<<<<<<<<< receive <<<<<<<<<<\n");
  41. struct tm *ptm = NULL;
  42. time_t tmp = 0;
  43. char ss[512] = {0};
  44. sscanf(p, "%ld", &tmp);//读取消息中的时间信息
  45. sscanf(p, "%*d %*d %[^\n]", ss);
  46. ptm = gmtime(&tmp);
  47. printf("%4d-%02d-%02d %02d:%02d:%02d\n",
  48. ptm->tm_year+1900,ptm->tm_mon+1,ptm->tm_mday,
  49. ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
  50. puts(ss);
  51. printf("\n");
  52. //清空共享内存中的数据
  53. memset(p, 0, 4096);
  54. break;
  55. case 2:
  56. printf("Bye Bye.\n");
  57. shmdt(p);
  58. shmctl(shmid, IPC_RMID, NULL);
  59. exit(0);
  60. break;
  61. case 3:
  62. printf(">>>>>>>>> send >>>>>>>>>>>\n");
  63. struct tm *ptm3 = NULL;
  64. time_t tmp3 = 0;
  65. char ss3[512] = {0};
  66. sscanf(p, "%ld", &tmp3);//读取消息中的时间信息
  67. sscanf(p, "%*d %*d %[^\n]", ss3);
  68. ptm3 = gmtime(&tmp3);
  69. printf("%4d-%02d-%02d %02d:%02d:%02d\n",
  70. ptm3->tm_year+1900,ptm3->tm_mon+1,ptm3->tm_mday,
  71. ptm3->tm_hour, ptm3->tm_min, ptm3->tm_sec);
  72. puts(ss3);
  73. printf("\n");
  74. //清空共享内存中的数据
  75. memset(p, 0, 4096);
  76. break;
  77. default:
  78. printf("sonething wrong!\n");
  79. }
  80. }
  81. }

b1.c

  1. //========== B1 ===========
  2. //接收来自B的消息,并打印
  3.  
  4. #include <func.h>
  5.  
  6. typedef struct myMsg{
  7. long mtype;
  8. char mtext[512];
  9. }myMsg_t;
  10.  
  11. int display();
  12.  
  13. int main()
  14. {
  15. int ret = display();
  16. ERROR_CHECK(ret, -1, "B1 display");
  17. return 0;
  18. }
  19.  
  20. int display(){
  21. printf("=========== B1 ===========\n");
  22. while(1){
  23. //接收来自B的消息
  24. int msqid = msgget(3000, IPC_CREAT|0666);
  25. ERROR_CHECK(msqid, -1, "B1 msgget");
  26. myMsg_t msgfromB;
  27. memset(&msgfromB, 0, sizeof(msgfromB));
  28. msgrcv(msqid, &msgfromB, sizeof(msgfromB.mtext), 1, 0);
  29. //1.如果是2类信号,退出
  30. int type = 0;
  31. sscanf(msgfromB.mtext, "%*d %d", &type);//读取消息的属性,根据不同属性,执行相应的操作
  32.  
  33. switch (type){
  34. case 1:
  35. //从B来的消息
  36. printf("<<<<<<<<< receive <<<<<<<<<<\n");
  37. struct tm *ptm = NULL;
  38. time_t tmp = 0;
  39. char ss[512] = {0};
  40. sscanf(msgfromB.mtext, "%ld", &tmp);//读取消息中的时间信息
  41. sscanf(msgfromB.mtext, "%*d %*d %[^\n]", ss);
  42. ptm = gmtime(&tmp);
  43. printf("%4d-%02d-%02d %02d:%02d:%02d\n",
  44. ptm->tm_year+1900,ptm->tm_mon+1,ptm->tm_mday,
  45. ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
  46. puts(ss);
  47. printf("\n");
  48. //清空共享内存中的数据
  49. break;
  50. case 2:
  51. //删除消息队列并退出
  52. printf("Bye Bye.\n");
  53. msgctl(msqid, IPC_RMID, NULL);
  54. exit(0);
  55. case 3:
  56. printf(">>>>>>>>> send >>>>>>>>>>>\n");
  57. struct tm *ptm3 = NULL;
  58. time_t tmp3 = 0;
  59. char ss3[512] = {0};
  60. sscanf(msgfromB.mtext, "%ld", &tmp3);//读取消息中的时间信息
  61. sscanf(msgfromB.mtext, "%*d %*d %[^\n]", ss3);
  62. ptm3 = gmtime(&tmp3);
  63. printf("%4d-%02d-%02d %02d:%02d:%02d\n",
  64. ptm3->tm_year+1900,ptm3->tm_mon+1,ptm3->tm_mday,
  65. ptm3->tm_hour, ptm3->tm_min, ptm3->tm_sec);
  66. puts(ss3);
  67. printf("\n");
  68. break;
  69. default:
  70. printf("Something wrong!\n");
  71. }
  72. }
  73. }

运行如下:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持w3xue。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号