基于TCP的网络聊天系统

Posted ld0524

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于TCP的网络聊天系统相关的知识,希望对你有一定的参考价值。

目录

一、前言

二、产品的介绍

1.产品具有的功能

2.产品的各个模块

 3.使用的开发工具以及应用的技术

三、产品的设计

1.服务端

1.1服务端总流程图

1.2数据库及其管理模块设计

1.3用户管理模块设计

1.4业务模块设计

1.5消息的操作

1.6消息队列

2.客户端

2.1登录及注册消息流转图

2.2注册界面

2.3聊天界面及消息流转图

2.4添加好友界面及消息流转

四、产品的测试(视频演示)


一、前言

QQ和微信是现代人们生活中必不可少的一部分,身边几乎很难找到人不使用QQ或者微信等聊天工具吧!那么你想要亲手打造一款属于自己的聊天系统吗?那么让我们一起打造出一款自己专属的应用吧。

二、产品的介绍

1.产品具有的功能

该聊天系统具有,登录,注册,添加好友,发送消息,四大功能。

2.产品的各个模块

3.使用的开发工具以及应用的技术

a.开发工具:VS2019,MFC

b.使用的技术:Socket编程,TCP网络通信,多线程,数据库,Json数据格式

三、产品的设计

1.服务端

1.1服务端总流程图

1.2数据库及其管理模块设计

数据库表:因为我们需要保存用户以及用户好友的信息,所以我们至少需要维护两个数据库表

friendInfo:保存用户好友信息的数据库表

user:保存用户信息的数据库表

管理数据库模块设计:

  • a.数据库表的初始化:bool MysqlInit()
  • b.获取所有用户的信息:bool GetAllUser(Json::Value* all_user)
  • c.获取用户好友信息:bool GetFriend(int userid, std::vector<int>* f_id)
  • d.用户注册时向数据库插入用户的信息:bool InsertUser(int userid, const std::string& nickname
  • e.添加好友:bool InsertFriend(int userid1, int userid2)
class DataBaseSvr
    public:
        DataBaseSvr()
            mysql_ = NULL;
        

        ~DataBaseSvr()
            if(mysql_ != NULL)
                mysql_close(mysql_);
            
        

        /*
         * 初始化mysql操作句柄, 并且连接后台mysql服务端, 设置字符集
         * */
        bool MysqlInit()
            mysql_ = mysql_init(NULL);
            if(mysql_ == NULL)
                std::cout << "mysql init failed" << std::endl;
                return false;
            

            if(mysql_real_connect(mysql_, HOST, USER, PASSWD, DB, DBPORT,NULL, 0) == NULL)
                std::cout << "msyql connect failed" << std::endl;
                mysql_close(mysql_);
                return false;
            

            mysql_set_character_set(mysql_, "utf8");
            return true;
        

        /*
         * 获取 all user info, to usermanager model
         *    参数为Json对象, 是一个出参
         * */
        bool GetAllUser(Json::Value* all_user)
#define GETALLUSER "select * from user;"
            lock_.lock();
            //1.数据库查询
            if(MysqlQuery(GETALLUSER) == false)
                lock_.unlock();
                return false;
            

            //2.获取结果集
            MYSQL_RES* res = mysql_store_result(mysql_);
            if(res == NULL)
                lock_.unlock();
                return false;
            
            lock_.unlock();
            //3.获取单行数据
            int row_nums = mysql_num_rows(res);
            for(int i = 0; i < row_nums; i++)
                MYSQL_ROW row = mysql_fetch_row(res);
                //4.将单行数据按照格式, 组织起来。 传递给调用者
                Json::Value tmp;
                tmp["userid"] = atoi(row[0]);
                tmp["nickname"] = row[1];
                tmp["school"] = row[2];
                tmp["telnum"] = row[3];
                tmp["passwd"] = row[4];
                all_user->append(tmp);
            

            mysql_free_result(res);
            return true;
        

        /*
         * 获取单个用户的好友信息, 在程序初始化阶段, 让用户管理模块维护起来
         *    userid : 用户的id
         *    f_id : 该用户的所有好友id
         * */
        bool GetFriend(int userid, std::vector<int>* f_id)
#define GETFRIEND "select friend from friendinfo where userid='%d';"

            //1.格式化sql语句
            char sql[1204] = 0;
            sprintf(sql, GETFRIEND, userid);

            lock_.lock();
            //2.查询
            if(MysqlQuery(sql) == false)
                lock_.unlock();
                return false;
            

            //3.获取结果集
            MYSQL_RES* res = mysql_store_result(mysql_);
            if(res == NULL)
                lock_.unlock();
                return false;
            
            lock_.unlock();
            //4.获取单行数据
            int row_nums = mysql_num_rows(res);
            for(int i = 0; i < row_nums; i++)
                MYSQL_ROW row = mysql_fetch_row(res);
                f_id->push_back(atoi(row[0]));
            

            mysql_free_result(res);
            return true;
        

        /*
         *  当用户注册的时候, 进行插入使用的函数
         * */
        bool InsertUser(int userid, const std::string& nickname
                , const std::string& school, const std::string& telnum
                , const std::string& passwd)
#define INSERTUSER "insert into user(userid, nickname, school, telnum, passwd) values('%d', '%s', '%s', '%s', '%s');"

            char sql[1024] = 0;
            sprintf(sql, INSERTUSER, userid, nickname.c_str(), school.c_str(), telnum.c_str(), passwd.c_str());
            std::cout << "Insert User: " << sql << std::endl;

            //2.查询
            if(MysqlQuery(sql) == false)
                return false;
            
            return true;
        

        /*
         * 添加好友
         * */
        bool InsertFriend(int userid1, int userid2)
#define INSERTFRIEND "insert into friendinfo values('%d', '%d');"
            char sql[1024] = 0;
            sprintf(sql, INSERTFRIEND, userid1, userid2);

            //2.查询
            if(MysqlQuery(sql) == false)
                return false;
            
            return true;
        

    private:
        bool MysqlQuery(const std::string& sql)
            if(mysql_query(mysql_, sql.c_str()) != 0)
                std::cout << "exec failed sql: " << sql << std::endl;
                return false;
            
            return true;
        


    private:
        MYSQL* mysql_;
        std::mutex lock_;
;

1.3用户管理模块设计

用户信息类

  • 1.用户注册时的相关信息,nickname_,school_,telnum_,passwd_,userid_
  • 2.用户状态:user_status_
  • 3.登录的客户端对应的套接字描述符
enum UserStatus
    OFFLINE, //0
    ONLINE //1
;

/*
 * 用户信息类
 * */
class UserInfo
    public:
        //注册的时候,
        UserInfo(const std::string& nickname, const std::string& school, const std::string& telnum, const std::string& passwd, int userid)
            nickname_ = nickname;
            school_ = school;
            telnum_ = telnum;
            passwd_ = passwd;
            userid_ = userid;
            user_status_ = OFFLINE;
            tcp_socket_ = -1;
            friend_id_.clear();
        

        UserInfo()

        

        ~UserInfo()

        

    public:
        std::string nickname_;
        std::string school_;
        std::string telnum_;
        std::string passwd_;

        int userid_;
        //用户状态 //OFFLINE ONLINE
        int user_status_;
        //登录的客户端对应的套接字描述符
        int tcp_socket_;

        std::vector<int> friend_id_;
;

用户信息管理模块

  • a.初始化管理模块:bool InitUserMana(),调用数据库模块GetAllUser函数获取数据库中用户信息,并使用unordered_map管理起来
  • b.处理注册请求:int DealRegister(const std::string& nickname, const std::string& school, const std::string& tel, const std::string& passwd, int* userid),组织用户信息,并插入到user_map_ 和数据库中。
  • c.处理用户登录请求:int DealLogin(const std::string& tel, const std::string& passwd, int sockfd)
  • d.判断当前用户的在线情况:int IsLogin(int userid)/int IsLogin(const std::string& telnum, UserInfo* ui)发送消息的时候需要判断对方在线与否
  • e.获取用户信息:bool GetUserInfo(int userid, UserInfo* ui),通过出参带出
  • f.获取用户好友信息:bool GetFriends(int userid, std::vector<int>* fri),通过出参带出
  • g.添加好友:void SetFriend(int userid1, int userid2)
  • h.转变用户状态为下线: void SetUserOffLine(int sockfd),客户端下线时使用
class UserManager
    public:
        UserManager()
            user_map_.clear();
            pthread_mutex_init(&map_lock_, NULL);
            //如果一开始就从0进行分配, 一定是不对的
            //   因为用户管理类还会从数据库当中将已经存在的用户信息读回来
            prepare_id_ = -1;
            db_ = NULL;
        

        ~UserManager()
            pthread_mutex_destroy(&map_lock_);

            if(db_)
                delete db_;
                db_ = NULL;
            
        

        bool InitUserMana()
            //1.连接数据库
            db_ = new DataBaseSvr();
            if(db_ == NULL)
                printf("create db case failed\\n");
                return false;
            

            if(db_->MysqlInit() == false)
                return false;
            
            //2.查询所有用户信息, 维护起来
            Json::Value all_user;
            if(db_->GetAllUser(&all_user) == false)
                return false;
            

            for(int i = 0; i < (int)all_user.size(); i++)
                //个人信息
                UserInfo ui;
                ui.nickname_ = all_user[i]["nickname"].asString();
                ui.school_ = all_user[i]["school"].asString();
                ui.telnum_ = all_user[i]["telnum"].asString();
                ui.passwd_ = all_user[i]["passwd"].asString();
                ui.userid_ = all_user[i]["userid"].asInt();
                ui.user_status_ = OFFLINE;

                //个人好友信息
                db_->GetFriend(ui.userid_, &ui.friend_id_);

                pthread_mutex_lock(&map_lock_);
                user_map_[ui.userid_] = ui;
                if(ui.userid_ > prepare_id_)
                    prepare_id_ = ui.userid_ + 1;
                
                pthread_mutex_unlock(&map_lock_);
            

            return true;
        

        /*
         * 处理用户注册
         *     userid : 如果注册成功, 通过userid,告诉注册的客户端,他的id是什么
         * */
        int DealRegister(const std::string& nickname, const std::string& school, const std::string& tel, const std::string& passwd, int* userid)
            //1.判断注册信息是否为空
            if(nickname.size() == 0 || school.size() == 0 || tel.size() == 0 || passwd.size() == 0)
                *userid = -2;
                return -1;
            
            //2.判断用户是否已经注册过了
            pthread_mutex_lock(&map_lock_);
            auto iter = user_map_.begin();
            while(iter != user_map_.end())
                if(iter->second.telnum_ == tel)
                    *userid = -2;
                    pthread_mutex_unlock(&map_lock_);
                    return -1;
                
                iter++;
            
            //3.创建UserInfo, 分配userid, 保存用户信息
            UserInfo ui(nickname, school, tel, passwd, prepare_id_);
            *userid = prepare_id_;

            user_map_[prepare_id_] = ui;
            prepare_id_++;
            pthread_mutex_unlock(&map_lock_);
            //4.插入到数据库当中
            db_->InsertUser(ui.userid_, nickname, school, tel, passwd);
            return 0;
        

        /*
         * 处理登录请求
         *    sockfd 是 服务端为登录客户端创建的新连接套接字
         * */
        int DealLogin(const std::string& tel, const std::string& passwd, int sockfd)
            //1.判断字段是否为空
            if(tel.size() == 0 || passwd.size() == 0)
                return -1;
            
            //2.判断用户是否合法
            pthread_mutex_lock(&map_lock_);
            auto iter = user_map_.begin();
            while(iter != user_map_.end())
                if(iter->second.telnum_ == tel)
                    break;
                
                iter++;
            
            if(iter == user_map_.end())
                pthread_mutex_unlock(&map_lock_);
                return -1;
            
            //3.校验密码是否正确
            if(iter->second.passwd_ != passwd)
                pthread_mutex_unlock(&map_lock_);
                return -1;
            

            //4.更改用户的状态信息为ONLINE
            iter->second.user_status_ = ONLINE;
            int userid = iter->second.userid_;
            iter->second.tcp_socket_ = sockfd;
            pthread_mutex_unlock(&map_lock_);
            return userid;
        

        int IsLogin(int userid)
            pthread_mutex_lock(&map_lock_);
            auto iter = user_map_.find(userid);
            if(iter == user_map_.end())
                //这个用户都不存在
                pthread_mutex_unlock(&map_lock_);
                return -1;
            

            if(iter->second.user_status_ == OFFLINE)
                pthread_mutex_unlock(&map_lock_);
                return OFFLINE;
            
            pthread_mutex_unlock(&map_lock_);
            return ONLINE;
        


        int IsLogin(const std::string& telnum, UserInfo* ui)
            pthread_mutex_lock(&map_lock_);
            auto iter = user_map_.begin();
            while(iter != user_map_.end())
                if(iter->second.telnum_ == telnum)
                    break;
                
                iter++;
            

            if(iter == user_map_.end())
                pthread_mutex_unlock(&map_lock_);
                return -1;
            

            *ui = iter->second;
            if(iter->second.user_status_ == OFFLINE)
                pthread_mutex_unlock(&map_lock_);
                return OFFLINE;
            
            pthread_mutex_unlock(&map_lock_);
            return ONLINE;
        


        bool GetUserInfo(int userid, UserInfo* ui)
            pthread_mutex_lock(&map_lock_);
            auto iter = user_map_.find(userid);
            if(iter == user_map_.end())
                //这个用户都不存在
                pthread_mutex_unlock(&map_lock_);
                return false;
            

            *ui = iter->second;
            pthread_mutex_unlock(&map_lock_);
            return true;
        

        bool GetFriends(int userid, std::vector<int>* fri)
            pthread_mutex_lock(&map_lock_);
            auto iter = user_map_.find(userid);
            if(iter == user_map_.end())
                //这个用户都不存在
                pthread_mutex_unlock(&map_lock_);
                return false;
            

            *fri = iter->second.friend_id_;
            pthread_mutex_unlock(&map_lock_);
            return true;

        

        void SetFriend(int userid1, int userid2)
            //1.找userid1, 将userid2放到userid1的好友列表当中
            pthread_mutex_lock(&map_lock_);
            auto iter = user_map_.find(userid1);
            if(iter == user_map_.end())
                //这个用户都不存在
                pthread_mutex_unlock(&map_lock_);
                return;
            
            iter->second.friend_id_.push_back(userid2);
            
            //2.找userid2, 将userid1放到userid2的好友列表当中
            iter = user_map_.find(userid2);
            if(iter == user_map_.end())
                //这个用户都不存在
                pthread_mutex_unlock(&map_lock_);
                return;
            
            iter->second.friend_id_.push_back(userid1);
            pthread_mutex_unlock(&map_lock_);
            //3.插入到数据库当中
            db_->InsertFriend(userid1, userid2);
            db_->InsertFriend(userid2, userid1);
        

        void SetUserOffLine(int sockfd)
            pthread_mutex_lock(&map_lock_);
            auto iter = user_map_.begin();
            while(iter != user_map_.end())
                if(iter->second.tcp_socket_ == sockfd)
                    iter->second.user_status_ = OFFLINE;
                
                iter++;
            
            pthread_mutex_unlock(&map_lock_);
        


    private:
        std::unordered_map<int, UserInfo> user_map_;
        pthread_mutex_t map_lock_;

        //针对注册用户分配的ID
        int prepare_id_;

        //数据库管理模块的实例化指针
        DataBaseSvr* db_;
;

1.4业务模块设计

  • a.初始化资源:int InitChatServer(uint16_t tcp_port = TCP_PORT, int thread_count=THREAD_COUNT),tcpSOCK初始化,客户端进行监听
  • b.启动各类线程函数:StartChatServer(),epoll等待线程,接收线程,发送线程,工作线程的创建
  • epoll等待线程:主线程循环的接收, 将接收回来的数据放到接收线程的队列当中, 等到工作线程从队列当中获取消息, 进而进行处理
  • 发送线程:从消息队列中拿消息进行发送
  • 工作线程:从接收队列中拿消息,并根据消息的类型进行处理(注册,登录,添加好友,添加好友应答,获取好友信息)
struct Msg
    Msg()
        sockfd_ = -1;
        memset(buf, '\\0', 1024);
    
    int sockfd_;
    char buf[1024];
;

class ChatServer
    public:
        ChatServer()
            tcp_sock_ = -1;
            tcp_port_ = TCP_PORT;
            user_mana_ = NULL;
            epoll_fd_ = -1;
            thread_count_ = THREAD_COUNT;

            send_que_ = NULL;
            ready_sockfd_que_ = NULL;
            recv_que_ = NULL;
        

        ~ChatServer()

        

        //初始化资源的函数
        int InitChatServer(uint16_t tcp_port = TCP_PORT, int thread_count=THREAD_COUNT)
            tcp_port_ = tcp_port;
            thread_count_ = thread_count;

            //tcp初始化
            tcp_sock_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if(tcp_sock_ < 0)
                perror("socket");
                return -1;
            

            //端口重用
            int opt = 1;
            setsockopt(tcp_sock_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(tcp_port_);
            addr.sin_addr.s_addr = inet_addr("0.0.0.0");
            int ret = bind(tcp_sock_, (struct sockaddr*)&addr, sizeof(addr));
            if(ret < 0)
                perror("bind");
                return -1;
            

            ret = listen(tcp_sock_, 1024);
            if(ret < 0)
                perror("listen");
                return -1;
            

            //epoll 初始化
            epoll_fd_ = epoll_create(5);
            if(epoll_fd_ < 0)
                return -1;
            

            //用户管理模块
            user_mana_ = new UserManager();
            if(user_mana_ == NULL)
                return -1;
            

            if(user_mana_->InitUserMana() == false)
                return -1;
            


            recv_que_ = new MsgQueue<ChatMsg>();
            if(recv_que_ == NULL)
                return -1;
            

            send_que_ = new MsgQueue<ChatMsg>();
            if(send_que_ == NULL)
                return -1;
            

            ready_sockfd_que_ = new MsgQueue<int>();
            if(ready_sockfd_que_ == NULL)
                return -1;
            

            return 0;
        

        //启动各类线程的函数 - 主线程调用的
        int StartChatServer()
            //1.创建epoll等待线程
            pthread_t tid;
            int ret  = pthread_create(&tid, NULL, epoll_wait_start, (void*)this);
            if(ret < 0)
                perror("pthread_create");
                return -1;
            
            //2.创建接收线程
            ret  = pthread_create(&tid, NULL, recv_msg_start, (void*)this);
            if(ret < 0)
                perror("pthread_create");
                return -1;
            
            //3.创建发送线程
            ret  = pthread_create(&tid, NULL, send_msg_start, (void*)this);
            if(ret < 0)
                perror("pthread_create");
                return -1;
            
            //4.创建工作线程
            for(int i = 0; i < thread_count_; i++)
                ret = pthread_create(&tid, NULL, deal_start, (void*)this);
                if(ret < 0)
                    thread_count_--;
                
            

            if(thread_count_ <= 0)
                return -1;
            
            
            //5.主线程循环接收新连接 & 将新连接的套接字放到epoll当中
            struct sockaddr_in cli_addr;
            socklen_t cli_addr_len = sizeof(cli_addr);
            while(1)
                int newsockfd = accept(tcp_sock_,(struct sockaddr*)&cli_addr, &cli_addr_len);
                if(newsockfd < 0)
                    continue;
                

                //接收上了, 添加到epoll当中进行监控
                struct epoll_event ee;
                ee.events = EPOLLIN;
                ee.data.fd = newsockfd;
                epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, newsockfd, &ee);
            

            return 0;
        

        static void* epoll_wait_start(void* arg)
            pthread_detach(pthread_self());
            ChatServer* cs = (ChatServer*)arg;
            while(1)
                struct epoll_event arr[10];
                int ret = epoll_wait(cs->epoll_fd_, arr, sizeof(arr)/sizeof(arr[0]), -1);
                if(ret < 0)
                    continue;
                

                //正常获取了就绪的事件结构, 一定全部都是新连接套接字
                for(int i = 0; i < ret; i++)
                    char buf[TCP_DATA_MAX_LEN] = 0;
                    //隐藏的问题: TCP粘包
                    ssize_t recv_size = recv(arr[i].data.fd, buf, sizeof(buf) - 1, 0);
                    if(recv_size < 0)
                        //接收失败了
                        std::cout << "recv failed : sockfd is " << arr[i].data.fd << std::endl;
                        continue;
                    else if(recv_size == 0)
                        //对端关闭连接了
                        epoll_ctl(cs->epoll_fd_, EPOLL_CTL_DEL,arr[i].data.fd, NULL);
                        close(arr[i].data.fd);
                        //组织一个更改用户状态的消息 , 鸡贼做法(客户端退出的时候, 发送下线通知)
                        cs->user_mana_->SetUserOffLine(arr[i].data.fd);
                        continue;
                    

                    printf("epoll_wait_start recv msg : %s  from sockfd is %d\\n", buf, arr[i].data.fd);
                    //正常接收回来了, 将接收回来的数据放到接收线程的队列当中, 等到工作线程从队列当中获取消息, 进而进行处理
                    //3.将接收到的数据放到接收队列当当中
                    std::string msg;
                    msg.assign(buf, strlen(buf));

                    ChatMsg cm;
                    cm.PraseChatMsg(arr[i].data.fd, msg);

                    cs->recv_que_->Push(cm);
                
            
            return NULL;
        

        static void* recv_msg_start(void* arg)
            pthread_detach(pthread_self());
            ChatServer* cs = (ChatServer*)arg;

            while(1)
            


            return NULL;
        

        static void* send_msg_start(void* arg)
            pthread_detach(pthread_self());
            ChatServer* cs = (ChatServer*)arg;

            while(1)
                //1.从队列拿出数据
                ChatMsg cm;
                cs->send_que_->Pop(&cm);

                std::string msg;
                cm.GetMsg(&msg);
                std::cout << "send thread: " << msg << std::endl;

                //2.发送数据
                send(cm.sockfd_, msg.c_str(), msg.size(), 0);
            

            return NULL;
        

        static void* deal_start(void* arg)
            pthread_detach(pthread_self());
            ChatServer* cs = (ChatServer*)arg;

            while(1)
                //1. 从接收队列当中获取消息
                ChatMsg cm;
                cs->recv_que_->Pop(&cm);
                //2. 通过消息类型分业务处理
                int msg_type = cm.msg_type_;
                switch(msg_type)
                    case Register:
                       cs->DealRegister(cm); 
                        break;
                    
                    case Login:
                        cs->DealLogin(cm);
                        break;
                    
                    case AddFriend:
                        cs->DealAddFriend(cm);
                        break;
                    
                    case PushAddFriendMsg_Resp:
                        cs->DealAddFriendResp(cm);
                        break;
                    
                    case SendMsg: 
                        cs->DealSendMsg(cm);
                        break;
                    
                    case GetFriendMsg:
                        cs->GetAllFriendInfo(cm);
                        break;
                    
                    default:
                        break;
                    
                

                //3. 组织应答
            
            return NULL;
        
        void DealRegister(ChatMsg& cm)
            //1.获取注册信息
            std::string nickname = cm.GetValue("nickname");
            std::string school = cm.GetValue("school");
            std::string telnum = cm.GetValue("telnum");
            std::string passwd = cm.GetValue("passwd");
            //2.调用用户管理系统当中的注册接口
            int userid = -1;
            int ret = user_mana_->DealRegister(nickname, school, telnum, passwd, &userid);
            //3.回复应答
            cm.Clear();
            cm.msg_type_ = Register_Resp;
            if(ret < 0)
                cm.reply_status_ = REGISTER_FAILED;
            else
                cm.reply_status_ = REGISTER_SUCCESS;
            
            cm.user_id_ = userid;

            send_que_->Push(cm);
        

        void DealLogin(ChatMsg& cm)
            //1.获取数据
            std::string telnum = cm.GetValue("telnum");
            std::string passwd = cm.GetValue("passwd");
            //2.调用用户管理模块的代码
            int ret = user_mana_->DealLogin(telnum, passwd, cm.sockfd_);
            //3.回复应答
            cm.Clear();
            cm.msg_type_ = Login_Resp;
            if(ret < 0)
                cm.reply_status_ = LOGIN_FAILED;
            else
                cm.reply_status_ = LOGIN_SUCESSS;
            
            cm.user_id_ = ret;

            send_que_->Push(cm);
        

        void DealAddFriend(ChatMsg& cm)
            //1.获取被添加方的电话号码
            std::string tel = cm.GetValue("telnum");
            //添加方的userid
            int add_userid = cm.user_id_;

            cm.Clear();
            //2.查询被添加方是否是登录状态
            UserInfo be_add_ui;
            int ret = user_mana_->IsLogin(tel, &be_add_ui);
            if(ret == -1)
                //用户不存在
                cm.json_msg_ = AddFriend_Resp;
                cm.reply_status_ = ADDFRIEND_FAILED;
                cm.SetValue("content", "user not exist, please check friend tel num.");
                send_que_->Push(cm);
                return;
            else if(ret == OFFLINE)
                std::cout << be_add_ui.nickname_ + " status is OFFLINE" << std::endl;
                //将消息先缓存下来, 择机发送
                return;
            
            //ONLINE状态的
            //3.给被添加方推送添加好友请求
            UserInfo add_ui;
            user_mana_->GetUserInfo(add_userid, &add_ui);

            cm.sockfd_ = be_add_ui.tcp_socket_;
            cm.msg_type_ = PushAddFriendMsg;
            cm.SetValue("adder_nickname", add_ui.nickname_);
            cm.SetValue("adder_school", add_ui.school_);
            cm.SetValue("adder_userid", add_ui.userid_);
            
            send_que_->Push(cm);
        

        void DealAddFriendResp(ChatMsg& cm)
            //1.获取双方的用户信息
            int reply_status = cm.reply_status_;
            //获取被添加方的用户信息
            int be_add_user = cm.user_id_;
            UserInfo be_userinfo;
            user_mana_->GetUserInfo(be_add_user, &be_userinfo);

            //获取添加方的用户信息-通过应答, 获取添加方的UserId
            int addr_user_id = atoi(cm.GetValue("userid").c_str());
            UserInfo ui;
            user_mana_->GetUserInfo(addr_user_id ,&ui);

            //2.判断响应状态
            cm.Clear();
            cm.sockfd_ = ui.tcp_socket_;
            cm.msg_type_ = AddFriend_Resp;
            if(reply_status == ADDFRIEND_FAILED)
                cm.reply_status_ = ADDFRIEND_FAILED;
                std::string content = "add user " + be_userinfo.nickname_ + " failed";
                cm.SetValue("content", content);
            else if(reply_status == ADDFRIEND_SUCCESS)
                cm.reply_status_ = ADDFRIEND_SUCCESS;
                std::string content = "add user " + be_userinfo.nickname_ + " success";
                cm.SetValue("content", content);
                cm.SetValue("peer_nick_name", be_userinfo.nickname_);
                cm.SetValue("peer_school", be_userinfo.school_);
                cm.SetValue("peer_userid", be_userinfo.userid_);

                //用户管理模块当中要维护好友信息
                user_mana_->SetFriend(addr_user_id, be_add_user);
            

            //TODO
            if(ui.user_status_ == OFFLINE)
                //消息就放到缓存队列当中, 择机发送
            

            //3.给添加方回复响应
            send_que_->Push(cm);
        


        void GetAllFriendInfo(ChatMsg& cm)
            //1. 好友信息的数据从用户管理模块当中获取到
            int user_id = cm.user_id_;

            cm.Clear();
            std::vector<int> fri;
            bool ret = user_mana_->GetFriends(user_id, &fri);
            if(ret == false)
                cm.reply_status_ = GETFRIEND_FAILED;
            else
                cm.reply_status_ = GETFRIEND_SUCCESS;
            
            cm.msg_type_ = GetFriendMsg_Resp;

            for(size_t i = 0; i < fri.size(); i++)
                UserInfo tmp;
                user_mana_->GetUserInfo(fri[i], &tmp);

                Json::Value val;
                val["nickname"] = tmp.nickname_;
                val["school"] = tmp.school_;
                val["userid"] = tmp.userid_;

                cm.json_msg_.append(val);
            

            send_que_->Push(cm);
        

        void DealSendMsg(ChatMsg& cm)
            int send_id = cm.user_id_;
            int recv_id = cm.json_msg_["recvmsgid"].asInt();
            std::string send_msg = cm.json_msg_["msg"].asString();

            cm.Clear();

            UserInfo recv_ui;
            bool ret = user_mana_->GetUserInfo(recv_id, &recv_ui);
            //区分用户不存在和不在线两种状态
            //   用户不存在 : 消息发送失败
            //   用户不在线: 发送方发送的消息缓存下来, 择机发送
            if(ret == false || recv_ui.user_status_ == OFFLINE)
                cm.msg_type_ = SendMsg_Resp;
                cm.reply_status_ = SENDMSG_FAILED;
                send_que_->Push(cm);
                return;
            

            //代码能走到这里, 说明要给接收方推送消息了
            cm.Clear();
            cm.msg_type_ = SendMsg_Resp;
            cm.reply_status_ = SENDMSG_SUCCESS;
            send_que_->Push(cm);


            //获取发送方的用户信息
            UserInfo send_ui;
            user_mana_->GetUserInfo(send_id, &send_ui);

            cm.Clear();
            cm.msg_type_ = PushMsg;
            cm.sockfd_ = recv_ui.tcp_socket_;
            cm.SetValue("peer_nickname", send_ui.nickname_);
            cm.SetValue("peer_school", send_ui.school_);
            cm.json_msg_["peer_userid"] = send_ui.userid_;
            cm.SetValue("peer_msg", send_msg);
            send_que_->Push(cm);
        

    private:
        //侦听套接字
        int tcp_sock_;
        int tcp_port_;

        //用户管理模块的实例化指针
        UserManager* user_mana_;

        //epoll操作句柄
        int epoll_fd_;

        //工作线程的数量
        int thread_count_;

        //就绪的文件描述符队列
        MsgQueue<int>* ready_sockfd_que_;

        //接收线程的队列
        MsgQueue<ChatMsg>* recv_que_;

        //发送线程的队列
        MsgQueue<ChatMsg>* send_que_;
;

1.5消息的操作

消息类型和响应类型

enum chat_msg_type
    Register = 0, //0, 注册请求
    Register_Resp, //1, 注册应答
    Login,   //2. 登录请求
    Login_Resp, //3, 登录应答
    AddFriend, //4, 添加好友请求
    AddFriend_Resp, //5, 添加好友请求应答
    SendMsg, //6, 发送消息
    SendMsg_Resp, //7, 发送消息应答
    PushMsg, //8, 推送消息
    PushMsg_Resp, //9, 推送消息应答
    PushAddFriendMsg, //10, 推送添加好友请求
    PushAddFriendMsg_Resp, //11, 推送添加好友请求的应答 
    GetFriendMsg, //12, 获取全部好友信息
    GetFriendMsg_Resp, //13, 获取全部好友信息应答
    SetUserOffLine //14
    //后续如果要增加业务, 可以在后面增加其他的消息类型
;

enum reply_status
    REGISTER_SUCCESS = 0, //0, 注册成功
    REGISTER_FAILED, //1,注册失败
    LOGIN_SUCESSS, //2, 登录成功
    LOGIN_FAILED,  //3, 登陆失败
    ADDFRIEND_SUCCESS, //4, 添加好友成功
    ADDFRIEND_FAILED, //5, 添加好友失败
    SENDMSG_SUCCESS, //6, 发送消息成功
    SENDMSG_FAILED, //7, 发送给消息失败
    GETFRIEND_SUCCESS, //8,获取好友列表成功
    GETFRIEND_FAILED  //9, 获取好友列表失败
;

消息类型的格式

/*
 * 注册请求的消息格式
 *   sockfd_ (消息达到服务端之后, 由服务端接收之后, 打上sockfd_)
 *   msg_type_ : Register
 *   json_msg: 
 *      nickname : 'xxx'
 *      school : "xxx"
 *      telnum : "xxxx"
 *      passwd : "xxxx"
 *   
 *
 * 注册的应答:
 *   msg_type_ : Register_Resp
 *   reply_status_ = REGISTER_SUCCESS / REGISTER_FAILED
 *      如果是REGISTER_SUCCESS : [user_id_]
 *
 *
 *
 * 登录的请求消息格式
 *   sockfd_ (消息达到服务端之后, 由服务端接收之后, 打上sockfd_)
 *   msg_type_ : Login
 *   json_msg_ : 
 *      telnum : xxx
 *      passwd : xxx
 *   
 *
 *   登录的应答:
 *   msg_type : Login_Resp;
 *   reply_status_ : LOGIN_SUCCESS/LOGIN_FAILED
 *       如果是LOGIN_SUCCESS : [user_id_]
 *
 *
 *
 * 添加好友请求:
 *    msg_type_ : AddFriend
 *    json_msg_ :
 *      fri_tel_num : xxxx
 *    
 *
 *
 *  推送添加好友的请求
 *      msg_type : PushAddFriendMsg
 *      sockfd_ : 被添加方的套接字描述符
 *      json_msg_: 
 *          adder_nickname : xxx
 *          adder_school : xxx
 *          adder_userid : xxx
 *      
 *
 * 推送添加好友的应答(被添加方发送给服务端的)
 *     msg_type : PushAddFriendMsg_Resp
 *     user_id : 被添加方的id
 *     reply_status : ADDFRIEND_SUCCESS / ADDFRIEND_FAILED
 *         如果说是ADDFRIEND_SUCCESS
 *             json_msg_ : 添加方的id
 *
 *  添加好友的应答:
 *      msg_type: AddFriend_Resp
 *      reply_status : ADDFRIEND_FAILED / ADDFRIEND_SUCCESS
 *          如果是成功:ADDFRIEND_SUCCESS
 *             json_msg_ : 
 *                 BeAdd_nickname : 被添加方的名字
 *                 BeAdd_school : 被添加方的学校
 *                 BeAdd_userid : 被添加方的id
 * */

消息的序列化和反序列化

原因:面向对象语言设计的程序是通过各种类的使用实现的,而在信息传输的过程中我们传输的是二进制的文件,因此我们不能直接来进行传输,需要先对消息进行序列化,同样的拿到消息后也需要先做反序列化处理

class JsonUtil
    public:
        static bool Serialize(const Json::Value& value, std::string* body) 
            Json::StreamWriterBuilder swb;
            std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
        
            std::stringstream ss;
            int ret = sw->write(value, &ss);
            if (ret != 0) 
                return false;
            
            *body = ss.str();
            return true;
        
        
        static bool UnSerialize(const std::string& body, Json::Value* value) 
            Json::CharReaderBuilder crb;
            std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
        
            std::string err;
            bool ret = cr->parse(body.c_str(), body.c_str() + body.size(), value, &err);
            if (ret == false)                                                                                                                                                                   
                return false;
            
            return true;
        
;

Json消息的操作

  • a.获取json_msg_当中的value值,string GetValue(const std::string& key)
  • b. 设置json_msg_当中的kv键值对,string GetValue(const std::string& key)/SetValue(const std::string& key, const std::string& value)
        /*
         * 提供序列化的接口 - 回复应答的时候使用
         *     msg : 出参, 用于获取序列化完毕的字符串
         * */
        bool GetMsg(std::string* msg)
            Json::Value tmp;
            tmp["msg_type"] = msg_type_;
            tmp["user_id"] = user_id_;
            tmp["reply_status"] = reply_status_;
            tmp["json_msg"] = json_msg_;

            return JsonUtil::Serialize(tmp, msg);
        

        /*
         * 获取json_msg_当中的value值
         * */
        std::string GetValue(const std::string& key)
            if(!json_msg_.isMember(key))
                return "";
            
            return json_msg_[key].asString();
        

        /*
         * 设置json_msg_当中的kv键值对
         * */

        void SetValue(const std::string& key, const std::string& value)
            json_msg_[key] = value;
        

        void SetValue(const std::string& key, int value)
            json_msg_[key] = value;
        
        void Clear()
            msg_type_ = -1;
            user_id_ = -1;
            reply_status_ = -1;
            json_msg_.clear();
        
    public:
        //存放的客户端文件名描述符, 方便发送线程, 通过该字段将数据发送给对应的客户端
        int sockfd_;

        int msg_type_;

        //用户id
        int user_id_;

        //应答的状态
        int reply_status_;

        /*
         * Json消息
         *   json消息的内容会随着消息类型的不同, 字段不一样
         * */
        Json::Value json_msg_;
;

1.6消息队列

  • a.向队列中放消息:void Push(const T& msg)
  • b.从队列中拿消息:void Pop(T* msg)
#define CAPACITY 10000

template <class T>
class MsgQueue
    public:
        MsgQueue()
            capacity_ = CAPACITY;
            pthread_mutex_init(&lock_vec_, NULL);
            pthread_cond_init(&cons_cond_, NULL);
            pthread_cond_init(&prod_cond_, NULL);
        

        ~MsgQueue()
            pthread_mutex_destroy(&lock_vec_);
            pthread_cond_destroy(&cons_cond_);
            pthread_cond_destroy(&prod_cond_);
        


        void Push(const T& msg)
            pthread_mutex_lock(&lock_vec_);
            while(vec_.size() >= capacity_)
                pthread_cond_wait(&prod_cond_, &lock_vec_);
            
            vec_.push(msg);
            pthread_mutex_unlock(&lock_vec_);

            pthread_cond_signal(&cons_cond_);
        

        void Pop(T* msg)
            pthread_mutex_lock(&lock_vec_);
            while(vec_.empty())
                pthread_cond_wait(&cons_cond_, &lock_vec_);
            
            *msg = vec_.front();
            vec_.pop();
            pthread_mutex_unlock(&lock_vec_);

            pthread_cond_signal(&prod_cond_);
        
    private:
        std::queue<T> vec_;
        size_t capacity_;

        pthread_mutex_t lock_vec_;
        pthread_cond_t cons_cond_;
        pthread_cond_t prod_cond_;
;

2.客户端

客户端使用VS2019的MFC功能创建的

2.1登录及注册消息流转图

 代码实现:


// ChatSystemLd.cpp: 定义应用程序的类行为。
//

#include "pch.h"
#include "framework.h"
#include "ChatSystemLd.h"
#include "ChatSystemLdDlg.h"

#include "TcpSvr.h"
#include "MsgQueue.h"
#include <thread>


#ifdef _DEBUG
#define new DEBUG_NEW
#endif


void RecvMsgStart() 
	TcpSvr* ts = TcpSvr::getInstance();
	if (ts == NULL) 
		return;
	

	MsgQueue* mq = MsgQueue::GetInstance();
	if (mq == NULL) 
		return;
	
	while (1) 
		std::string msg;
		int ret = ts->Recv(&msg);
		if (ret <= 0) 
			continue;
		

		ChatMsg cm;
		cm.PraseChatMsg(-1, msg);

		mq->Push(cm.msg_type_, msg);
	




// CChatSystemLdApp

BEGIN_MESSAGE_MAP(CChatSystemLdApp, CWinApp)
	ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
END_MESSAGE_MAP()


// CChatSystemLdApp 构造

CChatSystemLdApp::CChatSystemLdApp()

	// 支持重新启动管理器
	m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;

	// TODO: 在此处添加构造代码,
	// 将所有重要的初始化放置在 InitInstance 中



// 唯一的 CChatSystemLdApp 对象

CChatSystemLdApp theApp;


// CChatSystemLdApp 初始化

BOOL CChatSystemLdApp::InitInstance()

	// 如果一个运行在 Windows XP 上的应用程序清单指定要
	// 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
	//则需要 InitCommonControlsEx()。  否则,将无法创建窗口。
	INITCOMMONCONTROLSEX InitCtrls;
	InitCtrls.dwSize = sizeof(InitCtrls);
	// 将它设置为包括所有要在应用程序中使用的
	// 公共控件类。
	InitCtrls.dwICC = ICC_WIN95_CLASSES;
	InitCommonControlsEx(&InitCtrls);

	/*
	创建接收线程, 让接收线程, 去接收应答
*/

	std::thread recv_thread(RecvMsgStart);
	recv_thread.detach();


	CWinApp::InitInstance();


	AfxEnableControlContainer();

	// 创建 shell 管理器,以防对话框包含
	// 任何 shell 树视图控件或 shell 列表视图控件。
	CShellManager *pShellManager = new CShellManager;

	// 激活“Windows Native”视觉管理器,以便在 MFC 控件中启用主题
	CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));

	// 标准初始化
	// 如果未使用这些功能并希望减小
	// 最终可执行文件的大小,则应移除下列
	// 不需要的特定初始化例程
	// 更改用于存储设置的注册表项
	// TODO: 应适当修改该字符串,
	// 例如修改为公司或组织名
	SetRegistryKey(_T("应用程序向导生成的本地应用程序"));

	CChatSystemLdDlg dlg;
	m_pMainWnd = &dlg;
	INT_PTR nResponse = dlg.DoModal();
	if (nResponse == IDOK)
	
		// TODO: 在此放置处理何时用
		//  “确定”来关闭对话框的代码
	
	else if (nResponse == IDCANCEL)
	
		// TODO: 在此放置处理何时用
		//  “取消”来关闭对话框的代码
	
	else if (nResponse == -1)
	
		TRACE(traceAppMsg, 0, "警告: 对话框创建失败,应用程序将意外终止。\\n");
		TRACE(traceAppMsg, 0, "警告: 如果您在对话框上使用 MFC 控件,则无法 #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS。\\n");
	

	// 删除上面创建的 shell 管理器。
	if (pShellManager != nullptr)
	
		delete pShellManager;
	

#if !defined(_AFXDLL) && !defined(_AFX_NO_MFC_CONTROLS_IN_DIALOGS)
	ControlBarCleanUp();
#endif

	// 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
	//  而不是启动应用程序的消息泵。
	return FALSE;


2.2注册界面

代码实现:

// CDialogRegister.cpp: 实现文件
//

#include "pch.h"
#include "ChatSystemLd.h"
#include "CDialogRegister.h"
#include "afxdialogex.h"

#include "ChatMsg.h"
#include "TcpSvr.h"
#include "MsgQueue.h"

// CDialogRegister 对话框

IMPLEMENT_DYNAMIC(CDialogRegister, CDialogEx)

CDialogRegister::CDialogRegister(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_DIALOGREGISTER, pParent)
	, m_nickname_(_T(""))
	, m_school_(_T(""))
	, m_telnum_(_T(""))
	, m_passwd_(_T(""))




CDialogRegister::~CDialogRegister()



void CDialogRegister::DoDataExchange(CDataExchange* pDX)

	CDialogEx::DoDataExchange(pDX);
	DDX_Text(pDX, IDC_EDIT1, m_nickname_);
	DDX_Text(pDX, IDC_EDIT2, m_school_);
	DDX_Text(pDX, IDC_EDIT3, m_telnum_);
	DDX_Text(pDX, IDC_EDIT4, m_passwd_);



BEGIN_MESSAGE_MAP(CDialogRegister, CDialogEx)
	ON_BN_CLICKED(IDC_BUTTONCOMMIT, &CDialogRegister::OnBnClickedButtoncommit)
END_MESSAGE_MAP()


// CDialogRegister 消息处理程序


void CDialogRegister::OnBnClickedButtoncommit()

	// TODO: 在此添加控件通知处理程序代码
		// TODO: 在此添加控件通知处理程序代码
		//1.获取用户的输入
	//获取输入控件当中最新的值
	UpdateData(TRUE);
	if (m_nickname_.IsEmpty() ||
		m_school_.IsEmpty() ||
		m_telnum_.IsEmpty() ||
		m_passwd_.IsEmpty()) 
		MessageBox(TEXT("输入内容不能为空"));
		return;
	
	//2.组织登录消息(ChatMsg)
	ChatMsg cm;
	cm.msg_type_ = Register;
	cm.json_msg_["nickname"] = m_nickname_.GetString();
	cm.json_msg_["school"] = m_school_.GetString();
	cm.json_msg_["telnum"] = m_telnum_.GetString();
	cm.json_msg_["passwd"] = m_passwd_.GetString();
	//cm.SetValue("telnum", m_telnum_.GetString());
	//cm.SetValue("passwd", m_passwd_.GetString());
	std::string msg;
	cm.GetMsg(&msg);
	//3.获取TCP服务实例化指针
	TcpSvr* ts = TcpSvr::getInstance();
	if (ts == NULL) 
		MessageBox("获取tcp服务失败, 请重试..");
		return;
	
	//4.发送登录消息
	ts->Send(msg);
	//5.获取消息队列的实例化指针
	MsgQueue* mq = MsgQueue::GetInstance();
	if (mq == NULL) 
		MessageBox("获取消息队列失败, 请联系开发人员...");
		return;
	

	msg.clear();
	mq->Pop(Register_Resp, &msg);
	//6.获取登录应答
	cm.Clear();
	cm.PraseChatMsg(-1, msg);
	//7.判断登录应答当中的应答状态(LOGIN_SUCCESS/LOGIN_FAILED)
	if (cm.reply_status_ == REGISTER_SUCCESS) 
		MessageBox("register success");
		//退出当前的注册界面, 相当于回到了登录界面
		CDialog::OnCancel();
	
	else 
		MessageBox("register failed, please retry...");
	

2.3聊天界面及消息流转图

代码实现

// CDialogChatWin.cpp: 实现文件
//

#include "pch.h"
#include "ChatSystemLd.h"
#include "CDialogChatWin.h"
#include "afxdialogex.h"
#include "CDialogAddFriend.h"

#include "TcpSvr.h"
#include "MsgQueue.h"
#include "ChatMsg.h"



// CDialogChatWin 对话框

IMPLEMENT_DYNAMIC(CDialogChatWin, CDialogEx)

CDialogChatWin::CDialogChatWin(int  userid,CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_DIALOGCHATWIN, pParent)
	, m_sendmsg_(_T(""))
	, user_id_(userid)




CDialogChatWin::~CDialogChatWin()



void CDialogChatWin::DoDataExchange(CDataExchange* pDX)

	CDialogEx::DoDataExchange(pDX);
	//DDX_Text(pDX, IDC_EDIT1, m_sendmsg_);
	DDX_Text(pDX, IDC_EDITSENDMSG, m_sendmsg_);
	DDX_Control(pDX, IDC_LISTFRIENDLIST, m_userlist_);
	DDX_Control(pDX, IDC_LISTHISTORYMSG, m_output_);
	DDX_Control(pDX, IDC_EDITSENDMSG, m_sendmsg_edit_);
	//DDX_Control(pDX, IDC_BUTTONSENDMSG, user_id_);



BEGIN_MESSAGE_MAP(CDialogChatWin, CDialogEx)
	//ON_BN_CLICKED(IDC_BUTTON2, &CDialogChatWin::OnBnClickedButton2)
	ON_BN_CLICKED(IDC_BUTTONADDFRIEND, &CDialogChatWin::OnBnClickedButtonaddfriend)
	ON_BN_CLICKED(IDC_BUTTONSENDMSG, &CDialogChatWin::OnBnClickedButtonsendmsg)
	ON_LBN_SELCHANGE(IDC_LISTFRIENDLIST, &CDialogChatWin::OnLbnSelchangeListfriendlist)
END_MESSAGE_MAP()


// CDialogChatWin 消息处理程序


void DealPushMsg(CDialogChatWin* cc) 
	MsgQueue* mq = MsgQueue::GetInstance();
	if (mq == NULL) 
		return;
	

	while (1) 
		std::string msg;
		mq->Pop(PushMsg, &msg);

		ChatMsg cm;
		cm.PraseChatMsg(-1, msg);
		std::string peer_nickname = cm.json_msg_["peer_nickname"].asString();
		std::string peer_school = cm.json_msg_["peer_school"].asString();
		std::string peer_msg = cm.json_msg_["peer_msg"].asString();
		int peer_id = cm.json_msg_["peer_userid"].asInt();

		for (size_t i = 0; i < cc->fri_vec_.size(); i++) 
			if (peer_id == cc->fri_vec_[i].user_id_) 
				std::string tmp = peer_nickname + "-" + peer_school + ": " + peer_msg;
				cc->fri_vec_[i].history_msg_.push_back(tmp);
				if (peer_id == cc->send_user_id_) 
					//cc->m_output_.AddString(tmp.c_str());
					cc->m_output_.InsertString(cc->m_output_.GetCount(), tmp.c_str());
				
				else 
					cc->fri_vec_[i].msg_cnt_++;
				
			
		
		cc->RefreshUserList();
	


//能够调用到这个线程函数, 说明当前客户端作为被添加方
void DealPushAddFriendMsg(CDialogChatWin* cc) 
	//1.获取消息队列实例化指针 & tcp的实例化指针
	MsgQueue* mq = MsgQueue::GetInstance();
	if (mq == NULL) 
		MessageBox(cc->m_hWnd, "获取消息队列失败, 请联系开发人员...", "error", MB_YESNO);
		return;
	
	//2.循环获取 PushAddFriendMsg 消息类型的消息
	while (1) 
		std::string msg;
		mq->Pop(PushAddFriendMsg, &msg);

		ChatMsg cm;
		cm.PraseChatMsg(-1, msg);
		std::string adder_nickname = cm.json_msg_["adder_nickname"].asString();
		std::string adder_school = cm.json_msg_["adder_school"].asString();
		int adder_userid = cm.json_msg_["adder_userid"].asInt();
		//3.通过获取的消息内容, 展示是那个用户想要添加自己
		std::string show_msg = adder_nickname + ":" + adder_school + " want add you as friend.";

		cm.Clear();
		int i = MessageBox(cc->m_hWnd, show_msg.c_str(), "添加好友", MB_YESNO);
		if (i == IDYES) 
			//同意添加
			//a. 将新好友信息维护起来
			struct UserInfo ui;
			ui.nickname_ = adder_nickname;
			ui.school_ = adder_school;
			ui.user_id_ = adder_userid;
			ui.msg_cnt_ = 0;
			cc->fri_vec_.push_back(ui);
			//b. 刷新用户列表
			cc->RefreshUserList();
			//c. 组织应答
			cm.msg_type_ = PushAddFriendMsg_Resp;
			cm.reply_status_ = ADDFRIEND_SUCCESS;
			cm.user_id_ = cc->user_id_; // 被添加方的id、
			cm.json_msg_["userid"] = adder_userid;
		
		else 
			//不同意添加
			//a. 组织应答
			cm.msg_type_ = PushAddFriendMsg_Resp;
			cm.reply_status_ = ADDFRIEND_FAILED;
			cm.user_id_ = cc->user_id_; // 被添加方的id、
			cm.json_msg_["userid"] = adder_userid;
		
		msg.clear();
		cm.GetMsg(&msg);
		//4.根据不同结果返回应答
		TcpSvr* ts = TcpSvr::getInstance();
		if (ts == NULL) 
			continue;
		
		ts->Send(msg);
	




//能够调用到这个线程函数, 说明当前客户端作为添加方
void DealAddFriendResp(CDialogChatWin* cc) 
	//1.获取消息队列实例化指针 & tcp的实例化指针
	MsgQueue* mq = MsgQueue::GetInstance();
	if (mq == NULL) 
		MessageBox(cc->m_hWnd, "获取消息队列失败, 请联系开发人员...", "error", MB_YESNO);
		return;
	
	//2.循环获取 PushAddFriendMsg 消息类型的消息
	while (1) 
		std::string msg;
		mq->Pop(AddFriend_Resp, &msg);

		ChatMsg cm;
		cm.PraseChatMsg(-1, msg);
		std::string content = cm.GetValue("content");
		MessageBox(cc->m_hWnd, content.c_str(), "添加好友应答", MB_OK);
		if (cm.reply_status_ == ADDFRIEND_FAILED) 
			continue;
		
		std::string be_adder_nickname = cm.json_msg_["peer_nick_name"].asString();
		std::string be_adder_school = cm.json_msg_["peer_school"].asString();
		int be_adder_userid = cm.json_msg_["peer_userid"].asInt();

		//a. 将新好友信息维护起来
		struct UserInfo ui;
		ui.nickname_ = be_adder_nickname;
		ui.school_ = be_adder_school;
		ui.user_id_ = be_adder_userid;
		ui.msg_cnt_ = 0;
		cc->fri_vec_.push_back(ui);
		//b. 刷新用户列表
		cc->RefreshUserList();

	




BOOL CDialogChatWin::OnInitDialog()

	CDialogEx::OnInitDialog();

	// TODO:  在此添加额外的初始化
	std::thread recv_msg(DealPushMsg, this);
	recv_msg.detach();

	std::thread recv_addfriendmsg(DealPushAddFriendMsg, this);
	recv_addfriendmsg.detach();

	std::thread recv_addfriendrespmsg(DealAddFriendResp, this);
	recv_addfriendrespmsg.detach();
	// TODO:  在此添加额外的初始化
	//1. 获取TCP实例化指针
	T

以上是关于基于TCP的网络聊天系统的主要内容,如果未能解决你的问题,请参考以下文章

Java网络编程基础— 基于TCP的NIO简单聊天系统

基于TCP的聊天系统

一个基于TCP/IP的小项目,实现广播消息的功能。(超详细版)

基于 TCP 的 Java 可序列化安全性

基于MQTT协议谈谈物联网开发

基于TCP的聊天系统