基于TCP协议的聊天系统TCP-ChatSystem

news2024/11/17 8:19:42

基于TCP协议的点对点聊天系统

网络协议:TCP

聊天形式:点对点

所用技术:socket、多路转接、线程池、互斥锁、条件变量、MFC等。

功能点:注册、登录、添加好友、聊天等。

实现示意图

客户端登陆注册消息流转图:

 客户端添加好友消息流转图:

 客户端聊天消息流转图:

服务端处理请求消息流转图: 

服务端

Linux环境下的gcc升级

sudo yum install centos-release-scl-rh centos-release-scl
sudo yum install devtoolset-7-gcc devtoolset-7-gcc-c++
source /opt/rh/devtoolset-7/enable
echo "source /opt/rh/devtoolset-7/enable" >> ~/.bashrc

安装jsoncpp

yum install -y jsoncpp
yum install -y jsoncpp-devel

服务端模块划分

 数据库模块设计

数据库表设计

用户信息表:

Create Table: CREATE TABLE `user` (
 `userid` int(11) NOT NULL,
 `nickname` varchar(20) NOT NULL,
 `school` varchar(20) NOT NULL,
 `telnum` char(11) NOT NULL,
 `passwd` varchar(100) NOT NULL,
 `m_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTA
 PRIMARY KEY (`userid`),
 UNIQUE KEY `telnum` (`telnum`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

好友信息表:

Create Table: CREATE TABLE `friendinfo` (
 `userid` int(11) NOT NULL,
 `friend` int(11) NOT NULL,
 KEY `userid` (`userid`),
 KEY `friend` (`friend`),
 CONSTRAINT `friendinfo_ibfk_1` FOREIGN KEY (`userid`) REFERENCES `user` (`userid
 CONSTRAINT `friendinfo_ibfk_2` FOREIGN KEY (`friend`) REFERENCES `user` (`userid
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

MySQL-C-API(MySQL的C语言接口)

初始化MySQL的操作句柄

//函数原型
MYSQL*
mysql_init(MYSQL *mysql);
  • 函数意义:分配或初始化与mysql_real_connect()相适应的MYSQL对象。如果mysql是NULL指针,该函数将分配、初始化、并返回新对象。否则,将初始化对象,并返回对象的地址。
  • 调用示例:MYSQL* mysql_ = mysql_init(NULL);

连接mysql服务端

//函数原型:
MYSQL*
mysql_real_connect(MYSQL *mysql,             //mysql操作句柄
                   const char *host,         //服务端IP地址
                   const char *user,         //⽤⼾名
                   const char *passwd,       //密码
                   const char *db,           //数据库
                   unsigned int port,        //端⼝
                   const char *unix_socket,  //是否使⽤本地域套接字
                   unsigned long client_flag //数据库标志位, 通常为0, 采⽤默认属性
                   );
  • 函数含义:连接MySQL服务端,如果连接成功,则返回的是MySQL操作句柄,失败返回NULL。
  • 调用示例:mysql_real_connect(&mysql,"host","user","passwd","database",0,null,0);

设置连接对应的字符集

//函数原型
int 
mysql_set_character_set(MYSQL *mysql,
                        const char *csname);
  • 函数含义:用于设置当前连接的默认字符集。
  • 调用示例:mysql_set_character_set(mysql_,"utf8");

执行sql语句

//函数原型
int
mysql_query(MYSQL *mysql, //mysql操作句柄
            const char *stmt_str //执⾏的sql语句
            );
  • 函数含义:执行sql语句。成功返沪0;失败返回非0。
  • 调用示例:mysql_query(mysql_,"select * from user;");

获取结果集

//函数原型
MYSQL_RES*
mysql_store_result(MYSQL *mysql); // mysql操作句柄
  • 函数含义:获取查询的结果,称之为结果集。成功,返回MYSQL_RES指针。失败返回NULL。
  • 注意事项:需要调用。
  • 调用示例:mysql_store_result(mysql_);

获取结果集行数

//函数原型
MYSQL_ROW
mysql_fetch_row(MYSQL_RES *result);
  • 函数含义:获取结果集的下一行内容。

释放结果集内存

//函数原型
void
mysql_close(MYSQL *mysql);

编译链接

//头文件包含
#include<mysql/mysql.h>
//链接时
-L /user/lib64/mysql -lmysqlclient

 数据库模块代码设计

class DBServer{
  public:
    DBServer(){};
    ~DBServer(){};
    
    //获取全部⽤⼾信息, ⽤于⽤⼾管理系统初始化阶段
    //参数为出参
    bool GetAllUesr(Json::Value* all_user){}
   
    //获取好友ID, ⽤于客⼾端获取好友列表
    //参数为出参
    bool GetFriend(int userid, std::vector<int>* f_id){}
    
    //插⼊⽤⼾, ⽤于⽤⼾注册
    bool InsertUser(int userid, const std::string& nickname,
    const std::string& school, const std::string& telnum,
    const std::string& passwd){};

    //插⼊好友, ⽤于⽤⼾添加好友
    //参数:userid : ⽤⼾id, friendid : 好友id
    bool InsertFriend(int userid, int friendid){};
  public:

    //初始化函数, 连接mysql服务端, 设置字符集
    bool MysqlInit(){};

    //执⾏sql语句
    bool MysqlQuery(const std::string& sql){};

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

用户管理模块 

描述用户信息类

class UserInfo{
  public:
    UserInfo(const std::string& nick_name, const std::string& school,
             const std::string& passwd,
             const std::string tel_num, int user_id){}
    UserInfo(){}
    ~UserInfo(){}
  public:
    std::string nick_name_; // ⽤⼾名称
    std::string school_; //学校
    std::string passwd_; //密码
    std::string tel_num_; //电话
    //⽤⼾id
    int user_id_; //⽤⼾ID
    int user_status_; //⽤⼾状态:ONLINE/OFFLINE
    int tcp_sockfd_; //客⼾端对应的sockfd
    std::vector<int> friend_id_; //⽤⼾的好友列表:存储好友ID
};

管理用户信息类

class UserManager {
  public:
    UserManager(){}
    ~UserManager(){}
    bool InitUserMana(){}
    int DealRegister(const std::string& nick_name,
    const std::string& school,
    const std::string& passwd,
    const std::string& tel_num,
    int* user_id){}
    int DealLogin(const std::string& tel_num,
                  const std::string& passwd,
                  int cli_sockfd){}
    int IsLogin(int user_id, UserInfo* ui){
    return 0;}
    int IsLogin(const std::string& tel_num, UserInfo* ui){}
    bool GetFriends(int user_id, std::vector<int>* fri){}
    bool GetUserInfo(int user_id, UserInfo* ui){}
    void SetFriend(int user_id1, int user_id2){}
  private:
    /*
     * std::string ==> id
     * UserInfo ==> 保存的具体⽤⼾的信息 
     */
    std::unordered_map<int, UserInfo> user_map_;
    pthread_mutex_t map_lock_;
    //预分配的⽤⼾id, 当⽤⼾管理模块接收到注册请求之后, 将prepare_id分配给注册的⽤⼾,
    int prepare_id_;
    DBServer* db_; //数据库服务指针
};

线程安全的队列

/*
 * 消息池当中使⽤vector来保存消息,vector这个容器并不是线程安全, STL当中的容器都是线程不安全
 * 保证线程安全的机制:
 * 互斥锁+条件变量
 */
#define CAPACITY 1024
template <class T>
class MsgPool{
  public:
    MsgPool(size_t capa = CAPACITY){capacity_ = capa;
                                    pthread_mutex_init(&lock_vec_, NULL);
                                    pthread_cond_init(&cond_con_, NULL);
                                    pthread_cond_init(&cond_pro_, NULL);}
    
    ~MsgPool(){pthread_mutex_destroy(&lock_vec_);
               pthread_cond_destroy(&cond_con_);
               pthread_cond_destroy(&cond_pro_);}
    
    void PushMsg(const T& msg){
    pthread_mutex_lock(&lock_vec_);
    while(vec_.size() >= capacity_){
        pthread_cond_wait(&cond_pro_, &lock_vec_);}
    vec_.push(msg);
    pthread_mutex_unlock(&lock_vec_);
    pthread_cond_signal(&cond_con_);}
    
    void PopMsg(T* msg){
    pthread_mutex_lock(&lock_vec_);
    while(vec_.empty()){
        pthread_cond_wait(&cond_con_, &lock_vec_);}
    *msg = vec_.front();
    vec_.pop();
    pthread_mutex_unlock(&lock_vec_);
    pthread_cond_signal(&cond_pro_);}

private:
/*
 * vec_ : 保存消息的容器
 * capacity_ : 定义的容器的容量
 * lock_vec_ : 保护消息容器的锁
 * cond_con_ : 消费者的条件变量
 * cond_pro_ : ⽣产者的条件变量
 */
    std::queue<T> vec_;
    size_t capacity_;
    pthread_mutex_t lock_vec_;
    pthread_cond_t cond_con_;
    pthread_cond_t cond_pro_;
};

网络通信模块

主线程接收链接&网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);
}

监控&接收客户端的数据

static void* epoll_wait_start(void* arg){
    pthread_detach(pthread_self());
    ChatServer* cs = (ChatServer*)arg;
    while(1){
        //1.epoll_wait 等待事件发⽣
        struct epoll_event arr[10];
        int ret = epoll_wait(cs->epoll_fd_, arr, sizeof(arr)/sizeof(arr[0]), -1);
        if(ret < 0){
            continue;}
        //2.获取到事件, 准备接收数据
        for(int i = 0; i < 10; i++){
            char buf[TCP_MAX_DATA_LEN] = {0};
            ssize_t recv_size = recv(arr[i].data.fd, buf, sizeof(buf) - 1, 0);
                if(recv_size < 0){
                //2.1 接收失败了
                continue;}
                else if(recv_size == 0){
                //2.2 对端关闭连接了
                epoll_ctl(cs->epoll_fd_, EPOLL_CTL_DEL, arr[i].data.fd, NULL);
                close(arr[i].data.fd);
                continue;}
            //2.3 正常接收回来, 放到消息池等待处理
            printf("epoll_wait_start buf is %s by client id [%d]\n", buf, arr[i].
            std::string msg;
            msg.assign(buf, strlen(buf));
            ChatMsg cm;
            cm.PraseChatMsg(arr[i].data.fd, msg);
            cs->msg_pool_->PushMsg(cm);
        }
    }
}

发送线程

static void* send_msg_start(void* arg){
    pthread_detach(pthread_self());
    ChatServer* cs = (ChatServer*)arg;
    while(1){
        ChatMsg cm;
        cs->send_msg_queue_->PopMsg(&cm);
        int cli_sockfd = cm.sockfd_;
        string msg ;
        cm.GetMsg(&msg);
        cout << "msg: " << msg << endl;
        ssize_t send_size = send(cli_sockfd, msg.c_str(), msg.size(), 0);
            if(send_size < 0){
            //放到缓存队列当中, 由缓存队列进⾏发送
            perror("send");
            continue;
        }
    }
}

自定义消息

Json Value对象的认识

char name = "⼩明";
int age = 18;
float score[3] = {88.5, 99, 58};
则json这种数据交换格式是将这多种数据对象组织成为⼀个字符串:
[
    {
        "姓名" : "⼩明",
        "年龄" : 18,
        "成绩" : [88.5, 99, 58]
    },
    {
        "姓名" : "⼩⿊",
        "年龄" : 18,
        "成绩" : [88.5, 99, 58]
    }
]

json数据类型:对象,数组,字符串,数字

对象:使用花括号{}括起来的表示一个对象;

数组:使用中括号[]括起来的表示一个数组;

字符串:使用常规双引号“”括起来的表示一个字符串;

数字:包括整形和浮点型,直接使用。

/Json数据对象类
class Json::Value{
    Value& operator=(const Value &other); //Value重载了[]和=,因此所有的赋值和获取数据
    Value& operator[](const std::string& key);//简单的⽅式完成 val["姓名"] = "⼩明";
    Value& operator[](const char* key);
    Value removeMember(const char* key);//移除元素
    const Value& operator[](ArrayIndex index) const; //val["成绩"][0]
    Value& append(const Value& value);//添加数组元素val["成绩"].append(88);
    ArrayIndex size() const;//获取数组元素个数 val["成绩"].size();
    std::string asString() const;//转string string name = val["name"].asS
    const char* asCString() const;//转char* char *name = val["name"].asCString()
    Int asInt() const;//转int int age = val["age"]
    float asFloat() const;//转float
    bool asBool() const;//转 bool
};
//json序列化类,低版本⽤这个更简单
class JSON_API Writer {
    virtual std::string write(const Value& root) = 0;
}
class JSON_API FastWriter : public Writer {
    virtual std::string write(const Value& root);
}
class JSON_API StyledWriter : public Writer {
    virtual std::string write(const Value& root);
}
//json序列化类,⾼版本推荐,如果⽤低版本的接⼝可能会有警告
class JSON_API StreamWriter {
    virtual int write(Value const& root, std::ostream* sout) = 0;
}
class JSON_API StreamWriterBuilder : public StreamWriter::Factory {
    virtual StreamWriter* newStreamWriter() const;
}
//json反序列化类,低版本⽤起来更简单
class JSON_API Reader {
    bool parse(const std::string& document, Value& root, bool collectComments
}
//json反序列化类,⾼版本更推荐
class JSON_API CharReader {
    virtual bool parse(char const* beginDoc, char const* endDoc,
    Value* root, std::string* errs) = 0;
}
class JSON_API CharReaderBuilder : public CharReader::Factory {
    virtual CharReader* newCharReader() const;
}

Json序列化和反序列化

class JsonUtil {
  public:
    /*
    value : 待要序列化的json对象
    body : 序列化完毕产⽣的string对象 (出参)
    */
    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;
    }
    /*
    body : 待要反序列化的string对象
    value : 序列化完毕产⽣的json对象 (出参)
    */
    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, &er
        if (ret == false) {
            return false;
        }
        return true;
    }
};

使用Json数据格式封装自定义消息

class ChatMsg {
public:
    ChatMsg() {
        sockfd_ = -1;
        msg_type_ = -1;
        reply_status_ = -1;
        user_id_ = -1;
        json_msg_.clear();
    }
    ~ChatMsg() {};
    int PraseChatMsg(int sockfd, const std::string& msg) {
    Json::Value tmp;
    bool ret = JsonUtil::UnSerialize(msg, &tmp);
    if (ret == false) {
        return -1;
    }
    sockfd_ = sockfd;
    msg_type_ = tmp["msg_type"].asInt();
    user_id_ = tmp["user_id"].asInt();
    reply_status_ = tmp["reply_status"].asInt();
    json_msg_ = tmp["json_msg"];
    return 0;
}
void SetKeyValue(const std::string& key, const std::string& val) {
    json_msg_[key] = val;
}
std::string GetValue(const std::string& key) {
    if (!json_msg_.isMember(key)) {
        return "";
    }
    return json_msg_[key].asString();
}
/**/
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);
}
void Clear(int is_clear_jsonmsg = 1) {
    msg_type_ = -1;
    user_id_ = -1;
    reply_status_ = -1;
    if(is_clear_jsonmsg == 1){
        json_msg_.clear();
    }    
}
public:
    int sockfd_;
    /*
    Register = 0,
    Register_resp,
    Login,
    Login_resp,
    AddFriend,
    AddFriend_resp,
    SendMsg,
    PushMsg,
    PushAddFriendMsg,
    PushAddFriendMsgResp
    */
    int msg_type_;
    int user_id_;
    /*
    Register_Success : 0
    Register_Fail : 1
    Login_Success : 2
    Login_Fail : 3
    AddFriend_Success : 4
    AddFriend_Fail : 5
    SendMsg_Success : 6
    SendMsg_Fail : 7
    */
    int reply_status_;
    /*
    JsonлϢ
    */
    Json::Value json_msg_;
};

业务处理模块

根据消息类型处理不同类型的消息

static void* deal_start(void* arg){
    pthread_detach(pthread_self());
    ChatServer* cs = (ChatServer*)arg;
    while(1){
        //1.消息池当中获取消息
        ChatMsg cm;
        cs->msg_pool_->PopMsg(&cm);
        //3.分业务处理
        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 PushAddFriendMsgResp:{
                cs->DealAddFriendResp(cm);
                break;
            }
            case SendMsg:{
                cs->DealSendMsg(cm);
                break;
            }
            case GetFriendMsg:{
                cs->DealGetFriend(cm);
                break;
            }
            default:{
                break;
            }
        }
    }
}

客户端

MFC环境搭建

https://blog.csdn.net/qq_35392239/article/details/109190131

jsoncpp编译

https://blog.csdn.net/houxian1103/article/details/123343248

编译好的win版本的jsoncpp编译

32位debug版本

32位release版本

使用注意事项

  • 确保自己的创建的工程使用的同位数的jsoncpp库,例如32位程序九时用32位。
  • 确保自己的创建的工程和使用的jsoncpp同是debug版本或者release版本,否则会编译链接不通过。

windows下使用jsoncpp

win-tcp封装

准备工作

1. 要使⽤win socket,需要先引⼊ws2_32.lib库,该库提供了socket接⼝的实现具体可以在代码的最头部引⼊
//表⽰链接的时候,链接该库,当然你也可以在项⽬中进⾏设置,不过不推荐
#pragma comment(lib, "ws2_32.lib")

2. 选择socket库
使⽤win socket之前,需要先选择使⽤哪⼀个版本的socket库,并且和当前程序进⾏绑定,我们使⽤:
函数:
int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData ); //具体⻅MSDN
参数:
wVersionRequested:标识了⽤⼾调⽤的Winsock的版本号。⾼字节指明辅版本编号,
低字节指明主版本编号。通常使⽤MAKEWORD[函数:WORD MAKEWORD( BYTE b
lpWSAData:指向WSADATA结构体的指针,lpWSAData返回了系统对Windows Sockets 的描述。
//可以在vs2019单击右键,查到到对应的版本相关结构体
typedef struct WSAData {
    WORD         wVersion;
    WORD         wHighVersion;
#ifdef _WIN64
    unsigned short iMaxSockets;
    unsigned     short iMaxUdpDg;
    char FAR     * lpVendorInfo;
    char         szDescription[WSADESCRIPTION_LEN+1];
    char         szSystemStatus[WSASYS_STATUS_LEN+1];
#else
    char         szDescription[WSADESCRIPTION_LEN+1];
    char         szSystemStatus[WSASYS_STATUS_LEN+1];
    unsigned     short iMaxSockets;
    unsigned     short iMaxUdpDg;
    char FAR     * lpVendorInfo;
#endif
} WSADATA;
typedef WSADATA FAR *LPWSADATA;

3. 释放对Winsock链接库的调⽤,以及释放相关资源。必须和WSAStartup成对出现
int WSACleanup (void);

socket接口(只关心客户端相关接口)

//除了上述特殊情况,其他接⼝基本与Linux⼤同⼩异,具体可以详⻅MSDN说明或者vs 2019查看接⼝细
1. 创建套接字
SOCKET socket( int af, int type, int protocol );
//返回值就是⼀个unsigned int值
typedef _W64 unsigned int UINT_PTR, *PUINT_PTR;
typedef UINT_PTR SOCKET;

样例:
SOCKET sk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sk == INVALID_SOCKET) {
        std::cout << "Create Socket Error::" << GetLastError() <<
        return;
    }
其中:
IPPROTO_TCP = 6,

2. 发起链接请求
int connect(SOCKET s, const struct sockaddr FAR *name, int namelen);
样例:
struct sockaddr_in peer;
peer.sin_family = AF_INET;
peer.sin_port = htons(port);
peer.sin_addr.s_addr = inet_addr(ip.c_str());
memset(peer.sin_zero, 0, 8);
int ret = connect(sk, (struct sockaddr*)&peer, sizeof(peer));
if (ret == SOCKET_ERROR) {
    std::cout << "Connect Error" << std::endl;
    return -1;
}
其中:
struct sockaddr_in {
    short sin_family;
    u_short sin_port;
    struct in_addr sin_addr;
    char sin_zero[8];
};

3. 发送数据
int send(SOCKET s, const char FAR *buf, int len, int flags);

4. 接收数据
int recv(SOCKET s, char FAR *buf, int len, int flags);

设计tcp接口为单例模式

头文件:

#include <stdio.h>
#include <WinSock2.h>
#include <iostream>
#include <mutex>

#pragma comment(lib, "ws2_32.lib")

class TcpSvr {
public:
    static TcpSvr* getInstance();
    int Send(std::string& msg);
    int Recv(std::string* msg);
private:
    TcpSvr();
    TcpSvr(const TcpSvr&);
    static TcpSvr* instance_;
    SOCKET sockfd_;
private:
    int ConnectToSvr();
};

源文件:

#include "pch.h"
#include "tcp_svr.h"
class TcpSvr;
std::mutex mt;
TcpSvr* TcpSvr::instance_ = nullptr;
TcpSvr* TcpSvr::getInstance() {
    if (nullptr == instance_) {
        mt.lock();
        if (nullptr == instance_) {
            instance_ = new TcpSvr();
            if (instance_ != nullptr) {
                instance_->ConnectToSvr();
            }
        }
        mt.unlock();
    }
    return instance_;
}

TcpSvr::TcpSvr() {

}

int TcpSvr::ConnectToSvr() {
    /*1.加载套接字库*/
    WSADATA wsaData;
    int iRet = 0;
    iRet = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (0 != iRet) {
        std::cout << "WSAStartup(MAKEWORD(2, 2), &wsaData) execute failed!" << st
        return -1;
    }
    if (2 != LOBYTE(wsaData.wVersion) || 2 != HIBYTE(wsaData.wVersion)) {
        WSACleanup();
        return -1;
    }
    //创建套接字
    sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd_ == INVALID_SOCKET) {
        WSACleanup();
        return -1;
    }
    //初始化服务器端地址族变量
    SOCKADDR_IN srvAddr;
    srvAddr.sin_addr.S_un.S_addr = inet_addr("42.192.83.143");
    srvAddr.sin_family = AF_INET;
    srvAddr.sin_port = htons(19090);
    //连接服务器
    iRet = connect(sockfd_, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR));
    if (0 != iRet) {
        closesocket(sockfd_);
        WSACleanup();
        return -1;
    }
    return 0;
}

int TcpSvr::Send(std::string& msg) {
    return send(sockfd_, msg.c_str(), msg.size(), 0);
}

int TcpSvr::Recv(std::string* msg) {
    char buf[1024] = { 0 };
    int r_s = recv(sockfd_, buf, sizeof(buf) - 1, 0);
    if (r_s < 0) {
        return r_s;
    }
    else if(r_s == 0){
        //重新连
        exit(1);
    }    
    else {
    *msg = buf;
    }
    return 0;
}

消息队列

客户端的接收线程从网络当中将消息收回来之后,按照消息的类型丢入到消息队列当中,不同类的线程可以按照消息类型去获取消息。例如:注册线程只需关系注册消息,登陆线程只需要关心登陆消息。

所以:客户端的消息队列需要能够支持按照消息类型获取消息;设计成为单例模式。

头文件:

#pragma once
#include <vector>
#include <queue>
#include <string>
#include <mutex>
#include <iostream>
using namespace std;
class MsgQueue
{
public:
    static MsgQueue* getInstance();
    void Push(int msg_type, const string& msg);
    void Pop(int msg_type, string* msg);
private:
    MsgQueue();
    MsgQueue(const MsgQueue&);
    static MsgQueue* instance_;
    vector<queue<string>> v_msg_;
};

源文件:

#include "pch.h"
#include "MsgQueue.h"
static mutex g_lock;
MsgQueue* MsgQueue::instance_ = nullptr;
MsgQueue::MsgQueue() {
    v_msg_.resize(20);
}

MsgQueue* MsgQueue::getInstance() {
    if (nullptr == instance_) {
        g_lock.lock();
        if (nullptr == instance_) {
            instance_ = new MsgQueue();
        }
        g_lock.unlock();
    }
    return instance_;
}

void MsgQueue::Push(int msg_type, const string& msg) {
    g_lock.lock();
    v_msg_[msg_type].push(msg);
    g_lock.unlock();
}

void MsgQueue::Pop(int msg_type, string* msg) {
    while (1) {
        if (v_msg_[msg_type].empty()) {
            //单位毫秒
            Sleep(1);
            continue;
        }
        g_lock.lock();
        *msg = v_msg_[msg_type].front();
        v_msg_[msg_type].pop();
        g_lock.unlock();
        break;
    }
}

MFC基于对话框编程(vs)

创建基于对话框的MFC应用程序框架

 

 

对话框应用程序框架介绍

资源视图

在MFC中,与用户进行交互的对话框界面被认为是一种资源。

 

展开:“Dialog”,可以看到有一个ID为IDD_CHATSYSTEMCLIENT _DLALOG(中间部分(CHATSYSTEMCLIENT)与项目名称相同)的资源,对应中间的对话框设计界面。不管在何时,只要双击对话框资源的ID,对话框设计界面就会显示在中间。

 

类视图

在类视图中,可以看到生成了3个类:CAboutDlg、CDialogApp和CDialogDlg。

 

  • CAboutDlg:对应生成版本本信息对话框;
  • CDialogApp:应用程序类,从CWinApp继承过来,封装了初始化、运行、终止该程序的代码;
virtual BOOL InitInstance(); //这个函数就是初始化程序的函数
  • CDialogDlg:对话框类,从CdialogEx继承过来的,在程序运行时看到的对话框就是他的一个具体对象。(DoDataExchange函数:该函数完成对话框数据的交换和校验;OnlineDialog:相当于对对话框进行初始化处理。)

设计界面和工具箱

 

对话框模拟

当模拟对话框显示时。程序(之前窗口)会暂停执行,直到关闭这个模拟对话框之后,才能执行程序中的其他任务。

void CDialogDlg::OnBnClickedButton1()
{
    CDlgExec dlg;
    dlg.DoModal(); //以模态⽅式运⾏
}

聊天界面

好友信息维护

struct UserInfo {
public:
    std::string nickname_;
    std::string school_;
    int user_id_;
    std::vector<std::string> history_msg_;
    int msg_cnt_;
};

初始化:
 

BOOL CChatWin::OnInitDialog()
{
    CDialogEx::OnInitDialog();
    std::thread recv_msg(DealPushMsg, this);
    recv_msg.detach();
    std::thread recv_addfriendmsg(DealPushAddFriMsg, this);
    recv_addfriendmsg.detach();
    std::thread recv_AddFriendResp(DealAddFriendResp, this);
    recv_AddFriendResp.detach();
    //1.获取tcp连接
    TcpSvr* ts = TcpSvr::getInstance();
    if (ts == nullptr) {
        MessageBox(TEXT("连接后台失败, 请联系管理员"));
        return false;
    }

    //2.组织json数据, 发送获取好友的请求
    ChatMsg cm;
    cm.msg_type_ = GetFriendMsg;
    cm.user_id_ = user_id_;
    string msg;
    cm.GetMsg(&msg);
    ts->Send(msg);
    //3.获取结果进⾏分析
    MsgQueue* mq = MsgQueue::getInstance();
    if (mq == nullptr) {
        return false;
    }
    msg.clear();
    cm.Clear();
    mq->Pop(GetFriendMsgResp, &msg);
    cm.PraseChatMsg(-1, msg);
    for (int i = 0; i < (int)cm.json_msg_.size(); i++) {
        //给列表框中添加数据
        struct UserInfo ui;
        ui.user_id_ = cm.json_msg_[i]["userid"].asInt();
        if (ui.user_id_ == user_id_) {
            ui.nickname_ = "我";
        }else {
            ui.nickname_ = cm.json_msg_[i]["nickname"].asString();
        }
        ui.school_ = cm.json_msg_[i]["school"].asString();
        ui.msg_cnt_ = 0;
        fri_vec_.push_back(ui);
    }
    RefreshUserList();
    return TRUE; // return TRUE unless you set the focus to a control
    // 异常: OCX 属性⻚应返回 FALSE
}

创建接收推送消息的线程:

static void DealPushMsg(CChatWin* cc) {
    MsgQueue* mq = MsgQueue::getInstance();
    if (mq == nullptr) {
        return;
    }
    while (1) {
        //2.获取推送的消息
        std::string msg;
        mq->Pop(PushMsg, &msg);
        //3.解析消息
        ChatMsg cm;
        cm.PraseChatMsg(-1, msg);
        string peer_nick_name = cm.GetValue("peer_nick_name");
        string school = cm.GetValue("peer_school");
        int peer_user_id = atoi(cm.GetValue("peer_user_id").c_str());
        string peer_msg = cm.GetValue("msg");
        //4.将消息放到对应好友的消息池当中
        for (int i = 0; i < (int)cc->fri_vec_.size(); i++) {
            if (cc->fri_vec_[i].user_id_ == peer_user_id) {
                string tmp = peer_nick_name + "-" + school + ": "
                cc->fri_vec_[i].history_msg_.push_back(tmp);
                if (peer_user_id == cc->send_friend_id_) {
                    cc->m_output.AddString(tmp.c_str());
                }
                else {
                    cc->fri_vec_[i].msg_cnt_++;
                }
            }
        }
        cc->RefreshUserList();
    }
}

创建处理推送添加好友的线程:

static void DealPushAddFriMsg(CChatWin* cc) {
    MsgQueue* mq = MsgQueue::getInstance();
    if (mq == nullptr) {
        return;
    }
    while (1) {
        std::string msg;
        mq->Pop(PushAddFriendMsg, &msg);
        //解析消息
        ChatMsg cm;
        cm.PraseChatMsg(-1, msg);
        string peer_name = cm.GetValue("peer_nick_name");
        string peer_school = cm.GetValue("peer_school_name");
        int peer_user_id = atoi(cm.GetValue("peer_user_id").c_str());
        string show_msg = peer_name + "-" + peer_school + ": want add you"
        cm.Clear();
        UINT i = MessageBox(cc->m_hWnd, _T(show_msg.c_str()), _T("提⽰"),
        if (i == IDYES){
            //添加到⽤⼾信息当中
            struct UserInfo ui;
            ui.user_id_ = peer_user_id;
            ui.nickname_ = peer_name;
            ui.school_ = peer_school;
            ui.msg_cnt_ = 0;
            cc->fri_vec_.push_back(ui);
            cm.msg_type_ = PushAddFriendMsgResp;
            cm.reply_status_ = ADDFRIEND_SUCCESS;
            cm.user_id_ = cc->user_id_;
            cm.SetKeyValue("userid", std::to_string(peer_user_id));
            cc->RefreshUserList();
        }else {
            cm.msg_type_ = PushAddFriendMsgResp;
            cm.reply_status_ = ADDFRIEND_FAILED;
            cm.user_id_ = cc->user_id_;
            cm.SetKeyValue("userid", std::to_string(peer_user_id));
        }
        msg.clear();
        cm.GetMsg(&msg);
        TcpSvr* ts = TcpSvr::getInstance();
        if (ts == nullptr) {
            continue;
        }
        ts->Send(msg);
    }
}

添加处理添加好友应答的线程:

static void DealAddFriendResp(CChatWin* cc) {
    MsgQueue* mq = MsgQueue::getInstance();
    if (mq == nullptr) {
        return;
    }
    while (1) {
        //2.获取推送的消息
        std::string msg;
        mq->Pop(AddFriend_resp, &msg);
        //3.解析消息
        ChatMsg cm;
        cm.PraseChatMsg(-1, msg);
        string content = cm.GetValue("content");
        MessageBox(cc->m_hWnd, _T(content.c_str()), _T("add friend resp")
        if (cm.reply_status_ == ADDFRIEND_FAILED) {
            return;
        }
        string peer_nick_name = cm.GetValue("peer_nick_name");
        string peer_school = cm.GetValue("peer_school");
        int peer_user_id = atoi(cm.GetValue("peer_user_id").c_str());
        //4.将消息放到对应好友的消息池当中
        struct UserInfo ui;
        ui.user_id_ = peer_user_id;
        ui.nickname_ = peer_nick_name;
        ui.school_ = peer_school;
        ui.msg_cnt_ = 0;
        cc->fri_vec_.push_back(ui);
        cc->RefreshUserList();
    }
}

刷新好友列表:

void CChatWin::RefreshUserList() {
    int Count = m_userlist.GetCount();
    for (int i = Count; i >= 0; i--) {
        m_userlist.DeleteString(i);
    }
    for (int i = 0; i < (int)fri_vec_.size(); i++) {
        string tmp = fri_vec_[i].nickname_;
        if (fri_vec_[i].msg_cnt_ > 0) {
            tmp += " : ";
            tmp += std::to_string(fri_vec_[i].msg_cnt_);
        }
        m_userlist.AddString(tmp.c_str());
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/734487.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MATLAB第54期】基于LSTM长短期记忆网络的多输入多输出滑动窗口回归预测模型

【MATLAB第54期】基于LSTM长短期记忆网络的多输入多输出滑动窗口回归预测模型 往期第13期已实现多输入单输出滑动窗口回归预测 本次在此代码基础上&#xff0c;新增多输出滑动窗口功能。 多输入单输出滑动窗口回归预测 一、实现效果 往期文章提到了对单列时间序列数据进行滑…

spdlog入门教程(1)

spdlog入门教程&#xff08;1&#xff09; 文章目录 spdlog入门教程&#xff08;1&#xff09;1、前言1.1 为什么要使用日志库1.2 尽量使用通用日志框架而不要自己实现1.3 spdlog概述1.4 地址 2、源码编译配置spdlog3、在Qt中使用spdlog1.1 验证spdlog是否配置成功1.2 将日志输…

全网最牛,Pytest自动化测试-pytest.mark.parametrize参数化实战(详细)

目录&#xff1a;导读 前言 一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 pytest允许在多个…

删除数据库记录错误

删除数据库记录错误&#xff1a;Unexpected update count received (Actual: 2, Expected: 1). All changes will be rolled back. 解决&#xff1a;同时删掉ID为8的记录就行了 分析&#xff1a;这种情况是未设置主键约束&#xff0c;插入了相同的记录导致的。推测应该是框架对…

Clip-Path

前言 借助clip-path,我们可以实现一些复杂的animation动画效果,我们先来简单概述一下它的特性,如MDN所描述的。 The clip-path CSS property creates a clipping region that sets what part of an element should be shown. Parts that are inside the region are shown, whi…

基于aarch64分析kernel源码 一:环境搭建

一、测试环境 功能工具操作系统ubuntu 22.04编译工具gcc-12-aarch64-linux-gnu调试工具gdb-multiarch模拟器qemu 6.2.0busyboxbusybox-1.36.1kernellinux-6.4.1编辑器vscode 二、编译器 1、查找ubuntu仓库中aarch64编译器 lqlq-virtual-machine:~/my$ apt-cache search aar…

零矩阵

暴力解法&#xff1a;先全部检索&#xff0c;定位0所在的位置&#xff0c; 记录到新的数组 数组的行列分别进行去重 数组中记录的行列赋值为零 如果直接修改&#xff0c;在行被修改之后&#xff0c;修改列时会因为行已经被修改产生影响 import org.junit.Test;import java.uti…

tensorboard命令行使用方法

第一步&#xff1a;进入虚拟环境 conda activate 虚拟环境名称 第二步&#xff1a; tensorboard --logdir绝对地址 第三步&#xff1a;在浏览器输入提供的网址

day03 重新学python——python函数

文章目录 一、python函数1.函数介绍2.函数的定义3.函数的参数4.函数的返回值5.函数的说明文档6.函数的嵌套调用7.变量的作用域8.综合案例 一、python函数 1.函数介绍 函数&#xff1a;即组织好的、课重复利用&#xff0c;用来实现特殊功能的代码段&#xff0c;这样可以提高代码…

生产者消费者

前言 生产者消费者模式属于一种经典的多线程协作的模式&#xff0c;弄清生产者消费者问题能够让我们对于多线程编程有更深刻的理解&#xff0c;下面&#xff0c;为大家分享一个生产者消费者的案例。 一、案例描述 这里以快递为例&#xff0c;假设有一个快递柜&#xff0c;用来…

蚂蚁链发布零知识证明技术架构 可满足数据“隐私保护”、“可验证”双要求

7 月 8 日&#xff0c;在 2023WAIC 全球区块链产业高峰论坛上&#xff0c;蚂蚁链宣布隐私协作平台 AntChain FAIR 进行全新架构升级&#xff0c;引入零知识证明&#xff08;ZKP&#xff09;为核心的可验证计算技术&#xff0c;从可信数据流转拓展到计算过程、数据属性以及身份的…

Vmware环境下的CentOS安装

CentOS7 下载安装 因为 centos 是安装在 VMware 上面的&#xff0c;所以需要提前安装 VMware centos 下载 网址&#xff1a;http://isoredirect.centos.org/centos/7/isos/x86_64/ 镜像源&#xff1a;http://centos.mirror.rafal.ca/7.9.2009/isos/x86_64/ 安装 centos 创建…

哪些软件分析工具需要使用到pdb符号文件?

目录 1、什么是pdb文件&#xff1f;pdb文件有哪些用途&#xff1f; 2、pdb文件的时间戳与pdb文件名称 3、常用软件分析工具有哪些&#xff1f; 4、使用Windbg调试器查看函数调用堆栈时需要加载pdb文件 4.1、给Windbg设置pdb文件路径 4.2、为什么要设置系统库pdb文件下载服…

深度剖析线上应用节点流量隔离技术

作者&#xff1a;谢文欣&#xff08;风敬&#xff09; 为什么要做流量隔离 源于一个 EDAS 客户遇到的棘手情况&#xff1a;他们线上的一个 Pod CPU 指标异常&#xff0c;为了进一步诊断问题&#xff0c;客户希望在不重建此 Pod 的情况下保留现场&#xff0c;但诊断期间流量还…

Element-UI 实现动态增加多个输入框并校验

文章目录 前言实现通过按钮动态增加表单并验证必填实现动态多个输入框为行内模式&#xff0c;其它为行外模式 前言 在做复杂的动态表单&#xff0c;实现业务动态变动&#xff0c;比如有一条需要动态添加的el-form-item中包含了多个输入框&#xff0c;并实现表单验证&#xff0…

非线性激活函数

目录 理论介绍 常见的激活函数 A. sigmoid函数 B. tanh C.ReLu Leaky Relu 函数 Parametric ReLU (PReLU) Exponential Linear Unit (ELU) 实验结果及分析 理论介绍 在神经网络的计算中&#xff0c;无非就是矩阵相乘&#xff0c;输入的是线性&#xff0c;不论输出层有…

如何修复ssh漏洞进行版本升级

目录 一、ssh低版本漏洞信息 OpenSSH GSSAPI 处理远端代码执行漏洞 OpenSSH GSSAPI认证终止信息泄露漏洞 OpenSSH X连接会话劫持漏洞 二、升级ssh版本进行修复漏洞 第一步 安装Telnet服务 第二步 重启服务 第三步 安装依赖环境 第四步 备份ssh老版本文件 第五步 导入…

【JavaEE进阶】Spring 创建与使用

Spring 创建与使用 1&#xff0c;Spring项目的创建 使用Maven方式来创建一个Spring项目&#xff0c;创建Spring项目和Servlet类似&#xff0c;总共分为以下3步&#xff1a; 创建一个普通Maven项目添加 Spring 框架⽀持&#xff08;spring-context、spring-beans&#xff09;添…

UE特效案例 —— 魔法翅膀

一&#xff0c;环境配置 创建默认地形Landscape&#xff0c;如给地形上材质需确定比例&#xff1b;添加环境主光源DirectionalLight&#xff0c;设置相应的强度和颜色&#xff1b;PostProcessVolume设置曝光&#xff0c;设置Min/Max Brightness为1&#xff1b; 与关闭Game Sett…