操作系统之聊天系统的实现

实验要求
在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;
}

实验截图
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值