# chat-train **Repository Path**: cwhlol/chat-train ## Basic Information - **Project Name**: chat-train - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2020-11-26 - **Last Updated**: 2021-08-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # chat-train 主要业务: * [心跳业务](#心跳业务) * [消息转发](#消息转发) * [注册业务](#登录业务) * [登录业务](#登录业务) 工具: * [数据库工具](#数据库工具) * [socket工具](#socket工具) * [createmymsg](#createmymsg) 线程: * [客户端消息线程](#客户端消息线程) * [服务端消息线程](#服务端消息线程) * [服务端主线程](#服务端主线程) 业务实现: * [Client](#Client) * [Server](#Server) ## 数据库工具 增,删,改,查,创建表; ```c++ //基类 //包括了一个sqlite3实例,对应一个数据库,拥有最简单的创建数据库,创建表格,增删改查。 class Dbhandler{ protected: char dbpath[100] = ""; sqlite3 *sql = nullptr; //向表格插入一个数据,需要自己书写sql语句; int InsertTableItem(const char *sql_lan); //在数据库中创建表格; void CreateTable(const char *sql_lan); public: //调用opendb之前调用; bool SetDbPath(const char *str); bool OpenDb(); void CloseDb(); void DeleteTableItem(string table, string delete_item); void UpdateTableItem(string table, string set_item, string where); //查询语句,主要通过回调函数进行数据的处理并想要的数据传回给子类; int SelectTableItem(string table, string item, string condition, int (*callback)(void* data,int,char**, char**) , void* data); }; //与聊天工具的耦合度高 class ChatDbHandler : public DbHandler{ //操作USER表 int InsertUserItem(string name, string password); int GetPwInUserByName(string &a, string item, string condition, int (*callback)(void*,int,char**, char**)); void CreateUSER(); int GetAllUserName(vector &user_name); }; ``` ## socket工具 封装了winsock2.h的方法,封装只是为了自己调用方便; 初始化,服务器绑定监听,发送消息,接收消息,线程管理,摧毁; 服务器接受客户端连入放在了程序中,原因是需要有消息监听机制,accept之后需要监听有没有消息到达消息; ```c++ void SockDestory();//程序结束时调用,释放内存 int SockSendMsg(char *msg, SOCKET sk); int SockRecvMsg(char *msg,SOCKET sk); void SockClose(SOCKET sk);//关闭套接字; bool SockInit();//初始化sock,首次创建socket对象之前调用; //服务器和客户端连接建立的差别 //client bool SockCreatConnection(int server_port, char* server_ipaddr, SOCKET &sk, sockaddr_in &sin); //server,bind listen都放在这个函数中了 bool SockBindConnection(int client_port, SOCKET &sk, sockaddr_in &sin); //accept需要自己调用,忘记做成接口了TnT ``` ## createmymsg 使用结构体作为传输报文 ```c++ enum MsgType {REQ_LOGIN, RSP_LOGIN, REQ_REGIST, RSP_REGIST, MESSAGE, REQ_HEARTB, RSP_HEARTB}; const int UserNameSize = 30; const int ContentSize = 200; struct TransMsg{ MsgType msg_type; char msg_from[UserNameSize]; char msg_to[UserNameSize]; char msg_content[ContentSize]; }; void CreateReqLoginMsg(TransMsg &trans_msg, const char *from, const char* password); //正文内容有三种:成功,失败密码错误,失败用户已登录; void CreateRspLoginMsg(TransMsg &trans_msg, const char * t, const char * content); void CreateReqRegistMsg(TransMsg &trans_msg, const char * from, const char * password); //正文内容有两种:成功,失败用户已存在; void CreateRspRegistMsg(TransMsg &trans_msg, const char * t, const char * content); //正文内容为发送的内容; void CreateMessageMsg(TransMsg &trans_msg, const char * from, const char * t, const char * content); void CreateReqHeartbMsg(TransMsg &trans_msg, const char * from); void CreateRspHeartbMsg(TransMsg &trans_msg, char * to, char * content); void ShowTranslateMessage(const TransMsg& msg); //登录以及注册正文的内容,固定; { "RspLogin1":"success", "RspLogin2":"fail,password", "RspLogin3":"fail,nonexist", "RspRegist1":"success", "RspRegist2":"fail" } ``` ## 客户端消息线程 继承自QThread,在accpet之后对于监听是否有消息到达而创建一个子线程。 定义消息的类型,循环监听消息接收,处理收到的消息, ```c++ class MsgThread : public QThread{ Q_OBJECT public : explicit MsgThread(SOCKET receive); //构造函数 void run(); //线程循环函数,主要功能是接收服务器消息,同时发送消息对应类型的信号,信号连接界面槽函数 string GetName(); signals : //各种消息类型所对应的信号 void LoginRSP(string content); void RegistRSP(string content); void MsgComing(string from, string content); void HeartBRSP(string content); public slots : private: SOCKET receive_sock; string my_user_name; //当前线程所对应客户端的名字; void DealMsg(TransMsg trans_msg); //run函数主要循环的内容; }; ``` ### 服务端消息线程 继承自QThread,在accpet之后对于监听是否有消息到达而创建一个子线程。 ```c++ class MsgThread : public QThread{ Q_OBJECT public : MsgThread(SOCKET receive, SOCKET send_s, ChatDbHandler *db); void run(); //循环函数,接收客户端消息 void DealMsg(TransMsg trans_msg); //run()循环的主体,处理消息内容,根据类型进行处理函数 signals : void MessageComing(const TransMsg &msg); void HeartBComing(const TransMsg &msg); public slots : void IsMyMsg(const TransMsg &msg); //查看TransMsg对象是否为我这个线程客户端,服务器转发消息给另外一个客户端 void SendRspHeartB(string content); private: SOCKET receive_sock; SOCKET send_sock; ChatDbHandler *chat_db; string socket_user_name; //DealMsg不同消息类型所使用的处理函数 void DealReqLogin(const TransMsg &msg); void DealReqRegist(const TransMsg &msg); void DealMessage(TransMsg &msg); void DealReqHeartB(const TransMsg &msg); }; ``` ### 服务端主线程 ```c++ //记录服务器保存的用户最后一次发送心跳的时间 struct TransStruct{ string name; long time; }; class ListenThread : public QThread{ Q_OBJECT public: ListenThread(ChatDbHandler *db); void run(); //循环监听是否有新的客户端加入 ChatDbHandler *chatdb; // void ModifyUserTime() signals: void SendUserAliveList(string content); private slots: void freshTransStruct(const TransMsg &trans_msg); //客户端发送心跳包之后,刷新用户的时间; void DealUserState(); //将服务器存储的时间与当前时间比较超过某个值就设置用户离线,最后发送所有用户在线 //消息; private : HeartBTimer hb_timer; vector total_user; }; ``` --- ![基本类在客户端和服务端的调用](.\chattrainmain.png) ------ ## Client 主要内容:处理Login,Regist,SendMsg,dealMsg,HeartB五大功能. 添加请求用户列表 ### Login * 检查用户名以及密码是否有空 * 点击登录按钮发送登录消息给服务器 * 接受登录成功信号,跳转chat界面 * 接受登录失败信号密码错误, 弹窗 * 接受登录失败信号用户不存在,弹窗 * 接受登录失败信号已登录,弹窗 ### Regist * 点击注册按钮发送注册消息给服务器 * 接受注册成功信号,弹窗 * 接受注册失败信号,用户已存在 ### SendMsg * 获取要发送的用户名以及发送内容,设置成对应的报文格式,发送给服务器 * (进阶:有空做)发送图片; ### DealMsg * 接收收到的消息 * 可能一次性接收到多条消息,将接收到的内容分成多条单一的消息 * 判断消息类型,发送信号 * RSP_LOGIN 将接收到的登录消息用信号发出,之后交由LOGIN处理 * RSP_REGIST 将接收到的注册消息用信号发出,之后交由REGIST处理 * MESSAGE 将接收到的信息消息用信号发出,之后交由SendMsg处理 * RSP_HEARTB 处理心跳返回消息,服务器返回心跳返回消息,通过处理该消息获得所有用户的在线情况,refresh界面 ### HeartB * 定时器,定时发送一个ReqHeartB给服务器 * 发送内容为当前发送的时间 --- ![客户端类调用图](.\chattrainclient.png) --- ## Server 主要内容:接收req_login,发回rsp_login; 接收req_regist,发回rsp_regist; 接收message,转发message; 接收req_heartb,发回rsp_heartb; 发送用户列表 ### 接收req_login,发回rsp_login * 处理req_login消息,提取出用户名以及密码 * 在数据库中查找对应的用户名以及密码 * 如果存在,发送rsp_login,登录成功的报文,正文内容Success * 如果存在用户名,但是密码错误,发送rsp_login,登录失败密码错误的报文 * 如果用户不存在,发送rsp_login,登录失败用户不存在 * 如果用户已登录(可能要删掉,这样可以多地登录),发送rsp_login,登录失败用户已登录 ### 接收req_regist,发回rsp_regist * 处理req_login消息,提取出用户名以及密码 * 在数据库中查找用户是否存在 * 存在,发回rsp_regist,注册失败,用户已存在 * ?修改密码?存在,密码不对 * 不存在,创建数据库用户,发回rsp_regist,注册成功 ### 接收message,转发message * 处理message消息,提取出接收端 * 查找TransStruct,找出name为接收端的对象 * 找到transStruct,之后通过sock1转发该消息 ### 接收req_heartb,并定期发回rsp_heartb * 处理req_heartb消息,提取出发送方 * 查找clients容器中name为发送方的transstruct * 将transstruct中的time变量设置为当前时间 * 发回rsp_heartb ### 接收用户列表请求,发送用户列表 * 接收用户列表请求, 提取出发送方 * 查找clients容器中name为发送方的transstruct,命名为mts,同时获取当前clients的transstruct的name * 将clients中所有名字作为报文正文,发送给mts.sock1 --- ![](.\chattrainserver.png) --- ### 流程图 #### 心跳业务 ![心跳](./心跳业务.png) #### 消息转发 ![](.\消息转发.png) #### 登录业务 ![](.\登录业务.png) ### 注册业务与登录业务类似