1. 程式人生 > >消息隊列、socket(UDP)實現簡易聊天系統

消息隊列、socket(UDP)實現簡易聊天系統

sed sele perror 語句 sprintf display clu 隊列 success

  前言:

  最近在學進程間通信,所以做了一個小項目練習一下。主要用消息隊列和socket(UDP)實現這個系統,並數據庫存儲數據,對C語言操作數據庫不熟悉的可以參照我的這篇博客:https://www.cnblogs.com/liudw-0215/p/9593414.html,所有代碼提交我的Github上,地址:https://github.com/ldw0215/Chat-System.git,可以自行下載,然後make一下就可以了。

   一、項目要求

  1. 要求實現用戶註冊、用戶登錄功能,密碼需加密顯示
  2. 要求實現聊天功能,雙方能互發消息
  3. 數據要求數據庫存儲

  二、架構解析

  主要流程圖如下:

  技術分享圖片  

  主要有客戶端(用戶)和服務端,客戶端發送註冊、登錄請求,服務端回應請求,並且雙方可以模擬聊天(互相發送消息),主要客戶端請求界面見下圖:

  技術分享圖片

  註冊、登錄使用消息隊列進行通信的,聊天是通過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; } } #endif void 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信號,是進程退出,服務端響應截圖如下:

  技術分享圖片

  其實現代碼如下:

  

技術分享圖片
#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)實現的。

  總結:通過做這種小項目學到了很多,也發現許多不足,最重要的就是架構能力,之前都是做一小塊,沒有大局觀,雖然項目小,但五張俱全,很鍛煉人,繼續找項目做!

  

消息隊列、socket(UDP)實現簡易聊天系統