继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

消息队列、socket(UDP)实现简易聊天系统

慕桂英3389331
关注TA
已关注
手记 207
粉丝 43
获赞 187

 前言:

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

   一、项目要求

  1. 要求实现用户注册、用户登录功能,密码需加密显示

  2. 要求实现聊天功能,双方能互发消息  

  3. 数据要求数据库存储

  二、架构解析

  主要流程图如下:

  https://img1.mukewang.com/5b929d7f0001b4cc07100449.jpg  

  主要有客户端(用户)和服务端,客户端发送注册、登录请求,服务端回应请求,并且双方可以模拟聊天(互相发送消息),主要客户端请求界面见下图:

  https://img4.mukewang.com/5b929d860001c98802420587.jpg

  注册、登录使用消息队列进行通信的,聊天是通过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] = {'\0'};    char str[200] = {'\0'};    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] = {'\0'};    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;

    }
}#endifvoid recv1()
{    struct sockaddr_in dui,zj;
    socklen_t len = sizeof(dui);    int n;    char buf[200] = {'\0'};    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\n", 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 ;
}

复制代码

View Code

 

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

  四、服务端实现

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

  https://img1.mukewang.com/5b929d930001044707270305.jpg

  其实现代码如下:

  

复制代码

#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;
}

复制代码

View Code

 

  四、各模块及数据库解析

  数据库是通过数据库函数实现的,需要头文件<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\n");        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\n", insert_query);
        res = mysql_query(&conn, insert_query);        if (!res) {
            printf("insert %lu rows\n", (unsigned long)mysql_affected_rows(&conn));
            sprintf(msg_stoc.check,"%s","no");
        }        else {
            printf("insert error\n");
        }

    }
}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\n", 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);    
    }
}

复制代码

View Code

 

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

  

复制代码

#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\n");    char select_query[64] = {0};
    snprintf(select_query,64,"select name,password from user where name='%s'",per.name);
    printf("SQL语句: %s\n", select_query);    if (mysql_query(&conn, select_query) != 0) {
        fprintf(stderr, "查询失败\n");
        exit(1);
    }    else {    if ((result = mysql_store_result(&conn)) == NULL) {
            fprintf(stderr, "保存结果集失败\n");
            exit(1);
        }        else {            while ((row = mysql_fetch_row(result)) != NULL) {
                printf("name is %s , ", row[0]);
                printf("age is %s\n", 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\n", 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);    
    }
}

复制代码

View Code

  

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

 

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

  

作者:柳德维

出处:https://www.cnblogs.com/liudw-0215/p/9606604.html

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP