C语言实现四窗口聊天
更新时间:2021年6月14日 15:00 点击:2160
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
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <time.h> #include <sys/mman.h> #include <fcntl.h> #include <sys/select.h> #include <sys/shm.h> #include <sys/sem.h> #include <sys/msg.h> #include <signal.h> #define ARGS_CHECK(argc, num){if(argc!=num){fprintf(stderr,"args error!\n"); return -1;}} #define ERROR_CHECK(ret,num,msg){if(ret == num){perror(msg); return -1;}}
a.c
//========= A窗口 =========== //1.从标准输入读取数据,通过有名管道发送给B窗口 //2.接收从B窗口发送过来的数据 //3.通过共享内存和信号量,将从B来的数据发送给A1 //=========================== #include <func.h> int chatA(int shmid, int semid, char *p); int sndToA1(int semid, int shmid, char *p, char *msg);//把要打印的消息发送给A1 void closeAll();//把关闭消息发送出去,关闭共享内存和信号量集 void sigFunc(int signum);//新的2号和3号信号处理函数,如果在A窗口发生2号和3号信号就调用close函数 //全局变量,后面捕获到退出信号回收资源时使用 int semid; int shmid;//共享内存 char *p; int fdWrite; int fdRead; int main() { //1.创建信号量集,如果有新消息就往共享内存中写,类似生产者 semid = semget(2000, 1, IPC_CREAT|0666); ERROR_CHECK(semid, -1, "A semget"); shmid = shmget(1000, 4096, IPC_CREAT|0666);//创建一个共享内存 ERROR_CHECK(shmid, -1, "shmget"); p = (char *)shmat(shmid, NULL, 0); signal(SIGINT, sigFunc); signal(SIGQUIT, sigFunc); int ret = chatA(shmid, semid, p); ERROR_CHECK(ret, -1, "run A");//检查是否成功打开通信窗口 return 0; } int chatA(int shmid, int semid, char *p){ //成功运行返回1,否则返回-1 fdRead = open("1.pipe", O_RDONLY);//以只读模式打开管道1 ERROR_CHECK(fdRead, -1, "open fdRead");//检查是否成功打开 fdWrite = open("2.pipe", O_WRONLY);//以只写模式打开管道2 ERROR_CHECK(fdWrite, -1, "open fdWrite");//检查 setbuf(stdin, NULL); puts("=========== A ==========="); char buf[512] = {0}; fd_set rdset;//设置一个信箱,用来监控有没有读取到信息 while(1){ struct timeval timeout;//设置超时 timeout.tv_sec = 5; timeout.tv_usec = 15000000;//超过5秒没有接收到信息就是超时 FD_ZERO(&rdset);//初始化集合,清空信箱 FD_SET(fdRead, &rdset);//将要监听的管道1注册到集合中 FD_SET(STDIN_FILENO, &rdset);//将要监听的标准输入注册到集合中 int tret = select(fdRead + 1,&rdset,NULL,NULL,&timeout);//调用select进行监听 if(tret == 0){ puts("time out!"); } //select阻塞进程,任意一个FD就绪,解除阻塞 //解除阻塞,检查是谁就绪 if(FD_ISSET(fdRead, &rdset)){ //如果是管道就绪,读取管道中的内容,发送给A1 memset(buf, 0, sizeof(buf));//清空buf中的内容,用来接收管道中的信息 int ret = read(fdRead, buf, 1024);//将管道中的信息读取出来 if(ret == 0){ //如果另一端对管道的写先关闭了,退出聊天 sigFunc(2); break; } //获取从B来的消息的类型 int type = 0; sscanf(buf, "%*d %d", &type);//读取消息的类别,1类为正常,2类为关闭所有窗口 int snd_ret = 0; switch (type){ case 1: //如果是1号信息,通过共享内存直接把消息发送给A1 snd_ret = sndToA1(shmid, semid, p, buf); ERROR_CHECK(snd_ret, -1, "sndToA1"); break; case 2: //=====如果是从B发过来的2号信息,关闭所有窗口===== //向A1发送一个空的2号信号,让A1自己退出,然后自己再退出 sigFunc(2); exit(0); } } if(FD_ISSET(STDIN_FILENO, &rdset)){ //如果标准输入准备就绪,读取标准输入区的数据,标记为3号信号,发送给A1和B time_t localtm; time(&localtm);//获取当前时间 localtm += 8*3600; memset(buf, 0, sizeof(buf));//清空buf int ret = read(STDIN_FILENO, buf, 1024);//读取数据 if(ret == 0){ //如果在标准输入中读到了终止符,退出聊天窗口 puts("I quite."); break; } char sstoA1[1024] = {0};//用来拼接数据,发送给A1的数据 char sstoB[1024] = {0};//用来拼接数据,发送给B的数据 sprintf(sstoA1, "%ld %d %s", localtm, 3, buf); //标注为三号信号发送给A1 sprintf(sstoB, "%ld %d %s", localtm, 1, buf); //标注为1号信号发送给B sndToA1(shmid, semid, p, sstoA1);//发送给A1 write(fdWrite, sstoB, sizeof(sstoB));//通过管道发送给B } } close(fdRead); close(fdWrite); return 1;//程序成功运行结束,返回1 } int sndToA1(int shmid, int semid, char *p, char *msg){ //使用共享内存和信号量给A1传递信息 //信号量集的操作,如果有新消息就往共享内存中写,类似生产者 struct sembuf V; V.sem_num = 0; V.sem_op = +1; V.sem_flg = SEM_UNDO; semop(semid, &V, 1); /* int shmid = shmget(1000, 4096, IPC_CREAT|0666);//创建一个共享内存 */ ERROR_CHECK(shmid, -1, "shmget"); /* char *p = (char *)shmat(shmid, NULL, 0); */ memcpy(p, msg, strlen(msg));//向共享内存中写信息 return 1; } void closeAll(){ //根据共享内存和信号量级的标识符,关闭并删除它们 write(fdWrite, "0 2 0", 5);//通过管道发送给B shmdt(p); shmctl(shmid, IPC_RMID, NULL); semctl(semid, IPC_RMID, 0); close(fdWrite); close(fdRead); exit(0); } void sigFunc(int signum){ printf("Bye Bye.\n"); //捕捉2号和3号信号,发送关闭信息给A1,然后调用closeAll sndToA1(shmid, semid, p, "0 2 0");//发送给A1 usleep(500); closeAll(); }
b.c
//========= B窗口 =========== //1.从标准输入读取数据,通过有名管道发送给A窗口 //2.接收从A窗口发送过来的数据 //3.通过共享内存和信号量,将从A来的数据发送给B1 //=========================== #include <func.h> //自定义一个消息结构体,用来和B1传递数据 typedef struct myMsg{ long mtype; char mtext[512]; }myMsg_t; int chatB(char *pipe1, char *pipe2); int sndToB1(int msqid, char *msg);//把从A来的消息发送给B1 void closeAll(); void sigFunc(int signum); //全局变量,后面回收资源时要用到 int msqid; int fdWrite; int fdRead; int main() { msqid = msgget(3000, IPC_CREAT|0666); ERROR_CHECK(msqid, -1, "B msgget"); //注册新的信号处理函数 signal(SIGINT, sigFunc); signal(SIGQUIT, sigFunc); int ret = chatB("./1.pipe", "./2.pipe"); ERROR_CHECK(ret, -1, "run B"); return 0; } int chatB(char *pipe1, char *pipe2){ //通信窗口2,读管道2中的信息,向管道1写信息 fdWrite = open(pipe1, O_WRONLY); ERROR_CHECK(fdWrite, -1, "open pipe1"); fdRead = open(pipe2, O_RDONLY); ERROR_CHECK(fdRead, -1, "open pipe2"); setbuf(stdin, NULL); puts("============ B ============"); char buf[512] = {0}; fd_set rdset;//设置集合,用来监听 while(1){ //利用集合设置阻塞 struct timeval timeout; timeout.tv_sec = 5; timeout.tv_usec = 15000000; FD_ZERO(&rdset);//初始化集合 FD_SET(fdRead, &rdset); FD_SET(STDIN_FILENO, &rdset); int tret = select(fdRead+1,&rdset,NULL,NULL,&timeout); if(tret == 0){ puts("time out!"); } //集合中有就绪的,检查是谁就绪,并进行相应的操作 if(FD_ISSET(fdRead, &rdset)){ //如果是管道就绪,读取数据并发送给B1 memset(buf, 0, sizeof(buf)); int ret = read(fdRead, buf, 1024); if(ret == 0){ sigFunc(2); break; } int type = 0;//用来存储消息的类型 sscanf(buf, "%*d %d", &type);//从消息中获取类型信息 //如果是2号信息,关闭所有窗口 //向B1发送关闭信号,然后回收消息队列,再自己结束 if(type == 2){ sigFunc(2); exit(0); } //如果是其他有效信息,发送给B1 int snd_ret = sndToB1(msqid, buf); ERROR_CHECK(snd_ret, -1, "B sndToB1"); } if(FD_ISSET(STDIN_FILENO, &rdset)){ //如果是标准输入区就绪,读取数据,分别发给A和B1 time_t localtm; time(&localtm);//获取当前时间 localtm += 8*3600; memset(buf, 0, sizeof(buf)); int ret = read(STDIN_FILENO, buf, 1024); if(ret == 0){ puts("I quite."); break; } //按照协议拼接数据并发送出去 char sstoA[1024] = {0};//发送给A的数据 sprintf(sstoA, "%ld %d %s", localtm, 1, buf); write(fdWrite, sstoA, sizeof(sstoA)); char sstoB1[1024] = {0};//发送给B1的数据标注为3号 sprintf(sstoB1, "%ld %d %s", localtm, 3, buf); sndToB1(msqid, sstoB1); } } close(fdRead); close(fdWrite); return 1;//程序成功运行结束,返回1 } int sndToB1(int msqid, char *msg){ //通过消息队列,把数据发送给B1 myMsg_t msgtoB1;//创建一个消息结构体 msgtoB1.mtype = 1; memset(msgtoB1.mtext, 0, sizeof(msgtoB1.mtext)); memcpy(msgtoB1.mtext, msg, strlen(msg)); msgsnd(msqid, &msgtoB1, strlen(msg), 0); return 1; } void closeAll(){ msgctl(msqid, IPC_RMID, 0);//删除消息队列 close(fdWrite);//关闭管道 close(fdRead); exit(0); } void sigFunc(int signum){ printf("Bye Bye.\n"); //通过消息队列,把关闭信息发送给B1,然后删除消息队列,然后自己退出 sndToB1(msqid, "0 2 0");//发送给B1关闭信号 write(fdWrite, "0 2 0", 5);//发送给A关闭信号 usleep(500);//睡一下,等B1先关闭 //捕获2号和3号信号,调用closeAll函数 closeAll(); }
a1.c
//========== A1 ========== //1.从共享内存中读取消息 //2.打印 int display(); #include <func.h> int main() { int ret = display(); ERROR_CHECK(ret, -1, "A1 display"); return 0; } int display(){ //1.从共享内存中读取数据 //没有消息就等待,有消息就读取,使用信号量集,类似消费者 //1.1 创建一个信号量集,如果共享内存中有数据就读取,如果共享内存中没有数据就阻塞 int semid = semget(2000, 1, IPC_CREAT|0666); ERROR_CHECK(semid, -1, "A1 semget"); semctl(semid, 0, SETVAL, 0);//信号量初始值设为0 //设置信号量,测试并取资源,类似消费者操作 struct sembuf P; P.sem_num = 0; P.sem_op = -1; P.sem_flg = SEM_UNDO; printf("=========== A1 ===========\n"); while(1){ semop(semid, &P, 1);//P操作,测试并取资源 int shmid = shmget(1000, 4096, IPC_CREAT|0666); ERROR_CHECK(shmid, -1, "A1 shmget"); char *p = (char *)shmat(shmid, NULL, 0);//连接共享内存 int type = 0; sscanf(p, "%*d %d", &type);//获取消息的属性,然后根据协议执行相应的操作 switch (type){ case 1: //从B来的消息 printf("<<<<<<<<< receive <<<<<<<<<<\n"); struct tm *ptm = NULL; time_t tmp = 0; char ss[512] = {0}; sscanf(p, "%ld", &tmp);//读取消息中的时间信息 sscanf(p, "%*d %*d %[^\n]", ss); ptm = gmtime(&tmp); printf("%4d-%02d-%02d %02d:%02d:%02d\n", ptm->tm_year+1900,ptm->tm_mon+1,ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec); puts(ss); printf("\n"); //清空共享内存中的数据 memset(p, 0, 4096); break; case 2: printf("Bye Bye.\n"); shmdt(p); shmctl(shmid, IPC_RMID, NULL); exit(0); break; case 3: printf(">>>>>>>>> send >>>>>>>>>>>\n"); struct tm *ptm3 = NULL; time_t tmp3 = 0; char ss3[512] = {0}; sscanf(p, "%ld", &tmp3);//读取消息中的时间信息 sscanf(p, "%*d %*d %[^\n]", ss3); ptm3 = gmtime(&tmp3); printf("%4d-%02d-%02d %02d:%02d:%02d\n", ptm3->tm_year+1900,ptm3->tm_mon+1,ptm3->tm_mday, ptm3->tm_hour, ptm3->tm_min, ptm3->tm_sec); puts(ss3); printf("\n"); //清空共享内存中的数据 memset(p, 0, 4096); break; default: printf("sonething wrong!\n"); } } }
b1.c
//========== B1 =========== //接收来自B的消息,并打印 #include <func.h> typedef struct myMsg{ long mtype; char mtext[512]; }myMsg_t; int display(); int main() { int ret = display(); ERROR_CHECK(ret, -1, "B1 display"); return 0; } int display(){ printf("=========== B1 ===========\n"); while(1){ //接收来自B的消息 int msqid = msgget(3000, IPC_CREAT|0666); ERROR_CHECK(msqid, -1, "B1 msgget"); myMsg_t msgfromB; memset(&msgfromB, 0, sizeof(msgfromB)); msgrcv(msqid, &msgfromB, sizeof(msgfromB.mtext), 1, 0); //1.如果是2类信号,退出 int type = 0; sscanf(msgfromB.mtext, "%*d %d", &type);//读取消息的属性,根据不同属性,执行相应的操作 switch (type){ case 1: //从B来的消息 printf("<<<<<<<<< receive <<<<<<<<<<\n"); struct tm *ptm = NULL; time_t tmp = 0; char ss[512] = {0}; sscanf(msgfromB.mtext, "%ld", &tmp);//读取消息中的时间信息 sscanf(msgfromB.mtext, "%*d %*d %[^\n]", ss); ptm = gmtime(&tmp); printf("%4d-%02d-%02d %02d:%02d:%02d\n", ptm->tm_year+1900,ptm->tm_mon+1,ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec); puts(ss); printf("\n"); //清空共享内存中的数据 break; case 2: //删除消息队列并退出 printf("Bye Bye.\n"); msgctl(msqid, IPC_RMID, NULL); exit(0); case 3: printf(">>>>>>>>> send >>>>>>>>>>>\n"); struct tm *ptm3 = NULL; time_t tmp3 = 0; char ss3[512] = {0}; sscanf(msgfromB.mtext, "%ld", &tmp3);//读取消息中的时间信息 sscanf(msgfromB.mtext, "%*d %*d %[^\n]", ss3); ptm3 = gmtime(&tmp3); printf("%4d-%02d-%02d %02d:%02d:%02d\n", ptm3->tm_year+1900,ptm3->tm_mon+1,ptm3->tm_mday, ptm3->tm_hour, ptm3->tm_min, ptm3->tm_sec); puts(ss3); printf("\n"); break; default: printf("Something wrong!\n"); } } }
运行如下:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持猪先飞。
上一篇: C++实现单例模式的自动释放
下一篇: 详解C语言之缓冲区溢出
相关文章
- 这篇文章主要为大家详细介绍了C语言实现放烟花的程序,有音乐播放,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-02-23
- 本篇文章主要介绍C语言中char的知识,并附有代码实例,以便大家在学习的时候更好的理解,有需要的可以看一下...2020-04-25
- 这篇文章主要介绍了详解如何将c语言文件打包成exe可执行程序,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-25
- free函数是释放之前某一次malloc函数申请的空间,而且只是释放空间,并不改变指针的值。下面我们就来详细探讨下...2020-04-25
- 这篇文章主要介绍了C语言中计算正弦的相关函数总结,包括正弦和双曲线正弦以及反正弦的函数,需要的朋友可以参考下...2020-04-25
详解C语言中的rename()函数和remove()函数的使用方法
这篇文章主要介绍了详解C语言中的rename()函数和remove()函数的使用方法,是C语言入门学习中的基础知识,需要的朋友可以参考下...2020-04-25- 今天小编在这里就来给美图秀秀的这一款软件的使用者们来说下制作隔离区聊天背景的教程,各位想知道具体方法的,那么下面就快来跟着小编一起看一看吧。 给各位美图秀...2016-09-14
- 这篇文章主要介绍了C# Winform中实现主窗口打开登录窗口关闭的方法,这在需要用户名密码的软件项目中是必用的一个技巧,要的朋友可以参考下...2020-06-25
- 这篇文章主要介绍了C语言中求和、计算平均值、方差和标准差的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-12-10
- 最近项目不多忙,于是抽点时间巩固下切换窗口问题,感兴趣的朋友跟着小编一起学习吧...2020-06-25
- 这篇文章主要介绍了Element图表初始大小及窗口自适应实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-07-10
- 本篇文章主要讲解C语言 基本语法,这里提供简单的示例和代码来详细讲解C语言的基本语法,开始学习C语言的朋友可以看一下,希望能够给你带来帮助...2021-09-18
- 下面小编就为大家带来一篇C#中添加窗口的步骤详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
- 这篇文章主要介绍了C语言中send()函数和sendto()函数的使用方法,是C语言入门学习中的基础知识,需要的朋友可以参考下...2020-04-25
C# Windows API应用之基于GetDesktopWindow获得桌面所有窗口句柄的方法
这篇文章主要介绍了C# Windows API应用之基于GetDesktopWindow获得桌面所有窗口句柄的方法,结合实例形式分析了GetDesktopWindow函数用于获取窗口句柄的具体使用方法与相关注意事项,需要的朋友可以参考下...2020-06-25- C#获取进程的主窗口句柄的实现方法,需要的朋友可以参考一下...2020-06-25
- 今天小编就为大家分享一篇C语言实现从文件读入一个3*3数组,并计算每行的平均值,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-04-25
- 这篇文章主要介绍了Pyqt5 实现窗口缩放,控件在窗口内自动伸缩的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-09
- 这篇文章主要介绍了使用C语言操作文件的基本函数整理,包括创建和打开以及关闭文件的操作方法,需要的朋友可以参考下...2020-04-25
- 这篇文章主要介绍了C语言中memcpy 函数的用法详解的相关资料,需要的朋友可以参考下...2020-04-25