1. 程式人生 > >Linux下基於UDP協議實現的聊天室專案(附原始碼)

Linux下基於UDP協議實現的聊天室專案(附原始碼)

好久沒來更新了,這段時間一直在著手完成這個專案,由於之前沒有接觸過這種稍大型的專案,而且對於C/S架構以及UDP通訊的瞭解也不是很深,所以前面很大的一段時間都被浪費掉了,做了很大無用功。
剛開始弄的時候,也是在網上搜了很多資料,找了很多版本,發現大都有一個問題,就是旁人看起來不易理解 ,程式碼註釋行有限,導致看起來一頭霧水。所以,本文將會花點篇幅來理清思路(本文使用的是UDP協議進行通訊,相對於使用TCP協議的通訊方式不同,不過思路大致相同)。

下面先大致瞭解一下基本功能
聊 天 室 項 目 功 能
1. 採用 Client/Server 架構
2. Client A 登陸聊天伺服器前,需要註冊自己的 ID 和密碼
3. 註冊成功後,Client A 就可以通過自己的 ID 和密碼登陸聊天伺服器
4. 多個 Client X 可以同時登陸聊天伺服器之後,與其他使用者進行通訊聊天
5. Client A 成功登陸後可以檢視當前聊天室內其他線上使用者 Client x
6. Client A 可以選擇發訊息給某個特定的 Client X,即”悄悄話”功能
7. Client A 可以選擇發訊息全部的線上使用者,即”群發訊息”功能
8. Client A 在退出時需要儲存聊天記錄
9. Server 端維護一個所有登陸使用者的聊天會的記錄檔案,以便備查
可以選擇實現的附加功能:
1. Server 可以內建一個特殊許可權的賬號 admin,用於管理聊天室
2. Admin 可以將某個 Client X “踢出聊天室”
3. Admin 可以將某個 Client X ”設為只能旁聽,不能發言”
4. Client 端發言增加表情符號,可以設定某些自定義的特殊組合來表達感
情.如輸入:),則會自動傳送”XXX 向大家做了個笑臉”

這個專案大家可以參考QQ的功能不斷進行完善,有很多小功能都可以自己新增,比如,本文添加了點贊和個性簽名以及修改個籤的功能,總之,大夥可以根據興趣或需求自行新增。

********************************前方高能****************************
思 路 整 理
說明:本人完成這個專案總共使用了三個檔案,標頭檔案(UDP_chatroom.h)、伺服器端(UDP_Server.c)、客戶端(UDP_Client.c)。

1.第一步當然是搭建通訊環境了,具體在上一篇博文中講述過了

此處不再贅述。

2.理清功能,我們看一下這些基本功能:註冊、登入、私聊、群聊、點贊、發表情、檢視記錄balabala, 我們可以將這些功能分為兩大塊:登入前和登入後。登入前就包括註冊和登入以及退出客戶端, 登入後就包括私聊群聊那些功能了。所以,首先要解決的就是註冊和登入。

3.想想我們平時註冊賬號需要哪些東西,使用者名稱、賬號以及密碼,對吧(有興趣可以新增忘記密碼、密保或者新增手機號和郵箱等功能),當我們註冊好了之後,肯定是需要儲存下來的,所以需要用到資料庫進行儲存,而資料庫只能放在伺服器這端,只能伺服器才能訪問。客戶端首先給伺服器傳送一個註冊請求,伺服器收到請求之後開始查詢資料庫,如果該賬號沒有被註冊過,則將該客戶端發來的註冊資訊寫入資料庫並反饋一個訊息給該客戶端註冊成功,否則反饋給客戶端重新註冊。這樣,註冊就算完成了。

4.接下來就是登入,首先還是客戶端給伺服器傳送一個登入請求,伺服器收到請求之後開始查詢資料庫,如果查詢到的賬號和密碼與客戶端傳送過來的都一致,則允許登入並反饋給客戶端登入成功。這個地方要分三種情況討論:賬號密碼都正確、賬號或密碼不正確、賬號不存在(即未註冊)。

5.註冊登入完成之後就是登入後的功能了,需要注意的是,前面的註冊和登入都是隻有一個客戶端與伺服器通訊,只需要相互發送和接收訊息即可。但是登入之後可能需要一個伺服器與多個客戶端通訊,比如,私聊功能,客戶端A要與客戶端B私聊,A給B傳送訊息,首先A將要傳送的訊息以及B的地址給伺服器,伺服器再根據地址將訊息轉發給B。那麼,A又怎麼知道B的地址呢,所以這就需要綁定了,將客戶端的使用者名稱與地址繫結,這樣一來,A只需要知道B的使用者名稱即可。同時,伺服器還需反饋給客戶端A傳送成功還是失敗。也就是說,如果A給B傳送一個訊息,實際上是,A給伺服器傳送一條資訊,然後,伺服器給A和B傳送訊息。再比如,點贊功能,A給B點贊,A傳送請求給伺服器,伺服器查詢資料庫,發現數據庫中有B這個人,只需要給B的贊數加一即可,然後反饋給A點贊成功,同時給B傳送一天提醒訊息,告訴B,A給你點讚了。同樣的,禁言、解禁以及踢人都類似。。而有些功能,比如,修改個性簽名,就像當初的註冊登入一樣,只需要一個客戶端與伺服器交流即可,A給伺服器傳送請求,伺服器收到請求後開始查詢資料庫,找到A的個性簽名這一欄並修改,然後將結果反饋給A即可。

從上面可以看出,客戶端自己什麼也幹不了,它想幹什麼事,必須先發請求給伺服器,所以,在伺服器端必須有專門接收客戶端請求的,使用recefrom函式,可以放在main函式裡面,傳送訊息使用sendto函式放在功能函式裡面。在客戶端,需要傳送不同的請求以及接收伺服器發來的訊息或反饋,可以起兩個執行緒,一個傳送,一個接收,也可以只起一個執行緒用來接收,而傳送就直接交給本程序完成。(本文中客戶端只起一個執行緒,伺服器端不起執行緒)

還需要注意,以上的私聊群聊點贊等等,這些功能需要對方線上才能完成,所以,需要建立一個連結串列來對線上使用者進行管理
這裡寫圖片描述

接下來,我們再來梳理一下客戶端和伺服器傳送和接收的結構體裡面有哪些成員以及伺服器端的資料庫需要儲存哪些東西,如果不理解這兩者的區別與作用,很容易將兩者混淆。我們先在腦海中憧憬一下,首先我們執行客戶端,出來一個介面,註冊登入和退出(忘記密碼可自行新增),登入後又有一個介面,群聊私聊點贊等等許多功能。能跟我們互動的只有客戶端對吧,比如我們選擇註冊,只需要在客戶端來個switch case,選擇’1’即可,可是對於伺服器來說,它要怎麼理解來著客戶端的這個請求呢?其實很簡單,只需要我們在客戶端傳送給伺服器的結構體中新增一個成員cmd即可,這樣,客戶端只需要給這個SendBuf.cmd賦值就可以了,賦的值其實是一個巨集,可以在標頭檔案中預先巨集定義,但是儘量有一定的含義(便於理解與可讀性),比如,註冊的話,可以SendBuf.cmd = RegAcc; 這裡的RegAcc就是一個巨集,取名含義就是註冊賬號。其他的都類似這樣處理。然後等伺服器端處理完這些請求後,需要返回一個結果給客戶端,所以在伺服器的SendBuf裡應該有一個result的成員來反饋結果。
這樣看來,客戶端發給伺服器接收的結構體和伺服器發給客戶端接收的結構體,兩者的成員並不是相同的,那麼客戶端和伺服器每端都需要兩個結構體嗎?這樣是可以的,但是這樣一來就有四個結構體了,對於後面的處理可能就容易亂。所以,我們可以將傳送和接收用同一個結構體。需要注意的是結構體涉及到結構體大小的問題,所以需要客戶端和伺服器兩端的結構體完全一致,即成員順序必須一致。
這裡寫圖片描述
結構體的成員大致就這些了,接下來,我們來看看資料庫裡面有哪些內容,我們知道,資料庫是用來儲存使用者資訊的,所以使用者的基本資訊是需要的,使用者名稱、賬號、密碼、VIP、贊數、個籤,甚至還可以有密保等內容,這些都是為了保證使用者登入以及登入後該使用者之前的資訊沒有丟失。所以不要講這些東西與結構體中的成員混淆了。

原始碼(並非很完善,僅供參考)

/***************************************************
File name: UDP_chatroom.h
Author:華仔
Data:2017.12.25
Description:聊天室標頭檔案
***************************************************/

#ifndef _CHATROOM_H_
#define _CHATROOM_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <unistd.h>  
#include <signal.h>  
#include <pthread.h>  
#include <semaphore.h>  
#include <termios.h>  
#include <time.h>  
#include <sys/stat.h>  
#include <fcntl.h>  

/*********************註冊登入**********************************/  
#define REG             1         //註冊  
#define LOG             2         //登入  
#define FORGET          3         //忘記密碼  
#define EXIT            4         //退出  
#define EXISTING_ACC    5         //賬號已存在  
#define LOGGED_ACC      6         //賬號已登入  
#define ERROR           7         //賬號或密碼錯誤  
#define LOG_SUCCESS     8         //登入成功  
#define REG_SUCCESS     9         //註冊成功  
#define Exit            10        //下線  
/***************************************************************/  

/******************聊天室功能***********************************/  
#define private_chat    11        //私聊  
#define group_chat      12        //群聊  
#define group_result    13        //群聊接受  
#define file_transfer   14        //檔案傳輸  
#define online          15        //檢視線上人數  
#define expression      16        //表情  
#define phrases         17        //常用語  
#define motto           18        //個性簽名  
#define motto_change    19        //更改個性簽名  
#define like            20        //點贊  
#define Vip             21        //開會員  
#define Shutup          22        //禁言  
#define lifted          23        //解禁  
#define kick            24        //踢人  
/***************************************************************/  


/****************伺服器返回結果*********************************/  
#define VIP_SUCCESS         25          //開會員成功  
#define SHUTUP_SUCCESS      26          //禁言成功  
#define SEND_SUCCESS        27          //傳送成功  
#define SEND_FAILED         28          //操作失敗
#define KICK_SUCCESS        30          //踢人成功  
#define LIKE_SUCCESS        31          //點贊成功  
#define CHANGE_SUCCESS      32          //更改個性簽名成功  
/***************************************************************/  

#endif
/***************************************************
File name: UDP_server.c
Author:華仔
Data:2017.12.25
Description:聊天軟體伺服器端
***************************************************/

#include "UDP_chatroom.h"

#define PORT 8888


typedef struct info
{
    char username[20];              //使用者名稱
    char account[20];               //賬號
    char passwd[20];                //密碼
    char from_name[20];             //發信人
    char to_name[20];               //收信人
    char moto[30];                  //個性簽名
    char online_name[20][20];       //線上人名
    int  online_num;                //線上人數
    int  cmd;                       //提取操作符  
    int  vip;                       //會員標誌   
    int  likes;                     //點贊數
    int  result;                    //返回操作結果 
    char msg[1024];                 //傳送、接收訊息    
    char e_s;                       //確認傳送的表情
    char p_s;                       //確認傳送的常用語  
    char file_name[50];             //檔名
    char Ftp[2048];                 //檔案傳輸
}Info;

typedef struct node
{
    struct sockaddr_in client_addr;
    char name[20];
    char account[20];
    struct node *next;
}Node, *LinkList;
struct sockaddr_in client_addr;

Info SendBuf;
Info RecvBuf;
sqlite3 *ppdb = NULL;           //資料庫
LinkList head = NULL;           //線上使用者
int sockfd;         
int ret;


//註冊
void deal_reg(struct sockaddr_in temp_addr)                                     
{  
    char sql[100] = {0};     

    sprintf(sql, "insert into chatroom(username, account, passwd, likes, vip) values('%s','%s','%s','%d','%d')",RecvBuf.username, RecvBuf.account, RecvBuf.passwd, RecvBuf.likes, RecvBuf.vip);     

    char *errmsg = NULL;
    ret = sqlite3_exec(ppdb, sql, NULL, NULL, &errmsg);  
    if(ret != SQLITE_OK)  
    {  
        perror("sqlite3_exec2");
        SendBuf.result = EXISTING_ACC;

        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if (ret < 0)
        {
            perror("sendto_server_log");
            exit(1);
        }
        return;  
    }  

    SendBuf.result = REG_SUCCESS;
    printf("註冊資訊:使用者名稱:%s \t 賬號:%s \t 密碼:%s\n", RecvBuf.username, RecvBuf.account, RecvBuf.passwd);

    ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
    if (ret < 0)
    {
        perror("sendto_server_register");
        exit(1);
    }              
    bzero(&SendBuf, sizeof(SendBuf));
}

//登入
void deal_log(struct sockaddr_in temp_addr)
{
    char **Result = NULL;
    int nRow; //行數
    int nColumn; //列數
    int ret;
    char sql[100] = {0};

    sprintf(sql, "select username, passwd, likes, vip, moto from chatroom where account = '%s' and passwd = '%s'", RecvBuf.account, RecvBuf.passwd);
    ret = sqlite3_get_table(ppdb, sql, &Result, &nRow, &nColumn, NULL);
    if (ret != SQLITE_OK)
    {
        perror("sqlite3_get_table_deal_log");
        exit(1);
    }

    if(1 == nRow)
    {
        LinkList tmp = head->next;
        while(tmp != head)  
        {  
            if(!strcmp(tmp->account,RecvBuf.account))
            {    
                SendBuf.result = LOGGED_ACC;        //已登入

                ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
                if (ret < 0)
                {
                    perror("sendto_server_log");
                    exit(1);
                }
                return;  
            }  
            tmp = tmp->next;  
        }

        SendBuf.result = LOG_SUCCESS;               //登入成功 

        LinkList p =(LinkList)malloc(sizeof(Node));
        if(p == NULL)
        {
            printf("malloc error!\n");
            return;
        }           
        strcpy(p->account,RecvBuf.account);
        strcpy(p->name, Result[5]); 
        strcpy(SendBuf.username,Result[5]); 
        SendBuf.vip = *(Result[8]) - 48;
        SendBuf.likes = *(Result[7]) - 48;
//      strcpy(SendBuf.moto, Result[9]);  

        p->client_addr.sin_family = temp_addr.sin_family;
        p->client_addr.sin_port = temp_addr.sin_port;
        p->client_addr.sin_addr.s_addr = temp_addr.sin_addr.s_addr;

        printf("%s 上線!\n",Result[5]);   
        printf("登入資訊:使用者名稱:%s \t 賬號:%s \t 密碼:%s \t 埠號:%d\n",SendBuf.username, p->account, RecvBuf.passwd, p->client_addr.sin_port);

        p->next = head->next;
        head->next = p;                 
    }   

    else
    {
        SendBuf.result = ERROR;
    }

    ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
    if (ret < 0)
    {
        perror("sendto_server_log");
        exit(1);
    }   
}

//私聊
int deal_private(struct sockaddr_in temp_addr)
{
    int flag = 0;
    LinkList tmp = head->next;
    while(tmp != head)
    {
        if(!strcmp(tmp->name,RecvBuf.to_name))
        {
            flag = 1;
            strcpy(SendBuf.msg, RecvBuf.msg);
            strcpy(SendBuf.from_name,RecvBuf.username);

            SendBuf.result = private_chat;

            ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&tmp->client_addr, sizeof(tmp->client_addr));
            if(ret == -1)
            {
                perror("sendto_pchat");
                exit(1);
            }
            break;
        }
        tmp=tmp->next;
    }

     if(flag)
    {
        SendBuf.result = SEND_SUCCESS;
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if(ret == -1)
        {
            perror("sendto_success");
            exit(1);
        }       
    }
    else
    {
        SendBuf.result = SEND_FAILED;
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if(ret == -1)
        {
            perror("sendto_failure");
            exit(1);
        }
    }
}   

//群聊
int deal_group(struct sockaddr_in temp_addr)
{
    int flag = 0;
    LinkList tmp = head->next;

    while (tmp != head)
    {
        if (tmp->client_addr.sin_port != temp_addr.sin_port)
        {
            flag = 1;

            SendBuf.result = group_chat;


            strcpy(SendBuf.from_name, RecvBuf.username);
//          printf("%s\n",SendBuf.from_name);
            strcpy(SendBuf.msg, RecvBuf.msg);

            ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&tmp->client_addr, sizeof(tmp->client_addr));
            if (ret < 0)
            {
                perror("sendto_group_chat");
                exit(1);
            }
        }
        tmp = tmp->next;
    }
    if (flag)
    {
        SendBuf.result = SEND_SUCCESS;
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if (ret < 0)
        {
            perror("sendto_group_chat_success");
            exit(1);
        }
    }
    else 
    {
        SendBuf.result = SEND_FAILED;
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if (ret < 0)
        {
            perror("sendto_group_chat_failure");
            exit(1);
        }
    }
}

//檢視線上人數
int deal_online(struct sockaddr_in temp_addr)
{
    int i = 0;
    SendBuf.online_num = 0;
    LinkList tmp = head->next;
    while(tmp != head)
    {
        SendBuf.online_num++;
        strcpy(SendBuf.online_name[i],tmp->name);
        i++;
        tmp = tmp->next;
    }
    SendBuf.result = online;

    ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
    if(ret == -1)
    {
        perror("sendto_online");
    }
}

//點贊
int deal_like(struct sockaddr_in temp_addr)
{
    char *errmsg = NULL;  
    char **Result = NULL;  
    int  nRow;  
    int  nColumn;  
    char sql[100];  

    sprintf(sql, "select likes from chatroom where username = '%s'", RecvBuf.to_name);
    ret = sqlite3_get_table(ppdb, sql, &Result, &nRow, &nColumn, &errmsg);  
    if(ret != SQLITE_OK)  
    {  
        printf("select fail:%d(%s)\n", ret, errmsg);  
        return -1;  
    }  

    if(nRow == 1)  
    {   
        sprintf(sql, "update chatroom set likes = %d where username = '%s'", *(Result[1]) - 47, RecvBuf.to_name);  
        ret = sqlite3_exec(ppdb, sql, NULL, NULL, &errmsg);  
        if(ret != SQLITE_OK)  
        {  
            printf("update fail:%d(%s)\n", ret, errmsg);  
            return ;  
        }  

        LinkList tmp = head->next;

        while(tmp != head)              //遍歷資料庫,找到使用者
        {   
            if(strcmp(tmp->name, RecvBuf.to_name) == 0)  
            {  
                SendBuf.likes = *(Result[1]) - 47;  
                SendBuf.result = like;  
                strcpy(SendBuf.from_name,RecvBuf.username);  

                ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&tmp->client_addr, sizeof(tmp->client_addr));
                if(ret == -1)
                {
                    perror("sendto_like");
                }
                break;
            }
            tmp = tmp->next;
        }

        SendBuf.result = LIKE_SUCCESS;          
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if(ret == -1)
        {
            perror("sendto_like_suuess");
        }   
    }
    else  
    {  
        SendBuf.result = SEND_FAILED;  
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if(ret == -1)
        {
            perror("sendto_like_failed");
        }       
    }
}

//傳送表情(群發)
int deal_expression(struct sockaddr_in temp_addr)
{
    int flag = 0;
    LinkList tmp = head->next;
    while (tmp != head)
    {
        if (tmp->client_addr.sin_port != temp_addr.sin_port)
        {
            flag = 1;

            SendBuf.result = expression;
            strcpy(SendBuf.from_name, RecvBuf.username);
            SendBuf.e_s = RecvBuf.e_s;
            ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&tmp->client_addr, sizeof(tmp->client_addr));
            if (ret < 0)
            {
                perror("sendto_face");
                exit(1);
            }
        }
        tmp = tmp->next;
    }
    if (flag)
    {
        SendBuf.result = SEND_SUCCESS;
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if (ret < 0)
        {
            perror("sendto_face_success");
            exit(1);
        }
    }
    else
    {
        SendBuf.result = SEND_FAILED;
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if (ret < 0)
        {
            perror("sendto_face_failure");
            exit(1);
        }
    }
}

//傳送常用語(私發)
int deal_phrases(struct sockaddr_in temp_addr)
{
    int flag = 0;
    LinkList tmp = head->next;
    while(tmp != head)
    {
        if(strcmp(tmp->name,RecvBuf.to_name) == 0 )
        {
            flag = 1;
            SendBuf.result = phrases;
            SendBuf.p_s = RecvBuf.p_s;
            strcpy(SendBuf.from_name,RecvBuf.username);
            ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&tmp->client_addr, sizeof(tmp->client_addr));
            if(ret == -1)
            {
                perror("sendto_useful");
                exit(1);
            }
            break;
        }
        tmp=tmp->next;
    }

    if(flag)
    {
        //memset(SendBuf.result,0,sizeof(SendBuf.result));
        SendBuf.result = SEND_SUCCESS;
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if(ret == -1)
        {
            perror("sendto_success");
            exit(1);
        }
        //printf("%s send message to %s ...\n",userIn.name,userIn.toname);
    }
    else
    {
        SendBuf.result = SEND_FAILED;
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if(ret == -1)
        {
            perror("sendto_failure");
            exit(1);
        }
        //printf("%s send error...\n",userIn.name);
    }
}   

//註冊會員
void deal_vip(struct sockaddr_in temp_addr)
{
    char sql[100] = {0};

    sprintf(sql,"update chatroom set vip= %d where username = '%s';",RecvBuf.vip,RecvBuf.username);

    printf("%s 成為VIP\n",RecvBuf.username);

    ret = sqlite3_exec(ppdb,sql,NULL,NULL,NULL);
    if(ret!=SQLITE_OK)
    {
        perror("sqlite3_exec_vip");
        return;
    }

    SendBuf.vip = 1;
    SendBuf.result = VIP_SUCCESS;
    strcpy(SendBuf.username , RecvBuf.username);
    ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
    if(ret == -1)
    {
        perror("sendto_vip_failure");
        exit(1);
    }   
}

//檔案傳輸
void deal_ftp(struct sockaddr_in temp_addr)
{
    int flag = 0;
    LinkList tmp = head->next;

    while (tmp != head)
    {
        if (strcmp(tmp->name,RecvBuf.to_name) == 0)
        {
            flag = 1;
            strcpy(SendBuf.from_name,RecvBuf.username);
            strcpy(SendBuf.file_name, RecvBuf.file_name);//拷貝檔名
            strcpy(SendBuf.Ftp, RecvBuf.Ftp);//拷貝檔案內容

            SendBuf.result = file_transfer;

            ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&tmp->client_addr, sizeof(tmp->client_addr));
            if (ret < 0)
            {
                perror("sendto_sendfile");
                exit(1);
            }
        }
        tmp = tmp->next;
    }

    if (1 == flag)
    {
        SendBuf.result = SEND_SUCCESS;
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if (ret < 0)
        {
            perror("sendto_file_success");
            exit(1);
        }
    }
    else 
    {
        SendBuf.result = SEND_FAILED;
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if (ret < 0)
        {
            perror("sendto_file_failure");
            exit(1);
        }
    }   
}

//禁言
int deal_shutup(struct sockaddr_in temp_addr)
{
    LinkList tmp = head->next;
    int flag = 0;
    while(tmp != head)
    {
        if(strcmp(tmp->name,RecvBuf.to_name)==0)
        {
            flag = 1;
            SendBuf.result = Shutup;
            strcpy(SendBuf.from_name,RecvBuf.username);
            ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&tmp->client_addr, sizeof(tmp->client_addr));
            if (ret < 0)
            {
                perror("sendto_deal_shutup");
                return;
            }
        }
        tmp = tmp->next;
    }
    if(flag)
    {
        SendBuf.result = SHUTUP_SUCCESS;
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if (ret < 0)
        {
            perror("sendto_shutup_success");
            return;
        }
    }
    else
    {
        SendBuf.result = SEND_FAILED;
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if (ret < 0)
        {
            perror("sendto_shutup_failure");
            return;
        }
    }
}

//踢人
int deal_kick(struct sockaddr_in temp_addr)
{   
    int flag = 0;

    LinkList tmp = head->next;

    while(tmp != head)
    {
        if(strcmp(tmp->name, RecvBuf.to_name ) == 0)
        {
            flag = 1;

            SendBuf.result = kick;
            strcpy(SendBuf.from_name,RecvBuf.username);

            ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&tmp->client_addr, sizeof(tmp->client_addr));
            if (ret < 0)
            {
                perror("sendto_kick");
                return;
            }
        /*  
            LinkList q = tmp->next;
            tmp->next = q->next;
            free(q);
        */  
            printf("%s 下線!\n", RecvBuf.to_name );

            break;
        }
        tmp = tmp->next;
    }

    if(flag == 1)
    {
        SendBuf.result = KICK_SUCCESS;
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if (ret < 0)
        {
            perror("sendto_kick_success");
            exit(1);
        }
    }
    else
    {
        SendBuf.result = SEND_FAILED;
        ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
        if (ret < 0)
        {
            perror("sendto_kick_failed");
            exit(1);
        }
    }
}

//下線
int deal_quit(struct sockaddr_in temp_addr)
{
    LinkList tmp = head;

/*  if(tmp->next->next == head)
    {
        if(strcmp(tmp->next->name, RecvBuf.username ) == 0)
        {
            LinkList p = tmp->next;
            tmp->next = p->next;
            free(p);
            printf("%s 下線!\n", RecvBuf.username );
            return;
        }
        tmp = tmp->next;
    }
*/  printf("name : %s\n", RecvBuf.username);
    while(tmp->next != head)
    {
        if(strcmp(tmp->next->name, RecvBuf.username ) == 0)
        {
            LinkList q = tmp->next;
            tmp->next = q->next;
            free(q);
            printf("%s 下線!\n", RecvBuf.username );
            break;
        }
        tmp = tmp->next;
    }

    ret = sendto(sockfd, &SendBuf, sizeof(SendBuf), 0, (struct sockaddr *)&temp_addr, sizeof(temp_addr));
    if(ret == -1)
    {
        perror("sendto_quit");
    }

    return 0;
}


int main()
{
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    int length;
    int flag;
    char sql[100] = {0};

    head = (LinkList)malloc(sizeof(Node));
    if (NULL == head)
    {
        printf("Malloc Failure!\n");
        return;
    }
    head->next = head;

    sockfd = socket(PF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        perror("socket");
        exit(1);
    }

    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = PORT;
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (ret < 0)
    {
        perror("bind");
        exit(1);
    }

    ret = sqlite3_open("chatroom.db", &ppdb);
    if (ret != SQLITE_OK)
    {
        perror("sqlite3_open");
        exit(1);
    }

    sprintf(sql, "create table if not exists chatroom (username text, account text primary key, passwd text ,likes integer, vip text, moto text);");
    ret = sqlite3_exec(ppdb, sql, NULL, NULL, NULL);
    if (ret != SQLITE_OK)
    {
        perror("sqlite3_exec1");
        exit(1);
    }

    while (1)
    {
        bzero(&SendBuf, sizeof(SendBuf));

        length = sizeof(client_addr);
        ret = recvfrom(sockfd, &RecvBuf, sizeof(RecvBuf), 0, (struct sockaddr *)&client_addr, &length);
        if (ret < 0)
        {
            perror("recvfrom");
            exit(1);
        }


        switch (RecvBuf.cmd)  
        {  
            case (REG):                          //註冊  
            {   
                deal_reg(client_addr);
                break;
            }

            case (LOG):                         //登入
            {
                deal_log(client_addr);
                break;
            }

            case (EXIT):                        //退出
            {
                exit(1);
                break;
            }

            case (private_chat):                //私聊
            {
                deal_private(client_addr);
                break;
            }

            case(group_chat):                  //群聊
            {
                deal_group(client_addr);
                break;
            }

            case(online):                      //檢視線上人數
            {
                deal_online(client_addr);
                break;
            }

            case (like):                        //點贊
            {
                deal_like(client_addr);
                break;
            }

            case(expression):                   //群發表情
            {
                deal_expression(client_addr);
                break;
            }

            case(phrases):                      //傳送常用語
            {
                deal_phrases(client_addr);
                break;
            }   

            case(Vip):                          //開通會員
            {
                deal_vip(client_addr);
                break;
            }

            case(Shutup):                       //禁言
            {
                deal_shutup(client_addr);
                break;
            }

            case(kick):                         //踢人
            {
                deal_kick(client_addr);
                break;
            }

            case (file_transfer):               //檔案傳輸
            {
                deal_ftp(client_addr);
                break;
            }

            case(Exit):                         //下線
            {
                printf("NAME : %s\n", RecvBuf.username);
                deal_quit(client_addr);
                break;
            }
        }           
    } 
    sqlite3_close(ppdb);        

    return 0;
}
/***************************************************
File name: UDP_client.c
Author:華仔
Data:2017.12.25
Description:聊天軟客戶端
***************************************************/

#include "UDP_chatroom.h"

#define PORT 8888


typedef struct info
{
    char username[20];              //使用者名稱
    char account[20];               //賬號
    char passwd[20];                //密碼
    char from_name[20];             //發信人
    char to_name[20];               //收信人
    char moto[30];                  //個性簽名
    char online_name[20][20];       //線上人名
    int  online_num;                //線上人數
    int  cmd;                       //提取操作符  
    int  vip;                       //會員標誌   
    int  likes;                     //點贊數
    int  result;                    //返回操作結果 
    char msg[1024];                 //傳送、接收訊息    
    char e_s;                       //確認傳送的表情
    char p_s;                       //確認傳送的常用語  
    char file_name[50];             //檔名
    char Ftp[2048];                 //檔案傳輸
}Info;

struct sockaddr_in server_addr;

Info SendBuf;
Info RecvBuf;
sqlite3 *ppdb = NULL;           //資料庫

int  sockfd;    
int  State;         
int  LIKES;             //點贊標誌
int  OUT = 0;           //踢人標誌
char Name[20];
int  ret;

/****************************************************************/

//時間函式
void time_show()
{
     time_t rawtime;
     struct tm *timeinfo;
     time (&rawtime);
     timeinfo = localtime(&rawtime);
     printf("Time: %d 年 %d 月 %d 日 %d 時 %d 分 %d 秒\n\n", timeinfo->tm_year + 1900, timeinfo->tm_mon + 1,
                timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);

}

//防止空格影響
void SCAN_N()  
{  
    char ch;  
    while((getchar()) != '\n' && ch != EOF);  
}  

//歡迎介面
void Welcome(void)
{
    system("clear");  
    prin