C語言實現四窗口聊天
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"); } } }
運行如下:
以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- C語言中進程間通訊的方式詳解
- Linux之進程間通信(共享內存【mmap實現+系統V】)
- 超詳細的c語言字符串操作函數教程
- C語言文件操作零基礎新手入門保姆級教程
- MySQL系列教程之使用C語言來連接數據庫