项目展示
登录界面
注册界面
聊天界面
实现功能
实现注册、登录,一对一聊天的功能
服务端原理介绍
服务端整体业务示意图
各个模块的业务:
数据库:保证用户信息的持久化到数据库中(如昵称、账号、密码、学校、好友列表等)
用户信息管理:在服务器初始化阶段会调用相应mysql C-API接口将数据库的内容全部读到内存中,用户信息管理类将该信息通过unordermap<自定义格式>维护起来,如果工作线程在处理客户端请求时设计写操作,那么用户管理模块会调用相应的接口更改该数据库相应内容。
![](/qrcode.jpg)
所有的队列:由于是多线程编程,因此在设计安全队列时要考虑到线程安全,这里所有的队列采用的都是生产者消费者模型。
主线程:完成端口绑定、建立监听套接字、由于我的服务端在linux上,选用的是epoll进行多路转接,将监听到的文件描述符放入就绪文件描述符队列中
接收线程:循环从就绪文件描述符队列中去文件描述符,然后调用recv函数进行接收,并将网络中传输的序列化信息,转化成我们需要的非序列化息,然后放入待处理消息队列。
工作线程:循环从待处理消息队列中取出发送方即客户端发送的消息,然后根据消息请求的类型进行不同的处理(登录、注册、添加好友、聊天......)工作线程处理完请求会生成相对应的响应消息,如果接收方在线,则将消息放入已处理消息队列中,如果接收方不在线则推送到消息缓存块中
发送线程:发送线程循环从已处理消息队列取消息,然后根据消息确定发送方的sock_fd,然后将信息序列化并交给send函数处理。
推送缓存线程:推送缓存线程是循环从登录队列(上图未画出)中取消息,工作线程在处理用户的登录请求时,会更新用户信心管理中的信息,使它的套接字描述符和数据库当中的userid对应起来,因此将登录的sockfd放入登录队列中,推送缓存线程取到则说明该userid的用户上线了,就去查询缓存消息块中对应的消息队列是否为空,不为空则全部推送到已处理消息队列中(在推送之前要注意消息中的sockfd已经过时了,因此要将sockfd进行更新,然后再推送)
客户端原理
客户端基于vs2019的MFC框架
CAboutDlg:对应生成的版本信息对话框
CDialogApp :应用程序类,从CWinApp中继承过来,封装初始化、运行、终止该程序的代码
这个函数即为初始化我们要写的程序的函数
CDialogDlg:对话框类,从CdialogEx继承过来的,在程序运⾏时看到的对话框就是它的⼀个具体对象。
各个页面的功能
登录页面:将账号和密码按照服务端与客户端自定义约定的类型进行发送信息,并等待登录请求的响应 登录响应为成功,则跳转页面进入聊天界面
注册页面:将信息按照服务端与客户端自定义约定的类型进行发送信息,并等待注册请求的响应 组成响应为成功,则关闭当前页面返回登录界面
聊天界面业务流转图
消息队列,消息队列模拟的Linux进程通信中的消息队列。我底层采用的是vector<queue<string>>的格式。(由于多线程,我们要进行加锁访问临界资源)如Pop()方法的实现
void MsgQueue::Pop(int msg_type, std::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;
}
}
接收线程:在CDialogApp类中的初始化程序函数中创建该线程,使其获取tcp链接接口,然后持续进行recv,如果接收到数据则放入消息队列中。
推送消息线程:从消息队列中循环取消息类型为聊天消息类型的消息,然后通过检查好友信息表,推送到响应的文本框。
添加好友线程:从消息队列中循环取消息类型为添加好友类型的消息,然后更改好友信息表,然后更新好友列表。
推送好友线程:从消息队列中循环取消息类型好友请求类型的消息,然后向用户界面推送文本框,若果用户同意添加则更新好友列表,并向服务端发送相应的请求,如果不同意添加则无需更新用户请求,向服务端发送相应的请求。
注意:由于的登录页面、注册页面、聊天页面、属于三个不同的类,我们最好把有关tcp的操作封装起来设计为单例模式、以及对消息队列类也要设计为单例类