zoukankan      html  css  js  c++  java
  • 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.
    ");
            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.
    ");
            exit(1);
        }//if
    
        /*(4) 监听*/
        if(listen(listenfd , LISTENEQ) < 0)
        {
            perror("listen error.
    ");
            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.
    ");
                    exit(1);
                }//if
    
                printf("server: got connection from %s
    ", 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.
    ");
                    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的退出请求!
    ", inet_ntoa(message.sendAddr.sin_addr));       
            return NULL;            
        }//if               
        else{
            memcpy(&message , buf , sizeof(buf));               
            /*为每个客户操作链接创建一个线程*/                 
            switch(message.msgType)
            {
            case REGISTER:                      
                {
                    printf("来自%s的注册请求!
    ", 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
    ", stateMsg(ret));   
                    close(sockfd);
                    *fd = -1;
                    return NULL;
                    break;
                }//case
            case LOGIN:
                {
                    printf("来自%s的登陆请求!
    ", 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
    ", stateMsg(ret));
                    /*进入服务器处理聊天界面*/
                    enterChat(&sockfd);                                             
                    break;
                }//case         
            default:
                printf("unknown operation.
    ");
                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 "已达服务器链接上限!
    ";
            break;
        case SUCCESS: //成功
            return "操作成功!
    ";
            break;
        case FAILED:  //失败
            return "操作失败!
    ";
            break;    
        case DUPLICATEID: //重复的用户名
            return "重复的用户名!
    ";
            break;  
        case INVALID:   //不合法的用户名
            return "不合法输入!
    ";
            break;    
        case ID_NOT_EXIST: //账号不存在
            return "账号不存在!
    ";
            break;
        case WRONGPWD: //密码错误
            return "密码错误!
    ";
            break;
        case ALREADY_ONLINE:
            return "该用户已在线!
    ";
            break;
        case ID_NOT_ONLINE:
            return "该用户不在线!
    ";
            break;
        case ALL_NOT_ONLINE:
            return "无人在线!
    ";
            break;
        case MESSAGE_SELF:   //消息对象不能选择自己
            return "不能给自己发送消息
    ";
            break;  
        default:
            return "未知操作结果!
    ";
            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("更新在线列表!
    ");
        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
    ", 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.
    ");
            return FAILED;
        }//if
        printf("Opened database successfully.
    ");
    
        /*(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!
    ");
            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.
    ");
            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!
    ");
            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!
    ");
                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("消息不能发送到自己!
    ");
            /*改变消息类型为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("该用户不在线!
    ");
            /*改变消息类型为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!
    ");
                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
    " , 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 , "	");
                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
    ", 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.
    ");
            /*改变消息类型为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!
    ");
            /*改变消息类型为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	%s
    	%s
    
    ", dbRet[idx+1] , dbRet[idx+4] , dbRet[idx+3]);
            //printf("第%d条记录:%s
    ",i,record);
            idx = idx + nCol;
            strcat(message.content , record);
        }//for
        message.content[strlen(message.content)-1] = '';  
        /*关闭数据库*/
        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);
            if(n == 0)
            {
                //关闭当前描述符
                close(sockfd);
                return ;                    
            }//if               
            else{       
                memcpy(&message , buf , sizeof(buf));               
                //printf("server msgType = %d
    " , message.msgType);
                switch(message.msgType)
                {
                case GROUP_CHAT:
                    {
                        printf("来自%s的群聊请求!
    ", message.sendName);
                        /*转到群聊处理函数*/
                        ret = groupChat(&message , sockfd);
                        printf("群聊:%s
    ", stateMsg(ret));
                        break;
                    }
                case PERSONAL_CHAT:
                    {
                        printf("来自%s的私聊请求!
    ", message.sendName);
                        /*转到私聊处理函数*/
                        ret = personalChat(&message , sockfd);
                        printf("私聊:%s
    ", stateMsg(ret));
                    }
                    break;      
                case VIEW_USER_LIST:
                    {
                        printf("来自%s的查看在线用户列表请求!
    ", message.sendName);
                        /*转到查看在线用户列表处理函数*/
                        ret = viewUserList(&message , sockfd);
                        printf("查看在线列表:%s
    ", stateMsg(ret));
                        break;
                    }
                case VIEW_RECORDS:
                    {
                        printf("来自%s的查看聊天记录的请求!
    ", message.sendName);
                        ret = viewRecords(&message , sockfd);
                        printf("查看聊天记录:%s
    ", stateMsg(ret));
                        break;
                    }
                case EXIT:
                    {
                        /*用户退出聊天室*/
                        printf("用户%s退出聊天室!
    ", 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
    ",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("未找到命令,请重新输入!
    ");
                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("退出聊天室!
    ");
                exit(0);    /*用户退出*/
                break;
            default:
                printf("unknown operation.
    ");
                //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 "已达服务器链接上限!
    ";
            break;
        case SUCCESS: //成功
            return "操作成功!
    ";
            break;
        case FAILED:  //失败
            return "操作失败!
    ";
            break;    
        case DUPLICATEID: //重复的用户名
            return "重复的用户名!
    ";
            break;  
        case INVALID:   //不合法的用户名
            return "不合法输入!
    ";
            break;    
        case ID_NOT_EXIST: //账号不存在
            return "账号不存在!
    ";
            break;
        case WRONGPWD: //密码错误
            return "密码错误!
    ";
            break;
        case ALREADY_ONLINE:
            return "该用户已在线!
    ";
            break;
        case ID_NOT_ONLINE:
            return "该用户不在线!
    ";
            break;
        case ALL_NOT_ONLINE:
            return "无人在线!
    ";
            break;
        case MESSAGE_SELF:   //消息对象不能选择自己
            return "不能给自己发送消息
    ";   
            break;
        default:
            return "未知操作结果!
    ";
            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("-------------------------------------
    ");
            printf("    欢迎进入小Q聊天室~        
    ");
            printf("       1.注册                 
    ");
            printf("       2.登陆               
    ");
            printf("       3.帮助               
    ");
            printf("       4.退出               
    ");
            printf("-------------------------------------
    
    
    ");
    }
    
    /***************************************************
    函数名:helpInterface
    功能:主界面的帮助选项
    传入参数:无
    返回值:无
    ***************************************************/
    int helpInterface()
    {
    
            printf("-------------------------------------
    ");
            printf("         欢迎进入小帮助~         
    ");
            printf("               ^_^                 
    ");
            printf("        请在主界面选择操作~     
    ");
            printf("               ^_^                
    ");
            printf("-------------------------------------
    
    
    ");
    }
    
    /***************************************************
    函数名:helpInterface
    功能:主界面的帮助选项
    传入参数:无
    返回值:无
    ***************************************************/
    void chatInterface(char userName[])
    {
            printf("------------------------------------------
    ");
            printf("你好,%s                       
    ", userName);
            printf("         1. 查看在线用户                 
    ");
            printf("         2. 私聊                         
    ");
            printf("         3. 群聊                         
    ");
            printf("         4. 查看聊天记录                 
    ");
            printf("         5. 退出                         
    ");
            printf("请选择操作~                    
    ");
            printf("------------------------------------------
    
    
    ");
    }
    
    

    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("请输入注册用户名:
    ");
        memset(user.userName , 0 , sizeof(user.userName));
        scanf("%s" , user.userName);
        printf("user.UserName = %s
    " , user.userName);
    
        printf("请输入注册用户密码:
    ");
        memset(user.password , 0 , sizeof(user.password));
        scanf("%s" , user.password);
        printf("user.password = %s
    " , 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
    ",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("请输入用户名:
    ");
        memset(user.userName , 0 , sizeof(user.userName));
        scanf("%s" , user.userName);
        printf("user.UserName = %s
    " , user.userName);
    
        printf("请输入用户密码:
    ");
        memset(user.password , 0 , sizeof(user.password));
        scanf("%s" , user.password);
        printf("user.password = %s
    " , 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
    ",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("^_^ 接收聊天信息中~
    ");
        while(1)
        {
            /*接收服务器发来的消息*/
            nRead = recv(connfd , buf , sizeof(message) , 0);
            /*recv函数返回值 <0 出错  =0 链接关闭  >0接收到的字节数*/
            if(nRead <= 0)
            {
                printf("您已经异常掉线,请重新登录!
    ");
                close(connfd);
                exit(0);
            }//if
    
            memset(&message , 0 , sizeof(message));
            memcpy(&message , buf , sizeof(buf));
    
            switch(message.msgType)
            {
            case VIEW_USER_LIST:
                printf("当前在线用户有:
     %s
    ", message.content);
                break;
            case PERSONAL_CHAT:
                sprintf(str , "%s 	 %s 	对你说: %s
    ", message.sendName , message.msgTime , message.content);
                printf("
    %s
    ", str);
                break;
            case GROUP_CHAT:
                sprintf(str , "%s 	 %s 	发送群消息: %s
    ", message.sendName , message.msgTime , message.content);
                printf("
    %s
    ", str);
                break;
            case VIEW_RECORDS:
                if(strcmp(message.recvName , "") == 0)
                    printf("你参与的群消息记录:
    
    ");           
                else
                    printf("你和%s的聊天记录:
    
    ", message.recvName);
                printf("%s
    " , message.content);
                break;
            case RESULT:
                printf("你的操作结果:%s
    ", 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("软件异常,请重新登录!
    ");
            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("未知操作,请重新输入!
    ");
                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("请输入聊天对象:
    ");
                setbuf(stdin , NULL);
                scanf("%s" , str);
                strcpy(message.recvName , str);
    
                printf("请输入聊天内容:
    ");
                setbuf(stdin , NULL);           
                fgets(message.content , MAX_LINE , stdin);
                (message.content)[strlen(message.content) - 1] = '';
    
                /*获得当前时间*/
                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("请输入聊天内容:
    ");
                setbuf(stdin , NULL);           
                fgets(message.content , MAX_LINE , stdin);
                (message.content)[strlen(message.content) - 1] = '';
    
                /*获得当前时间*/
                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("请输入查看的聊天对象:
    ");
                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) 
    
    

    总结

    以上便是此小项目的全部内容,如有不当,敬请指教!谢谢!

  • 相关阅读:
    LA 6621 /ZOJ 3736 Pocket Cube 打表+暴力
    UVA 10273
    UVA 10158 并查集的经典应用
    CodeForces 382B 数学推导
    UVA 10806 最小费用最大流
    UVA 10330 最大流
    图论:匹配与覆盖+独立集 团与支配集
    sdut oj 操作系统实验--SSTF磁盘调度算法【操作系统算法】
    【转载】单调队列学习
    poj 3006 Dirichlet's Theorem on Arithmetic Progressions【素数问题】
  • 原文地址:https://www.cnblogs.com/shine-yr/p/5214711.html
Copyright © 2011-2022 走看看