C语言实现四窗口聊天

作者:带着你的名字 时间:2021-12-28 01:02:12 

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语言实现四窗口聊天

来源:https://blog.csdn.net/weixin_42565760/article/details/115708548

标签:C语言,窗口,聊天
0
投稿

猜你喜欢

  • C++ socket实现miniFTP

    2022-05-01 11:45:16
  • C#实现XML与实体类之间相互转换的方法(序列化与反序列化)

    2022-05-14 03:39:32
  • Lombok为啥这么牛逼?SpringBoot和IDEA官方都要支持它

    2021-10-18 23:04:50
  • Java调用.dll文件的方法

    2023-11-23 21:16:22
  • C#判断多个文本框是否为空的方法

    2022-05-12 23:05:32
  • Android中如何安全地打印日志详解

    2023-02-10 12:17:42
  • Unity封装延时调用定时器

    2022-10-14 05:43:53
  • SpringBoot整合rockerMQ消息队列详解

    2021-10-03 10:55:14
  • C#实现创建,删除,查找,配置虚拟目录实例详解

    2022-09-27 06:38:16
  • 全局记录Feign的请求和响应日志方式

    2021-08-19 18:48:02
  • Android通过wifi连接手机(不需要root)

    2023-10-12 00:33:23
  • SpringMVC框架post提交数据库出现乱码解决方案

    2022-03-01 09:50:41
  • Java调用shell命令涉及管道、重定向时不生效问题及解决

    2021-07-18 17:02:25
  • C# 多网卡 Server Listen

    2022-05-30 16:09:18
  • Java常量池知识点总结

    2023-01-09 10:23:09
  • 关于idea更新到2020.2.3无法创建web项目原因 library is not specified

    2022-11-24 10:13:28
  • 教你轻松制作java音乐播放器

    2023-09-27 11:17:11
  • 详解SpringBoot注入数据的方式

    2022-05-09 06:34:24
  • Redisson RedLock红锁加锁实现过程及原理

    2022-12-29 20:49:42
  • Spring Security实现HTTP认证

    2021-10-31 14:21:47
  • asp之家 软件编程 m.aspxhome.com