Linux聊天室項目 -- ChatRome(select實現)


項目簡介:采用I/O復用技術select實現socket通信,采用多線程負責每個客戶操作處理,完成Linux下的多客戶聊天室!

OS:Ubuntu 15.04

IDE:vim gcc make

DB:Sqlite 3

Time:2015-12-09 ~ 2012-12-21

項目功能架構:

  1. 采用client/server結構;
  2. 給出客戶操作主界面(注冊、登錄、幫助和退出)、登錄后主界面(查看在線列表、私聊、群聊、查看聊天記錄、退出);
  3. 多客戶可同時連接服務器進行自己操作;

部分操作如下圖所示:

  1. 多用戶注冊:
    (1)服務器監聽終端
    1
    (2)用戶注冊終端
    2

  2. 多用戶登錄、聊天:
    (1)用戶yy登錄
    yy
    yy2
    (2)用戶rr登錄
    rr
    (3)服務器監聽終端
    server

程序結構

公共數據庫

chatRome.db

服務器端

server

  1. server.c:服務器端主程序代碼文件;
  2. config.h:服務器端配置文件(包含需要的頭文件、常量、數據結構及函數聲明);
  3. config.c:服務器端公共函數的實現文件;
  4. list.c:鏈表實現文件,用於維護在線用戶鏈表的添加、更新、刪除操作;
  5. register.c:服務器端實現用戶注冊;
  6. login.c:服務器端實現用戶登錄;
  7. chat.c:服務器端實現用戶的聊天互動操作;
  8. Makefile:服務器端make文件,控制台執行make命令可直接生成可執行文件server

客戶端

client
1. client.c:客戶端主程序代碼文件;
2. config.h:客戶端配置文件(包含需要的頭文件、常量、數據結構及函數聲明);
3. config.c:客戶端公共函數的實現文件;
4. register.c:客戶端實現用戶注冊;
5. login.c:客戶端實現用戶登錄;
6. chat.c:客戶端實現用戶的聊天互動操作;
7. Makefile:客戶端make文件,控制台執行make命令可直接生成可執行文件client;
8. interface.c:客戶端界面文件;

源碼

GitHub下ChatRome源碼網址

CSDN資源下載

服務器端

server.c

/******************************************************************************* * 服務器端程序代碼server.c * 2015-12-09 yrr實現 * ********************************************************************************/

#include "config.h"

/*定義全局變量 -- 在線用戶鏈表*/
ListNode *userList = NULL;

/********************************************* 函數名:main 功能:聊天室服務器main函數入口 參數:無 返回值:正常退出返回 0 否則返回 1 **********************************************/
int main(void)
{
    /*聲明服務器監聽描述符和客戶鏈接描述符*/
    int i , n , ret , maxi , maxfd , listenfd , connfd , sockfd;

    socklen_t clilen;

    pthread_t pid;

    /*套接字選項*/
    int opt = 1;

    /*聲明服務器地址和客戶地址結構*/
    struct sockaddr_in servaddr , cliaddr;

    /*聲明描述符集*/
    fd_set rset , allset;
    //nready為當前可用的描述符數量
    int nready , client_sockfd[FD_SETSIZE];

    /*聲明消息變量*/
    Message message;
    /*聲明消息緩沖區*/
    char buf[MAX_LINE];

    /*UserInfo*/
    User user;  

    /*(1) 創建套接字*/
    if((listenfd = socket(AF_INET , SOCK_STREAM , 0)) == -1)
    {
        perror("socket error.\n");
        exit(1);
    }//if

    /*(2) 初始化地址結構*/
    bzero(&servaddr , sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(PORT);

    /*(3) 綁定套接字和端口*/
    setsockopt(listenfd , SOL_SOCKET , SO_REUSEADDR , &opt , sizeof(opt));

    if(bind(listenfd , (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0)
    {
        perror("bind error.\n");
        exit(1);
    }//if

    /*(4) 監聽*/
    if(listen(listenfd , LISTENEQ) < 0)
    {
        perror("listen error.\n");
        exit(1);
    }//if 

    /*(5) 首先初始化客戶端描述符集*/
    maxfd = listenfd;
    maxi = -1;
    for(i=0; i<FD_SETSIZE; ++i)
    {
        client_sockfd[i] = -1;
    }//for

    /*清空allset描述符集*/
    FD_ZERO(&allset);

    /*將監聽描述符加到allset中*/
    FD_SET(listenfd , &allset);

    /*(6) 接收客戶鏈接*/
    while(1)
    {
        rset = allset;
        /*得到當前可讀的文件描述符數*/
        nready = select(maxfd+1 , &rset , NULL , NULL , 0);

        /*測試listenfd是否在rset描述符集中*/
        if(FD_ISSET(listenfd , &rset))
        {
            /*接收客戶端的請求*/
            clilen = sizeof(cliaddr);
            if((connfd = accept(listenfd , (struct sockaddr *)&cliaddr , &clilen)) < 0)
            {
                perror("accept error.\n");
                exit(1);
            }//if

            printf("server: got connection from %s\n", inet_ntoa(cliaddr.sin_addr));

            /*查找空閑位置,設置客戶鏈接描述符*/
            for(i=0; i<FD_SETSIZE; ++i)
            {
                if(client_sockfd[i] < 0)
                {
                    client_sockfd[i] = connfd; /*將處理該客戶端的鏈接套接字設置在該位置*/
                    break;              
                }//if
            }//for

            if(i == FD_SETSIZE)
            {       
                perror("too many connection.\n");
                exit(1);
            }//if

            /* 將來自客戶的連接connfd加入描述符集 */
            FD_SET(connfd , &allset);

            /*新的連接描述符 -- for select*/
            if(connfd > maxfd)
                maxfd = connfd;

            /*max index in client_sockfd[]*/
            if(i > maxi)
                maxi = i;

            /*no more readable descriptors*/
            if(--nready <= 0)
                continue;
        }//if
        /*接下來逐個處理連接描述符*/
        for(i=0 ; i<=maxi ; ++i)
        {
            if((sockfd = client_sockfd[i]) < 0)
                continue;

            if(FD_ISSET(sockfd , &rset))
            {
                /*如果當前沒有可以讀的套接字,退出循環*/
                if(--nready < 0)
                    break;                          
                pthread_create(&pid , NULL , (void *)handleRequest , (void *)&sockfd);

            }//if
            /*清除處理完的鏈接描述符*/
            FD_CLR(sockfd , &allset);
            client_sockfd[i] = -1;          
        }//for
    }//while

    close(listenfd);
    return 0;
}

/*處理客戶請求的線程*/
void* handleRequest(int *fd)
{
    int sockfd , ret , n;
    /*聲明消息變量*/
    Message message;
    /*聲明消息緩沖區*/
    char buf[MAX_LINE];

    sockfd = *fd;

    memset(buf , 0 , MAX_LINE);
    memset(&message , 0 , sizeof(message));

    //接收用戶發送的消息
    n = recv(sockfd , buf , sizeof(buf)+1 , 0);
    if(n <= 0)
    {
        //關閉當前描述符,並清空描述符數組 
        fflush(stdout);
        close(sockfd);
        *fd = -1;
        printf("來自%s的退出請求!\n", inet_ntoa(message.sendAddr.sin_addr));       
        return NULL;            
    }//if 
    else{
        memcpy(&message , buf , sizeof(buf));               
        /*為每個客戶操作鏈接創建一個線程*/                 
        switch(message.msgType)
        {
        case REGISTER:                      
            {
                printf("來自%s的注冊請求!\n", inet_ntoa(message.sendAddr.sin_addr));
                ret = registerUser(&message , sockfd);
                memset(&message , 0 , sizeof(message));
                message.msgType = RESULT;
                message.msgRet = ret;
                strcpy(message.content , stateMsg(ret));        
                memset(buf , 0 , MAX_LINE);
                memcpy(buf , &message , sizeof(message));                       
                /*發送操作結果消息*/
                send(sockfd , buf , sizeof(buf) , 0);
                printf("注冊:%s\n", stateMsg(ret));   
                close(sockfd);
                *fd = -1;
                return NULL;
                break;
            }//case
        case LOGIN:
            {
                printf("來自%s的登陸請求!\n", inet_ntoa(message.sendAddr.sin_addr));
                ret = loginUser(&message , sockfd);                         
                memset(&message , 0 , sizeof(message));
                message.msgType = RESULT;
                message.msgRet = ret;
                strcpy(message.content , stateMsg(ret));                            
                memset(buf , 0 , MAX_LINE);
                memcpy(buf , &message , sizeof(message));                       
                /*發送操作結果消息*/
                send(sockfd , buf , sizeof(buf) , 0);
                printf("登錄:%s\n", stateMsg(ret));
                /*進入服務器處理聊天界面*/
                enterChat(&sockfd);                                             
                break;
            }//case 
        default:
            printf("unknown operation.\n");
            break;
        }//switch 
    }//else 

    close(sockfd);
    *fd = -1;
    return NULL;
}

config.h

/******************************************************************************* * 基本配置文件 -- 包含所需頭文件 * 用戶信息結構體定義 * 在線用戶鏈表定義 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <memory.h> /*使用memcpy所需的頭文件*/

#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>

#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/time.h>
#include <pthread.h>

#include <sqlite3.h>

/*FD_SETSIZE定義描述符集的大小,定義在sys/types.h中*/
#ifndef FD_SETSIZE
#define FD_SETSIZE 256
#endif

#define MAX_LINE 8192
#define PORT 8888
#define LISTENEQ 6000

/*預定義數據庫名稱*/
#define DB_NAME "/home/yangrui/projects/Socket/ChatRome_select/chatRome.db"

/*標志*/
enum Flag{
    YES,    /*代表被禁言*/
    NO      /*代表沒有被禁言*/
};

/*定義服務器--客戶端 消息傳送類型*/
enum MessageType{   
    REGISTER = 1,   /*注冊請求*/        
    LOGIN,      /*登陸請求*/
    HELP,       /*幫助請求*/
    EXIT,               /*退出請求*/
    VIEW_USER_LIST,     /*查看在線列表*/
    GROUP_CHAT,     /*群聊請求*/
    PERSONAL_CHAT,      /*私聊請求*/
    VIEW_RECORDS,       /*查看聊天記錄請求*/
    RESULT,             /*結果消息類型*/
    UNKONWN             /*未知請求類型*/
};

/*定義操作結果 */
enum StateRet{
    EXCEED, //已達服務器鏈接上限
    SUCCESS, //成功
    FAILED,  //失敗
    DUPLICATEID, //重復的用戶名
    INVALID,    //不合法的用戶名
    ID_NOT_EXIST, //賬號不存在
    WRONGPWD, //密碼錯誤
    ALREADY_ONLINE,     //已經在線
    ID_NOT_ONLINE,  //賬號不在線
    ALL_NOT_ONLINE,     //無人在線
    MESSAGE_SELF    //消息對象不能選擇自己
};


/*定義服務器 -- 客戶端 消息傳送結構體*/
typedef struct _Message{
    char content[2048];     /*針對聊天類型的消息,填充該字段*/
    int msgType;    /*消息類型 即為MessageType中的值*/
    int msgRet;     /*針對操作結果類型的消息,填充該字段*/
    struct sockaddr_in sendAddr; /*發送者IP*/
    struct sockaddr_in recvAddr;
    char sendName[20]; /*發送者名稱*/
    char recvName[20]; /*接收者名稱*/
    char msgTime[20];  /*消息發送時間*/
}Message;

//用戶信息結構體
typedef struct _User{
    char userName[20];      //用戶名
    char password[20];
    struct sockaddr_in userAddr;    //用戶IP地址,選擇IPV4
    int sockfd;         //當前用戶套接字描述符
    int speak;          //是否禁言標志
    char registerTime[20];  //記錄用戶注冊時間 
}User;

/*定義用戶鏈表結構體*/
typedef struct _ListNode{
    User user;
    struct _ListNode *next;
}ListNode;


/*定義在線用戶鏈表*/
extern ListNode *userList;

/*server.c 客戶請求處理函數*/
extern void* handleRequest(int *fd);

/*config.c文件函數聲明*/
extern char *stateMsg(int stateRet);
extern void copyUser(User *user1 , User *user2);

/*chat.c文件函數聲明*/
extern void enterChat(int *fd);
extern int groupChat(Message *msg , int sockfd);
extern int personalChat(Message *msg , int sockfd);
extern int viewUserList(Message *msg , int sockfd);
extern int viewRecords(Message *msg , int sockfd);

/*list.c文件函數聲明*/
extern ListNode* insertNode(ListNode *list , User *user);
extern int isOnLine(ListNode *list , User *user);
extern void deleteNode(ListNode *list , User *user);
extern void displayList(ListNode *list);

/*login.c文件函數聲明*/
extern int loginUser(Message *msg , int sockfd);

/*register.c文件函數聲明*/
extern int registerUser(Message *msg , int sockfd);

config.c

/******************************************************************************* * 基本配置文件實現 -- 包含所需頭文件 * 用戶信息結構體定義 * 在線用戶鏈表定義 ********************************************************************************/
#include "config.h"

/************************************* 函數名:StateMsg 功能:根據操作結果得到相應的消息內容 參數:stateRet -- 操作結果整數值 返回值:操作結果字符串 **************************************/
char *stateMsg(int stateRet)
{
    switch(stateRet)
    {
    case EXCEED://已達服務器鏈接上限
        return "已達服務器鏈接上限!\n";
        break;
    case SUCCESS: //成功
        return "操作成功!\n";
        break;
    case FAILED:  //失敗
        return "操作失敗!\n";
        break;    
    case DUPLICATEID: //重復的用戶名
        return "重復的用戶名!\n";
        break;  
    case INVALID:   //不合法的用戶名
        return "不合法輸入!\n";
        break;    
    case ID_NOT_EXIST: //賬號不存在
        return "賬號不存在!\n";
        break;
    case WRONGPWD: //密碼錯誤
        return "密碼錯誤!\n";
        break;
    case ALREADY_ONLINE:
        return "該用戶已在線!\n";
        break;
    case ID_NOT_ONLINE:
        return "該用戶不在線!\n";
        break;
    case ALL_NOT_ONLINE:
        return "無人在線!\n";
        break;
    case MESSAGE_SELF:   //消息對象不能選擇自己
        return "不能給自己發送消息\n";
        break;  
    default:
        return "未知操作結果!\n";
        break;
    }//switch
};

/************************************* 函數名:copyUser 功能:用戶結構體對象拷貝操作 參數:user1--目標拷貝對象 user2--源拷貝對象 返回值:無 **************************************/
void copyUser(User *user1 , User *user2)
{
    strcpy((*user1).userName , (*user2).userName);
    strcpy((*user1).password , (*user2).password);
    (*user1).userAddr = (*user2).userAddr;
    (*user1).sockfd = (*user2).sockfd;
    (*user1).speak = (*user2).speak;
    strcpy((*user2).registerTime , (*user2).registerTime);

}

list.c

/*******************************************************************************
* 服務器端 在線客戶 鏈表結構與操作
* 2015-12-14 yrr實現
* ********************************************************************************/

#include "config.h"

/****************************************************
函數名:insertNode
功能:插入在線用戶鏈表新節點
參數:list--當前在線用戶鏈表 elem--要插入的元素
返回值:返回創建的鏈表
***************************************************/
ListNode* insertNode(ListNode *list , User *user)
{
 /*建立新節點*/
 ListNode *node = (ListNode *)calloc(1, sizeof(ListNode));

 copyUser(&(node->user) , user);

 node->next = NULL;
 if(list == NULL)
 { 
 list = node;
 }//if
 else{
 ListNode *p = list;
 while(p->next != NULL)
 {
 p = p->next;
 }//while
 p->next = node;
 }//else

 printf("更新在線列表!\n");
 return list; 
}

/****************************************************
函數名:isOnLine
功能:查看某用戶是否在線
參數:list--當前在線用戶鏈表 elem--要查看的用戶元素
返回值:true or false
***************************************************/
int isOnLine(ListNode *list , User *user)
{
 ListNode *p = list , *pre = p;
 while(p!=NULL && strcmp(p->user.userName , (*user).userName) != 0)
 {
 pre = p;
 p = p->next;
 }//while

 /*不存在該在線用戶*/
 if(p == NULL)
 return 0;
 return 1;
}

/****************************************************
函數名:deleteNode
功能:刪除在線用戶鏈表指定節點
參數:list--當前在線用戶鏈表 elem--要刪除的元素
返回值:返回創建的鏈表
*****************************************************/
void deleteNode(ListNode *list , User *user)
{
 if(list == NULL)
 return;

 ListNode *p = list , *pre = p;
 while(p!=NULL && strcmp(p->user.userName , (*user).userName) != 0)
 {
 pre = p;
 p = p->next;
 }//while

 /*不存在該在線用戶*/
 if(p == NULL)
 return ;
 /*該用戶位於鏈表頭部*/
 else if(p == list)
 {
 list = list->next;
 }//elif
 /*該用戶位於鏈表尾部*/
 else if(p->next == NULL)
 {
 pre->next = NULL;
 }//elif
 /*該用戶節點位於鏈表中間*/
 else
 {
 pre->next = p->next;
 }//else
 /*釋放該用戶節點占用的空間*/
 free(p);
 p = NULL;
}

/****************************************************
函數名:displayList
功能:顯示在線用戶鏈表
參數:list--當前在線用戶鏈表
返回值:返回創建的鏈表
*****************************************************/
void displayList(ListNode *list)
{
 if(list == NULL)
 return;
 else
 {
 ListNode *p = list;
 while(p->next != NULL)
 {
 printf("%s --> ", p->user.userName);
 p = p->next;
 }//while
 printf("%s\n", p->user.userName);
 }//else
}

register.c

/******************************************************************************* * 服務器處理用戶基本操作處理實現文件 * 2015-12-10 yrr實現 * ********************************************************************************/

#include "config.h"

/********************************************* 函數名:registerUser 功能:用戶注冊函數實現 參數:msg--用戶發送的注冊消息 sockfd--套接字描述符 返回值:成功登陸返回SUCCESS 否則返回異常類型 **********************************************/
int registerUser(Message *msg , int sockfd)
{
    int ret;
    /*聲明用戶需要的注冊信息*/
    User user;
    char buf[MAX_LINE];

    /*聲明數據庫變量*/
    sqlite3 *db;
    sqlite3_stmt *stmt;
    const char *tail;

    /*聲明sql語句存儲變量*/
    char sql[128];

    /*當前系統時間*/
    time_t timeNow;

    /*存儲操作結果消息*/
    Message message;

    /*接收用戶注冊信息*/
    recv(sockfd , buf , sizeof(user) , 0);
    memset(&user , 0 , sizeof(user));
    memcpy(&user , buf , sizeof(buf));
    user.userAddr = (*msg).sendAddr;
    user.sockfd = sockfd;

    if(strlen(user.userName) > 20)
    {   
        return INVALID;
    }//if

    /*(1)打開數據庫*/
    ret = sqlite3_open(DB_NAME, &db);
    if(ret != SQLITE_OK)
    {
        printf("unable open database.\n");
        return FAILED;
    }//if
    printf("Opened database successfully.\n");

    /*(2)檢查要注冊用戶名是否已存在?*/
    memset(sql , 0 , sizeof(sql));
    sprintf(sql , "select * from User where userName='%s';",(user.userName));

    ret = sqlite3_prepare(db , sql , strlen(sql) , &stmt , &tail);  
    if(ret != SQLITE_OK)
    {
        ret = sqlite3_step(stmt);
        sqlite3_finalize(stmt);
        sqlite3_close(db);
        printf("database select fail!\n");
        return FAILED;
    }//if
    /*執行*/
    ret = sqlite3_step(stmt);
    //如果有數據則返回SQLITE_ROW,當到達末尾返回SQLITE_DONE
     while (ret == SQLITE_ROW)
     {
         ret = sqlite3_step(stmt);
         sqlite3_finalize(stmt);
         sqlite3_close(db);
         return FAILED;
     }
    /*銷毀句柄,關閉數據庫*/
    sqlite3_finalize(stmt);

    /*執行插入操作*/
    memset(sql , 0 , sizeof(sql));
    time(&timeNow);
    sprintf(sql , "insert into User(userName , password , userAddr , sockfd , speak , registerTime)\ values('%s','%s','%s',%d, %d , '%s');",user.userName , user.password , 
            inet_ntoa(user.userAddr.sin_addr),user.sockfd , YES, asctime(gmtime(&timeNow)));

    ret = sqlite3_prepare(db , sql , strlen(sql) , &stmt , &tail);  
    if(ret != SQLITE_OK)
    {
        ret = sqlite3_step(stmt);
        sqlite3_finalize(stmt);
        sqlite3_close(db);
        return FAILED;
    }//if

    /*順利注冊*/
    ret = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    sqlite3_close(db);
    /*注冊成功*/    
    return SUCCESS;
}

login.c

/*******************************************************************************
* 服務器處理用戶基本操作處理實現文件
* 2015-12-14 yrr實現
* ********************************************************************************/

#include "config.h"

/*聲明全局變量 -- 在線用戶鏈表*/
extern ListNode *userList;

/**************************************************
函數名:loginUser
功能:用戶登陸函數實現
參數:msg--用戶發送的登陸消息 sockfd--套接字描述符
返回值:成功登陸返回SUCCESS 否則返回異常類型
****************************************************/
int loginUser(Message *msg , int sockfd)
{
 int ret;
 /*聲明用戶信息*/
 User user;
 char buf[MAX_LINE];

 /*聲明數據庫變量*/
 sqlite3 *db;
 sqlite3_stmt *stmt;
 const char *tail;

 /*聲明sql語句存儲變量*/
 char sql[128];

 /*存儲操作結果消息*/
 Message message;

 /*接收用戶信息*/
 recv(sockfd , buf , sizeof(user) , 0);
 memset(&user , 0 , sizeof(user));
 memcpy(&user , buf , sizeof(buf));
 user.userAddr = (*msg).sendAddr;
 user.sockfd = sockfd;

 /*查看在線用戶列表,該用戶是否已在線*/
 if(isOnLine(userList , &user) == 1)
 return ALREADY_ONLINE;

 /*(1)打開數據庫*/
 ret = sqlite3_open(DB_NAME, &db);
 if(ret != SQLITE_OK)
 {
 printf("unable open database.\n");
 return FAILED;
 }//if

 /*(2)檢查登陸用戶名和密碼*/
 memset(sql , 0 , sizeof(sql));
 sprintf(sql , "select * from User where userName='%s' and password='%s';",user.userName , user.password);

 ret = sqlite3_prepare(db , sql , strlen(sql) , &stmt , &tail); 
 if(ret != SQLITE_OK)
 {
 ret = sqlite3_step(stmt);
 sqlite3_finalize(stmt);
 sqlite3_close(db);
 printf("database select fail!\n");
 return FAILED; 
 }//if
 /*執行*/
 ret = sqlite3_step(stmt);
 //如果有數據則返回SQLITE_ROW,當到達末尾返回SQLITE_DONE
 while(ret == SQLITE_ROW)
 {
 ret = sqlite3_step(stmt);
 sqlite3_finalize(stmt);
 sqlite3_close(db);
 ret = SUCCESS;
 /*如果登陸操作成功,添加到在線用戶鏈表*/
 userList = insertNode(userList , &user);
 return ret;
 }//while
 /*銷毀句柄,關閉數據庫*/
 sqlite3_finalize(stmt);
 sqlite3_close(db); 

 return FAILED;
}

chat.c

/******************************************************************************* * 服務器處理用戶聊天操作實現文件 * 2015-12-16 yrr實現 * ********************************************************************************/

#include "config.h"

extern ListNode *userList;

/************************************************** 函數名:groupChat 功能:群聊函數實現 參數:msg--用戶發送的群聊消息 sockfd -- 發送者套接字 返回值:成功登陸返回SUCCESS 否則返回異常類型 ****************************************************/
int groupChat(Message *msg , int sockfd)
{
    ListNode *p;

    int ret;

    /*聲明數據庫變量*/
    sqlite3 *db;
    sqlite3_stmt *stmt;
    const char *tail;
    /*聲明sql語句存儲變量*/
    char sql[128];

    /*消息發送緩沖區*/
    char buf[MAX_LINE];
    /*消息內容*/
    Message message;    
    memset(&message , 0 , sizeof(message));
    strcpy(message.sendName , (*msg).sendName);
    strcpy(message.recvName , (*msg).recvName);
    message.msgType = (*msg).msgType;

    /*查看在線用戶*/
    p = userList;
    /*除了自己無人在線*/
    if(p->next == NULL)
    {
        /*改變消息類型為RESULT*/
        message.msgType = RESULT;
        strcpy(message.content, stateMsg(ALL_NOT_ONLINE));  
        memset(buf , 0 , MAX_LINE);
        memcpy(buf , &message , sizeof(message));
        send(sockfd , buf , sizeof(buf) , 0);
        return ALL_NOT_ONLINE;
    }//if
    /*向所有在線用戶發送消息*/
    else
    {
        strcpy(message.recvName , "");
        strcpy(message.content , (*msg).content);
        strcpy(message.msgTime , (*msg).msgTime);
        while(p!=NULL)
        {
            if(strcmp((p->user).userName , message.sendName) != 0)
            {
                memset(buf , 0 , MAX_LINE);
                memcpy(buf , &message , sizeof(message));
                send((p->user).sockfd , buf , sizeof(buf) , 0);
            }//else
            p = p->next;
        }//while
        /*(1)打開數據庫*/
        ret = sqlite3_open(DB_NAME, &db);
        if(ret != SQLITE_OK)
        {
            printf("unable open database!\n");
            return FAILED;
        }//if
        /*(2)執行插入操作*/
        memset(sql , 0 , sizeof(sql));
        sprintf(sql , "insert into Message(msgType , sendName , recvName , content , msgTime)\ values(%d,'%s','%s','%s', '%s');",message.msgType , message.sendName , 
                message.recvName,message.content , message.msgTime);

        ret = sqlite3_prepare(db , sql , strlen(sql) , &stmt , &tail);  
        if(ret != SQLITE_OK)
        {
            ret = sqlite3_step(stmt);
            sqlite3_finalize(stmt);
            sqlite3_close(db);
            return FAILED;
        }//if

        /*(3)順利插入*/
        ret = sqlite3_step(stmt);
        sqlite3_finalize(stmt);
        sqlite3_close(db);
        /*群聊處理成功*/  
        return SUCCESS;
    }//else 
}

/************************************************** 函數名:personalChat 功能:私聊函數實現 參數:msg--用戶發送的群聊消息 sockfd -- 發送者套接字 返回值:成功登陸返回SUCCESS 否則返回異常類型 ****************************************************/
int personalChat(Message *msg , int sockfd)
{
    ListNode *p;

    int ret;

    /*聲明數據庫變量*/
    sqlite3 *db;
    sqlite3_stmt *stmt;
    const char *tail;
    /*聲明sql語句存儲變量*/
    char sql[128];

    /*消息發送緩沖區*/
    char buf[MAX_LINE];
    /*消息內容*/
    Message message;    
    memset(&message , 0 , sizeof(message)); 
    strcpy(message.sendName , (*msg).sendName);
    strcpy(message.recvName , (*msg).recvName);
    message.msgType = (*msg).msgType;
    /*消息發送對象和接收對象相同*/
    if(strcmp((*msg).sendName , (*msg).recvName) == 0)
    {
        printf("消息不能發送到自己!\n");
        /*改變消息類型為RESULT*/
        message.msgType = RESULT;
        strcpy(message.content, stateMsg(MESSAGE_SELF));
        memset(buf , 0 , MAX_LINE);
        memcpy(buf , &message , sizeof(message));
        send(sockfd , buf , sizeof(buf) , 0);
        return MESSAGE_SELF;
    }//if

    /*查找接收信息用戶*/
    p = userList;
    while(p != NULL && strcmp((p->user).userName , (*msg).recvName) != 0)
    {       
        p = p->next;
    }//while

    if(p == NULL)
    {
        printf("該用戶不在線!\n");
        /*改變消息類型為RESULT*/
        message.msgType = RESULT;
        strcpy(message.content, stateMsg(ID_NOT_ONLINE));
        memset(buf , 0 , MAX_LINE);
        memcpy(buf , &message , sizeof(message));
        send(sockfd , buf , sizeof(buf) , 0);
        return ID_NOT_ONLINE;
    }//if
    else{
        strcpy(message.content , (*msg).content);
        strcpy(message.msgTime , (*msg).msgTime);
        memset(buf , 0 , MAX_LINE);
        memcpy(buf , &message , sizeof(message));
        send((p->user).sockfd , buf , sizeof(buf) , 0);

        /*寫到數據庫*/
        /*(1)打開數據庫*/
        ret = sqlite3_open(DB_NAME, &db);
        if(ret != SQLITE_OK)
        {
            printf("unable open database!\n");
            return FAILED;
        }//if
        /*(2)執行插入操作*/
        memset(sql , 0 , sizeof(sql));
        sprintf(sql , "insert into Message(msgType , sendName , recvName , content , msgTime)\ values(%d,'%s','%s','%s', '%s');",message.msgType , message.sendName , 
                message.recvName,message.content , message.msgTime);
        printf("%s\n" , sql);

        ret = sqlite3_prepare(db , sql , strlen(sql) , &stmt , &tail);  
        if(ret != SQLITE_OK)
        {
            ret = sqlite3_step(stmt);
            sqlite3_finalize(stmt);
            sqlite3_close(db);
            return FAILED;
        }//if

        /*(3)順利插入*/
        ret = sqlite3_step(stmt);
        sqlite3_finalize(stmt);
        sqlite3_close(db);
        /*私聊處理成功*/  
        return SUCCESS;
    }//else 
}

/************************************************** 函數名:viewUserList 功能:查看在線用戶列表函數實現 參數:msg--用戶發送的群聊消息 sockfd -- 發送者套接字 返回值:成功登陸返回SUCCESS 否則返回異常類型 ****************************************************/
int viewUserList(Message *msg , int sockfd)
{
    ListNode *p;
    int ret;

    /*消息發送緩沖區*/
    char buf[MAX_LINE];
    /*消息內容*/
    Message message;    
    memset(&message , 0 , sizeof(message));
    strcpy(message.sendName , (*msg).sendName);
    strcpy(message.recvName , (*msg).recvName);
    message.msgType = (*msg).msgType;

    /*查看在線用戶*/
    p = userList;
    if(p == NULL)
    {
        /*改變消息類型為RESULT*/
        message.msgType = RESULT;
        strcpy(message.content, stateMsg(ALL_NOT_ONLINE));
        memset(buf , 0 , MAX_LINE);
        memcpy(buf , &message , sizeof(message));
        send(sockfd , buf , sizeof(buf) , 0);
        return ALL_NOT_ONLINE;
    }//if
    else{
        /*否則消息類型不變*/
        strcpy(message.content , "");
        while(p!=NULL)
        {
            strcat(message.content , "\t");
            strcat(message.content , (p->user).userName);

            p = p->next;
        }//while
        memset(buf , 0 , MAX_LINE);
        memcpy(buf , &message , sizeof(message));
        send(sockfd , buf , sizeof(buf) , 0);
        printf("查看在線列表結果:%s\n", message.content);
    }
    return SUCCESS;
}

/************************************************** 函數名:viewUserList 功能:查看聊天記錄 參數:msg--用戶發送的群聊消息 sockfd -- 發送者套接字 返回值:成功登陸返回SUCCESS 否則返回異常類型 ****************************************************/
int viewRecords(Message *msg , int sockfd)
{
    int ret;

    char buf[MAX_LINE] , record[MAX_LINE];

    /*聲明數據庫變量*/
    sqlite3 *db;
    char *errmsg = NULL;
    char **dbRet;
    int nRow , nCol , i , j , idx;

    /*聲明sql語句存儲變量*/
    char sql[128];

    /*存儲操作結果消息*/
    Message message;
    memset(&message , 0 , sizeof(message));
    strcpy(message.sendName , (*msg).sendName);
    /*判斷是否接收群消息*/
    if(strcmp( (*msg).recvName , "all") == 0)
        strcpy(message.recvName , "");
    else
        strcpy(message.recvName , (*msg).recvName);
    message.msgType = (*msg).msgType;

    /*(1)打開數據庫*/
    ret = sqlite3_open(DB_NAME, &db);
    if(ret != SQLITE_OK)
    {
        printf("unable open database.\n");
        /*改變消息類型為RESULT*/
        message.msgType = RESULT;
        strcpy(message.content, stateMsg(FAILED));
        memset(buf , 0 , MAX_LINE);
        memcpy(buf , &message , sizeof(message));
        send(sockfd , buf , sizeof(buf) , 0);

        return FAILED;
    }//if

    /*(2)讀出兩者的聊天記錄,以二進制方式*/
    memset(sql , 0 , sizeof(sql));
    if(strcmp(message.recvName , "") == 0)
        sprintf(sql , "select * from Message where recvName='%s' order by msgTime;",message.recvName);
    else
        sprintf(sql , "select * from Message where sendName='%s' and recvName='%s' or sendName='%s' and recvName='%s' order by msgTime;",message.sendName , message.recvName , message.recvName , message.sendName);


    ret = sqlite3_get_table(db , sql , &dbRet , &nRow , &nCol , &errmsg);   
    /*查詢不成功*/
    if(ret != SQLITE_OK)
    {
        sqlite3_close(db);
        printf("database select fail!\n");
        /*改變消息類型為RESULT*/
        message.msgType = RESULT;
        strcpy(message.content, stateMsg(FAILED));
        memset(buf , 0 , MAX_LINE);
        memcpy(buf , &message , sizeof(message));
        send(sockfd , buf , sizeof(buf) , 0);       
        return FAILED;      
    }//if

    /*查詢成功,dbRet 前面第一行數據是字段名稱,從 nColumn 索引開始才是真正的數據*/
    idx = nCol;
    for(i=0; i<nRow; ++i)
    {
        memset(record , 0 , MAX_LINE);
        sprintf(record , "%s\t%s\n\t%s\n\n", dbRet[idx+1] , dbRet[idx+4] , dbRet[idx+3]);
        //printf("第%d條記錄:%s\n",i,record);
        idx = idx + nCol;
        strcat(message.content , record);
    }//for
    message.content[strlen(message.content)-1] = '\0';  
    /*關閉數據庫*/
    sqlite3_close(db);  

    /*直接發送控制台*/ 
    memset(buf , 0 , MAX_LINE);
    memcpy(buf , &message , sizeof(message));
    send(sockfd , buf , sizeof(buf) , 0);
    return SUCCESS;
}

/************************************************** 函數名:enterChat 功能:服務器處理登錄成功函數實現 參數:sockfd -- 用戶套接字 返回值:成功登陸返回SUCCESS 否則返回異常類型 ****************************************************/
void enterChat(int *fd)
{
    int n,ret,sockfd;
    User user;
    /*消息發送緩沖區*/
    char buf[MAX_LINE];
    memset(buf , 0 , MAX_LINE);

    /*消息內容*/
    Message message;    
    memset(&message , 0 , sizeof(message));

    sockfd = *fd;

    while(1)
    {
        //接收用戶發送的消息
        n = recv(sockfd , buf , sizeof(buf)+1 , 0);
        //printf("enterChat n = %d\n" , n);
        if(n == 0)
        {
            //關閉當前描述符
            close(sockfd);
            return ;                    
        }//if 
        else{       
            memcpy(&message , buf , sizeof(buf));               
            //printf("server msgType = %d\n" , message.msgType);
            switch(message.msgType)
            {
            case GROUP_CHAT:
                {
                    printf("來自%s的群聊請求!\n", message.sendName);
                    /*轉到群聊處理函數*/
                    ret = groupChat(&message , sockfd);
                    printf("群聊:%s\n", stateMsg(ret));
                    break;
                }
            case PERSONAL_CHAT:
                {
                    printf("來自%s的私聊請求!\n", message.sendName);
                    /*轉到私聊處理函數*/
                    ret = personalChat(&message , sockfd);
                    printf("私聊:%s\n", stateMsg(ret));
                }
                break;      
            case VIEW_USER_LIST:
                {
                    printf("來自%s的查看在線用戶列表請求!\n", message.sendName);
                    /*轉到查看在線用戶列表處理函數*/
                    ret = viewUserList(&message , sockfd);
                    printf("查看在線列表:%s\n", stateMsg(ret));
                    break;
                }
            case VIEW_RECORDS:
                {
                    printf("來自%s的查看聊天記錄的請求!\n", message.sendName);
                    ret = viewRecords(&message , sockfd);
                    printf("查看聊天記錄:%s\n", stateMsg(ret));
                    break;
                }
            case EXIT:
                {
                    /*用戶退出聊天室*/
                    printf("用戶%s退出聊天室!\n", message.sendName);
                    memset(&user , 0 , sizeof(user));
                    strcpy(user.userName , message.sendName);
                    deleteNode(userList , &user);
                    close(sockfd);
                    return;
                }
            default:            
                break;
            }//switch
        }//else
    }//while
    return ;
}

Makefile

MYNAME = makefile
CC = gcc

objects = server.o register.o login.o list.o config.o chat.o

server: $(objects)
    cc -g -o server $(objects) -lsqlite3 -lpthread

server.o: server.c config.h
    cc -c server.c 

register.o: register.c config.h
    cc -c register.c

login.o: login.c config.h
    cc -c login.c

list.o: list.c config.h
    cc -c list.c

config.o: config.c config.h
    cc -c config.c

chat.o: chat.c config.h
    cc -c chat.c
#比較穩健的clean做法,表示clean是一個偽目標
.PHONY: clean

#前面-的意思是:也許某些文件出現問題,忽略,繼續執行
clean:
    -rm server $(objects) 

客戶端

client.c

/******************************************************************************* * 客戶端程序代碼server.c * 2015-12-09 yrr實現 * ********************************************************************************/

#include "config.h"


/********************************************* 函數名:main 功能:聊天室客戶端main函數入口 參數:參數個數argc 用戶鏈接地址argv 返回值:正常退出返回 0 否則返回 1 **********************************************/
int main(int argc , char *argv[])
{
    int sockfd , choice , ret; //choice代表用戶在主界面所做選擇,ret代表操作結果
    struct sockaddr_in servaddr;
    struct hostent *host;

    /*聲明消息變量*/
    Message message;
    /*聲明消息緩沖區*/
    char buf[MAX_LINE];

    /*UserInfo*/
    User user;
    strcpy(user.userName , "***");
    user.speak = 1;


    /*判斷是否為合法輸入*/
    if(argc != 2)
    {
        perror("usage:tcpcli <IPaddress>");
        exit(1);
    }//if

    while(1)
    {
         /*(1) 創建套接字*/
        if((sockfd = socket(AF_INET , SOCK_STREAM , 0)) == -1)
        {
            perror("socket error");
            exit(1);
        }//if

        /*(2) 設置鏈接服務器地址結構*/
        bzero(&servaddr , sizeof(servaddr));        /*清空地址結構*/
        servaddr.sin_family = AF_INET;              /*使用IPV4通信域*/
        servaddr.sin_port = htons(PORT);            /*端口號轉換為網絡字節序*/
        //servaddr.sin_addr = *((struct in_addr *)host->h_addr); /*可接受任意地址*/
        if(inet_pton(AF_INET , argv[1] , &servaddr.sin_addr) < 0)
        {
            printf("inet_pton error for %s\n",argv[1]);
            exit(1);
        }//if

         /*(3) 發送鏈接服務器請求*/
        if( connect(sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0)
        {
            perror("connect error");
            exit(1);
        }//if 

        /*(4) 顯示聊天室主界面*/        
        mainInterface();    
        setbuf(stdin,NULL); //是linux中的C函數,主要用於打開和關閉緩沖機制
        scanf("%d",&choice);
        setbuf(stdin,NULL);
        while(choice != 1 && choice != 2 && choice != 3 && choice !=4)
        {
            printf("未找到命令,請重新輸入!\n");
            setbuf(stdin,NULL); //是linux中的C函數,主要用於打開和關閉緩沖機制
            scanf("%d",&choice);
            setbuf(stdin,NULL);
        }//while

        /*清空緩沖區*/       
        switch(choice)
        {       
        case REGISTER:  /*注冊請求*/        
            memset(&message , 0 , sizeof(message));
            memset(buf , 0 , MAX_LINE);     
            message.msgType = REGISTER;
            strcpy(message.content , "");
            message.sendAddr = servaddr;
            /*首先向服務器發送注冊請求*/        
            memcpy(buf , &message , sizeof(message));   
            send(sockfd , buf , sizeof(buf) , 0);   
            registerUser(sockfd);
            //goto sign;
            break;
        case LOGIN:     /*登陸請求*/
            memset(&message , 0 , sizeof(message));
            memset(buf , 0 , MAX_LINE);
            message.msgType = LOGIN;
            strcpy(message.content , "");
            message.sendAddr = servaddr;
            /*向服務器發送登陸請求*/
            memcpy(buf , &message , sizeof(message));
            send(sockfd , buf , sizeof(buf) , 0);
            loginUser(sockfd);                  
            break;  
        case HELP:      /*幫助請求,顯示幫助界面*/
            helpInterface(); 
            //goto sign; 
            break;
        case EXIT:
            close(sockfd);
            printf("退出聊天室!\n");
            exit(0);    /*用戶退出*/
            break;
        default:
            printf("unknown operation.\n");
            //goto sign;
            break;  
        }//switch 
    }//while 
    close(sockfd);
    return 0;   
}

config.h

/******************************************************************************* * 基本配置文件 -- 包含所需頭文件 * 用戶信息結構體定義 * 在線用戶鏈表定義 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <memory.h> /*使用memcpy所需的頭文件*/

#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>

#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/time.h>
#include <pthread.h>

#include <sqlite3.h>

/*FD_SETSIZE定義描述符集的大小,定義在sys/types.h中*/
#ifndef FD_SETSIZE
#define FD_SETSIZE 256
#endif

#define MAX_LINE 8192
#define PORT 8888
#define LISTENEQ 6000

/*預定義數據庫名稱*/
#define DB_NAME "/home/yangrui/projects/Socket/ChatRome_select/chatRome.db"

/*標志*/
enum Flag{
    YES,    /*代表被禁言*/
    NO      /*代表沒有被禁言*/
};

/*定義服務器--客戶端 消息傳送類型*/
enum MessageType{   
    REGISTER = 1,   /*注冊請求*/        
    LOGIN,      /*登陸請求*/
    HELP,       /*幫助請求*/
    EXIT,               /*退出請求*/
    VIEW_USER_LIST,     /*查看在線列表*/
    GROUP_CHAT,     /*群聊請求*/
    PERSONAL_CHAT,      /*私聊請求*/
    VIEW_RECORDS,       /*查看聊天記錄請求*/
    RESULT,             /*結果消息類型*/
    UNKONWN             /*未知請求類型*/
};

/*定義操作結果 */
enum StateRet{
    EXCEED, //已達服務器鏈接上限
    SUCCESS, //成功
    FAILED,  //失敗
    DUPLICATEID, //重復的用戶名
    INVALID,    //不合法的用戶名
    ID_NOT_EXIST, //賬號不存在
    WRONGPWD, //密碼錯誤
    ALREADY_ONLINE,     //已經在線
    ID_NOT_ONLINE,  //賬號不在線
    ALL_NOT_ONLINE,     //無人在線
    MESSAGE_SELF   //消息對象不能選擇自己
};


/*定義服務器 -- 客戶端 消息傳送結構體*/
typedef struct _Message{
    char content[2048];     /*針對聊天類型的消息,填充該字段*/
    int msgType;    /*消息類型 即為MessageType中的值*/
    int msgRet;     /*針對操作結果類型的消息,填充該字段*/
    struct sockaddr_in sendAddr; /*發送者IP*/
    struct sockaddr_in recvAddr;
    char sendName[20]; /*發送者名稱*/
    char recvName[20]; /*接收者名稱*/
    char msgTime[20];  /*消息發送時間*/
}Message;

//用戶信息結構體
typedef struct _User{
    char userName[20];      //用戶名
    char password[20];
    struct sockaddr_in userAddr;    //用戶IP地址,選擇IPV4
    int sockfd;         //當前用戶套接字描述符
    int speak;          //是否禁言標志
    char registerTime[20];  //記錄用戶注冊時間 
}User;

/*定義用戶鏈表結構體*/
typedef struct _ListNode{
    User user;
    struct _ListNode *next;
}ListNode;


/*定義在線用戶鏈表*/
ListNode *userList;

extern char *stateMsg(int stateRet);
extern void copyUser(User *user1 , User *user2);

config.c

/******************************************************************************* * 基本配置文件實現 -- 包含所需頭文件 * 用戶信息結構體定義 * 在線用戶鏈表定義 ********************************************************************************/
#include "config.h"

/************************************* 函數名:StateMsg 功能:根據操作結果得到相應的消息內容 參數:stateRet -- 操作結果整數值 返回值:操作結果字符串 **************************************/
char *stateMsg(int stateRet)
{
    switch(stateRet)
    {
    case EXCEED://已達服務器鏈接上限
        return "已達服務器鏈接上限!\n";
        break;
    case SUCCESS: //成功
        return "操作成功!\n";
        break;
    case FAILED:  //失敗
        return "操作失敗!\n";
        break;    
    case DUPLICATEID: //重復的用戶名
        return "重復的用戶名!\n";
        break;  
    case INVALID:   //不合法的用戶名
        return "不合法輸入!\n";
        break;    
    case ID_NOT_EXIST: //賬號不存在
        return "賬號不存在!\n";
        break;
    case WRONGPWD: //密碼錯誤
        return "密碼錯誤!\n";
        break;
    case ALREADY_ONLINE:
        return "該用戶已在線!\n";
        break;
    case ID_NOT_ONLINE:
        return "該用戶不在線!\n";
        break;
    case ALL_NOT_ONLINE:
        return "無人在線!\n";
        break;
    case MESSAGE_SELF:   //消息對象不能選擇自己
        return "不能給自己發送消息\n";   
        break;
    default:
        return "未知操作結果!\n";
        break;
    }//switch
};

/************************************* 函數名:copyUser 功能:用戶結構體對象拷貝操作 參數:user1--目標拷貝對象 user2--源拷貝對象 返回值:無 **************************************/
void copyUser(User *user1 , User *user2)
{
    strcpy((*user1).userName , (*user2).userName);
    strcpy((*user1).password , (*user2).password);
    (*user1).userAddr = (*user2).userAddr;
    (*user1).sockfd = (*user2).sockfd;
    (*user1).speak = (*user2).speak;
    strcpy((*user2).registerTime , (*user2).registerTime);

}

interface.c

/*******************************************************************************
* 客戶端界面設計
* 2015-12-15 yrr實現
* ********************************************************************************/

#include "config.h"

/***************************************************
函數名:mainInterface
功能:登錄界面
傳入參數:無
返回值:無
***************************************************/
int mainInterface()
{

 printf("-------------------------------------\n");
 printf(" 歡迎進入小Q聊天室~ \n");
 printf(" 1.注冊 \n");
 printf(" 2.登陸 \n");
 printf(" 3.幫助 \n");
 printf(" 4.退出 \n");
 printf("-------------------------------------\n\n\n");
}

/***************************************************
函數名:helpInterface
功能:主界面的幫助選項
傳入參數:無
返回值:無
***************************************************/
int helpInterface()
{

 printf("-------------------------------------\n");
 printf(" 歡迎進入小幫助~ \n");
 printf(" ^_^ \n");
 printf(" 請在主界面選擇操作~ \n");
 printf(" ^_^ \n");
 printf("-------------------------------------\n\n\n");
}

/***************************************************
函數名:helpInterface
功能:主界面的幫助選項
傳入參數:無
返回值:無
***************************************************/
void chatInterface(char userName[])
{
 printf("------------------------------------------\n");
 printf("你好,%s \n", userName);
 printf(" 1. 查看在線用戶 \n");
 printf(" 2. 私聊 \n");
 printf(" 3. 群聊 \n");
 printf(" 4. 查看聊天記錄 \n");
 printf(" 5. 退出 \n");
 printf("請選擇操作~ \n");
 printf("------------------------------------------\n\n\n");
}

register.c

/*******************************************************************************
* 客戶端用戶基本操作處理實現文件
* 2015-12-10 yrr實現
* ********************************************************************************/

#include "config.h"

/*********************************************
函數名:registerUser
功能:用戶注冊函數實現
參數:套接字描述符
返回值:成功登陸返回SUCCESS 否則返回異常類型
**********************************************/
int registerUser(int sockfd)
{
 int ret;
 /*聲明用戶需要的注冊信息*/
 User user;
 char buf[MAX_LINE];
 Message message;
 /*獲取用戶輸入*/
 printf("請輸入注冊用戶名:\n");
 memset(user.userName , 0 , sizeof(user.userName));
 scanf("%s" , user.userName);
 printf("user.UserName = %s\n" , user.userName);

 printf("請輸入注冊用戶密碼:\n");
 memset(user.password , 0 , sizeof(user.password));
 scanf("%s" , user.password);
 printf("user.password = %s\n" , user.password);
 //當前用戶允許發言
 user.speak = YES;

 memset(buf , 0 , MAX_LINE);
 memcpy(buf , &user , sizeof(user));
 send(sockfd , buf , sizeof(buf) , 0);

 /*接收注冊結果*/
 memset(buf , 0 , MAX_LINE);
 recv(sockfd , buf , sizeof(buf) , 0);
 memset(&message , 0 , sizeof(message));
 memcpy(&message , buf , sizeof(buf));

 printf("%s\n",message.content); 
 return message.msgRet;
}

login.c

/*******************************************************************************
* 客戶端用戶登陸處理實現文件
* 2015-12-14 yrr實現
* ********************************************************************************/

#include "config.h"

/*********************************************
函數名:loginUser
功能:用戶登陸函數實現
參數:套接字描述符
返回值:成功登陸返回SUCCESS 否則返回異常類型
**********************************************/
int loginUser(int sockfd)
{
 int ret;
 /*聲明用戶登陸信息*/
 User user;
 char buf[MAX_LINE];
 Message message;
 /*獲取用戶輸入*/
 printf("請輸入用戶名:\n");
 memset(user.userName , 0 , sizeof(user.userName));
 scanf("%s" , user.userName);
 printf("user.UserName = %s\n" , user.userName);

 printf("請輸入用戶密碼:\n");
 memset(user.password , 0 , sizeof(user.password));
 scanf("%s" , user.password);
 printf("user.password = %s\n" , user.password);

 /*發送用戶登陸信息到服務器*/
 memset(buf , 0 , MAX_LINE);
 memcpy(buf , &user , sizeof(user));
 send(sockfd , buf , sizeof(buf) , 0);

 /*接收登陸結果*/
 memset(buf , 0 , MAX_LINE);
 recv(sockfd , buf , sizeof(buf) , 0);
 memset(&message , 0 , sizeof(message));
 memcpy(&message , buf , sizeof(buf));

 printf("%s\n",message.content);

 /*如果登陸成功,則進入聊天界面*/
 if(SUCCESS == message.msgRet)
 {
 enterChat(&user , sockfd);
 }//if
 return message.msgRet;
}


chat.c

/******************************************************************************* * 客戶端用戶聊天界面處理實現文件 * 2015-12-14 yrr實現 * ********************************************************************************/

#include "config.h"


/*********************************************** 函數名:enterChat 功能:用戶登陸成功后進入聊天模式 參數:user--當前用戶 , sockfd -- 套接字描述符 返回值:正常退出返回 0 , 否則返回 1 *************************************************/
void recvMsg(int *sockfd)
{
    int connfd = *sockfd;
    int nRead;

    char buf[MAX_LINE] , str[MAX_LINE];
    Message message;

    time_t timep;

    printf("^_^ 接收聊天信息中~\n");
    while(1)
    {
        /*接收服務器發來的消息*/
        nRead = recv(connfd , buf , sizeof(message) , 0);
        /*recv函數返回值 <0 出錯 =0 鏈接關閉 >0接收到的字節數*/
        if(nRead <= 0)
        {
            printf("您已經異常掉線,請重新登錄!\n");
            close(connfd);
            exit(0);
        }//if

        memset(&message , 0 , sizeof(message));
        memcpy(&message , buf , sizeof(buf));

        switch(message.msgType)
        {
        case VIEW_USER_LIST:
            printf("當前在線用戶有:\n %s\n", message.content);
            break;
        case PERSONAL_CHAT:
            sprintf(str , "%s \t %s \t對你說: %s\n", message.sendName , message.msgTime , message.content);
            printf("\n%s\n", str);
            break;
        case GROUP_CHAT:
            sprintf(str , "%s \t %s \t發送群消息: %s\n", message.sendName , message.msgTime , message.content);
            printf("\n%s\n", str);
            break;
        case VIEW_RECORDS:
            if(strcmp(message.recvName , "") == 0)
                printf("你參與的群消息記錄:\n\n");           
            else
                printf("你和%s的聊天記錄:\n\n", message.recvName);
            printf("%s\n" , message.content);
            break;
        case RESULT:
            printf("你的操作結果:%s\n", message.content);
        default:
            break; 
        }//switch
    }//while 
}

/*********************************************** 函數名:enterChat 功能:用戶登陸成功后進入聊天模式 參數:user--當前用戶 , sockfd -- 套接字描述符 返回值:正常退出返回 0 , 否則返回 1 *************************************************/
void enterChat(User *user , int sockfd)
{
    int choice , ret;
    char c , buf[MAX_LINE] , str[MAX_LINE];
    Message message;    /*消息對象*/
    time_t timep;   /*存儲當前時間*/

    pthread_t pid;  /*處理接收消息線程*/

    /*創建接收消息線程*/
    ret = pthread_create(&pid , NULL , (void *)recvMsg , (void *)&sockfd);
    if(ret != 0)
    {
        printf("軟件異常,請重新登錄!\n");
        memset(&message , 0 , sizeof(message));
        strcpy(message.sendName , (*user).userName);
        message.msgType = EXIT;
        send(sockfd , buf , sizeof(buf) , 0);
        close(sockfd);
        exit(1);
    }
    /*清空標准輸入緩沖區*/
    setbuf(stdin, NULL);

    /*進入處理用戶發送消息緩沖區*/
    while(1)
    {
        memset(&message , 0 , sizeof(message));
        strcpy(message.sendName , (*user).userName);
        memset(&str , 0 , MAX_LINE);
        memset(buf , 0 , MAX_LINE);
        /*usleep函數將該進程掛起一定時間,單位微秒,頭文件unistd.h*/
        usleep(100000);

        /*進入聊天主界面*/
        chatInterface((*user).userName);
        setbuf(stdin,NULL); //是linux中的C函數,主要用於打開和關閉緩沖機制
        scanf("%d",&choice);
        while(choice != 1 && choice != 2 && choice != 3 && choice !=4 && choice != 5)
        {
            printf("未知操作,請重新輸入!\n");
            setbuf(stdin,NULL); //是linux中的C函數,主要用於打開和關閉緩沖機制
            scanf("%d",&choice);
            setbuf(stdin,NULL);
        }//while

        switch(choice)
        {
        case 1: /*查看當前在線用戶列表*/
            message.msgType = VIEW_USER_LIST;
            memcpy(buf , &message , sizeof(message));
            send(sockfd , buf , sizeof(buf) , 0);                       
            break;  
        case 2: /*私聊*/
            message.msgType = PERSONAL_CHAT;
            printf("請輸入聊天對象:\n");
            setbuf(stdin , NULL);
            scanf("%s" , str);
            strcpy(message.recvName , str);

            printf("請輸入聊天內容:\n");
            setbuf(stdin , NULL);           
            fgets(message.content , MAX_LINE , stdin);
            (message.content)[strlen(message.content) - 1] = '\0';

            /*獲得當前時間*/
            time(&timep);
            strcpy(message.msgTime , ctime(&timep));
            memcpy(buf , &message , sizeof(message));
            send(sockfd , buf , sizeof(buf) , 0);
            break;
        case 3: /*群聊*/
            message.msgType = GROUP_CHAT;
            strcpy(message.recvName , "");

            printf("請輸入聊天內容:\n");
            setbuf(stdin , NULL);           
            fgets(message.content , MAX_LINE , stdin);
            (message.content)[strlen(message.content) - 1] = '\0';

            /*獲得當前時間*/
            time(&timep);
            strcpy(message.msgTime , ctime(&timep));
            memcpy(buf , &message , sizeof(message));
            send(sockfd , buf , sizeof(buf) , 0);
            break;
        case 4: /*查看聊天記錄*/
            message.msgType = VIEW_RECORDS;         
            printf("請輸入查看的聊天對象:\n");
            setbuf(stdin , NULL);
            scanf("%s" , str);
            strcpy(message.recvName , str);         
            memcpy(buf , &message , sizeof(message));
            send(sockfd , buf , sizeof(buf) , 0);
            break;
        case 5: /*退出登陸*/
            message.msgType = EXIT;
            memcpy(buf , &message , sizeof(message));
            send(sockfd , buf , sizeof(buf) , 0);
            close(sockfd);
            exit(0);
        default:    /*未知操作類型*/
            break;
        }//switch
    }//while
    //close(sockfd);
}


Makefile

MYNAME = makefile
CC = gcc

objects = client.o config.o register.o login.o interface.o chat.o

server: $(objects)
    cc -g -o client $(objects) -lsqlite3 -lpthread

client.o: client.c config.h
    cc -c client.c 

register.o: register.c config.h
    cc -c register.c

login.o: login.c config.h
    cc -c login.c

interface.o: interface.c config.h
    cc -c interface.c

chat.o: chat.c config.h
    cc -c chat.c

config.o: config.c config.h
    cc -c config.c
#比較穩健的clean做法,表示clean是一個偽目標
.PHONY: clean

#前面-的意思是:也許某些文件出現問題,忽略,繼續執行
clean:
    -rm client $(objects) 

總結

以上便是此小項目的全部內容,如有不當,敬請指教!謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM