项目内容
本项目基于C++实现了一个具有群聊功能的聊天室。客户端基于QT实现,服务端基于epoll实现。目前已上传到https://github.com/cswen-scut/chatroom,喜欢的朋友记得给个星星哦O(∩_∩)O。
项目演示
技术点
-
客户端涉及到的技术点
- 常用QT控件(QWidget, QListWidget, QLabel, QPushButton)
- QT信号与槽
- QJsonObject完成json数据的解析
- QT多线程
- QTcpSocket连接服务器
-
服务端涉及到的技术点
- epoll多路IO转接机制
- 常用STL(vector, map)
- 文件读写(fstream)
- jsoncpp解析json数据
- MySQL基本操作
实现的功能
- 注册
- 单点登录
- 登出
- 群聊(支持文本和图片的传送)
- 上线下线公告
- 在线用户记录
通信协议
为保证数据能够被正确发送和接收,本项目还自定义了一个协议
- 开始1B表示这个数据包是注册请求、登录请求、发送请求、登出请求等
- 接下来2B表示用户的账号
- 接下来1B表示数据包的数据格式,文本或图片
- 接下来4B表示数据的大小
- 最后就是真实数据了
遇到的问题及解决方案
1. 服务端端口被占用问题
问题描述:
服务端强制关闭后TCP连接进入TIME_WAIT状态,此状态持续2MSL(大概40多秒),由于端口被占用,若此时若再次启动服务端,会失败。
解决方案:
通过setsockopt函数实现端口复用。
2. 数据包"粘包"问题
问题描述:
TCP是流式协议,所传输的数据没有明确的界线,需要用户自己区分。
解决方案:
在接收数据时,先解析协议头部,根据头部数据大小字段来决定接收多少数据,然后循环接收数据,直到接收完该数据大小的包后再接收下一个包。
3. QT readyRead信号丢失问题
问题描述:
若服务端发送给客户端的数据较大,且发送速度较快,客户端可能来不及接收完本次readyRead信号的bytesAvailable大小的数据,此时服务端却在不停地发,可能会造成客户端readyRead信号的丢失,从而导致接收数据不全。
解决方案:
使用确认机制,即客户端每读完一个readyRead信号所携带的数据后,给服务端发送一个确认包,告诉服务端自己已经接收了多少数据,然后服务端再接着发下一部分的数据,直到接收完数据。
4. 客户端发送数据与接收数据的冲突问题
问题描述:
若客户端与服务端仅建立一个连接,那么当客户端在接收数据时,若客户端此时向服务端发送聊天数据,服务端本应接收客户端的确认包,但是却接收了客户端的聊天数据,从而造成确认信息异常。
解决方案:
客户端与服务端建立两个连接,一个用于写数据,一个用于读数据。
5. 客户端强制退出问题
问题描述:
客户端在接收数据时,强制退出了,服务端由于发给客户端的数据没有收到确认,在read确认包时进入了阻塞状态。
解决方案:
每次向客户端发送数据时,先用getsockopt获取客户端的连接状态,若客户端的连接状态不是ESTABLISHED,则直接结束发送,并取消监听客户端的fd。
总结
本项目的主要目的是熟悉C++和Linux网络编程,目前还有很多不完善之处,例如服务端仅采用单线程实现,这样效率会很低,后续考虑引入线程池。