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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
//========= 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
//========= 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
//========== 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
//========== 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" );
}
}
}
|
运行如下:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/weixin_42565760/article/details/115708548