C語言實現(xiàn)四窗口聊天
C語言實現(xiàn)四窗口聊天,供大家參考,具體內(nèi)容如下
為了練習(xí)前段時間學(xué)習(xí)的共享內(nèi)存、管道、消息隊列等進程同步機制,做了一個聊天小項目。
項目描述:
有4個進程,A進程和B進程負責(zé)通信,從標(biāo)準(zhǔn)輸入讀到的字符串通過管道發(fā)給對方,A1和B1進程負責(zé)顯示,其中:
- A進程和B進程通過管道通信,A進程和A1進程通過共享內(nèi)存通信,B進程和B1進程通過消息隊列通信;
- A進程從標(biāo)準(zhǔn)輸入讀到的字符串后,放到管道和共享內(nèi)存里,從管道中讀到的字符串放到共享內(nèi)存里,B進程從管道中拿到A放的字符串,A1進程到共享內(nèi)存中拿到字符串,打印到屏幕上;
- B進程從標(biāo)準(zhǔn)輸入讀到的字符串發(fā)給A進程,同時通過消息隊列發(fā)給B1進程,B1進程從消息隊列中讀出消息,打印到屏幕上;
- 退出時,在A和B任意一個進程中輸入 Crtl+c 或者 Ctrl+\ ,四個進程會刪除所有管道、共享內(nèi)存、消息隊列等資源,然后有序退出。
操作系統(tǒng):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.從標(biāo)準(zhǔn)輸入讀取數(shù)據(jù),通過有名管道發(fā)送給B窗口
//2.接收從B窗口發(fā)送過來的數(shù)據(jù)
//3.通過共享內(nèi)存和信號量,將從B來的數(shù)據(jù)發(fā)送給A1
//===========================
#include <func.h>
int chatA(int shmid, int semid, char *p);
int sndToA1(int semid, int shmid, char *p, char *msg);//把要打印的消息發(fā)送給A1
void closeAll();//把關(guān)閉消息發(fā)送出去,關(guān)閉共享內(nèi)存和信號量集
void sigFunc(int signum);//新的2號和3號信號處理函數(shù),如果在A窗口發(fā)生2號和3號信號就調(diào)用close函數(shù)
//全局變量,后面捕獲到退出信號回收資源時使用
int semid;
int shmid;//共享內(nèi)存
char *p;
int fdWrite;
int fdRead;
int main()
{
//1.創(chuàng)建信號量集,如果有新消息就往共享內(nèi)存中寫,類似生產(chǎn)者
semid = semget(2000, 1, IPC_CREAT|0666);
ERROR_CHECK(semid, -1, "A semget");
shmid = shmget(1000, 4096, IPC_CREAT|0666);//創(chuàng)建一個共享內(nèi)存
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;//設(shè)置一個信箱,用來監(jiān)控有沒有讀取到信息
while(1){
struct timeval timeout;//設(shè)置超時
timeout.tv_sec = 5;
timeout.tv_usec = 15000000;//超過5秒沒有接收到信息就是超時
FD_ZERO(&rdset);//初始化集合,清空信箱
FD_SET(fdRead, &rdset);//將要監(jiān)聽的管道1注冊到集合中
FD_SET(STDIN_FILENO, &rdset);//將要監(jiān)聽的標(biāo)準(zhǔn)輸入注冊到集合中
int tret = select(fdRead + 1,&rdset,NULL,NULL,&timeout);//調(diào)用select進行監(jiān)聽
if(tret == 0){
puts("time out!");
}
//select阻塞進程,任意一個FD就緒,解除阻塞
//解除阻塞,檢查是誰就緒
if(FD_ISSET(fdRead, &rdset)){
//如果是管道就緒,讀取管道中的內(nèi)容,發(fā)送給A1
memset(buf, 0, sizeof(buf));//清空buf中的內(nèi)容,用來接收管道中的信息
int ret = read(fdRead, buf, 1024);//將管道中的信息讀取出來
if(ret == 0){
//如果另一端對管道的寫先關(guān)閉了,退出聊天
sigFunc(2);
break;
}
//獲取從B來的消息的類型
int type = 0;
sscanf(buf, "%*d %d", &type);//讀取消息的類別,1類為正常,2類為關(guān)閉所有窗口
int snd_ret = 0;
switch (type){
case 1:
//如果是1號信息,通過共享內(nèi)存直接把消息發(fā)送給A1
snd_ret = sndToA1(shmid, semid, p, buf);
ERROR_CHECK(snd_ret, -1, "sndToA1");
break;
case 2:
//=====如果是從B發(fā)過來的2號信息,關(guān)閉所有窗口=====
//向A1發(fā)送一個空的2號信號,讓A1自己退出,然后自己再退出
sigFunc(2);
exit(0);
}
}
if(FD_ISSET(STDIN_FILENO, &rdset)){
//如果標(biāo)準(zhǔn)輸入準(zhǔn)備就緒,讀取標(biāo)準(zhǔn)輸入?yún)^(qū)的數(shù)據(jù),標(biāo)記為3號信號,發(fā)送給A1和B
time_t localtm;
time(&localtm);//獲取當(dāng)前時間
localtm += 8*3600;
memset(buf, 0, sizeof(buf));//清空buf
int ret = read(STDIN_FILENO, buf, 1024);//讀取數(shù)據(jù)
if(ret == 0){
//如果在標(biāo)準(zhǔn)輸入中讀到了終止符,退出聊天窗口
puts("I quite.");
break;
}
char sstoA1[1024] = {0};//用來拼接數(shù)據(jù),發(fā)送給A1的數(shù)據(jù)
char sstoB[1024] = {0};//用來拼接數(shù)據(jù),發(fā)送給B的數(shù)據(jù)
sprintf(sstoA1, "%ld %d %s", localtm, 3, buf); //標(biāo)注為三號信號發(fā)送給A1
sprintf(sstoB, "%ld %d %s", localtm, 1, buf); //標(biāo)注為1號信號發(fā)送給B
sndToA1(shmid, semid, p, sstoA1);//發(fā)送給A1
write(fdWrite, sstoB, sizeof(sstoB));//通過管道發(fā)送給B
}
}
close(fdRead);
close(fdWrite);
return 1;//程序成功運行結(jié)束,返回1
}
int sndToA1(int shmid, int semid, char *p, char *msg){
//使用共享內(nèi)存和信號量給A1傳遞信息
//信號量集的操作,如果有新消息就往共享內(nèi)存中寫,類似生產(chǎn)者
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);//創(chuàng)建一個共享內(nèi)存 */
ERROR_CHECK(shmid, -1, "shmget");
/* char *p = (char *)shmat(shmid, NULL, 0); */
memcpy(p, msg, strlen(msg));//向共享內(nèi)存中寫信息
return 1;
}
void closeAll(){
//根據(jù)共享內(nèi)存和信號量級的標(biāo)識符,關(guān)閉并刪除它們
write(fdWrite, "0 2 0", 5);//通過管道發(fā)送給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號信號,發(fā)送關(guān)閉信息給A1,然后調(diào)用closeAll
sndToA1(shmid, semid, p, "0 2 0");//發(fā)送給A1
usleep(500);
closeAll();
}
b.c
//========= B窗口 ===========
//1.從標(biāo)準(zhǔn)輸入讀取數(shù)據(jù),通過有名管道發(fā)送給A窗口
//2.接收從A窗口發(fā)送過來的數(shù)據(jù)
//3.通過共享內(nèi)存和信號量,將從A來的數(shù)據(jù)發(fā)送給B1
//===========================
#include <func.h>
//自定義一個消息結(jié)構(gòu)體,用來和B1傳遞數(shù)據(jù)
typedef struct myMsg{
long mtype;
char mtext[512];
}myMsg_t;
int chatB(char *pipe1, char *pipe2);
int sndToB1(int msqid, char *msg);//把從A來的消息發(fā)送給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");
//注冊新的信號處理函數(shù)
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;//設(shè)置集合,用來監(jiān)聽
while(1){
//利用集合設(shè)置阻塞
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!");
}
//集合中有就緒的,檢查是誰就緒,并進行相應(yīng)的操作
if(FD_ISSET(fdRead, &rdset)){
//如果是管道就緒,讀取數(shù)據(jù)并發(fā)送給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號信息,關(guān)閉所有窗口
//向B1發(fā)送關(guān)閉信號,然后回收消息隊列,再自己結(jié)束
if(type == 2){
sigFunc(2);
exit(0);
}
//如果是其他有效信息,發(fā)送給B1
int snd_ret = sndToB1(msqid, buf);
ERROR_CHECK(snd_ret, -1, "B sndToB1");
}
if(FD_ISSET(STDIN_FILENO, &rdset)){
//如果是標(biāo)準(zhǔn)輸入?yún)^(qū)就緒,讀取數(shù)據(jù),分別發(fā)給A和B1
time_t localtm;
time(&localtm);//獲取當(dāng)前時間
localtm += 8*3600;
memset(buf, 0, sizeof(buf));
int ret = read(STDIN_FILENO, buf, 1024);
if(ret == 0){
puts("I quite.");
break;
}
//按照協(xié)議拼接數(shù)據(jù)并發(fā)送出去
char sstoA[1024] = {0};//發(fā)送給A的數(shù)據(jù)
sprintf(sstoA, "%ld %d %s", localtm, 1, buf);
write(fdWrite, sstoA, sizeof(sstoA));
char sstoB1[1024] = {0};//發(fā)送給B1的數(shù)據(jù)標(biāo)注為3號
sprintf(sstoB1, "%ld %d %s", localtm, 3, buf);
sndToB1(msqid, sstoB1);
}
}
close(fdRead);
close(fdWrite);
return 1;//程序成功運行結(jié)束,返回1
}
int sndToB1(int msqid, char *msg){
//通過消息隊列,把數(shù)據(jù)發(fā)送給B1
myMsg_t msgtoB1;//創(chuàng)建一個消息結(jié)構(gòu)體
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);//關(guān)閉管道
close(fdRead);
exit(0);
}
void sigFunc(int signum){
printf("Bye Bye.\n");
//通過消息隊列,把關(guān)閉信息發(fā)送給B1,然后刪除消息隊列,然后自己退出
sndToB1(msqid, "0 2 0");//發(fā)送給B1關(guān)閉信號
write(fdWrite, "0 2 0", 5);//發(fā)送給A關(guān)閉信號
usleep(500);//睡一下,等B1先關(guān)閉
//捕獲2號和3號信號,調(diào)用closeAll函數(shù)
closeAll();
}
a1.c
//========== A1 ==========
//1.從共享內(nèi)存中讀取消息
//2.打印
int display();
#include <func.h>
int main()
{
int ret = display();
ERROR_CHECK(ret, -1, "A1 display");
return 0;
}
int display(){
//1.從共享內(nèi)存中讀取數(shù)據(jù)
//沒有消息就等待,有消息就讀取,使用信號量集,類似消費者
//1.1 創(chuàng)建一個信號量集,如果共享內(nèi)存中有數(shù)據(jù)就讀取,如果共享內(nèi)存中沒有數(shù)據(jù)就阻塞
int semid = semget(2000, 1, IPC_CREAT|0666);
ERROR_CHECK(semid, -1, "A1 semget");
semctl(semid, 0, SETVAL, 0);//信號量初始值設(shè)為0
//設(shè)置信號量,測試并取資源,類似消費者操作
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);//連接共享內(nèi)存
int type = 0;
sscanf(p, "%*d %d", &type);//獲取消息的屬性,然后根據(jù)協(xié)議執(zhí)行相應(yīng)的操作
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");
//清空共享內(nèi)存中的數(shù)據(jù)
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");
//清空共享內(nèi)存中的數(shù)據(jù)
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);//讀取消息的屬性,根據(jù)不同屬性,執(zhí)行相應(yīng)的操作
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");
//清空共享內(nèi)存中的數(shù)據(jù)
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");
}
}
}
運行如下:

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C++中HTTP?代理服務(wù)器的設(shè)計與實現(xiàn)詳解
代理服務(wù)器,即允許一個網(wǎng)絡(luò)終端(一般為客戶端)通過這個服務(wù)與另一?個網(wǎng)絡(luò)終端(一般為服務(wù)器)進行非直接的連接,下面我們就來看看如何使用C++設(shè)計與實現(xiàn)一個HTTP?代理服務(wù)器吧2024-01-01
C++二叉樹的前序中序后序非遞歸實現(xiàn)方法詳細講解
前序遍歷的順序是根、左、右。任何一顆樹都可以認為分為左路節(jié)點,左路節(jié)點的右子樹。先訪問左路節(jié)點,再來訪問左路節(jié)點的右子樹。把訪問左路節(jié)點的右子樹看成一個子問題,就可以完整遞歸訪問了2023-03-03
簡單談?wù)勱P(guān)于C++中大隨機數(shù)的問題
這篇文章主要介紹了關(guān)于C++中大隨機數(shù)的問題,文中給出了詳細的示例代碼,相信對大家的學(xué)習(xí)或者工作具有一定的參考借鑒價值,有需要的朋友可以一起來學(xué)習(xí)學(xué)習(xí)。2017-01-01
C++集體數(shù)據(jù)交換實現(xiàn)示例講解
這篇文章主要介紹了C++集體數(shù)據(jù)交換實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-11-11
C++?AVL樹插入新節(jié)點后的四種調(diào)整情況梳理介紹
AVL樹是高度平衡的而二叉樹,它的特點是AVL樹中任何節(jié)點的兩個子樹的高度最大差別為1,本文主要給大家介紹了C++如何實現(xiàn)AVL樹,需要的朋友可以參考下2022-08-08
linux環(huán)境下C++實現(xiàn)俄羅斯方塊
這篇文章主要為大家詳細介紹了linux環(huán)境下C++實現(xiàn)俄羅斯方塊,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-06-06

