zoukankan      html  css  js  c++  java
  • 用消息队列和socket实现聊天系统

      前言:最近在学进程间通信,所以做了一个小项目练习一下。主要用消息队列和socket(UDP)实现这个系统,并数据库存储数据,对C语言操作不熟悉的可以参照我的这篇博客:https://www.cnblogs.com/liudw-0215/p/9593414.html,所有代码提交我的Github上,地址:https://github.com/ldw0215/Chat-System.git,可以自行下载,然后make一下就可以了。

      一、架构解析

      主要有客户端和服务端,客户端发送请求,服务端回应请求,客户端实现的功能主要见下图:

      

      注册、登录使用消息队列进行通信的,聊天是通过socket(UDP)实现的!数据存在数据库中,需要一张数据表,建表数据语句如下:

      

    CREATE TABLE `user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(64) NOT NULL DEFAULT '',
      `password` varchar(64) NOT NULL DEFAULT '',
      `check` varchar(64) NOT NULL DEFAULT '',
      PRIMARY KEY (`id`),
      UNIQUE KEY `name` (`name`)
      ) ENGINE=InnoDB AUTO_INCREMENT=52 DEFAULT CHARSET=utf8;

      二、客户端实现

      client.c创建不同的消息队列的键,根据不同的消息类型的进行发送,并等待服务端响应,client.c代码如下:

      

    #include "my.h"
    
    Msg m;
    Msg_stoc msg_stoc;
    
    static int msgid_ctos;
    static int msgid_stoc;
    
    void showmenu()
    {
        puts("-------CHAT----------");
        puts("|  1:发送  2:接收   |");
        puts("|      3:退出       |");
        puts("--------------------");
    }
    
    void show()
    {
        puts("-------CHAT----------");
        puts("|  1:注册  2:登录   |");
        puts("|      0:退出       |");
        puts("--------------------");
    }
    
    void send1()
    {
        printf("%s","send");
        char buf[16] = {''};
        char str[200] = {''};
        struct sockaddr_in dui,zj;
        int n;
        short x;
        int sockfd = socket(AF_INET,SOCK_DGRAM,0);
        if(sockfd < 0)
        {
            perror("socket");
            exit(-1);
    
        }
        zj.sin_family = AF_INET;
        zj.sin_port = htons(5555);
        zj.sin_addr.s_addr = htonl(INADDR_ANY);
        n = bind(sockfd,(struct sockaddr *)&zj,sizeof(zj));
        if(n < 0)
        {
            close(sockfd);
            perror("bind");
            exit(-1);
    
        }
        //puts("请输入对方号码 端口 IP ");
        //scanf("%hd%s",&x,buf);
        getchar();
        dui.sin_addr.s_addr = inet_addr("10.10.3.129");
        dui.sin_port = htons(8888);
        dui.sin_family = AF_INET;
        puts("请输入想要发送的内容:");
        //gets(str);
        fgets(str,200,stdin);
        
        n = sendto(sockfd,str,sizeof(str),0,(struct sockaddr *)&dui,sizeof(dui));
        if(n <= 0)
        {
            close(sockfd);
            perror("sendto");
            exit(-1);
    
        }
        close(sockfd);
        return;
        
    }
    #if 1 
    void asend(int sockfd,struct sockaddr_in dui)
    {
        char buf[200] = {''};
        int n;
        puts("请输入要回复的内容:");
        fgets(buf,200,stdin);
        //gets(buf);
        n = sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&dui,sizeof(dui));
        if(n <= 0)
        {
            perror("sendto");
            close(sockfd);
            exit(-1);
    
        }
        close(sockfd);
        return ;
    }
    
    void choose1(char ch ,int sockfd,struct sockaddr_in dui)
    {
        switch(ch)
        {
        case 'a':
            asend(sockfd,dui);
            break;
        case 'n':
            break;
        default:
            puts("input error!");
            break;
    
        }
    }
    #endif
    void recv1()
    {
        struct sockaddr_in dui,zj;
        socklen_t len = sizeof(dui);
        int n;
        char buf[200] = {''};
        char ch;
        int sockfd = socket(AF_INET,SOCK_DGRAM,0);
        if(sockfd < 0)
        {
            perror("socket");
            exit(-1);
    
        }
        zj.sin_family = AF_INET;
        zj.sin_port = htons(5555);
        zj.sin_addr.s_addr = htonl(INADDR_ANY);
        n = bind(sockfd,(struct sockaddr *)&zj,sizeof(zj));
        if(n < 0)
        {
            close(sockfd);
            perror("bind");
            exit(-1);
    
        }
        n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&dui,&len);
        if(n <= 0)
        {
            close(sockfd);
            perror("recvfrom");
            exit(-1);
        }
        puts(buf);
        puts("是否要回复: 回复-》a,不回复-》n");
        ch = getchar();
        getchar();
        choose1(ch,sockfd,dui);
    }
    
    void choose(int ch)
    {
        printf("%d",ch);
        switch(ch)
        {
        case 1:
            m.type = 5;
            msgsnd(msgid_ctos,&m,sizeof(m)-sizeof(m.type),0);
            send1();
            break;
        case 2:
            recv1();
            break;
        case 3:
            exit(0);
            break;
        default:
            puts("input error!");
            break;
    
        }
    }
    
    void regis()
    {
        printf("请输入姓名:");
        scanf("%s",m.name);
        
        printf("请输入密码:");
        scanf("%s",m.passwd);
        m.type=1;
    }
    
    void login(void)
    {
        printf("请输入用户名:");
        scanf("%s",&m.name);
    
        printf("请输入密码:");
        scanf("%s",&m.passwd);
        m.type=3;
    }
    
    int main(int argc ,char *argv[])
    {
        msgid_ctos=get_ctos_msg();
        msgid_stoc=get_stoc_msg();
        
        int temptype;
        
        while(1)
        {
            show();
            int a = 0;
            scanf("%d",&a);
            getchar();
            switch(a)
            {
                    case 0:return 0;
                    case 1:regis();temptype=2;break;     //注册
                    case 2:login();temptype=4;break;   //登录
            }
            
            int ret;
            long type=0;
            
            
            printf("name:%s,passwd:%s
    ", m.name,m.passwd);
            msgsnd(msgid_ctos,&m,sizeof(m)-sizeof(m.type),0);
    
            sleep(2);
            msgrcv(msgid_stoc,&msg_stoc,sizeof(msg_stoc),temptype,0);
            
            if(0 == strcmp(msg_stoc.check,"yes"))    
            {
                printf("%s",msg_stoc.info);
                break;
            }
        }
        
        while(1)
        {
            showmenu();
            int ch;
            puts("请输入功能");
            scanf("%d",&ch);
            getchar();
            printf("%d",ch);
            choose(ch);
        }
        return ;
    }

      注意,不同的通信,要用创建不同消息队列的键,并且消息类型也要不同!

      三、服务端实现

      服务端主要接送并响应客户端,主要创建不同的子进程,然后调用exec族函数,调用二进制文件,并通过消息队列接收阻塞执行,并建立信号,检测Ctrl+c信号,是进程退出,代码如下:

      

    #include"my.h"
    
    static pid_t sub_pid[9];
    
    static int msgid_ctos;
    static int msgid_stoc;
    
    void sigint(int signum)
    {
        int i;
        for(i=0;i<3;i++)
        {
            kill(sub_pid[i],SIGKILL);
        }
        
    }
    
    int main(int argc,char*argv[])
    {
        signal(SIGINT,sigint);
    
        msgid_ctos=get_ctos_msg();
        msgid_stoc=get_stoc_msg();
        
        sub_pid[0]=vfork();
        if(0==sub_pid[0])
        {
            execl("register","register",NULL);
        }
    
    
        sub_pid[1]=vfork();
        if(0==sub_pid[1])
        {
            execl("login","login",NULL);
        }
    
        sub_pid[2]=vfork();
        if(0==sub_pid[2])
        {
            execl("chat","chat",NULL);
        }
    
        wait(NULL);
        return 0;
    }

      四、各模块及数据库解析

      数据库是通过数据库函数实现的,需要头文件<mysql.h>,并链上动态库-I/usr/include/mysql -L/usr/lib64/mysql -lmysqlclient,数据量比较小,之后还要考虑优化的问题;

      注册、登录、聊天都是不同的.c文件生成二进制实现的:

      注册通过消息队列接收用户名和密码存入数据库,代码如下:

      

    #include"my.h"
    
    Msg per;
    Msg_stoc msg_stoc;
    
    static int msgid_ctos;
    static int msgid_stoc;
    
    void open_cli()
    {
        MYSQL conn;
        int res;
        //MYSQL_RES * result;
        //MYSQL_ROW row;
        mysql_init(&conn);
    
        //第三、四和五个参数,需要自己修改一下
        if (mysql_real_connect(&conn, "localhost", "root", "p@s#0fSPV", "pvault", 0, NULL, 0)) {
            printf("coneect mysql successful
    ");
            char insert_query[80];            
            //insert
            memset(insert_query, 0, sizeof(insert_query));
            strcat(insert_query, "insert into user(name,password) values('");
            strcat(insert_query, per.name);
            strcat(insert_query, "','");
            strcat(insert_query, per.passwd);
            strcat(insert_query, "')");
            printf("SQL语句: %s
    ", insert_query);
            res = mysql_query(&conn, insert_query);
            if (!res) {
                printf("insert %lu rows
    ", (unsigned long)mysql_affected_rows(&conn));
                sprintf(msg_stoc.check,"%s","no");
            }
            else {
                printf("insert error
    ");
            }
    
        }
    }
    
    
    int main()
    {
        msgid_ctos = get_ctos_msg();
        msgid_stoc = get_stoc_msg();
    
        //int    sockfd = socket_rcv();
        while(1)
        {
            int n;
            long type;
            //per.type = 1;    
            msgrcv(msgid_ctos,&per,sizeof(per)-sizeof(type),1,0);
            printf("name:%s,passwd:%s
    ", per.name,per.passwd);
            sleep(1);    
            open_cli();
            //Msg m;
            msg_stoc.type = 2;
            sprintf(msg_stoc.check,"%s","no");
            printf("check:%s", msg_stoc.check);    
            msgsnd(msgid_stoc,&msg_stoc,sizeof(msg_stoc)-sizeof(type),0);    
        }
    }

      登录也是通过消息队列接收用户名和密码,并从查询出数据,进行对比,是否可以登录,代码如下:

      

    #include"my.h"
    
    Msg per;
    Msg_stoc msg_stoc;
    
    static int msgid_ctos;
    static int msgid_stoc;
    
    void login(void)
    {
        MYSQL conn;
        int res;
        MYSQL_RES * result;
        MYSQL_ROW row;
        mysql_init(&conn);
    
        //第三、四和五个参数,需要自己修改一下
        if (mysql_real_connect(&conn, "localhost", "root", "p@s#0fSPV", "pvault", 0, NULL, 0)) {
            printf("coneect mysql successful
    ");
        char select_query[64] = {0};
        snprintf(select_query,64,"select name,password from user where name='%s'",per.name);
        printf("SQL语句: %s
    ", select_query);
        if (mysql_query(&conn, select_query) != 0) {
            fprintf(stderr, "查询失败
    ");
            exit(1);
        }
        else {
        if ((result = mysql_store_result(&conn)) == NULL) {
                fprintf(stderr, "保存结果集失败
    ");
                exit(1);
            }
            else {
                while ((row = mysql_fetch_row(result)) != NULL) {
                    printf("name is %s , ", row[0]);
                    printf("age is %s
    ", row[1]);
                    if((0 == strcmp(row[0],per.name)) && (0 == strcmp(row[1],per.passwd)))
                        strcpy(per.info,"login success!");
                }
            }
        }
        
        }
    }
    
    int main()
    {
        msgid_ctos = get_ctos_msg();
        msgid_stoc = get_stoc_msg();
    
        //int    sockfd = socket_rcv();
        while(1)
        {
            int n;
            long type;
            //per.type = 3;    
            msgrcv(msgid_ctos,&per,sizeof(per)-sizeof(type),3,0);
            //printf("name:%s,passwd:%s
    ", per.name,per.passwd);
            sleep(1);    
            login();
            //Msg m;
            msg_stoc.type = 4;
            sprintf(msg_stoc.check,"%s","yes");
            printf("check:%s", per.info);    
            msgsnd(msgid_stoc,&msg_stoc,sizeof(msg_stoc)-sizeof(type),0);    
        }
    }

      聊天是通过socket(UDP)实现的。

      总结:通过做这个小项目学到了很多,也发现许多不足,最重要的就是架构能力,之前都是做一小块,没有大局观,虽然项目小,但五张俱全,很锻炼人,继续找项目做!

      

  • 相关阅读:
    js四舍五入
    文本框只能输入整数,输入其他的自动不显示
    [转]关于C#程序部署到Android
    ajax在火狐中传中文出现乱码的解决方法
    Vue 记录 Cannot read property '_withTask' of undefined
    vs中 VMDebugger未能加载导致异常
    System.InvalidOperationException: 支持“XXX”上下文的模型已在数据库创建后发生更改。请考虑使用 Code First 迁移更新数据库(http://go.microsoft.com/fwlink/?LinkId=238269)。
    eclipse中将java项目转换成javaweb项目
    Android之SOAP协议与WebService服务器交互,解决超时的问题
    SymmetricDS 快速和灵活的数据库复制
  • 原文地址:https://www.cnblogs.com/liudw-0215/p/9600532.html
Copyright © 2011-2022 走看看