实验要求
在linux系统用c/c++语言编写一个多用户的聊天室管理系统。主要功能:
1 能做到3个以上用户之间的聊天;
2 系统要有用户管理功能;
3 每个用户能管理自己的权限,比如 不接受信息,撤销已发的信息等;可以自己发挥;
4 聊天信息的保存,比如保存三天内的信息,或其他规定;
5 敏感词的过滤等等;
6 自己添加的其他功能。
所用技术主要有:
C语言的多进程编程;进程间的通信;文件处理等技术。
注意:在一个linux主机上的多个用户的聊天室管理系统。
实验代码
server.c
#include<sys/types.h>
#include<sys/shm.h>
#include<sys/ipc.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<sys/wait.h>
#include<signal.h>
#include<string.h>
#include <stdbool.h>
#include<time.h>
#include <pthread.h>
#define SHMKEY 75
#define MSGMAXN 35 //最大消息数
typedef struct{
time_t t; //时间
int pid; //进程号
char text[15]; //文本内容
}MSG; //消息
typedef struct{
int pid; //进程号
bool state; //是否在线
bool send_state; //是否被禁言
}PROCESS; //进程信息
typedef struct{
int spid; //server进程号
int psum; //进程总数
int temp; //临时变量
int msgnum; //消息数量
PROCESS process[15];
MSG msg[MSGMAXN];
}MEM; //内存内容
int shmid;
MEM *addr; //共享存储区首地址
void init() //初始化
{
printf("server pid:%d\n",getpid());
shmid=shmget(SHMKEY,1024,0777|IPC_CREAT); //创建共享存储区
system("ipcs -m");//相当于在SHELL下执行 ipcs -m命令,该命令是显示系统所有的共享内存
addr=(MEM*)shmat(shmid,0,0); //获取首地址
(*addr).psum=0;
(*addr).msgnum=0;
(*addr).spid=getpid();
}
void check(int signo)
{
printf("checking...\n");
char s[20][15];
memset(s,'\0',200);
FILE *file;
char *FILE_NAME=(char*)"sensitive_word.bin";
file = fopen(FILE_NAME,"rb"); //以rb方式打开文件
if (file == NULL) perror("errno");
int len;
fseek(file, 0, SEEK_END); //文件指针定位到文件末尾,偏移0个字节
len = ftell(file); //获取文件长度, ftell函数用于得到文件位置指针当前位置相对于文件首的偏移字节数
fseek(file,0,SEEK_SET); //注意将文件指针移回文件的开头
for (int i=0;i<len/15;i++)
fread(s[i],15,1,file); //读取文件内容,并存入s
int curidx =((*addr).msgnum) - 1;
char *current=(*addr).msg[curidx].text;
int n = len/15;
for (int i=0;i<n;i++)
{
if(strstr(current,s[i])!=NULL)
{
printf("%d发送敏感词%s",(*addr).msg[curidx].pid,s[i]);
strcpy((*addr).msg[curidx].text,"******\n"); //覆盖敏感词
break;
}
}
}
void write_senwd()
{
printf("请输入敏感词\n");
char *FILE_NAME = (char*)"sensitive_word.bin"; //敏感词词库文件
FILE *file;
char s[15];
file = fopen(FILE_NAME,"wb"); //创建文件,并以wb方式打开
if (file == NULL) perror("errno");
int i;
for (i=0;i<15;i++)
{
fgets(s,15,stdin);
if (strstr(s,"1#")!=NULL) break;
fwrite(&s,15,1,file); //将敏感词写入文件
}
printf("写入敏感词成功\n\n");
fclose(file);
}
void delete_senwd()
{
printf("\n请输入敏感词\n");
char a[30][15];
int senwdnum;
for(senwdnum=0;senwdnum<30;senwdnum++)
{
fgets(a[senwdnum],15,stdin);
if (strstr(a[senwdnum],"2#")!=NULL) break;
}
char s[30][15];
memset(s,'\0',200);
FILE *file ,*file1;
char *FILE_NAME=(char*)"sensitive_word.bin";
file = fopen(FILE_NAME,"rb"); //以rb方式打开文件
if (file == NULL) perror("errno");
int len;
fseek(file, 0, SEEK_END); //文件指针定位到文件末尾,偏移0个字节
len = ftell(file); //获取文件长度, ftell函数用于得到文件位置指针当前位置相对于文件首的偏移字节数
fseek(file,0,SEEK_SET); //注意将文件指针移回文件的开头
for (int i=0;i<len/15;i++)
fread(s[i],15,1,file); //读取文件内容,并存入s
fclose(file);
remove(FILE_NAME); //删除原文件
file1 = fopen(FILE_NAME,"wb"); //新建文件
for(int i=0;i<len/15;i++)
for(int j=0;j<senwdnum;j++)
if(strstr(a[j],s[i]) == NULL)
fwrite(&s[i],15,1,file1);
printf("删除敏感词成功\n\n");
fclose(file1);
}
void show_senwd()
{
char s[30][15];
memset(s,'\0',200);
FILE *file;
char *FILE_NAME=(char*)"sensitive_word.bin";
file = fopen(FILE_NAME,"rb"); //以rb方式打开文件
if (file == NULL) perror("errno");
int len;
fseek(file, 0, SEEK_END); //文件指针定位到文件末尾,偏移0个字节
len = ftell(file); //获取文件长度, ftell函数用于得到文件位置指针当前位置相对于文件首的偏移字节数
fseek(file,0,SEEK_SET); //注意将文件指针移回文件的开头
for (int i=0;i<len/15;i++)
{
fread(s[i],15,1,file); //读取文件内容,并存入s
printf("%d---%s",i+1,s[i]);
}
printf("\n");
}
void manage()
{
int psum=(*addr).psum;
printf("\n所有进程及状态:\n");
for (int i=0;i<psum;i++)
{
printf("%d ",(*addr).process[i].pid);
if ((*addr).process[i].state) printf("\t在线");
else printf("\t离开");
if ((*addr).process[i].send_state) printf("\t可发言\n");
else printf("\t被禁言\n");
}
int ctrl;
printf("\n输入管理信号:1--移除进程\n2--禁言\n3--解除禁言\n");
scanf("%d",&ctrl);
if (ctrl==1) {
int n;
printf("输入要移除的进程号:");
scanf("%d",&n);
kill(n,SIGUSR2); //向要移除的进程发送信号
printf("\n进程%d已移除\n",n);
}
if (ctrl==2 ||ctrl==3) {
int n;
printf("输入进程号:");
scanf("%d",&n);
for(int i=0;i<psum;i++)
{
if ((*addr).process[i].pid==n){
if (ctrl==2) (*addr).process[i].send_state = 0;
else (*addr).process[i].send_state = 1;
break;
} //改变进程是否被禁言状态
}
}
}
void signal_to_all()
{
int n = (*addr).psum;
for (int i=0;i<n;i++)
{
if ((*addr).process[i].state == 1) {
kill((*addr).process[i].pid,16);
}
}
}
void *cleanmsg(void* arg)
{
MSG *msg;
time_t t;
struct tm *p,*msg_p; //通过tm结构来获得日期和时间
int i;
while(1)
{
time(&t); //此函数会返回从公元 1970 年1 月1 日的UTC 时间从0 时0 分0 秒算起到现在所经过的秒数。如果t 并非空指针的话,此函数也会将返回值存到t 指针所指的内存。
for (i = 0;i<(*addr).msgnum;i++)
{
double fl=difftime(t,(*addr).msg[i].t); //返回两个time_t型变量之间的时间间隔
if (fl<=120.0) break; //时间差小于120s
}
if (i == 0)
{
sleep(10);
continue;
} //没有可清除msg
lockf(1,1,0); //锁定屏幕输出
/*将未过期的消息向内存区内消息的区域前移动*/
msg = (MSG *)malloc((*addr).msgnum-i); //申请一块内存,大小是共享存储区内未过期消息的大小
memcpy(msg,&((*addr).msg[i]),((*addr).msgnum-i)*sizeof(MSG)); //备份未过期消息
memcpy((*addr).msg,msg,((*addr).msgnum-i)*sizeof(MSG)); //复制备份
(*addr).msgnum=(*addr).msgnum-i; //改变消息数
(*addr).temp=i; //清除的消息数
signal_to_all(); //向所有在线进程发送信号
lockf(1,0,0); //解锁.
free(msg);
sleep(10);
}
}
void EXIT()
{
int psum=(*addr).psum;
for (int i=0;i<psum;i++)
{
kill((*addr).process[i].pid,SIGUSR2);
}
exit(0);
}
int main()
{
init();
signal(SIGUSR1,check);
printf("1#---添加敏感词\n2#---删除敏感词\n3#---显示敏感词\n4#---管理线程\n5#---退出\n");
pthread_t id1;
int err = pthread_create(&id1, NULL, cleanmsg, NULL);
if (err != 0)
printf("can't create thread 1: %d\n", err);
char control[10]; //控制字符
while (1)
{
fgets(control,10,stdin);
if (strstr(control,"1#")!=NULL) write_senwd();
if (strstr(control,"2#")!=NULL) delete_senwd();
if (strstr(control,"3#")!=NULL) show_senwd();
if (strstr(control,"4#")!=NULL) manage();
if (strstr(control,"5#")!=NULL) {EXIT();exit(0);}
}
shmctl(shmid,IPC_RMID,0); //撤消共享存储区,归还资源
exit(0);
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <signal.h>
#include <stdbool.h>
#define SHMKEY 75
#define MSGMAXN 35
typedef struct{
time_t t;
int pid;
char text[15];
}MSG;
typedef struct{
int pid;
bool state;
bool send_state;
}PROCESS;
typedef struct{
int spid;
int psum;
int temp;
int msgnum;
PROCESS process[15];
MSG msg[MSGMAXN];
}MEM;
MEM *addr; //共享存储区首地址
int shmid;
int i1,i2; //i1是消息的写指针,i2是消息的读指针
int inx=0; //进程信息process数组的相对号
int recv_flag = 1; //是否接收消息
void init() //初始化
{
time_t t;
struct tm *p;
time(&t);
p = gmtime(&t);
printf("%d:%d:%d %d: 你已进入群聊\n",p->tm_hour,p->tm_min,p->tm_sec,getpid());
int shmid=shmget(SHMKEY,1024,0777); //打开共享存储区
if (shmid == -1)
{
perror("shmget"); //输出上一个函数错误的原因
exit(-2);
}
//映射共享内存
addr = (MEM*)shmat(shmid, 0, 0);
if (addr == (void *)-1)
{
perror("shmat");
exit(-3);
}
lockf(1,1,0);
(*addr).process[(*addr).psum].pid=getpid();
(*addr).process[(*addr).psum].state=1;
(*addr).process[(*addr).psum].send_state=1;
inx=(*addr).psum;
(*addr).psum++;
int in1=(*addr).msgnum;
strcpy((*addr).msg[in1].text,"进入群聊\n");
time(&(*addr).msg[in1].t);
(*addr).msg[in1].pid=getpid();
(*addr).msgnum++;
lockf(1,0,0);
}
void send()
{
char c[15];
while (1)
{
fgets(c,15,stdin);
if (!(*addr).process[inx].send_state) {
printf("你已被禁言\n消息发送失败\n");
continue;
} //被禁言,忽略发送内容,进入下一次循环
if (strstr(c,"1#")!=NULL) {recv_flag = 0;continue;} //不接受消息
if (strstr(c,"2#")!=NULL) {recv_flag = 1;continue;} //接收消息
lockf(1,1,0);
i1=(*addr).msgnum;
strcpy((*addr).msg[i1].text,c);
time(&(*addr).msg[i1].t);
(*addr).msg[i1].pid=getpid();
kill((*addr).spid,SIGUSR1); //向server发送信号,提醒server检查消息
(*addr).msgnum++;
lockf(1,0,0);
}
}
void *recv(void* arg) //接收消息
{
i2=0;
struct tm *p;
while (1)
{
while (((*addr).msgnum) <= i2)
{
sleep(5);
}
if (!recv_flag) {
i2++;
sleep(10);
continue;
} //不接收信息
lockf(1,1,0);
if ((*addr).msg[i2].pid==getpid())
{
lockf(1,0,0);
(i2)++;
continue;
}
p = gmtime(&(*addr).msg[i2].t);
printf("%d:%d:%d ",p->tm_hour,p->tm_min,p->tm_sec);
printf("%d: ",(*addr).msg[i2].pid);
printf(" %s",(*addr).msg[i2].text);
lockf(1,0,0);
(i2)++;
}
}
void pexit(int sig)
{
lockf(1,1,0);
(*addr).process[inx].state=0; //不在线状态
int in1=(*addr).msgnum;
time(&(*addr).msg[in1].t);
(*addr).msg[in1].pid=getpid();
if (sig==SIGUSR2)
{
strcpy((*addr).msg[in1].text,"已被踢出群聊\n");
printf("你已被踢出群聊\n");
}
else
{
strcpy((*addr).msg[in1].text,"已退出群聊\n");
printf("你已退出群聊\n");
}
(*addr).msgnum++;
lockf(1,0,0);
exit(0);
}
void func(int signo)
{
i2 -= (*addr).temp; //减去删除的消息数
}
int main()
{
init();
signal(SIGUSR2,pexit);
signal(SIGINT,pexit);
signal(16,func);
pthread_t id1;
int err = pthread_create(&id1, NULL, recv, NULL);
if (err != 0)
printf("can't create thread 1: %d\n", err);
send();
pthread_join(id1, 0); //等待线程结束
return 0;
}
实验截图