• 基于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* f_id)
    • d.用户注册时向数据库插入用户的信息:bool InsertUser(int userid, const std::string& nickname
    • e.添加好友:bool InsertFriend(int userid1, int userid2)
    1. class DataBaseSvr{
    2. public:
    3. DataBaseSvr(){
    4. mysql_ = NULL;
    5. }
    6. ~DataBaseSvr(){
    7. if(mysql_ != NULL){
    8. mysql_close(mysql_);
    9. }
    10. }
    11. /*
    12. * 初始化mysql操作句柄, 并且连接后台mysql服务端, 设置字符集
    13. * */
    14. bool MysqlInit(){
    15. mysql_ = mysql_init(NULL);
    16. if(mysql_ == NULL){
    17. std::cout << "mysql init failed" << std::endl;
    18. return false;
    19. }
    20. if(mysql_real_connect(mysql_, HOST, USER, PASSWD, DB, DBPORT,NULL, 0) == NULL){
    21. std::cout << "msyql connect failed" << std::endl;
    22. mysql_close(mysql_);
    23. return false;
    24. }
    25. mysql_set_character_set(mysql_, "utf8");
    26. return true;
    27. }
    28. /*
    29. * 获取 all user info, to usermanager model
    30. * 参数为Json对象, 是一个出参
    31. * */
    32. bool GetAllUser(Json::Value* all_user){
    33. #define GETALLUSER "select * from user;"
    34. lock_.lock();
    35. //1.数据库查询
    36. if(MysqlQuery(GETALLUSER) == false){
    37. lock_.unlock();
    38. return false;
    39. }
    40. //2.获取结果集
    41. MYSQL_RES* res = mysql_store_result(mysql_);
    42. if(res == NULL){
    43. lock_.unlock();
    44. return false;
    45. }
    46. lock_.unlock();
    47. //3.获取单行数据
    48. int row_nums = mysql_num_rows(res);
    49. for(int i = 0; i < row_nums; i++){
    50. MYSQL_ROW row = mysql_fetch_row(res);
    51. //4.将单行数据按照格式, 组织起来。 传递给调用者
    52. Json::Value tmp;
    53. tmp["userid"] = atoi(row[0]);
    54. tmp["nickname"] = row[1];
    55. tmp["school"] = row[2];
    56. tmp["telnum"] = row[3];
    57. tmp["passwd"] = row[4];
    58. all_user->append(tmp);
    59. }
    60. mysql_free_result(res);
    61. return true;
    62. }
    63. /*
    64. * 获取单个用户的好友信息, 在程序初始化阶段, 让用户管理模块维护起来
    65. * userid : 用户的id
    66. * f_id : 该用户的所有好友id
    67. * */
    68. bool GetFriend(int userid, std::vector<int>* f_id){
    69. #define GETFRIEND "select friend from friendinfo where userid='%d';"
    70. //1.格式化sql语句
    71. char sql[1204] = {0};
    72. sprintf(sql, GETFRIEND, userid);
    73. lock_.lock();
    74. //2.查询
    75. if(MysqlQuery(sql) == false){
    76. lock_.unlock();
    77. return false;
    78. }
    79. //3.获取结果集
    80. MYSQL_RES* res = mysql_store_result(mysql_);
    81. if(res == NULL){
    82. lock_.unlock();
    83. return false;
    84. }
    85. lock_.unlock();
    86. //4.获取单行数据
    87. int row_nums = mysql_num_rows(res);
    88. for(int i = 0; i < row_nums; i++){
    89. MYSQL_ROW row = mysql_fetch_row(res);
    90. f_id->push_back(atoi(row[0]));
    91. }
    92. mysql_free_result(res);
    93. return true;
    94. }
    95. /*
    96. * 当用户注册的时候, 进行插入使用的函数
    97. * */
    98. bool InsertUser(int userid, const std::string& nickname
    99. , const std::string& school, const std::string& telnum
    100. , const std::string& passwd){
    101. #define INSERTUSER "insert into user(userid, nickname, school, telnum, passwd) values('%d', '%s', '%s', '%s', '%s');"
    102. char sql[1024] = {0};
    103. sprintf(sql, INSERTUSER, userid, nickname.c_str(), school.c_str(), telnum.c_str(), passwd.c_str());
    104. std::cout << "Insert User: " << sql << std::endl;
    105. //2.查询
    106. if(MysqlQuery(sql) == false){
    107. return false;
    108. }
    109. return true;
    110. }
    111. /*
    112. * 添加好友
    113. * */
    114. bool InsertFriend(int userid1, int userid2){
    115. #define INSERTFRIEND "insert into friendinfo values('%d', '%d');"
    116. char sql[1024] = {0};
    117. sprintf(sql, INSERTFRIEND, userid1, userid2);
    118. //2.查询
    119. if(MysqlQuery(sql) == false){
    120. return false;
    121. }
    122. return true;
    123. }
    124. private:
    125. bool MysqlQuery(const std::string& sql){
    126. if(mysql_query(mysql_, sql.c_str()) != 0){
    127. std::cout << "exec failed sql: " << sql << std::endl;
    128. return false;
    129. }
    130. return true;
    131. }
    132. private:
    133. MYSQL* mysql_;
    134. std::mutex lock_;
    135. };

    1.3用户管理模块设计

    用户信息类

    • 1.用户注册时的相关信息,nickname_,school_,telnum_,passwd_,userid_
    • 2.用户状态:user_status_
    • 3.登录的客户端对应的套接字描述符
    1. enum UserStatus{
    2. OFFLINE, //0
    3. ONLINE //1
    4. };
    5. /*
    6. * 用户信息类
    7. * */
    8. class UserInfo{
    9. public:
    10. //注册的时候,
    11. UserInfo(const std::string& nickname, const std::string& school, const std::string& telnum, const std::string& passwd, int userid){
    12. nickname_ = nickname;
    13. school_ = school;
    14. telnum_ = telnum;
    15. passwd_ = passwd;
    16. userid_ = userid;
    17. user_status_ = OFFLINE;
    18. tcp_socket_ = -1;
    19. friend_id_.clear();
    20. }
    21. UserInfo(){
    22. }
    23. ~UserInfo(){
    24. }
    25. public:
    26. std::string nickname_;
    27. std::string school_;
    28. std::string telnum_;
    29. std::string passwd_;
    30. int userid_;
    31. //用户状态 //OFFLINE ONLINE
    32. int user_status_;
    33. //登录的客户端对应的套接字描述符
    34. int tcp_socket_;
    35. std::vector<int> friend_id_;
    36. };

    用户信息管理模块

    • 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* fri),通过出参带出
    • g.添加好友:void SetFriend(int userid1, int userid2)
    • h.转变用户状态为下线: void SetUserOffLine(int sockfd),客户端下线时使用
    1. class UserManager{
    2. public:
    3. UserManager(){
    4. user_map_.clear();
    5. pthread_mutex_init(&map_lock_, NULL);
    6. //如果一开始就从0进行分配, 一定是不对的
    7. // 因为用户管理类还会从数据库当中将已经存在的用户信息读回来
    8. prepare_id_ = -1;
    9. db_ = NULL;
    10. }
    11. ~UserManager(){
    12. pthread_mutex_destroy(&map_lock_);
    13. if(db_){
    14. delete db_;
    15. db_ = NULL;
    16. }
    17. }
    18. bool InitUserMana(){
    19. //1.连接数据库
    20. db_ = new DataBaseSvr();
    21. if(db_ == NULL){
    22. printf("create db case failed\n");
    23. return false;
    24. }
    25. if(db_->MysqlInit() == false){
    26. return false;
    27. }
    28. //2.查询所有用户信息, 维护起来
    29. Json::Value all_user;
    30. if(db_->GetAllUser(&all_user) == false){
    31. return false;
    32. }
    33. for(int i = 0; i < (int)all_user.size(); i++){
    34. //个人信息
    35. UserInfo ui;
    36. ui.nickname_ = all_user[i]["nickname"].asString();
    37. ui.school_ = all_user[i]["school"].asString();
    38. ui.telnum_ = all_user[i]["telnum"].asString();
    39. ui.passwd_ = all_user[i]["passwd"].asString();
    40. ui.userid_ = all_user[i]["userid"].asInt();
    41. ui.user_status_ = OFFLINE;
    42. //个人好友信息
    43. db_->GetFriend(ui.userid_, &ui.friend_id_);
    44. pthread_mutex_lock(&map_lock_);
    45. user_map_[ui.userid_] = ui;
    46. if(ui.userid_ > prepare_id_){
    47. prepare_id_ = ui.userid_ + 1;
    48. }
    49. pthread_mutex_unlock(&map_lock_);
    50. }
    51. return true;
    52. }
    53. /*
    54. * 处理用户注册
    55. * userid : 如果注册成功, 通过userid,告诉注册的客户端,他的id是什么
    56. * */
    57. int DealRegister(const std::string& nickname, const std::string& school, const std::string& tel, const std::string& passwd, int* userid){
    58. //1.判断注册信息是否为空
    59. if(nickname.size() == 0 || school.size() == 0 || tel.size() == 0 || passwd.size() == 0){
    60. *userid = -2;
    61. return -1;
    62. }
    63. //2.判断用户是否已经注册过了
    64. pthread_mutex_lock(&map_lock_);
    65. auto iter = user_map_.begin();
    66. while(iter != user_map_.end()){
    67. if(iter->second.telnum_ == tel){
    68. *userid = -2;
    69. pthread_mutex_unlock(&map_lock_);
    70. return -1;
    71. }
    72. iter++;
    73. }
    74. //3.创建UserInfo, 分配userid, 保存用户信息
    75. UserInfo ui(nickname, school, tel, passwd, prepare_id_);
    76. *userid = prepare_id_;
    77. user_map_[prepare_id_] = ui;
    78. prepare_id_++;
    79. pthread_mutex_unlock(&map_lock_);
    80. //4.插入到数据库当中
    81. db_->InsertUser(ui.userid_, nickname, school, tel, passwd);
    82. return 0;
    83. }
    84. /*
    85. * 处理登录请求
    86. * sockfd 是 服务端为登录客户端创建的新连接套接字
    87. * */
    88. int DealLogin(const std::string& tel, const std::string& passwd, int sockfd){
    89. //1.判断字段是否为空
    90. if(tel.size() == 0 || passwd.size() == 0){
    91. return -1;
    92. }
    93. //2.判断用户是否合法
    94. pthread_mutex_lock(&map_lock_);
    95. auto iter = user_map_.begin();
    96. while(iter != user_map_.end()){
    97. if(iter->second.telnum_ == tel){
    98. break;
    99. }
    100. iter++;
    101. }
    102. if(iter == user_map_.end()){
    103. pthread_mutex_unlock(&map_lock_);
    104. return -1;
    105. }
    106. //3.校验密码是否正确
    107. if(iter->second.passwd_ != passwd){
    108. pthread_mutex_unlock(&map_lock_);
    109. return -1;
    110. }
    111. //4.更改用户的状态信息为ONLINE
    112. iter->second.user_status_ = ONLINE;
    113. int userid = iter->second.userid_;
    114. iter->second.tcp_socket_ = sockfd;
    115. pthread_mutex_unlock(&map_lock_);
    116. return userid;
    117. }
    118. int IsLogin(int userid){
    119. pthread_mutex_lock(&map_lock_);
    120. auto iter = user_map_.find(userid);
    121. if(iter == user_map_.end()){
    122. //这个用户都不存在
    123. pthread_mutex_unlock(&map_lock_);
    124. return -1;
    125. }
    126. if(iter->second.user_status_ == OFFLINE){
    127. pthread_mutex_unlock(&map_lock_);
    128. return OFFLINE;
    129. }
    130. pthread_mutex_unlock(&map_lock_);
    131. return ONLINE;
    132. }
    133. int IsLogin(const std::string& telnum, UserInfo* ui){
    134. pthread_mutex_lock(&map_lock_);
    135. auto iter = user_map_.begin();
    136. while(iter != user_map_.end()){
    137. if(iter->second.telnum_ == telnum){
    138. break;
    139. }
    140. iter++;
    141. }
    142. if(iter == user_map_.end()){
    143. pthread_mutex_unlock(&map_lock_);
    144. return -1;
    145. }
    146. *ui = iter->second;
    147. if(iter->second.user_status_ == OFFLINE){
    148. pthread_mutex_unlock(&map_lock_);
    149. return OFFLINE;
    150. }
    151. pthread_mutex_unlock(&map_lock_);
    152. return ONLINE;
    153. }
    154. bool GetUserInfo(int userid, UserInfo* ui){
    155. pthread_mutex_lock(&map_lock_);
    156. auto iter = user_map_.find(userid);
    157. if(iter == user_map_.end()){
    158. //这个用户都不存在
    159. pthread_mutex_unlock(&map_lock_);
    160. return false;
    161. }
    162. *ui = iter->second;
    163. pthread_mutex_unlock(&map_lock_);
    164. return true;
    165. }
    166. bool GetFriends(int userid, std::vector<int>* fri){
    167. pthread_mutex_lock(&map_lock_);
    168. auto iter = user_map_.find(userid);
    169. if(iter == user_map_.end()){
    170. //这个用户都不存在
    171. pthread_mutex_unlock(&map_lock_);
    172. return false;
    173. }
    174. *fri = iter->second.friend_id_;
    175. pthread_mutex_unlock(&map_lock_);
    176. return true;
    177. }
    178. void SetFriend(int userid1, int userid2){
    179. //1.找userid1, 将userid2放到userid1的好友列表当中
    180. pthread_mutex_lock(&map_lock_);
    181. auto iter = user_map_.find(userid1);
    182. if(iter == user_map_.end()){
    183. //这个用户都不存在
    184. pthread_mutex_unlock(&map_lock_);
    185. return;
    186. }
    187. iter->second.friend_id_.push_back(userid2);
    188. //2.找userid2, 将userid1放到userid2的好友列表当中
    189. iter = user_map_.find(userid2);
    190. if(iter == user_map_.end()){
    191. //这个用户都不存在
    192. pthread_mutex_unlock(&map_lock_);
    193. return;
    194. }
    195. iter->second.friend_id_.push_back(userid1);
    196. pthread_mutex_unlock(&map_lock_);
    197. //3.插入到数据库当中
    198. db_->InsertFriend(userid1, userid2);
    199. db_->InsertFriend(userid2, userid1);
    200. }
    201. void SetUserOffLine(int sockfd){
    202. pthread_mutex_lock(&map_lock_);
    203. auto iter = user_map_.begin();
    204. while(iter != user_map_.end()){
    205. if(iter->second.tcp_socket_ == sockfd){
    206. iter->second.user_status_ = OFFLINE;
    207. }
    208. iter++;
    209. }
    210. pthread_mutex_unlock(&map_lock_);
    211. }
    212. private:
    213. std::unordered_map<int, UserInfo> user_map_;
    214. pthread_mutex_t map_lock_;
    215. //针对注册用户分配的ID
    216. int prepare_id_;
    217. //数据库管理模块的实例化指针
    218. DataBaseSvr* db_;
    219. };

    1.4业务模块设计

    • a.初始化资源:int InitChatServer(uint16_t tcp_port = TCP_PORT, int thread_count=THREAD_COUNT),tcpSOCK初始化,客户端进行监听
    • b.启动各类线程函数:StartChatServer(),epoll等待线程,接收线程,发送线程,工作线程的创建
    • epoll等待线程:主线程循环的接收, 将接收回来的数据放到接收线程的队列当中, 等到工作线程从队列当中获取消息, 进而进行处理
    • 发送线程:从消息队列中拿消息进行发送
    • 工作线程:从接收队列中拿消息,并根据消息的类型进行处理(注册,登录,添加好友,添加好友应答,获取好友信息)
    1. struct Msg{
    2. Msg(){
    3. sockfd_ = -1;
    4. memset(buf, '\0', 1024);
    5. }
    6. int sockfd_;
    7. char buf[1024];
    8. };
    9. class ChatServer{
    10. public:
    11. ChatServer(){
    12. tcp_sock_ = -1;
    13. tcp_port_ = TCP_PORT;
    14. user_mana_ = NULL;
    15. epoll_fd_ = -1;
    16. thread_count_ = THREAD_COUNT;
    17. send_que_ = NULL;
    18. ready_sockfd_que_ = NULL;
    19. recv_que_ = NULL;
    20. }
    21. ~ChatServer(){
    22. }
    23. //初始化资源的函数
    24. int InitChatServer(uint16_t tcp_port = TCP_PORT, int thread_count=THREAD_COUNT){
    25. tcp_port_ = tcp_port;
    26. thread_count_ = thread_count;
    27. //tcp初始化
    28. tcp_sock_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    29. if(tcp_sock_ < 0){
    30. perror("socket");
    31. return -1;
    32. }
    33. //端口重用
    34. int opt = 1;
    35. setsockopt(tcp_sock_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    36. struct sockaddr_in addr;
    37. addr.sin_family = AF_INET;
    38. addr.sin_port = htons(tcp_port_);
    39. addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    40. int ret = bind(tcp_sock_, (struct sockaddr*)&addr, sizeof(addr));
    41. if(ret < 0){
    42. perror("bind");
    43. return -1;
    44. }
    45. ret = listen(tcp_sock_, 1024);
    46. if(ret < 0){
    47. perror("listen");
    48. return -1;
    49. }
    50. //epoll 初始化
    51. epoll_fd_ = epoll_create(5);
    52. if(epoll_fd_ < 0){
    53. return -1;
    54. }
    55. //用户管理模块
    56. user_mana_ = new UserManager();
    57. if(user_mana_ == NULL){
    58. return -1;
    59. }
    60. if(user_mana_->InitUserMana() == false){
    61. return -1;
    62. }
    63. recv_que_ = new MsgQueue();
    64. if(recv_que_ == NULL){
    65. return -1;
    66. }
    67. send_que_ = new MsgQueue();
    68. if(send_que_ == NULL){
    69. return -1;
    70. }
    71. ready_sockfd_que_ = new MsgQueue<int>();
    72. if(ready_sockfd_que_ == NULL){
    73. return -1;
    74. }
    75. return 0;
    76. }
    77. //启动各类线程的函数 - 主线程调用的
    78. int StartChatServer(){
    79. //1.创建epoll等待线程
    80. pthread_t tid;
    81. int ret = pthread_create(&tid, NULL, epoll_wait_start, (void*)this);
    82. if(ret < 0){
    83. perror("pthread_create");
    84. return -1;
    85. }
    86. //2.创建接收线程
    87. ret = pthread_create(&tid, NULL, recv_msg_start, (void*)this);
    88. if(ret < 0){
    89. perror("pthread_create");
    90. return -1;
    91. }
    92. //3.创建发送线程
    93. ret = pthread_create(&tid, NULL, send_msg_start, (void*)this);
    94. if(ret < 0){
    95. perror("pthread_create");
    96. return -1;
    97. }
    98. //4.创建工作线程
    99. for(int i = 0; i < thread_count_; i++){
    100. ret = pthread_create(&tid, NULL, deal_start, (void*)this);
    101. if(ret < 0){
    102. thread_count_--;
    103. }
    104. }
    105. if(thread_count_ <= 0){
    106. return -1;
    107. }
    108. //5.主线程循环接收新连接 & 将新连接的套接字放到epoll当中
    109. struct sockaddr_in cli_addr;
    110. socklen_t cli_addr_len = sizeof(cli_addr);
    111. while(1){
    112. int newsockfd = accept(tcp_sock_,(struct sockaddr*)&cli_addr, &cli_addr_len);
    113. if(newsockfd < 0){
    114. continue;
    115. }
    116. //接收上了, 添加到epoll当中进行监控
    117. struct epoll_event ee;
    118. ee.events = EPOLLIN;
    119. ee.data.fd = newsockfd;
    120. epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, newsockfd, &ee);
    121. }
    122. return 0;
    123. }
    124. static void* epoll_wait_start(void* arg){
    125. pthread_detach(pthread_self());
    126. ChatServer* cs = (ChatServer*)arg;
    127. while(1){
    128. struct epoll_event arr[10];
    129. int ret = epoll_wait(cs->epoll_fd_, arr, sizeof(arr)/sizeof(arr[0]), -1);
    130. if(ret < 0){
    131. continue;
    132. }
    133. //正常获取了就绪的事件结构, 一定全部都是新连接套接字
    134. for(int i = 0; i < ret; i++){
    135. char buf[TCP_DATA_MAX_LEN] = {0};
    136. //隐藏的问题: TCP粘包
    137. ssize_t recv_size = recv(arr[i].data.fd, buf, sizeof(buf) - 1, 0);
    138. if(recv_size < 0){
    139. //接收失败了
    140. std::cout << "recv failed : sockfd is " << arr[i].data.fd << std::endl;
    141. continue;
    142. }else if(recv_size == 0){
    143. //对端关闭连接了
    144. epoll_ctl(cs->epoll_fd_, EPOLL_CTL_DEL,arr[i].data.fd, NULL);
    145. close(arr[i].data.fd);
    146. //组织一个更改用户状态的消息 , 鸡贼做法(客户端退出的时候, 发送下线通知)
    147. cs->user_mana_->SetUserOffLine(arr[i].data.fd);
    148. continue;
    149. }
    150. printf("epoll_wait_start recv msg : %s from sockfd is %d\n", buf, arr[i].data.fd);
    151. //正常接收回来了, 将接收回来的数据放到接收线程的队列当中, 等到工作线程从队列当中获取消息, 进而进行处理
    152. //3.将接收到的数据放到接收队列当当中
    153. std::string msg;
    154. msg.assign(buf, strlen(buf));
    155. ChatMsg cm;
    156. cm.PraseChatMsg(arr[i].data.fd, msg);
    157. cs->recv_que_->Push(cm);
    158. }
    159. }
    160. return NULL;
    161. }
    162. static void* recv_msg_start(void* arg){
    163. pthread_detach(pthread_self());
    164. ChatServer* cs = (ChatServer*)arg;
    165. while(1){
    166. }
    167. return NULL;
    168. }
    169. static void* send_msg_start(void* arg){
    170. pthread_detach(pthread_self());
    171. ChatServer* cs = (ChatServer*)arg;
    172. while(1){
    173. //1.从队列拿出数据
    174. ChatMsg cm;
    175. cs->send_que_->Pop(&cm);
    176. std::string msg;
    177. cm.GetMsg(&msg);
    178. std::cout << "send thread: " << msg << std::endl;
    179. //2.发送数据
    180. send(cm.sockfd_, msg.c_str(), msg.size(), 0);
    181. }
    182. return NULL;
    183. }
    184. static void* deal_start(void* arg){
    185. pthread_detach(pthread_self());
    186. ChatServer* cs = (ChatServer*)arg;
    187. while(1){
    188. //1. 从接收队列当中获取消息
    189. ChatMsg cm;
    190. cs->recv_que_->Pop(&cm);
    191. //2. 通过消息类型分业务处理
    192. int msg_type = cm.msg_type_;
    193. switch(msg_type){
    194. case Register:{
    195. cs->DealRegister(cm);
    196. break;
    197. }
    198. case Login:{
    199. cs->DealLogin(cm);
    200. break;
    201. }
    202. case AddFriend:{
    203. cs->DealAddFriend(cm);
    204. break;
    205. }
    206. case PushAddFriendMsg_Resp:{
    207. cs->DealAddFriendResp(cm);
    208. break;
    209. }
    210. case SendMsg: {
    211. cs->DealSendMsg(cm);
    212. break;
    213. }
    214. case GetFriendMsg:{
    215. cs->GetAllFriendInfo(cm);
    216. break;
    217. }
    218. default:{
    219. break;
    220. }
    221. }
    222. //3. 组织应答
    223. }
    224. return NULL;
    225. }
    226. void DealRegister(ChatMsg& cm){
    227. //1.获取注册信息
    228. std::string nickname = cm.GetValue("nickname");
    229. std::string school = cm.GetValue("school");
    230. std::string telnum = cm.GetValue("telnum");
    231. std::string passwd = cm.GetValue("passwd");
    232. //2.调用用户管理系统当中的注册接口
    233. int userid = -1;
    234. int ret = user_mana_->DealRegister(nickname, school, telnum, passwd, &userid);
    235. //3.回复应答
    236. cm.Clear();
    237. cm.msg_type_ = Register_Resp;
    238. if(ret < 0){
    239. cm.reply_status_ = REGISTER_FAILED;
    240. }else{
    241. cm.reply_status_ = REGISTER_SUCCESS;
    242. }
    243. cm.user_id_ = userid;
    244. send_que_->Push(cm);
    245. }
    246. void DealLogin(ChatMsg& cm){
    247. //1.获取数据
    248. std::string telnum = cm.GetValue("telnum");
    249. std::string passwd = cm.GetValue("passwd");
    250. //2.调用用户管理模块的代码
    251. int ret = user_mana_->DealLogin(telnum, passwd, cm.sockfd_);
    252. //3.回复应答
    253. cm.Clear();
    254. cm.msg_type_ = Login_Resp;
    255. if(ret < 0){
    256. cm.reply_status_ = LOGIN_FAILED;
    257. }else{
    258. cm.reply_status_ = LOGIN_SUCESSS;
    259. }
    260. cm.user_id_ = ret;
    261. send_que_->Push(cm);
    262. }
    263. void DealAddFriend(ChatMsg& cm){
    264. //1.获取被添加方的电话号码
    265. std::string tel = cm.GetValue("telnum");
    266. //添加方的userid
    267. int add_userid = cm.user_id_;
    268. cm.Clear();
    269. //2.查询被添加方是否是登录状态
    270. UserInfo be_add_ui;
    271. int ret = user_mana_->IsLogin(tel, &be_add_ui);
    272. if(ret == -1){
    273. //用户不存在
    274. cm.json_msg_ = AddFriend_Resp;
    275. cm.reply_status_ = ADDFRIEND_FAILED;
    276. cm.SetValue("content", "user not exist, please check friend tel num.");
    277. send_que_->Push(cm);
    278. return;
    279. }else if(ret == OFFLINE){
    280. std::cout << be_add_ui.nickname_ + " status is OFFLINE" << std::endl;
    281. //将消息先缓存下来, 择机发送
    282. return;
    283. }
    284. //ONLINE状态的
    285. //3.给被添加方推送添加好友请求
    286. UserInfo add_ui;
    287. user_mana_->GetUserInfo(add_userid, &add_ui);
    288. cm.sockfd_ = be_add_ui.tcp_socket_;
    289. cm.msg_type_ = PushAddFriendMsg;
    290. cm.SetValue("adder_nickname", add_ui.nickname_);
    291. cm.SetValue("adder_school", add_ui.school_);
    292. cm.SetValue("adder_userid", add_ui.userid_);
    293. send_que_->Push(cm);
    294. }
    295. void DealAddFriendResp(ChatMsg& cm){
    296. //1.获取双方的用户信息
    297. int reply_status = cm.reply_status_;
    298. //获取被添加方的用户信息
    299. int be_add_user = cm.user_id_;
    300. UserInfo be_userinfo;
    301. user_mana_->GetUserInfo(be_add_user, &be_userinfo);
    302. //获取添加方的用户信息-通过应答, 获取添加方的UserId
    303. int addr_user_id = atoi(cm.GetValue("userid").c_str());
    304. UserInfo ui;
    305. user_mana_->GetUserInfo(addr_user_id ,&ui);
    306. //2.判断响应状态
    307. cm.Clear();
    308. cm.sockfd_ = ui.tcp_socket_;
    309. cm.msg_type_ = AddFriend_Resp;
    310. if(reply_status == ADDFRIEND_FAILED){
    311. cm.reply_status_ = ADDFRIEND_FAILED;
    312. std::string content = "add user " + be_userinfo.nickname_ + " failed";
    313. cm.SetValue("content", content);
    314. }else if(reply_status == ADDFRIEND_SUCCESS){
    315. cm.reply_status_ = ADDFRIEND_SUCCESS;
    316. std::string content = "add user " + be_userinfo.nickname_ + " success";
    317. cm.SetValue("content", content);
    318. cm.SetValue("peer_nick_name", be_userinfo.nickname_);
    319. cm.SetValue("peer_school", be_userinfo.school_);
    320. cm.SetValue("peer_userid", be_userinfo.userid_);
    321. //用户管理模块当中要维护好友信息
    322. user_mana_->SetFriend(addr_user_id, be_add_user);
    323. }
    324. //TODO
    325. if(ui.user_status_ == OFFLINE){
    326. //消息就放到缓存队列当中, 择机发送
    327. }
    328. //3.给添加方回复响应
    329. send_que_->Push(cm);
    330. }
    331. void GetAllFriendInfo(ChatMsg& cm){
    332. //1. 好友信息的数据从用户管理模块当中获取到
    333. int user_id = cm.user_id_;
    334. cm.Clear();
    335. std::vector<int> fri;
    336. bool ret = user_mana_->GetFriends(user_id, &fri);
    337. if(ret == false){
    338. cm.reply_status_ = GETFRIEND_FAILED;
    339. }else{
    340. cm.reply_status_ = GETFRIEND_SUCCESS;
    341. }
    342. cm.msg_type_ = GetFriendMsg_Resp;
    343. for(size_t i = 0; i < fri.size(); i++){
    344. UserInfo tmp;
    345. user_mana_->GetUserInfo(fri[i], &tmp);
    346. Json::Value val;
    347. val["nickname"] = tmp.nickname_;
    348. val["school"] = tmp.school_;
    349. val["userid"] = tmp.userid_;
    350. cm.json_msg_.append(val);
    351. }
    352. send_que_->Push(cm);
    353. }
    354. void DealSendMsg(ChatMsg& cm){
    355. int send_id = cm.user_id_;
    356. int recv_id = cm.json_msg_["recvmsgid"].asInt();
    357. std::string send_msg = cm.json_msg_["msg"].asString();
    358. cm.Clear();
    359. UserInfo recv_ui;
    360. bool ret = user_mana_->GetUserInfo(recv_id, &recv_ui);
    361. //区分用户不存在和不在线两种状态
    362. // 用户不存在 : 消息发送失败
    363. // 用户不在线: 发送方发送的消息缓存下来, 择机发送
    364. if(ret == false || recv_ui.user_status_ == OFFLINE){
    365. cm.msg_type_ = SendMsg_Resp;
    366. cm.reply_status_ = SENDMSG_FAILED;
    367. send_que_->Push(cm);
    368. return;
    369. }
    370. //代码能走到这里, 说明要给接收方推送消息了
    371. cm.Clear();
    372. cm.msg_type_ = SendMsg_Resp;
    373. cm.reply_status_ = SENDMSG_SUCCESS;
    374. send_que_->Push(cm);
    375. //获取发送方的用户信息
    376. UserInfo send_ui;
    377. user_mana_->GetUserInfo(send_id, &send_ui);
    378. cm.Clear();
    379. cm.msg_type_ = PushMsg;
    380. cm.sockfd_ = recv_ui.tcp_socket_;
    381. cm.SetValue("peer_nickname", send_ui.nickname_);
    382. cm.SetValue("peer_school", send_ui.school_);
    383. cm.json_msg_["peer_userid"] = send_ui.userid_;
    384. cm.SetValue("peer_msg", send_msg);
    385. send_que_->Push(cm);
    386. }
    387. private:
    388. //侦听套接字
    389. int tcp_sock_;
    390. int tcp_port_;
    391. //用户管理模块的实例化指针
    392. UserManager* user_mana_;
    393. //epoll操作句柄
    394. int epoll_fd_;
    395. //工作线程的数量
    396. int thread_count_;
    397. //就绪的文件描述符队列
    398. MsgQueue<int>* ready_sockfd_que_;
    399. //接收线程的队列
    400. MsgQueue* recv_que_;
    401. //发送线程的队列
    402. MsgQueue* send_que_;
    403. };

    1.5消息的操作

    消息类型和响应类型

    1. enum chat_msg_type{
    2. Register = 0, //0, 注册请求
    3. Register_Resp, //1, 注册应答
    4. Login, //2. 登录请求
    5. Login_Resp, //3, 登录应答
    6. AddFriend, //4, 添加好友请求
    7. AddFriend_Resp, //5, 添加好友请求应答
    8. SendMsg, //6, 发送消息
    9. SendMsg_Resp, //7, 发送消息应答
    10. PushMsg, //8, 推送消息
    11. PushMsg_Resp, //9, 推送消息应答
    12. PushAddFriendMsg, //10, 推送添加好友请求
    13. PushAddFriendMsg_Resp, //11, 推送添加好友请求的应答
    14. GetFriendMsg, //12, 获取全部好友信息
    15. GetFriendMsg_Resp, //13, 获取全部好友信息应答
    16. SetUserOffLine //14
    17. //后续如果要增加业务, 可以在后面增加其他的消息类型
    18. };
    19. enum reply_status{
    20. REGISTER_SUCCESS = 0, //0, 注册成功
    21. REGISTER_FAILED, //1,注册失败
    22. LOGIN_SUCESSS, //2, 登录成功
    23. LOGIN_FAILED, //3, 登陆失败
    24. ADDFRIEND_SUCCESS, //4, 添加好友成功
    25. ADDFRIEND_FAILED, //5, 添加好友失败
    26. SENDMSG_SUCCESS, //6, 发送消息成功
    27. SENDMSG_FAILED, //7, 发送给消息失败
    28. GETFRIEND_SUCCESS, //8,获取好友列表成功
    29. GETFRIEND_FAILED //9, 获取好友列表失败
    30. };

    消息类型的格式

    1. /*
    2. * 注册请求的消息格式
    3. * sockfd_ (消息达到服务端之后, 由服务端接收之后, 打上sockfd_)
    4. * msg_type_ : Register
    5. * json_msg: {
    6. * nickname : 'xxx'
    7. * school : "xxx"
    8. * telnum : "xxxx"
    9. * passwd : "xxxx"
    10. * }
    11. *
    12. * 注册的应答:
    13. * msg_type_ : Register_Resp
    14. * reply_status_ = REGISTER_SUCCESS / REGISTER_FAILED
    15. * 如果是REGISTER_SUCCESS : [user_id_]
    16. *
    17. *
    18. *
    19. * 登录的请求消息格式
    20. * sockfd_ (消息达到服务端之后, 由服务端接收之后, 打上sockfd_)
    21. * msg_type_ : Login
    22. * json_msg_ : {
    23. * telnum : xxx
    24. * passwd : xxx
    25. * }
    26. *
    27. * 登录的应答:
    28. * msg_type : Login_Resp;
    29. * reply_status_ : LOGIN_SUCCESS/LOGIN_FAILED
    30. * 如果是LOGIN_SUCCESS : [user_id_]
    31. *
    32. *
    33. *
    34. * 添加好友请求:
    35. * msg_type_ : AddFriend
    36. * json_msg_ :{
    37. * fri_tel_num : xxxx
    38. * }
    39. *
    40. *
    41. * 推送添加好友的请求
    42. * msg_type : PushAddFriendMsg
    43. * sockfd_ : 被添加方的套接字描述符
    44. * json_msg_: {
    45. * adder_nickname : xxx
    46. * adder_school : xxx
    47. * adder_userid : xxx
    48. * }
    49. *
    50. * 推送添加好友的应答(被添加方发送给服务端的)
    51. * msg_type : PushAddFriendMsg_Resp
    52. * user_id : 被添加方的id
    53. * reply_status : ADDFRIEND_SUCCESS / ADDFRIEND_FAILED
    54. * 如果说是ADDFRIEND_SUCCESS
    55. * json_msg_ : 添加方的id
    56. *
    57. * 添加好友的应答:
    58. * msg_type: AddFriend_Resp
    59. * reply_status : ADDFRIEND_FAILED / ADDFRIEND_SUCCESS
    60. * 如果是成功:ADDFRIEND_SUCCESS
    61. * json_msg_ :
    62. * BeAdd_nickname : 被添加方的名字
    63. * BeAdd_school : 被添加方的学校
    64. * BeAdd_userid : 被添加方的id
    65. * */

    消息的序列化和反序列化

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

    1. class JsonUtil{
    2. public:
    3. static bool Serialize(const Json::Value& value, std::string* body) {
    4. Json::StreamWriterBuilder swb;
    5. std::unique_ptr sw(swb.newStreamWriter());
    6. std::stringstream ss;
    7. int ret = sw->write(value, &ss);
    8. if (ret != 0) {
    9. return false;
    10. }
    11. *body = ss.str();
    12. return true;
    13. }
    14. static bool UnSerialize(const std::string& body, Json::Value* value) {
    15. Json::CharReaderBuilder crb;
    16. std::unique_ptr cr(crb.newCharReader());
    17. std::string err;
    18. bool ret = cr->parse(body.c_str(), body.c_str() + body.size(), value, &err);
    19. if (ret == false) {
    20. return false;
    21. }
    22. return true;
    23. }
    24. };

    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)
    1. /*
    2. * 提供序列化的接口 - 回复应答的时候使用
    3. * msg : 出参, 用于获取序列化完毕的字符串
    4. * */
    5. bool GetMsg(std::string* msg){
    6. Json::Value tmp;
    7. tmp["msg_type"] = msg_type_;
    8. tmp["user_id"] = user_id_;
    9. tmp["reply_status"] = reply_status_;
    10. tmp["json_msg"] = json_msg_;
    11. return JsonUtil::Serialize(tmp, msg);
    12. }
    13. /*
    14. * 获取json_msg_当中的value值
    15. * */
    16. std::string GetValue(const std::string& key){
    17. if(!json_msg_.isMember(key)){
    18. return "";
    19. }
    20. return json_msg_[key].asString();
    21. }
    22. /*
    23. * 设置json_msg_当中的kv键值对
    24. * */
    25. void SetValue(const std::string& key, const std::string& value){
    26. json_msg_[key] = value;
    27. }
    28. void SetValue(const std::string& key, int value){
    29. json_msg_[key] = value;
    30. }
    31. void Clear(){
    32. msg_type_ = -1;
    33. user_id_ = -1;
    34. reply_status_ = -1;
    35. json_msg_.clear();
    36. }
    37. public:
    38. //存放的客户端文件名描述符, 方便发送线程, 通过该字段将数据发送给对应的客户端
    39. int sockfd_;
    40. int msg_type_;
    41. //用户id
    42. int user_id_;
    43. //应答的状态
    44. int reply_status_;
    45. /*
    46. * Json消息
    47. * json消息的内容会随着消息类型的不同, 字段不一样
    48. * */
    49. Json::Value json_msg_;
    50. };

    1.6消息队列

    • a.向队列中放消息:void Push(const T& msg)
    • b.从队列中拿消息:void Pop(T* msg)
    1. #define CAPACITY 10000
    2. template <class T>
    3. class MsgQueue{
    4. public:
    5. MsgQueue(){
    6. capacity_ = CAPACITY;
    7. pthread_mutex_init(&lock_vec_, NULL);
    8. pthread_cond_init(&cons_cond_, NULL);
    9. pthread_cond_init(&prod_cond_, NULL);
    10. }
    11. ~MsgQueue(){
    12. pthread_mutex_destroy(&lock_vec_);
    13. pthread_cond_destroy(&cons_cond_);
    14. pthread_cond_destroy(&prod_cond_);
    15. }
    16. void Push(const T& msg){
    17. pthread_mutex_lock(&lock_vec_);
    18. while(vec_.size() >= capacity_){
    19. pthread_cond_wait(&prod_cond_, &lock_vec_);
    20. }
    21. vec_.push(msg);
    22. pthread_mutex_unlock(&lock_vec_);
    23. pthread_cond_signal(&cons_cond_);
    24. }
    25. void Pop(T* msg){
    26. pthread_mutex_lock(&lock_vec_);
    27. while(vec_.empty()){
    28. pthread_cond_wait(&cons_cond_, &lock_vec_);
    29. }
    30. *msg = vec_.front();
    31. vec_.pop();
    32. pthread_mutex_unlock(&lock_vec_);
    33. pthread_cond_signal(&prod_cond_);
    34. }
    35. private:
    36. std::queue vec_;
    37. size_t capacity_;
    38. pthread_mutex_t lock_vec_;
    39. pthread_cond_t cons_cond_;
    40. pthread_cond_t prod_cond_;
    41. };

    2.客户端

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

    2.1登录及注册消息流转图

     代码实现:

    1. // ChatSystemLd.cpp: 定义应用程序的类行为。
    2. //
    3. #include "pch.h"
    4. #include "framework.h"
    5. #include "ChatSystemLd.h"
    6. #include "ChatSystemLdDlg.h"
    7. #include "TcpSvr.h"
    8. #include "MsgQueue.h"
    9. #include
    10. #ifdef _DEBUG
    11. #define new DEBUG_NEW
    12. #endif
    13. void RecvMsgStart() {
    14. TcpSvr* ts = TcpSvr::getInstance();
    15. if (ts == NULL) {
    16. return;
    17. }
    18. MsgQueue* mq = MsgQueue::GetInstance();
    19. if (mq == NULL) {
    20. return;
    21. }
    22. while (1) {
    23. std::string msg;
    24. int ret = ts->Recv(&msg);
    25. if (ret <= 0) {
    26. continue;
    27. }
    28. ChatMsg cm;
    29. cm.PraseChatMsg(-1, msg);
    30. mq->Push(cm.msg_type_, msg);
    31. }
    32. }
    33. // CChatSystemLdApp
    34. BEGIN_MESSAGE_MAP(CChatSystemLdApp, CWinApp)
    35. ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
    36. END_MESSAGE_MAP()
    37. // CChatSystemLdApp 构造
    38. CChatSystemLdApp::CChatSystemLdApp()
    39. {
    40. // 支持重新启动管理器
    41. m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;
    42. // TODO: 在此处添加构造代码,
    43. // 将所有重要的初始化放置在 InitInstance 中
    44. }
    45. // 唯一的 CChatSystemLdApp 对象
    46. CChatSystemLdApp theApp;
    47. // CChatSystemLdApp 初始化
    48. BOOL CChatSystemLdApp::InitInstance()
    49. {
    50. // 如果一个运行在 Windows XP 上的应用程序清单指定要
    51. // 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
    52. //则需要 InitCommonControlsEx()。 否则,将无法创建窗口。
    53. INITCOMMONCONTROLSEX InitCtrls;
    54. InitCtrls.dwSize = sizeof(InitCtrls);
    55. // 将它设置为包括所有要在应用程序中使用的
    56. // 公共控件类。
    57. InitCtrls.dwICC = ICC_WIN95_CLASSES;
    58. InitCommonControlsEx(&InitCtrls);
    59. /*
    60. 创建接收线程, 让接收线程, 去接收应答
    61. */
    62. std::thread recv_thread(RecvMsgStart);
    63. recv_thread.detach();
    64. CWinApp::InitInstance();
    65. AfxEnableControlContainer();
    66. // 创建 shell 管理器,以防对话框包含
    67. // 任何 shell 树视图控件或 shell 列表视图控件。
    68. CShellManager *pShellManager = new CShellManager;
    69. // 激活“Windows Native”视觉管理器,以便在 MFC 控件中启用主题
    70. CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));
    71. // 标准初始化
    72. // 如果未使用这些功能并希望减小
    73. // 最终可执行文件的大小,则应移除下列
    74. // 不需要的特定初始化例程
    75. // 更改用于存储设置的注册表项
    76. // TODO: 应适当修改该字符串,
    77. // 例如修改为公司或组织名
    78. SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
    79. CChatSystemLdDlg dlg;
    80. m_pMainWnd = &dlg;
    81. INT_PTR nResponse = dlg.DoModal();
    82. if (nResponse == IDOK)
    83. {
    84. // TODO: 在此放置处理何时用
    85. // “确定”来关闭对话框的代码
    86. }
    87. else if (nResponse == IDCANCEL)
    88. {
    89. // TODO: 在此放置处理何时用
    90. // “取消”来关闭对话框的代码
    91. }
    92. else if (nResponse == -1)
    93. {
    94. TRACE(traceAppMsg, 0, "警告: 对话框创建失败,应用程序将意外终止。\n");
    95. TRACE(traceAppMsg, 0, "警告: 如果您在对话框上使用 MFC 控件,则无法 #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS。\n");
    96. }
    97. // 删除上面创建的 shell 管理器。
    98. if (pShellManager != nullptr)
    99. {
    100. delete pShellManager;
    101. }
    102. #if !defined(_AFXDLL) && !defined(_AFX_NO_MFC_CONTROLS_IN_DIALOGS)
    103. ControlBarCleanUp();
    104. #endif
    105. // 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
    106. // 而不是启动应用程序的消息泵。
    107. return FALSE;
    108. }

    2.2注册界面

    代码实现:

    1. // CDialogRegister.cpp: 实现文件
    2. //
    3. #include "pch.h"
    4. #include "ChatSystemLd.h"
    5. #include "CDialogRegister.h"
    6. #include "afxdialogex.h"
    7. #include "ChatMsg.h"
    8. #include "TcpSvr.h"
    9. #include "MsgQueue.h"
    10. // CDialogRegister 对话框
    11. IMPLEMENT_DYNAMIC(CDialogRegister, CDialogEx)
    12. CDialogRegister::CDialogRegister(CWnd* pParent /*=nullptr*/)
    13. : CDialogEx(IDD_DIALOGREGISTER, pParent)
    14. , m_nickname_(_T(""))
    15. , m_school_(_T(""))
    16. , m_telnum_(_T(""))
    17. , m_passwd_(_T(""))
    18. {
    19. }
    20. CDialogRegister::~CDialogRegister()
    21. {
    22. }
    23. void CDialogRegister::DoDataExchange(CDataExchange* pDX)
    24. {
    25. CDialogEx::DoDataExchange(pDX);
    26. DDX_Text(pDX, IDC_EDIT1, m_nickname_);
    27. DDX_Text(pDX, IDC_EDIT2, m_school_);
    28. DDX_Text(pDX, IDC_EDIT3, m_telnum_);
    29. DDX_Text(pDX, IDC_EDIT4, m_passwd_);
    30. }
    31. BEGIN_MESSAGE_MAP(CDialogRegister, CDialogEx)
    32. ON_BN_CLICKED(IDC_BUTTONCOMMIT, &CDialogRegister::OnBnClickedButtoncommit)
    33. END_MESSAGE_MAP()
    34. // CDialogRegister 消息处理程序
    35. void CDialogRegister::OnBnClickedButtoncommit()
    36. {
    37. // TODO: 在此添加控件通知处理程序代码
    38. // TODO: 在此添加控件通知处理程序代码
    39. //1.获取用户的输入
    40. //获取输入控件当中最新的值
    41. UpdateData(TRUE);
    42. if (m_nickname_.IsEmpty() ||
    43. m_school_.IsEmpty() ||
    44. m_telnum_.IsEmpty() ||
    45. m_passwd_.IsEmpty()) {
    46. MessageBox(TEXT("输入内容不能为空"));
    47. return;
    48. }
    49. //2.组织登录消息(ChatMsg)
    50. ChatMsg cm;
    51. cm.msg_type_ = Register;
    52. cm.json_msg_["nickname"] = m_nickname_.GetString();
    53. cm.json_msg_["school"] = m_school_.GetString();
    54. cm.json_msg_["telnum"] = m_telnum_.GetString();
    55. cm.json_msg_["passwd"] = m_passwd_.GetString();
    56. //cm.SetValue("telnum", m_telnum_.GetString());
    57. //cm.SetValue("passwd", m_passwd_.GetString());
    58. std::string msg;
    59. cm.GetMsg(&msg);
    60. //3.获取TCP服务实例化指针
    61. TcpSvr* ts = TcpSvr::getInstance();
    62. if (ts == NULL) {
    63. MessageBox("获取tcp服务失败, 请重试..");
    64. return;
    65. }
    66. //4.发送登录消息
    67. ts->Send(msg);
    68. //5.获取消息队列的实例化指针
    69. MsgQueue* mq = MsgQueue::GetInstance();
    70. if (mq == NULL) {
    71. MessageBox("获取消息队列失败, 请联系开发人员...");
    72. return;
    73. }
    74. msg.clear();
    75. mq->Pop(Register_Resp, &msg);
    76. //6.获取登录应答
    77. cm.Clear();
    78. cm.PraseChatMsg(-1, msg);
    79. //7.判断登录应答当中的应答状态(LOGIN_SUCCESS/LOGIN_FAILED)
    80. if (cm.reply_status_ == REGISTER_SUCCESS) {
    81. MessageBox("register success");
    82. //退出当前的注册界面, 相当于回到了登录界面
    83. CDialog::OnCancel();
    84. }
    85. else {
    86. MessageBox("register failed, please retry...");
    87. }
    88. }

    2.3聊天界面及消息流转图

    代码实现

    1. // CDialogChatWin.cpp: 实现文件
    2. //
    3. #include "pch.h"
    4. #include "ChatSystemLd.h"
    5. #include "CDialogChatWin.h"
    6. #include "afxdialogex.h"
    7. #include "CDialogAddFriend.h"
    8. #include "TcpSvr.h"
    9. #include "MsgQueue.h"
    10. #include "ChatMsg.h"
    11. // CDialogChatWin 对话框
    12. IMPLEMENT_DYNAMIC(CDialogChatWin, CDialogEx)
    13. CDialogChatWin::CDialogChatWin(int userid,CWnd* pParent /*=nullptr*/)
    14. : CDialogEx(IDD_DIALOGCHATWIN, pParent)
    15. , m_sendmsg_(_T(""))
    16. , user_id_(userid)
    17. {
    18. }
    19. CDialogChatWin::~CDialogChatWin()
    20. {
    21. }
    22. void CDialogChatWin::DoDataExchange(CDataExchange* pDX)
    23. {
    24. CDialogEx::DoDataExchange(pDX);
    25. //DDX_Text(pDX, IDC_EDIT1, m_sendmsg_);
    26. DDX_Text(pDX, IDC_EDITSENDMSG, m_sendmsg_);
    27. DDX_Control(pDX, IDC_LISTFRIENDLIST, m_userlist_);
    28. DDX_Control(pDX, IDC_LISTHISTORYMSG, m_output_);
    29. DDX_Control(pDX, IDC_EDITSENDMSG, m_sendmsg_edit_);
    30. //DDX_Control(pDX, IDC_BUTTONSENDMSG, user_id_);
    31. }
    32. BEGIN_MESSAGE_MAP(CDialogChatWin, CDialogEx)
    33. //ON_BN_CLICKED(IDC_BUTTON2, &CDialogChatWin::OnBnClickedButton2)
    34. ON_BN_CLICKED(IDC_BUTTONADDFRIEND, &CDialogChatWin::OnBnClickedButtonaddfriend)
    35. ON_BN_CLICKED(IDC_BUTTONSENDMSG, &CDialogChatWin::OnBnClickedButtonsendmsg)
    36. ON_LBN_SELCHANGE(IDC_LISTFRIENDLIST, &CDialogChatWin::OnLbnSelchangeListfriendlist)
    37. END_MESSAGE_MAP()
    38. // CDialogChatWin 消息处理程序
    39. void DealPushMsg(CDialogChatWin* cc) {
    40. MsgQueue* mq = MsgQueue::GetInstance();
    41. if (mq == NULL) {
    42. return;
    43. }
    44. while (1) {
    45. std::string msg;
    46. mq->Pop(PushMsg, &msg);
    47. ChatMsg cm;
    48. cm.PraseChatMsg(-1, msg);
    49. std::string peer_nickname = cm.json_msg_["peer_nickname"].asString();
    50. std::string peer_school = cm.json_msg_["peer_school"].asString();
    51. std::string peer_msg = cm.json_msg_["peer_msg"].asString();
    52. int peer_id = cm.json_msg_["peer_userid"].asInt();
    53. for (size_t i = 0; i < cc->fri_vec_.size(); i++) {
    54. if (peer_id == cc->fri_vec_[i].user_id_) {
    55. std::string tmp = peer_nickname + "-" + peer_school + ": " + peer_msg;
    56. cc->fri_vec_[i].history_msg_.push_back(tmp);
    57. if (peer_id == cc->send_user_id_) {
    58. //cc->m_output_.AddString(tmp.c_str());
    59. cc->m_output_.InsertString(cc->m_output_.GetCount(), tmp.c_str());
    60. }
    61. else {
    62. cc->fri_vec_[i].msg_cnt_++;
    63. }
    64. }
    65. }
    66. cc->RefreshUserList();
    67. }
    68. }
    69. //能够调用到这个线程函数, 说明当前客户端作为被添加方
    70. void DealPushAddFriendMsg(CDialogChatWin* cc) {
    71. //1.获取消息队列实例化指针 & tcp的实例化指针
    72. MsgQueue* mq = MsgQueue::GetInstance();
    73. if (mq == NULL) {
    74. MessageBox(cc->m_hWnd, "获取消息队列失败, 请联系开发人员...", "error", MB_YESNO);
    75. return;
    76. }
    77. //2.循环获取 PushAddFriendMsg 消息类型的消息
    78. while (1) {
    79. std::string msg;
    80. mq->Pop(PushAddFriendMsg, &msg);
    81. ChatMsg cm;
    82. cm.PraseChatMsg(-1, msg);
    83. std::string adder_nickname = cm.json_msg_["adder_nickname"].asString();
    84. std::string adder_school = cm.json_msg_["adder_school"].asString();
    85. int adder_userid = cm.json_msg_["adder_userid"].asInt();
    86. //3.通过获取的消息内容, 展示是那个用户想要添加自己
    87. std::string show_msg = adder_nickname + ":" + adder_school + " want add you as friend.";
    88. cm.Clear();
    89. int i = MessageBox(cc->m_hWnd, show_msg.c_str(), "添加好友", MB_YESNO);
    90. if (i == IDYES) {
    91. //同意添加
    92. //a. 将新好友信息维护起来
    93. struct UserInfo ui;
    94. ui.nickname_ = adder_nickname;
    95. ui.school_ = adder_school;
    96. ui.user_id_ = adder_userid;
    97. ui.msg_cnt_ = 0;
    98. cc->fri_vec_.push_back(ui);
    99. //b. 刷新用户列表
    100. cc->RefreshUserList();
    101. //c. 组织应答
    102. cm.msg_type_ = PushAddFriendMsg_Resp;
    103. cm.reply_status_ = ADDFRIEND_SUCCESS;
    104. cm.user_id_ = cc->user_id_; // 被添加方的id、
    105. cm.json_msg_["userid"] = adder_userid;
    106. }
    107. else {
    108. //不同意添加
    109. //a. 组织应答
    110. cm.msg_type_ = PushAddFriendMsg_Resp;
    111. cm.reply_status_ = ADDFRIEND_FAILED;
    112. cm.user_id_ = cc->user_id_; // 被添加方的id、
    113. cm.json_msg_["userid"] = adder_userid;
    114. }
    115. msg.clear();
    116. cm.GetMsg(&msg);
    117. //4.根据不同结果返回应答
    118. TcpSvr* ts = TcpSvr::getInstance();
    119. if (ts == NULL) {
    120. continue;
    121. }
    122. ts->Send(msg);
    123. }
    124. }
    125. //能够调用到这个线程函数, 说明当前客户端作为添加方
    126. void DealAddFriendResp(CDialogChatWin* cc) {
    127. //1.获取消息队列实例化指针 & tcp的实例化指针
    128. MsgQueue* mq = MsgQueue::GetInstance();
    129. if (mq == NULL) {
    130. MessageBox(cc->m_hWnd, "获取消息队列失败, 请联系开发人员...", "error", MB_YESNO);
    131. return;
    132. }
    133. //2.循环获取 PushAddFriendMsg 消息类型的消息
    134. while (1) {
    135. std::string msg;
    136. mq->Pop(AddFriend_Resp, &msg);
    137. ChatMsg cm;
    138. cm.PraseChatMsg(-1, msg);
    139. std::string content = cm.GetValue("content");
    140. MessageBox(cc->m_hWnd, content.c_str(), "添加好友应答", MB_OK);
    141. if (cm.reply_status_ == ADDFRIEND_FAILED) {
    142. continue;
    143. }
    144. std::string be_adder_nickname = cm.json_msg_["peer_nick_name"].asString();
    145. std::string be_adder_school = cm.json_msg_["peer_school"].asString();
    146. int be_adder_userid = cm.json_msg_["peer_userid"].asInt();
    147. //a. 将新好友信息维护起来
    148. struct UserInfo ui;
    149. ui.nickname_ = be_adder_nickname;
    150. ui.school_ = be_adder_school;
    151. ui.user_id_ = be_adder_userid;
    152. ui.msg_cnt_ = 0;
    153. cc->fri_vec_.push_back(ui);
    154. //b. 刷新用户列表
    155. cc->RefreshUserList();
    156. }
    157. }
    158. BOOL CDialogChatWin::OnInitDialog()
    159. {
    160. CDialogEx::OnInitDialog();
    161. // TODO: 在此添加额外的初始化
    162. std::thread recv_msg(DealPushMsg, this);
    163. recv_msg.detach();
    164. std::thread recv_addfriendmsg(DealPushAddFriendMsg, this);
    165. recv_addfriendmsg.detach();
    166. std::thread recv_addfriendrespmsg(DealAddFriendResp, this);
    167. recv_addfriendrespmsg.detach();
    168. // TODO: 在此添加额外的初始化
    169. //1. 获取TCP实例化指针
    170. TcpSvr* ts = TcpSvr::getInstance();
    171. if (ts == NULL) {
    172. MessageBox("获取tcp服务失败, 请重试..");
    173. return false;
    174. }
    175. //2. 组织获取好友信息的数据
    176. ChatMsg cm;
    177. cm.msg_type_ = GetFriendMsg;
    178. cm.user_id_ = user_id_;
    179. std::string msg;
    180. cm.GetMsg(&msg);
    181. ts->Send(msg);
    182. //3. 解析应答
    183. MsgQueue* mq = MsgQueue::GetInstance();
    184. if (mq == NULL) {
    185. MessageBox("获取消息队列失败, 请联系开发人员...");
    186. return false;
    187. }
    188. msg.clear();
    189. mq->Pop(GetFriendMsg_Resp, &msg);
    190. //4. 展示好友信息到userlist(展示? 要不要保存呢?)
    191. cm.Clear();
    192. cm.PraseChatMsg(-1, msg);
    193. for (int i = 0; i < (int)cm.json_msg_.size(); i++) {
    194. struct UserInfo ui;
    195. ui.nickname_ = cm.json_msg_[i]["nickname"].asString();
    196. ui.school_ = cm.json_msg_[i]["school"].asString();
    197. ui.user_id_ = cm.json_msg_[i]["userid"].asInt();
    198. ui.msg_cnt_ = 0;
    199. if (i == 0) {
    200. send_user_id_ = ui.user_id_;
    201. }
    202. fri_vec_.push_back(ui);
    203. }
    204. //5.刷新userlist
    205. RefreshUserList();
    206. return TRUE; // return TRUE unless you set the focus to a control
    207. // 异常: OCX 属性页应返回 FALSE
    208. }
    209. BOOL CDialogChatWin::PreTranslateMessage(MSG* pMsg)
    210. {
    211. // TODO: 在此添加专用代码和/或调用基类
    212. if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN) {
    213. OnBnClickedButtonsendmsg();
    214. return TRUE;
    215. }
    216. if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_ESCAPE) {
    217. return TRUE;
    218. }
    219. return CDialogEx::PreTranslateMessage(pMsg);
    220. }
    221. void CDialogChatWin::OnBnClickedButtonsendmsg()
    222. {
    223. // TODO: 在此添加控件通知处理程序代码
    224. //1. 获取输入框的内容
    225. UpdateData(TRUE);
    226. if (m_sendmsg_.IsEmpty()) {
    227. MessageBox(TEXT("输入内容不能为空"));
    228. return;
    229. }
    230. //2. 组织发送消息
    231. ChatMsg cm;
    232. cm.msg_type_ = SendMsg;
    233. //消息是谁发的
    234. cm.user_id_ = user_id_;
    235. //消息发给谁
    236. cm.json_msg_["recvmsgid"] = send_user_id_;
    237. //消息内容
    238. cm.json_msg_["msg"] = m_sendmsg_.GetString();
    239. std::string msg;
    240. cm.GetMsg(&msg);
    241. TcpSvr* ts = TcpSvr::getInstance();
    242. if (ts == NULL) {
    243. MessageBox("获取tcp服务失败, 请重试..");
    244. return;
    245. }
    246. //3. 将这个消息发送出去
    247. ts->Send(msg);
    248. //4. 接收应答, 判断消息是否发送成功了
    249. MsgQueue* mq = MsgQueue::GetInstance();
    250. if (mq == NULL) {
    251. MessageBox("获取消息队列失败, 请联系开发人员...");
    252. return;
    253. }
    254. msg.clear();
    255. mq->Pop(SendMsg_Resp, &msg);
    256. cm.Clear();
    257. cm.PraseChatMsg(-1, msg);
    258. //5. 添加到该好友的历史消息当中, 并且展示到输出框
    259. for (size_t i = 0; i < fri_vec_.size(); i++) {
    260. if (send_user_id_ == fri_vec_[i].user_id_) {
    261. std::string tmp = "我: ";
    262. tmp += m_sendmsg_.GetString();
    263. if (cm.reply_status_ == SENDMSG_SUCCESS) {
    264. tmp += " (send success)";
    265. }
    266. else {
    267. tmp += " (send failed)";
    268. }
    269. fri_vec_[i].history_msg_.push_back(tmp);
    270. m_output_.InsertString(m_output_.GetCount(), tmp.c_str());
    271. //m_output_.AddString(tmp.c_str());
    272. }
    273. }
    274. //6. 清空编辑框
    275. m_sendmsg_.Empty();
    276. m_sendmsg_edit_.SetWindowTextA(0);
    277. }
    278. void CDialogChatWin::OnLbnSelchangeListfriendlist()
    279. {
    280. // TODO: 在此添加控件通知处理程序代码
    281. //1. 获取点击的文本内容
    282. CString strText;
    283. int cur = m_userlist_.GetCurSel();
    284. m_userlist_.GetText(cur, strText);
    285. //2. 判断当前点击的是哪一个用户, 更改发送id
    286. for (size_t i = 0; i < fri_vec_.size(); i++) {
    287. if (strstr(strText, fri_vec_[i].nickname_.c_str()) != NULL) {
    288. send_user_id_ = fri_vec_[i].user_id_;
    289. }
    290. }
    291. //3. 清空output区域
    292. for (int i = m_output_.GetCount(); i >= 0; i--) {
    293. m_output_.DeleteString(i);
    294. }
    295. //4. 展示该用户的历史消息
    296. for (size_t i = 0; i < fri_vec_.size(); i++) {
    297. if (send_user_id_ == fri_vec_[i].user_id_) {
    298. //把历史消息展示再output区域
    299. for (size_t j = 0; j < fri_vec_[i].history_msg_.size(); j++) {
    300. m_output_.AddString(fri_vec_[i].history_msg_[j].c_str());
    301. }
    302. fri_vec_[i].msg_cnt_ = 0;
    303. }
    304. }
    305. //5. 刷新userlist
    306. RefreshUserList();
    307. }
    308. void CDialogChatWin::RefreshUserList() {
    309. int Count = m_userlist_.GetCount();
    310. //先清空
    311. for (int i = Count; i >= 0; i--) {
    312. m_userlist_.DeleteString(i);
    313. }
    314. //再展示
    315. for (int i = 0; i < (int)fri_vec_.size(); i++) {
    316. std::string tmp = fri_vec_[i].nickname_;
    317. //当该好友的未读消息个数是大于0 的时候, 展示未读消息个数
    318. if (fri_vec_[i].msg_cnt_ > 0) {
    319. tmp += " : ";
    320. tmp += std::to_string(fri_vec_[i].msg_cnt_);
    321. }
    322. m_userlist_.AddString(tmp.c_str());
    323. }
    324. }
    325. void CDialogChatWin::OnBnClickedButtonaddfriend()
    326. {
    327. // TODO: 在此添加控件通知处理程序代码
    328. //1.获取消息队列实例化指针 & tcp的实例化指针
    329. CDialogAddFriend caf(user_id_);
    330. caf.DoModal();
    331. }

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

    1. // CDialogAddFriend.cpp: 实现文件
    2. //
    3. #include "pch.h"
    4. #include "ChatSystemLd.h"
    5. #include "CDialogAddFriend.h"
    6. #include "afxdialogex.h"
    7. #include
    8. #include "ChatMsg.h"
    9. #include "MsgQueue.h"
    10. #include "TcpSvr.h"
    11. // CDialogAddFriend 对话框
    12. IMPLEMENT_DYNAMIC(CDialogAddFriend, CDialogEx)
    13. CDialogAddFriend::CDialogAddFriend(int user_id,CWnd* pParent /*=nullptr*/)
    14. : CDialogEx(IDD_DIALOGADDFRIEND, pParent)
    15. , m_fri_telnum_(_T(""))
    16. ,user_id_(user_id)
    17. {
    18. }
    19. CDialogAddFriend::~CDialogAddFriend()
    20. {
    21. }
    22. void CDialogAddFriend::DoDataExchange(CDataExchange* pDX)
    23. {
    24. CDialogEx::DoDataExchange(pDX);
    25. DDX_Text(pDX, IDC_EDITTELNUM, m_fri_telnum_);
    26. }
    27. BEGIN_MESSAGE_MAP(CDialogAddFriend, CDialogEx)
    28. ON_BN_CLICKED(IDC_BUTTONADD, &CDialogAddFriend::OnBnClickedButtonadd)
    29. END_MESSAGE_MAP()
    30. // CDialogAddFriend 消息处理程序
    31. void CDialogAddFriend::OnBnClickedButtonadd()
    32. {
    33. // TODO: 在此添加控件通知处理程序代码
    34. //1.获取用户的输入
    35. UpdateData(TRUE);//获取输入控件当中最新的值
    36. if (m_fri_telnum_.IsEmpty())
    37. {
    38. MessageBox("输入内容不能为空");
    39. return;
    40. }
    41. //2.组织登录消息ChatMsg
    42. ChatMsg cm;
    43. cm.msg_type_ = AddFriend;
    44. //消息是谁发的
    45. cm.user_id_ = user_id_;
    46. //消息内容
    47. cm.json_msg_["telnum"] = m_fri_telnum_.GetString();
    48. std::string msg;
    49. cm.GetMsg(&msg);
    50. //3.获取TCP服务的指针
    51. TcpSvr* ts = TcpSvr::getInstance();
    52. if (ts == nullptr)
    53. {
    54. MessageBox("获取tcp服务失败");
    55. return;
    56. }
    57. //4.发送登录消息
    58. ts->Send(msg);
    59. CDialog::OnCancel();//取消添加好友界面
    60. //线程等待接收应答,因为添加好友的应答不会立即到
    61. }

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

    TCP聊天系统测试

  • 相关阅读:
    【2023集创赛】加速科技杯三等奖作品:私密性高精度刷手身份认证系统
    Python笔记二之多线程
    简易机器学习笔记(十一)opencv 简易使用-人脸识别、分类任务
    不断完成新项目才能升职?前首席工程师“怒喷”谷歌晋升机制
    手机通过WiFi连接调试UR机器人
    最新版SpringBoot整合Mybatis,实现增删改查(CRUD)
    centos中nacos设置开机自启动
    【漏洞复现】typecho_v1.0-14.10.10_unserialize
    从 DevOps 到平台工程:软件开发的新范式
    一款带数字传输信号的OVP芯片
  • 原文地址:https://blog.csdn.net/qq_57822158/article/details/126170184