├── README.md
├── client
├── exec
│ ├── Qt5Core.dll
│ ├── Qt5Gui.dll
│ ├── Qt5Network.dll
│ ├── Qt5Widgets.dll
│ ├── chatroom.exe
│ ├── config
│ │ └── server.conf
│ ├── image
│ │ └── README.md
│ ├── libgcc_s_seh-1.dll
│ ├── libstdc++-6.dll
│ ├── libwinpthread-1.dll
│ └── plugins
│ │ ├── platforms
│ │ ├── qdirect2d.dll
│ │ ├── qminimal.dll
│ │ ├── qoffscreen.dll
│ │ └── qwindows.dll
│ │ ├── platformthemes
│ │ └── qxdgdesktopportal.dll
│ │ └── styles
│ │ └── qwindowsvistastyle.dll
└── source
│ ├── chatroom.pro
│ ├── chatroom.pro.user
│ ├── client.cpp
│ ├── client.h
│ ├── config
│ └── server.conf
│ ├── configreader.cpp
│ ├── configreader.h
│ ├── dataencoder.cpp
│ ├── dataencoder.h
│ ├── dataparser.cpp
│ ├── dataparser.h
│ ├── icon.qrc
│ ├── login.ui
│ ├── loginform.cpp
│ ├── loginform.h
│ ├── loginform.ui
│ ├── main.cpp
│ ├── mytime.cpp
│ ├── mytime.h
│ ├── protocolmsg.h
│ ├── readclient.cpp
│ ├── readclient.h
│ ├── readthread.cpp
│ ├── readthread.h
│ ├── registerForm.ui
│ ├── registerform.cpp
│ ├── registerform.h
│ ├── user.cpp
│ ├── user.h
│ ├── widget.cpp
│ ├── widget.h
│ ├── widget.ui
│ ├── writeclient.cpp
│ └── writeclient.h
├── server
├── Dao
│ ├── MySQLConnector.cpp
│ └── MySQLConnector.h
├── Makefile
├── ProtocolHead
│ ├── DataEncoder.cpp
│ ├── DataEncoder.h
│ ├── HeadData.cpp
│ ├── HeadData.h
│ └── protocolmsg.h
├── Service
│ ├── DataProcesser.cpp
│ ├── DataProcesser.h
│ ├── Online.cpp
│ ├── Online.h
│ ├── UserService.cpp
│ └── UserService.h
├── Util
│ ├── MyTime.cpp
│ └── MyTime.h
├── config
│ ├── mysql_config.h
│ └── server_config.h
├── image
│ └── README.md
├── main.cpp
└── out
│ └── README.md
├── sql
└── user.sql
└── 聊天室演示.mp4
/README.md:
--------------------------------------------------------------------------------
1 | # 基于C++和epoll实现的聊天室
2 |
3 | - 客户端涉及到的技术点
4 | - 常用QT控件(QWidget, QListWidget, QLabel, QPushButton)
5 | - QT信号与槽
6 | - QJsonObject完成json数据的解析
7 | - QT多线程
8 | - QTcpSocket连接服务器
9 |
10 | - 服务端涉及到的技术点
11 | - epoll多路IO转接机制
12 | - 常用STL(vector, map)
13 | - 文件读写(fstream)
14 | - jsoncpp解析json数据
15 | - MySQL基本操作
16 |
17 | - 实现的功能
18 | - 注册
19 | - 单点登录
20 | - 登出
21 | - 群聊(支持文本和图片的传送)
22 | - 上线下线公告
23 | - 在线用户记录
24 |
25 | - 客户端使用方式:
26 | - ./client/source/文件夹是客户端源码,若安装了QT,则可打开chatroom.pro,若在QT中构建项目,请在构建
27 | 的文件夹下新建一个image文件夹用于保存图片,另外新建一个config文件夹,再在config文件夹中新建一个server.conf用于配置服务端IP和
28 | 端口,server.conf的格式按照./client/source/server.conf填写
29 | - ./client/exec是编译后的可执行文件以及依赖的库,请先修改./client/exec/config/server.conf,
30 | 然后将chatroom.exe所在的文件夹路径加入环境变量,再直接运行chatroom.exe
31 |
32 | - 服务端使用方式
33 | - 1.安装jsoncpp库, 详情请见https://github.com/open-source-parsers/jsoncpp
34 | - 安装完后将./vcpkg/installed/x64-linux/include/json拷贝到/usr/local/include/
35 | - 将./vcpkg/installed/x64-linux/lib/libjsoncpp.a拷贝到/usr/local/include
36 | - 在/etc/profile里追加
37 | - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
38 | - export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/lib
39 | - 最后source /etc/profile
40 | - 2.安装MySQL连接库
41 | - ubuntu使用命令apt-get install libmysqlclient-dev
42 | - centos使用命令yum install mysql-devel -y
43 | - 3.利用./sql/user.sql在MySQL中创建一张user表
44 | - 4.进入./server/config目录,将服务器的IP和端口等配置信息换成适合自己的
45 | - 5.进入./server/, 然后make即可
46 | - 6.运行步骤5生成的可执行文件
47 |
48 | - 通信协议
49 | - 开始1B表示这个数据包是登录请求、发送请求、登出请求等(详情请见./server/ProtocolHead/protocolmsg.h)
50 | - 接下来2B表示用户的账号
51 | - 接下来1B表示数据包的数据格式,文本或图片
52 | - 接下来4B表示数据的大小
53 | - 最后就是真实数据了
54 |
55 | - 遇到的问题及解决方案
56 | - 服务端端口被占用问题。服务端强制关闭后TCP连接进入TIME_WAIT状态,此状态持续2MSL(大概40多秒),由于端口被占用,若此时若再次启动服务端,会失败
57 | - 通过setsockopt函数实现端口复用
58 | - 数据包"粘包"问题。TCP是流式协议,所传输的数据没有明确的界线,需要用户自己区分。
59 | - 在接收数据时,先解析协议头部,根据头部数据大小字段来决定接收多少数据,然后循环接收数据,直到接收完该数据大小的包后再接收下一个包
60 | - QT readyRead信号丢失问题,若服务端发送给客户端的数据较大,且发送速度较快,客户端可能来不及接收完本次readyRead信号的bytesAvailable大小的数据,此时服务端却在不停地发,可能会造成客户端readyRead信号的丢失,从而导致接收数据不全
61 | - 使用确认机制,即客户端每读完一个readyRead信号所携带的数据后,给服务端发送一个确认包,告诉服务端自己已经接收了多少数据,然后服务端再接着发下一部分的数据,直到接收完数据
62 | - 客户端发送数据与接收数据的冲突问题。若客户端与服务端仅建立一个连接,那么当客户端在接收数据时,若客户端此时向服务端发送聊天数据,服务端本应接收客户端的确认包,但是却接收了客户端的聊天数据,从而造成确认信息异常
63 | - 客户端与服务端建立两个连接,一个用于写数据,一个用于读数据
64 | - 客户端强制退出问题。客户端在接收数据时,强制退出了,服务端由于发给客户端的数据没有收到确认,在read确认包时进入了阻塞状态。
65 | - 每次向客户端发送数据时,先用getsockopt获取客户端的连接状态,若客户端的连接状态不是ESTABLISHED,则直接结束发送,并取消监听客户端的fd
66 |
67 | - 总结
68 | - 本项目的主要目的是熟悉C++和Linux网络编程,目前还有很多不完善之处,例如服务端仅采用单线程实现,这样效率会很低,后续考虑引入线程池。
69 |
70 |
--------------------------------------------------------------------------------
/client/exec/Qt5Core.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cswen-scut/chatroom/1f7723e93b6236c474c628891ac2530d4fe9b198/client/exec/Qt5Core.dll
--------------------------------------------------------------------------------
/client/exec/Qt5Gui.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cswen-scut/chatroom/1f7723e93b6236c474c628891ac2530d4fe9b198/client/exec/Qt5Gui.dll
--------------------------------------------------------------------------------
/client/exec/Qt5Network.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cswen-scut/chatroom/1f7723e93b6236c474c628891ac2530d4fe9b198/client/exec/Qt5Network.dll
--------------------------------------------------------------------------------
/client/exec/Qt5Widgets.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cswen-scut/chatroom/1f7723e93b6236c474c628891ac2530d4fe9b198/client/exec/Qt5Widgets.dll
--------------------------------------------------------------------------------
/client/exec/chatroom.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cswen-scut/chatroom/1f7723e93b6236c474c628891ac2530d4fe9b198/client/exec/chatroom.exe
--------------------------------------------------------------------------------
/client/exec/config/server.conf:
--------------------------------------------------------------------------------
1 | HOST=127.0.0.1
2 | PORT=8888
3 |
--------------------------------------------------------------------------------
/client/exec/image/README.md:
--------------------------------------------------------------------------------
1 | # 用于保存图片
2 |
--------------------------------------------------------------------------------
/client/exec/libgcc_s_seh-1.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cswen-scut/chatroom/1f7723e93b6236c474c628891ac2530d4fe9b198/client/exec/libgcc_s_seh-1.dll
--------------------------------------------------------------------------------
/client/exec/libstdc++-6.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cswen-scut/chatroom/1f7723e93b6236c474c628891ac2530d4fe9b198/client/exec/libstdc++-6.dll
--------------------------------------------------------------------------------
/client/exec/libwinpthread-1.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cswen-scut/chatroom/1f7723e93b6236c474c628891ac2530d4fe9b198/client/exec/libwinpthread-1.dll
--------------------------------------------------------------------------------
/client/exec/plugins/platforms/qdirect2d.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cswen-scut/chatroom/1f7723e93b6236c474c628891ac2530d4fe9b198/client/exec/plugins/platforms/qdirect2d.dll
--------------------------------------------------------------------------------
/client/exec/plugins/platforms/qminimal.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cswen-scut/chatroom/1f7723e93b6236c474c628891ac2530d4fe9b198/client/exec/plugins/platforms/qminimal.dll
--------------------------------------------------------------------------------
/client/exec/plugins/platforms/qoffscreen.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cswen-scut/chatroom/1f7723e93b6236c474c628891ac2530d4fe9b198/client/exec/plugins/platforms/qoffscreen.dll
--------------------------------------------------------------------------------
/client/exec/plugins/platforms/qwindows.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cswen-scut/chatroom/1f7723e93b6236c474c628891ac2530d4fe9b198/client/exec/plugins/platforms/qwindows.dll
--------------------------------------------------------------------------------
/client/exec/plugins/platformthemes/qxdgdesktopportal.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cswen-scut/chatroom/1f7723e93b6236c474c628891ac2530d4fe9b198/client/exec/plugins/platformthemes/qxdgdesktopportal.dll
--------------------------------------------------------------------------------
/client/exec/plugins/styles/qwindowsvistastyle.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cswen-scut/chatroom/1f7723e93b6236c474c628891ac2530d4fe9b198/client/exec/plugins/styles/qwindowsvistastyle.dll
--------------------------------------------------------------------------------
/client/source/chatroom.pro:
--------------------------------------------------------------------------------
1 | QT += core gui
2 | QT += network
3 |
4 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
5 |
6 | CONFIG += c++11
7 |
8 | # The following define makes your compiler emit warnings if you use
9 | # any Qt feature that has been marked deprecated (the exact warnings
10 | # depend on your compiler). Please consult the documentation of the
11 | # deprecated API in order to know how to port your code away from it.
12 | DEFINES += QT_DEPRECATED_WARNINGS
13 |
14 | # You can also make your code fail to compile if it uses deprecated APIs.
15 | # In order to do so, uncomment the following line.
16 | # You can also select to disable deprecated APIs only up to a certain version of Qt.
17 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
18 |
19 | SOURCES += \
20 | client.cpp \
21 | configreader.cpp \
22 | dataencoder.cpp \
23 | dataparser.cpp \
24 | loginform.cpp \
25 | main.cpp \
26 | mytime.cpp \
27 | readclient.cpp \
28 | readthread.cpp \
29 | registerform.cpp \
30 | user.cpp \
31 | widget.cpp \
32 | writeclient.cpp
33 |
34 | HEADERS += \
35 | client.h \
36 | configreader.h \
37 | dataencoder.h \
38 | dataparser.h \
39 | loginform.h \
40 | mytime.h \
41 | protocolmsg.h \
42 | readclient.h \
43 | readthread.h \
44 | registerform.h \
45 | user.h \
46 | widget.h \
47 | writeclient.h
48 |
49 | FORMS += \
50 | loginform.ui \
51 | registerform.ui \
52 | widget.ui
53 |
54 | # Default rules for deployment.
55 | qnx: target.path = /tmp/$${TARGET}/bin
56 | else: unix:!android: target.path = /opt/$${TARGET}/bin
57 | !isEmpty(target.path): INSTALLS += target
58 |
59 | RESOURCES +=
60 |
61 | DISTFILES += \
62 | config/server.conf
63 |
--------------------------------------------------------------------------------
/client/source/chatroom.pro.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | EnvironmentId
7 | {e445060c-341f-4070-940b-1519d72264de}
8 |
9 |
10 | ProjectExplorer.Project.ActiveTarget
11 | 0
12 |
13 |
14 | ProjectExplorer.Project.EditorSettings
15 |
16 | true
17 | false
18 | true
19 |
20 | Cpp
21 |
22 | CppGlobal
23 |
24 |
25 |
26 | QmlJS
27 |
28 | QmlJSGlobal
29 |
30 |
31 | 2
32 | UTF-8
33 | false
34 | 4
35 | false
36 | 80
37 | true
38 | true
39 | 1
40 | true
41 | false
42 | 0
43 | true
44 | true
45 | 0
46 | 8
47 | true
48 | 1
49 | true
50 | true
51 | true
52 | false
53 |
54 |
55 |
56 | ProjectExplorer.Project.PluginSettings
57 |
58 |
59 | -fno-delayed-template-parsing
60 |
61 | true
62 |
63 |
64 |
65 | ProjectExplorer.Project.Target.0
66 |
67 | Desktop Qt 5.14.2 MinGW 64-bit
68 | Desktop Qt 5.14.2 MinGW 64-bit
69 | qt.qt5.5142.win64_mingw73_kit
70 | 1
71 | 0
72 | 0
73 |
74 | C:/Users/fangwen/Documents/build-chatroom-Desktop_Qt_5_14_2_MinGW_64_bit-Debug
75 |
76 |
77 | true
78 | QtProjectManager.QMakeBuildStep
79 | true
80 |
81 | false
82 | false
83 | false
84 |
85 |
86 | true
87 | Qt4ProjectManager.MakeStep
88 |
89 | false
90 |
91 |
92 | false
93 |
94 | 2
95 | Build
96 | Build
97 | ProjectExplorer.BuildSteps.Build
98 |
99 |
100 |
101 | true
102 | Qt4ProjectManager.MakeStep
103 |
104 | true
105 | clean
106 |
107 | false
108 |
109 | 1
110 | Clean
111 | Clean
112 | ProjectExplorer.BuildSteps.Clean
113 |
114 | 2
115 | false
116 |
117 | Debug
118 | Qt4ProjectManager.Qt4BuildConfiguration
119 | 2
120 |
121 |
122 | C:/Users/fangwen/Documents/build-chatroom-Desktop_Qt_5_14_2_MinGW_64_bit-Release
123 |
124 |
125 | true
126 | QtProjectManager.QMakeBuildStep
127 | false
128 |
129 | false
130 | false
131 | true
132 |
133 |
134 | true
135 | Qt4ProjectManager.MakeStep
136 |
137 | false
138 |
139 |
140 | false
141 |
142 | 2
143 | Build
144 | Build
145 | ProjectExplorer.BuildSteps.Build
146 |
147 |
148 |
149 | true
150 | Qt4ProjectManager.MakeStep
151 |
152 | true
153 | clean
154 |
155 | false
156 |
157 | 1
158 | Clean
159 | Clean
160 | ProjectExplorer.BuildSteps.Clean
161 |
162 | 2
163 | false
164 |
165 | Release
166 | Qt4ProjectManager.Qt4BuildConfiguration
167 | 0
168 |
169 |
170 | C:/Users/fangwen/Documents/build-chatroom-Desktop_Qt_5_14_2_MinGW_64_bit-Profile
171 |
172 |
173 | true
174 | QtProjectManager.QMakeBuildStep
175 | true
176 |
177 | false
178 | true
179 | true
180 |
181 |
182 | true
183 | Qt4ProjectManager.MakeStep
184 |
185 | false
186 |
187 |
188 | false
189 |
190 | 2
191 | Build
192 | Build
193 | ProjectExplorer.BuildSteps.Build
194 |
195 |
196 |
197 | true
198 | Qt4ProjectManager.MakeStep
199 |
200 | true
201 | clean
202 |
203 | false
204 |
205 | 1
206 | Clean
207 | Clean
208 | ProjectExplorer.BuildSteps.Clean
209 |
210 | 2
211 | false
212 |
213 | Profile
214 | Qt4ProjectManager.Qt4BuildConfiguration
215 | 0
216 |
217 | 3
218 |
219 |
220 | 0
221 | Deploy
222 | Deploy
223 | ProjectExplorer.BuildSteps.Deploy
224 |
225 | 1
226 | ProjectExplorer.DefaultDeployConfiguration
227 |
228 | 1
229 |
230 |
231 | dwarf
232 |
233 | cpu-cycles
234 |
235 |
236 | 250
237 |
238 | -e
239 | cpu-cycles
240 | --call-graph
241 | dwarf,4096
242 | -F
243 | 250
244 |
245 | -F
246 | true
247 | 4096
248 | false
249 | false
250 | 1000
251 |
252 | true
253 |
254 | false
255 | false
256 | false
257 | false
258 | true
259 | 0.01
260 | 10
261 | true
262 | kcachegrind
263 | 1
264 | 25
265 |
266 | 1
267 | true
268 | false
269 | true
270 | valgrind
271 |
272 | 0
273 | 1
274 | 2
275 | 3
276 | 4
277 | 5
278 | 6
279 | 7
280 | 8
281 | 9
282 | 10
283 | 11
284 | 12
285 | 13
286 | 14
287 |
288 | 2
289 |
290 | chatroom2
291 | Qt4ProjectManager.Qt4RunConfiguration:C:/Users/fangwen/Documents/chatroom/chatroom.pro
292 | C:/Users/fangwen/Documents/chatroom/chatroom.pro
293 |
294 | false
295 |
296 | false
297 | true
298 | true
299 | false
300 | false
301 | true
302 |
303 |
304 |
305 | 1
306 |
307 |
308 |
309 | ProjectExplorer.Project.TargetCount
310 | 1
311 |
312 |
313 | ProjectExplorer.Project.Updater.FileVersion
314 | 22
315 |
316 |
317 | Version
318 | 22
319 |
320 |
321 |
--------------------------------------------------------------------------------
/client/source/client.cpp:
--------------------------------------------------------------------------------
1 | #include "client.h"
2 |
3 | Client::Client()
4 | {
5 |
6 | }
7 |
8 | QJsonObject Client::readServerMsg(){
9 | readHeadData();
10 | tcpClient.read(buffer, dataLength);
11 | QString serverMsg = QString::fromStdString(string(buffer, dataLength));
12 | QJsonObject serverInfoObj = QJsonDocument::fromJson(serverMsg.toUtf8()).object();
13 | return serverInfoObj;
14 | }
15 |
16 | void Client:: readHeadData(){
17 | int size = tcpClient.read(buffer, BASE_BUFFER_SIZE);
18 | DataParser parser(buffer);
19 | parser.baseParse();
20 | protocolId = parser.getProtocolId();
21 | account = parser.getAccount();
22 | dataType = parser.getDataType();
23 | dataLength = parser.getDataLength();
24 | }
25 |
26 | void Client:: writeText(unsigned int account ,string text, unsigned int protocolId){
27 | DataEncoder encoder;
28 | qDebug() << "------------输入的字节数-------------" << text.length();
29 | string headStr = encoder.encode(protocolId, account, TEXT, text.length());
30 | tcpClient.write(headStr.data(), headStr.length());
31 | qDebug() << "------------头部大小-------------" << headStr.length();
32 | //text = headStr + text;
33 | if(text.length() != 0){
34 | tcpClient.write(text.data(), text.length());
35 | }
36 | qDebug() << "------------发送成功,总数据大小-------------" << text.length();
37 | }
38 |
39 | QTcpSocket * Client::getTcpClient(){
40 | return &tcpClient;
41 | }
42 |
43 | void Client:: closeTcpSocket(){
44 | tcpClient.disconnectFromHost();
45 | tcpClient.close();
46 | }
47 |
48 | Client::~Client(){
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/client/source/client.h:
--------------------------------------------------------------------------------
1 | #ifndef CLIENT_H
2 | #define CLIENT_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include"dataparser.h"
15 | #include"dataencoder.h"
16 | #include"configreader.h"
17 | #define IMAGE_PATH "./image/"
18 | #define TCP_BUFSIZ 8192
19 | using namespace std;
20 |
21 | class Client
22 | {
23 | public:
24 | QString hostName;
25 | quint16 port;
26 | QTcpSocket tcpClient;
27 | char buffer[TCP_BUFSIZ];
28 | unsigned int protocolId;
29 | unsigned int account;
30 | unsigned int dataType;
31 | unsigned int dataLength;
32 | const string ACK_PACKET = DataEncoder().encode(ACK,0,0,0);
33 |
34 | public:
35 | Client();
36 |
37 | void readHeadData();
38 |
39 | QJsonObject readServerMsg();
40 |
41 | void writeText(unsigned int account ,string text, unsigned int protocolId = SEND);
42 |
43 | void closeTcpSocket();
44 |
45 | QTcpSocket * getTcpClient();
46 |
47 | ~Client();
48 | };
49 |
50 | #endif // CLIENT_H
51 |
--------------------------------------------------------------------------------
/client/source/config/server.conf:
--------------------------------------------------------------------------------
1 | HOST=127.0.0.1
2 | PORT=8888
3 |
--------------------------------------------------------------------------------
/client/source/configreader.cpp:
--------------------------------------------------------------------------------
1 | #include "configreader.h"
2 |
3 | ConfigReader::ConfigReader()
4 | {
5 | }
6 | unordered_map ConfigReader::readServerConfig(){
7 | ifstream in(SERVER_CONFIG_PATH, ios::in);
8 | unordered_map configMap;
9 | if(in.is_open()){
10 | string line;
11 | while(getline(in, line)){
12 | int index = line.find('=');
13 | configMap[line.substr(0, index)] = line.substr(index+1);
14 | }
15 | }
16 | return configMap;
17 | }
18 | string ConfigReader::SERVER_CONFIG_PATH = "./config/server.conf";
19 |
--------------------------------------------------------------------------------
/client/source/configreader.h:
--------------------------------------------------------------------------------
1 | #ifndef CONFIGREADER_H
2 | #define CONFIGREADER_H
3 | #include
4 | #include
5 | #include
6 | using namespace std;
7 | class ConfigReader
8 | {
9 | private:
10 | static string SERVER_CONFIG_PATH;
11 | public:
12 | ConfigReader();
13 | static unordered_map readServerConfig();
14 | };
15 |
16 | #endif // CONFIGREADER_H
17 |
--------------------------------------------------------------------------------
/client/source/dataencoder.cpp:
--------------------------------------------------------------------------------
1 | #include "dataencoder.h"
2 | #include
3 | DataEncoder::DataEncoder() {
4 |
5 | }
6 |
7 | string DataEncoder::encode(unsigned int protocolId, unsigned int account, unsigned int dataType, unsigned int dataLength) {
8 | hp = head;
9 | encodeElement(protocolId, PROTOCOL_ID_SIZE);
10 | encodeElement(account, ACCOUNT_SIZE);
11 | encodeElement(dataType, DATA_TYPE_SIZE);
12 | encodeElement(dataLength, DATA_SIZE);
13 | return string(head,sizeof(head));
14 | }
15 |
16 | void DataEncoder::encodeElement(unsigned int data, unsigned int len) {
17 | char* c = hp + len - 1;
18 | for (int i = len; i > 0; i--) {
19 | *c = (char)(data & 0xff);
20 | c--;
21 | data >>= 8;
22 | }
23 | hp = hp + len;
24 | }
25 |
--------------------------------------------------------------------------------
/client/source/dataencoder.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #ifndef ENCODEPACKET_H
3 | #define ENCODEPACKET_H
4 | #include"protocolmsg.h"
5 | #include
6 | #include
7 | using namespace std;
8 | class DataEncoder
9 | {
10 | private:
11 | char head[BASE_BUFFER_SIZE];
12 | char * hp;
13 | void encodeElement(unsigned int data, unsigned int len);
14 |
15 | public:
16 | DataEncoder();
17 | string encode(unsigned int protocolId, unsigned int account, unsigned int dataType, unsigned int dataLength);
18 |
19 | };
20 |
21 |
22 | #endif // ENCODEPACKET_H
23 |
--------------------------------------------------------------------------------
/client/source/dataparser.cpp:
--------------------------------------------------------------------------------
1 | #include "dataparser.h"
2 |
3 | DataParser::DataParser(char * buffer)
4 | {
5 | bp = buffer;
6 | }
7 |
8 | bool DataParser:: baseParse() {
9 | this->protocolId = parseInt(PROTOCOL_ID_SIZE);
10 | this->account = parseInt(ACCOUNT_SIZE);
11 | this->dataType = parseInt(DATA_TYPE_SIZE);
12 | this->dataLength = parseInt(DATA_SIZE);
13 | }
14 |
15 | unsigned int DataParser::parseInt(int len) {
16 | unsigned int sum = 0;
17 | unsigned int i = 0;
18 | for (char* end = bp + len - 1; bp <= end; end--) {
19 | sum = sum + (((unsigned int)((unsigned char)(*end))) << i);
20 | i += 8;
21 | }
22 | bp = bp + len;
23 | return sum;
24 | }
25 |
26 | unsigned int DataParser::getProtocolId() {
27 | return this->protocolId;
28 | }
29 | unsigned int DataParser::getAccount() {
30 | return this->account;
31 | }
32 | unsigned int DataParser::getDataType() {
33 | return this->dataType;
34 | }
35 | unsigned int DataParser::getDataLength() {
36 | return this->dataLength;
37 | }
38 |
39 | DataParser::~DataParser() {
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/client/source/dataparser.h:
--------------------------------------------------------------------------------
1 | #ifndef DATAPARSER_H
2 | #define DATAPARSER_H
3 | #include"protocolmsg.h"
4 | class DataParser
5 | {
6 | //协议号(1B)-账号(2B)-数据类型(1B)-数据长度(4B)-数据
7 | private:
8 |
9 | char* bp;
10 | unsigned int protocolId;
11 | unsigned int account;
12 | unsigned int dataType;
13 | unsigned int dataLength;
14 |
15 | unsigned int parseInt(int len);
16 |
17 | public:
18 |
19 | DataParser(char * buffer);
20 |
21 | bool baseParse();
22 |
23 | unsigned int getProtocolId();
24 |
25 | unsigned int getAccount();
26 |
27 | unsigned int getDataType();
28 |
29 | unsigned int getDataLength();
30 |
31 | ~DataParser();
32 | };
33 |
34 | #endif // DATAPARSER_H
35 |
--------------------------------------------------------------------------------
/client/source/icon.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | icon/icon-test.png
4 | icon/Head.png
5 | icon/icon-test(1).png
6 |
7 |
8 |
--------------------------------------------------------------------------------
/client/source/login.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | LoginForm
4 |
5 |
6 |
7 | 0
8 | 0
9 | 400
10 | 300
11 |
12 |
13 |
14 | Form
15 |
16 |
17 |
18 |
19 | 60
20 | 100
21 | 81
22 | 16
23 |
24 |
25 |
26 | 用户名
27 |
28 |
29 |
30 |
31 |
32 | 60
33 | 130
34 | 54
35 | 12
36 |
37 |
38 |
39 | 密码
40 |
41 |
42 |
43 |
44 |
45 | 110
46 | 100
47 | 113
48 | 20
49 |
50 |
51 |
52 |
53 |
54 |
55 | 110
56 | 130
57 | 113
58 | 20
59 |
60 |
61 |
62 |
63 |
64 |
65 | 110
66 | 170
67 | 111
68 | 20
69 |
70 |
71 |
72 | 登录
73 |
74 |
75 |
76 |
77 |
78 | 10
79 | 270
80 | 80
81 | 20
82 |
83 |
84 |
85 | 注册
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/client/source/loginform.cpp:
--------------------------------------------------------------------------------
1 | #include "loginform.h"
2 | #include "ui_loginform.h"
3 | #include "widget.h"
4 | #include"writeclient.h"
5 | #include "readclient.h"
6 | #include "registerform.h"
7 |
8 | #include
9 | #include
10 |
11 | LoginForm::LoginForm(QWidget *parent) :
12 | QWidget(parent),
13 | ui(new Ui::LoginForm)
14 | {
15 | ui->setupUi(this);
16 | connect(ui->loginPushButton, &QPushButton::clicked, this, &LoginForm::connectServer);
17 | connect(ui->registerPushButton, &QPushButton::clicked, this, [=](){
18 | writeClient = WriteClient::getTcpSocketClient();
19 | if(writeClient == nullptr){
20 | qDebug() << "连接服务器超时";
21 | QMessageBox::information(this, "登录提示", "连接服务器超时");
22 | return;
23 | }
24 | RegisterForm * rf = new RegisterForm;
25 | rf->loginForm = this;
26 | rf->show();
27 | this->hide();
28 | });
29 |
30 | }
31 |
32 | LoginForm::~LoginForm()
33 | {
34 | delete ui;
35 | }
36 |
37 | void LoginForm::connectServer(){
38 | qDebug() << "-------------开始连接服务器----------------";
39 | writeClient = WriteClient::getTcpSocketClient();
40 | if(writeClient == nullptr){
41 | qDebug() << "连接服务器超时";
42 | QMessageBox::information(this, "登录提示", "连接服务器超时");
43 | return;
44 | }
45 | QString account = ui->userIdLineEdit->text();
46 | qDebug() << "账号:" << account;
47 | QString password = ui->passwordLineEdit->text();
48 | qDebug() << "密码:" << password;
49 |
50 | QJsonObject information;
51 | information.insert("account", account);
52 | information.insert("password", password);
53 | QJsonDocument doc;
54 | doc.setObject(information);
55 | string loginMsg =doc.toJson(QJsonDocument::Compact).toStdString();
56 | writeClient->writeText(account.toUtf8().toInt(), loginMsg, LOGIN);
57 | writeClient->getTcpClient()->waitForReadyRead(-1);
58 | qDebug() << "收到后台确认信息---------------------";
59 | QJsonObject serverInfoObj = writeClient->readServerMsg();
60 | qDebug() << serverInfoObj["status"];
61 | int status = serverInfoObj["status"].toInt();
62 | qDebug() << "status:" << status;
63 | if(status == LOGIN_SUCCESS){
64 | readClient = ReadClient::getTcpSocketClient();
65 | qDebug() << "登陆成功, 开始read端---------";
66 | //readClient->writeText(account, "", READ);
67 | Widget * widget = new Widget;
68 | widget->loginForm = this;
69 | user = new User(account.toUtf8().toInt(), serverInfoObj["username"].toString());
70 | widget->user = user;
71 | qDebug() << "account:" << account << "->" << "username:" << serverInfoObj["username"].toString();
72 | widget->disPlayUserInfo();
73 | widget->show();
74 | readClient->writeText(account.toUtf8().toInt(), "", READ);
75 | this->hide();
76 | }
77 | else if(status == LOGIN_FAIL){
78 | QMessageBox::information(this, "登录提示", "账号或密码错误,请重试");
79 | }
80 | else if(status == LOGIN_EXIST){
81 | QMessageBox::information(this, "登录提示", "该账号已登录,不可重复登录");
82 | }
83 | }
84 |
85 | void LoginForm::closeEvent ( QCloseEvent * e )
86 | {
87 | qDebug() << "loginform关闭了";
88 | int userId = user != nullptr? user->getUserId():0;
89 |
90 | if(readClient != nullptr){
91 | qDebug() << "readClient关闭了";
92 | writeClient->writeText(userId, "", LOGOUT);
93 | //确保登出信息能够及时发到服务端
94 | sleep(300);
95 | writeClient->getTcpClient()->disconnectFromHost();
96 | sleep(100);
97 | readClient->getTcpClient()->disconnectFromHost();
98 | sleep(100);
99 | }
100 | else if(writeClient != nullptr){
101 | qDebug() << "writeClient关闭了";
102 | //确保关闭信息能够及时发到服务端
103 | writeClient->writeText(userId, "", CLOSE);
104 | sleep(300);
105 | writeClient->getTcpClient()->disconnectFromHost();
106 | sleep(100);
107 | }
108 | }
109 |
110 | void LoginForm::sleep(int msec){
111 | QEventLoop eventloop;
112 | QTimer::singleShot(msec, &eventloop, SLOT(quit()));
113 | eventloop.exec();
114 | }
115 |
116 |
117 |
--------------------------------------------------------------------------------
/client/source/loginform.h:
--------------------------------------------------------------------------------
1 | #ifndef LOGINFORM_H
2 | #define LOGINFORM_H
3 |
4 | #include
5 | #include
6 | #include"dataencoder.h"
7 | #include "readclient.h"
8 | #include "user.h"
9 | #include "writeclient.h"
10 |
11 | namespace Ui {
12 | class LoginForm;
13 | }
14 |
15 | class LoginForm : public QWidget
16 | {
17 | Q_OBJECT
18 |
19 | public:
20 | explicit LoginForm(QWidget *parent = nullptr);
21 | ~LoginForm();
22 | void connectServer();
23 |
24 | private:
25 | Ui::LoginForm *ui;
26 | WriteClient * writeClient = nullptr;
27 | ReadClient * readClient = nullptr;
28 | User * user = nullptr;
29 | void closeEvent(QCloseEvent * e);
30 | void sleep(int msec);
31 | };
32 |
33 | #endif // LOGINFORM_H
34 |
--------------------------------------------------------------------------------
/client/source/loginform.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | LoginForm
4 |
5 |
6 |
7 | 0
8 | 0
9 | 400
10 | 300
11 |
12 |
13 |
14 |
15 | 400
16 | 300
17 |
18 |
19 |
20 |
21 | 400
22 | 300
23 |
24 |
25 |
26 | 登录窗口
27 |
28 |
29 | -
30 |
31 |
32 | Qt::Vertical
33 |
34 |
35 |
36 | 20
37 | 40
38 |
39 |
40 |
41 |
42 | -
43 |
44 |
45 |
46 | 0
47 | 0
48 |
49 |
50 |
51 |
-
52 |
53 |
54 | 密码
55 |
56 |
57 |
58 | -
59 |
60 |
61 | 16
62 |
63 |
64 | QLineEdit::Password
65 |
66 |
67 |
68 | -
69 |
70 |
71 | 6
72 |
73 |
74 |
75 | -
76 |
77 |
78 | Qt::Horizontal
79 |
80 |
81 |
82 | 40
83 | 20
84 |
85 |
86 |
87 |
88 | -
89 |
90 |
91 | Qt::Horizontal
92 |
93 |
94 |
95 | 40
96 | 20
97 |
98 |
99 |
100 |
101 | -
102 |
103 |
104 | 用户名
105 |
106 |
107 |
108 | -
109 |
110 |
111 | Qt::Vertical
112 |
113 |
114 |
115 | 20
116 | 10
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 | -
125 |
126 |
127 |
128 | 0
129 | 0
130 |
131 |
132 |
133 |
-
134 |
135 |
136 | Qt::Horizontal
137 |
138 |
139 |
140 | 60
141 | 20
142 |
143 |
144 |
145 |
146 | -
147 |
148 |
149 | 注册
150 |
151 |
152 |
153 | -
154 |
155 |
156 | 登录
157 |
158 |
159 |
160 | -
161 |
162 |
163 | Qt::Horizontal
164 |
165 |
166 |
167 | 80
168 | 20
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | -
177 |
178 |
179 | Qt::Vertical
180 |
181 |
182 |
183 | 20
184 | 40
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
--------------------------------------------------------------------------------
/client/source/main.cpp:
--------------------------------------------------------------------------------
1 | #include "widget.h"
2 | #include "loginform.h"
3 | #include
4 |
5 | int main(int argc, char *argv[])
6 | {
7 | QApplication a(argc, argv);
8 | LoginForm loginForm;
9 | loginForm.show();
10 | return a.exec();
11 | }
12 |
--------------------------------------------------------------------------------
/client/source/mytime.cpp:
--------------------------------------------------------------------------------
1 | #include "mytime.h"
2 |
3 | string MyTime::getCurrentFormatTimeStr() {
4 | time_t timep;
5 | time(&timep);
6 | char tmp[64] = { '\0' };
7 | strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", localtime(&timep));
8 | return string(tmp);
9 | }
10 |
11 | string MyTime::getTimeStampStr(){
12 | struct timeval stamp;
13 | gettimeofday(&stamp, NULL);
14 | return to_string(stamp.tv_sec) + to_string(stamp.tv_usec);
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/client/source/mytime.h:
--------------------------------------------------------------------------------
1 | #ifndef MYTIME_H
2 | #define MYTIME_H
3 |
4 |
5 | #include
6 | #include
7 | #include
8 | using namespace std;
9 | class MyTime
10 | {
11 | public:
12 | static string getCurrentFormatTimeStr();
13 | static string getTimeStampStr();
14 | };
15 |
16 |
17 | #endif // MYTIME_H
18 |
--------------------------------------------------------------------------------
/client/source/protocolmsg.h:
--------------------------------------------------------------------------------
1 | #ifndef PROTOCOLMSG_H
2 | #define PROTOCOLMSG_H
3 | #define BASE_BUFFER_SIZE 8
4 | #define PROTOCOL_ID_SIZE 1
5 | #define ACCOUNT_SIZE 2
6 | #define DATA_TYPE_SIZE 1
7 | #define DATA_SIZE 4
8 | #define LOGIN 1
9 | #define SEND 2
10 | #define READ 3
11 | #define NOTICE 4
12 | #define ACK 5
13 | #define LOGOUT 6
14 | #define REGISTER 7
15 | #define ONLINELIST 8
16 | #define CLOSE 9
17 | #define TEXT 1
18 | #define IMAGE 2
19 | #define LOGIN_SUCCESS 0
20 | #define LOGIN_FAIL 1
21 | #define LOGIN_EXIST 2
22 | #define REGISTER_SUCCESS 0
23 | #define REGISTER_FAIL 1
24 | #endif // PROTOCOLMSG_H
25 |
--------------------------------------------------------------------------------
/client/source/readclient.cpp:
--------------------------------------------------------------------------------
1 | #include "mytime.h"
2 | #include "readclient.h"
3 |
4 | ReadClient::ReadClient()
5 | {
6 |
7 | }
8 | ReadClient * ReadClient::readClient = nullptr;
9 | ReadClient * ReadClient::getTcpSocketClient(){
10 | if(readClient == nullptr){
11 | readClient = new ReadClient;
12 | unordered_map configMap = ConfigReader::readServerConfig();
13 | qDebug() << "从配置文件读取的" << QString::fromStdString(configMap["HOST"]) << "->" << QString::fromStdString(configMap["PORT"]);
14 | readClient->hostName = QString::fromStdString(configMap["HOST"]);
15 | readClient->port = stoi(configMap["PORT"]);
16 | qDebug() << "------------正在连接服务器--------------";
17 | readClient->tcpClient.connectToHost(readClient->hostName, readClient->port);
18 | qDebug() << "------------连接服务器成功--------------";
19 | }
20 | return readClient;
21 | }
22 |
23 | bool ReadClient::readData(){
24 | //读取数据头
25 | readHeadData();
26 | //确认信息
27 | tcpClient.write(ACK_PACKET.data(), ACK_PACKET.length());
28 | content = "";
29 | qDebug() << "----------数据长度-----------" << dataLength;
30 | if(dataType == TEXT){
31 | readTextContent();
32 | return true;
33 | }
34 | else if(dataType == IMAGE){
35 | readImageContent();
36 | return true;
37 | }
38 | return false;
39 | }
40 |
41 | void ReadClient::readTextContent(){
42 | while(tcpClient.waitForReadyRead(-1)){
43 | unsigned int size = 0;
44 | qDebug() << "-------readTextContent需要读取的字节数---------" << dataLength;
45 | unsigned int splitDataLength = tcpClient.bytesAvailable();
46 | unsigned int bufsize = TCP_BUFSIZ;
47 | while(dataLength != 0 && tcpClient.bytesAvailable()){
48 | qDebug() << "-----------------可读数据------------" << tcpClient.bytesAvailable();
49 | size = tcpClient.read(buffer, min(bufsize, dataLength));
50 | dataLength -= size;
51 | qDebug() << "-------readTextContent剩余读取的字节数---------" << dataLength;
52 | content = content + string(buffer, size);
53 | }
54 | // tcpClient.write(ACK_PACKET.data(), ACK_PACKET.length());
55 | string SPLIT_ACK_PACKET = DataEncoder().encode(ACK,account,TEXT,splitDataLength);
56 | tcpClient.write(SPLIT_ACK_PACKET.data(), SPLIT_ACK_PACKET.length());
57 | if(dataLength == 0){
58 | switch(protocolId){
59 | case SEND: emit textDisplayAble();break;
60 | case NOTICE:notice = content; emit noticeDisplayAble();break;
61 | case ONLINELIST: emit onlineDisplayAble();break;
62 | }
63 | break;
64 | }
65 | qDebug() << "-----------readTextContent剩余读取的字节数------" << dataLength;
66 | }
67 | qDebug() << "读取完毕.......";
68 | }
69 |
70 | void ReadClient::readImageContent(){
71 | //获取当前时间
72 | imagePath = IMAGE_PATH + MyTime::getTimeStampStr() + ".png";
73 | //保存图片
74 | ofstream os(imagePath, ios::out | ios::binary | ios::app);
75 | while(tcpClient.waitForReadyRead(-1)){
76 | unsigned int size = 0;
77 | qDebug() << "-------readImageContent需要读取的字节数---------" << dataLength;
78 | unsigned int splitDataLength = tcpClient.bytesAvailable();
79 | unsigned int bufsize = TCP_BUFSIZ;
80 | while(dataLength != 0 && tcpClient.bytesAvailable()){
81 | qDebug() << "-----------------可读数据------------" << tcpClient.bytesAvailable();
82 | size = tcpClient.read(buffer, min(bufsize, dataLength));
83 | dataLength -= size;
84 | qDebug() << "-------readImageContent剩余读取的字节数---------" << dataLength;
85 | os.write(buffer,size);
86 | }
87 | string SPLIT_ACK_PACKET = DataEncoder().encode(ACK,0,0,splitDataLength);
88 | tcpClient.write(SPLIT_ACK_PACKET.data(), SPLIT_ACK_PACKET.length());
89 | if(dataLength == 0){
90 | break;
91 | }
92 | qDebug() << "-----------readTextContent一轮读取后剩余读取的字节数------" << dataLength;
93 | }
94 | os.close();
95 | emit imageDisplayAble();
96 | qDebug() << "图片读取完毕.......";
97 | }
98 |
99 | string ReadClient::getTextContent(){
100 | return content;
101 | }
102 |
103 | string ReadClient::getNoticeContent(){
104 | return notice;
105 | }
106 |
107 | string ReadClient::getImagePath(){
108 | return imagePath;
109 | }
110 |
111 | ReadClient::~ReadClient(){
112 | delete readClient;
113 | readClient = nullptr;
114 | }
115 |
--------------------------------------------------------------------------------
/client/source/readclient.h:
--------------------------------------------------------------------------------
1 | #ifndef READCLIENT_H
2 | #define READCLIENT_H
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include"dataparser.h"
14 | #include"dataencoder.h"
15 | #include "client.h"
16 |
17 | using namespace std;
18 | class ReadClient : public QObject, public Client
19 | {
20 | Q_OBJECT
21 | private:
22 | ReadClient();
23 | static ReadClient *readClient;
24 | string imagePath;
25 | string content = "";
26 | string notice = "";
27 | public:
28 | static ReadClient * getTcpSocketClient();
29 |
30 | bool readData();
31 |
32 | void readTextContent();
33 |
34 | void readImageContent();
35 |
36 | string getTextContent();
37 |
38 | string getNoticeContent();
39 |
40 | string getImagePath();
41 |
42 | ~ReadClient();
43 | signals:
44 | void textDisplayAble();
45 | void imageDisplayAble();
46 | void noticeDisplayAble();
47 | void onlineDisplayAble();
48 | };
49 |
50 | #endif // READCLIENT_H
51 |
--------------------------------------------------------------------------------
/client/source/readthread.cpp:
--------------------------------------------------------------------------------
1 | #include "readthread.h"
2 | #include "readclient.h"
3 | #include
4 | ReadThread::ReadThread()
5 | {
6 |
7 | }
8 |
9 | void ReadThread::run(){
10 | // Client * client = Client::getTcpSocketClient();
11 | // QTcpSocket * tcpSocket = client->getTcpClient();
12 | ReadClient * readClient = ReadClient::getTcpSocketClient();
13 | QTcpSocket * tcpSocket = readClient->getTcpClient();
14 | while(true){
15 | if(tcpSocket->waitForReadyRead(-1)){
16 | readClient->readData();
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/client/source/readthread.h:
--------------------------------------------------------------------------------
1 | #ifndef READTHREAD_H
2 | #define READTHREAD_H
3 | #include
4 |
5 | class ReadThread : public QThread
6 | {
7 | public:
8 | ReadThread();
9 | protected:
10 | void run() override;
11 | private:
12 |
13 | };
14 |
15 | #endif // READTHREAD_H
16 |
--------------------------------------------------------------------------------
/client/source/registerForm.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | RegisterForm
4 |
5 |
6 |
7 | 0
8 | 0
9 | 400
10 | 300
11 |
12 |
13 |
14 | 注册窗口
15 |
16 |
17 |
18 |
19 | 10
20 | 63
21 | 382
22 | 121
23 |
24 |
25 |
26 |
27 | 0
28 | 0
29 |
30 |
31 |
32 | -
33 |
34 |
35 | Qt::Horizontal
36 |
37 |
38 |
39 | 40
40 | 20
41 |
42 |
43 |
44 |
45 | -
46 |
47 |
48 | 账号
49 |
50 |
51 |
52 | -
53 |
54 |
55 | 密码
56 |
57 |
58 |
59 | -
60 |
61 |
62 | 昵称
63 |
64 |
65 |
66 | -
67 |
68 |
69 | 6
70 |
71 |
72 |
73 | -
74 |
75 |
76 | 16
77 |
78 |
79 | QLineEdit::Password
80 |
81 |
82 |
83 | -
84 |
85 |
86 | 9
87 |
88 |
89 |
90 | -
91 |
92 |
93 | Qt::Horizontal
94 |
95 |
96 |
97 | 40
98 | 20
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | 10
109 | 190
110 | 382
111 | 61
112 |
113 |
114 |
115 |
116 | 0
117 | 0
118 |
119 |
120 |
121 | -
122 |
123 |
124 | Qt::Horizontal
125 |
126 |
127 |
128 | 80
129 | 20
130 |
131 |
132 |
133 |
134 | -
135 |
136 |
137 | 提交
138 |
139 |
140 |
141 | -
142 |
143 |
144 | Qt::Horizontal
145 |
146 |
147 |
148 | 40
149 | 20
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
--------------------------------------------------------------------------------
/client/source/registerform.cpp:
--------------------------------------------------------------------------------
1 | #include "registerform.h"
2 | #include "ui_registerform.h"
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | RegisterForm::RegisterForm(QWidget *parent) :
11 | QWidget(parent),
12 | ui(new Ui::RegisterForm)
13 | {
14 | ui->setupUi(this);
15 | //只能输入数字
16 | writeClient = WriteClient::getTcpSocketClient();
17 | ui->accountLineEdit->setValidator(new QRegExpValidator(QRegExp("[0-9]{3,6}$")));
18 | ui->usernameLineEdit->setValidator(new QRegExpValidator(QRegExp("[\u4e00-\u9fa5a-zA-Z]{1,9}$")));
19 | ui->passwordLineEdit->setValidator(new QRegExpValidator(QRegExp("[0-9a-zA-Z]{6,16}$")));
20 | connect(ui->submitPushButton, &QPushButton::clicked, this, [=](){
21 | QString account = ui->accountLineEdit->text();
22 | if(account.length() < 3){
23 | QMessageBox::information(this,"注册提示","账号长度应为3-6位");
24 | return;
25 | }
26 | if(account.toInt() > 65535){
27 | QMessageBox::information(this,"注册提示","账号数字不能超过65535");
28 | return;
29 | }
30 | QString username = ui->usernameLineEdit->text();
31 | if(username.length() < 3){
32 | QMessageBox::information(this,"注册提示","昵称长度应为3-9位");
33 | return;
34 | }
35 | QString password = ui->passwordLineEdit->text();
36 | if(password.length() < 6){
37 | QMessageBox::information(this,"注册提示","密码长度应为6-16位");
38 | return;
39 | }
40 | QJsonObject information;
41 | information.insert("account", account);
42 | information.insert("username", username);
43 | information.insert("password", password);
44 | QJsonDocument doc;
45 | doc.setObject(information);
46 | QString s = doc.toJson(QJsonDocument::Compact);
47 | writeClient->writeText(0, s.toStdString(), REGISTER);
48 | writeClient->getTcpClient()->waitForReadyRead(-1);
49 | QJsonObject serverInfoObj = writeClient->readServerMsg();
50 | int status = serverInfoObj["status"].toInt();
51 | if(status == REGISTER_SUCCESS){
52 | QMessageBox::information(this,"注册提示","注册成功");
53 | this->close();
54 | }
55 | else if(status == REGISTER_FAIL){
56 | QMessageBox::information(this,"注册提示","账号已存在, 请填写其它账号");
57 | }
58 | });
59 | }
60 |
61 | RegisterForm::~RegisterForm()
62 | {
63 | delete ui;
64 | }
65 |
66 | void RegisterForm::closeEvent ( QCloseEvent * e )
67 | {
68 | this->loginForm->show();
69 | //this->close();
70 | }
71 |
--------------------------------------------------------------------------------
/client/source/registerform.h:
--------------------------------------------------------------------------------
1 | #ifndef REGISTERFORM_H
2 | #define REGISTERFORM_H
3 |
4 | #include "loginform.h"
5 | #include "writeclient.h"
6 |
7 | #include
8 |
9 | namespace Ui {
10 | class RegisterForm;
11 | }
12 |
13 | class RegisterForm : public QWidget
14 | {
15 | Q_OBJECT
16 |
17 | public:
18 | explicit RegisterForm(QWidget *parent = nullptr);
19 | ~RegisterForm();
20 | LoginForm * loginForm;
21 |
22 | private:
23 | Ui::RegisterForm *ui;
24 | WriteClient *writeClient;
25 | void closeEvent(QCloseEvent * e);
26 | };
27 |
28 | #endif // REGISTERFORM_H
29 |
--------------------------------------------------------------------------------
/client/source/user.cpp:
--------------------------------------------------------------------------------
1 | #include "user.h"
2 |
3 | User::User(unsigned int userId, QString username)
4 | {
5 | this->userId = userId;
6 | this->username = username;
7 | }
8 |
9 | QString User::getUsername(){
10 | return username;
11 | }
12 | unsigned int User::getUserId(){
13 | return userId;
14 | }
15 |
--------------------------------------------------------------------------------
/client/source/user.h:
--------------------------------------------------------------------------------
1 | #ifndef USER_H
2 | #define USER_H
3 | #include
4 | #include
5 | using namespace std;
6 |
7 | class User
8 | {
9 | private:
10 | QString username;
11 | unsigned int userId;
12 | public:
13 | User(unsigned int userId, QString username);
14 |
15 | QString getUsername();
16 |
17 | unsigned int getUserId();
18 | };
19 |
20 | #endif // USER_H
21 |
--------------------------------------------------------------------------------
/client/source/widget.cpp:
--------------------------------------------------------------------------------
1 | #include "widget.h"
2 | #include "ui_widget.h"
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | Widget::Widget(QWidget *parent)
12 | : QWidget(parent)
13 | , ui(new Ui::Widget)
14 | {
15 | ui->setupUi(this);
16 | //获取客户端连接
17 | writeClient = WriteClient::getTcpSocketClient();
18 | //获取读端
19 | qDebug() << "开始read端1---------";
20 | readClient = ReadClient::getTcpSocketClient();
21 | //发送文本数据
22 | connect(ui->sendTextPushButton, &QPushButton::clicked, this, [=](){
23 | QString text = ui->inputTextEdit->toPlainText();
24 | if(text.trimmed().length() == 0){
25 | QMessageBox::information(this,"发送提示","不能发送空内容");
26 | return;
27 | }
28 | writeClient->writeText(user->getUserId(), text.toStdString());
29 | ui->inputTextEdit->clear();
30 | });
31 | thread = new ReadThread;
32 | thread->start();
33 | //发送图片
34 | connect(ui->sendImagePushButton, &QPushButton::clicked, this, [=](){
35 | QString fileName=QFileDialog::getOpenFileName(this,tr("Open Image"),".",tr("Image Files(*.png *.jpg *jpeg *.bmp)"));
36 | qDebug() << "文件名---------------" << fileName;
37 | QTextCodec *code = QTextCodec::codecForName("GB2312");//解决中文路径问题
38 | string imagePath = code->fromUnicode(fileName).data();
39 | //string imagePath = fileName.toStdString();
40 | if(imagePath != ""){
41 | writeClient->writeImage(user->getUserId(), imagePath);
42 | qDebug() << "发送文件成功";
43 | }
44 | });
45 |
46 | connect(readClient, &ReadClient::textDisplayAble, this, [=](){
47 | ui->contentListWidget->addItem(QString::fromStdString(readClient->getTextContent()));
48 | });
49 | ui->contentListWidget->setIconSize(QSize(200,200));
50 | connect(readClient, &ReadClient::imageDisplayAble, this, [=](){
51 | QListWidgetItem * pic = new QListWidgetItem;
52 | pic->setIcon(QIcon(QString::fromStdString(readClient->getImagePath())));
53 | ui->contentListWidget->addItem(pic);
54 | });
55 | connect(ui->exitPushButton, &QPushButton::clicked, this, [=](){
56 | //client->getTcpClient()->waitForReadyRead(3);
57 | //QString logoutMsg = client->readMsg();
58 | // qDebug() << logoutMsg;
59 | thread->exit();
60 | this->close();
61 | this->loginForm->close();
62 | });
63 |
64 | connect(readClient, &ReadClient::noticeDisplayAble, this, [=](){
65 | ui->noticeListWidget->addItem(QString::fromStdString(readClient->getNoticeContent()));
66 | });
67 | connect(readClient, &ReadClient::onlineDisplayAble, this, [=](){
68 | ui->onlineListWidget->clear();
69 | QVariantList onlineList = QJsonDocument::fromJson(QString::fromStdString(readClient->getTextContent()).toUtf8()).toVariant().toList();
70 | for(auto & user: onlineList){
71 | QVariantMap map = user.toMap();
72 | QString element = map["account"].toString() + "\t" + map["username"].toString() + "\t" + map["loginTime"].toString();
73 | ui->onlineListWidget->addItem(element);
74 | }
75 | ui->onlineLabel->setText("当前在线人数(" + QString::number(onlineList.size()) + "人)");
76 | });
77 |
78 | }
79 |
80 | Widget::~Widget()
81 | {
82 | delete ui;
83 | delete user;
84 | }
85 |
86 | void Widget::disPlayUserInfo(){
87 | ui->userLabel->setText(user->getUsername() + "(" + QString::number(user->getUserId()) + ")");
88 | }
89 |
90 | void Widget::closeEvent ( QCloseEvent * e )
91 | {
92 | thread->exit();
93 | this->loginForm->close();
94 | //this->close();
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/client/source/widget.h:
--------------------------------------------------------------------------------
1 | #ifndef WIDGET_H
2 | #define WIDGET_H
3 |
4 | #include
5 | #include "writeclient.h"
6 | #include "loginform.h"
7 | #include "readclient.h"
8 | #include "readthread.h"
9 | #include "user.h"
10 | QT_BEGIN_NAMESPACE
11 | namespace Ui { class Widget; }
12 | QT_END_NAMESPACE
13 |
14 | class Widget : public QWidget
15 | {
16 | Q_OBJECT
17 |
18 | public:
19 | Widget(QWidget *parent = nullptr);
20 | ~Widget();
21 | void disPlayUserInfo();
22 | LoginForm * loginForm;
23 | User * user = nullptr;
24 |
25 | private:
26 | Ui::Widget *ui;
27 | WriteClient * writeClient = nullptr;
28 | ReadClient * readClient = nullptr;
29 | void closeEvent(QCloseEvent * e);
30 | ReadThread *thread;
31 | };
32 | #endif // WIDGET_H
33 |
--------------------------------------------------------------------------------
/client/source/widget.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Widget
4 |
5 |
6 |
7 | 0
8 | 0
9 | 965
10 | 745
11 |
12 |
13 |
14 |
15 | 965
16 | 745
17 |
18 |
19 |
20 |
21 | 965
22 | 745
23 |
24 |
25 |
26 | 聊天室
27 |
28 |
29 |
30 |
31 | 10
32 | 40
33 | 681
34 | 411
35 |
36 |
37 |
38 |
39 |
40 |
41 | 10
42 | 460
43 | 681
44 | 211
45 |
46 |
47 |
48 |
49 |
50 |
51 | 610
52 | 690
53 | 80
54 | 31
55 |
56 |
57 |
58 | 发送文本
59 |
60 |
61 |
62 |
63 |
64 | 520
65 | 690
66 | 80
67 | 31
68 |
69 |
70 |
71 | 发送图片
72 |
73 |
74 |
75 |
76 |
77 | 10
78 | 690
79 | 80
80 | 31
81 |
82 |
83 |
84 | 退出
85 |
86 |
87 |
88 |
89 |
90 | 710
91 | 40
92 | 231
93 | 271
94 |
95 |
96 |
97 |
98 |
99 |
100 | 710
101 | 350
102 | 231
103 | 321
104 |
105 |
106 |
107 |
108 |
109 |
110 | 800
111 | 10
112 | 51
113 | 21
114 |
115 |
116 |
117 | 公告
118 |
119 |
120 |
121 |
122 |
123 | 770
124 | 320
125 | 151
126 | 20
127 |
128 |
129 |
130 | 当前在线人数(0人)
131 |
132 |
133 |
134 |
135 |
136 | 310
137 | 10
138 | 191
139 | 20
140 |
141 |
142 |
143 | 聊天内容
144 |
145 |
146 |
147 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/client/source/writeclient.cpp:
--------------------------------------------------------------------------------
1 | #include "writeclient.h"
2 |
3 | WriteClient::WriteClient()
4 | {
5 |
6 | }
7 | //初始化静态成员变量
8 | WriteClient* WriteClient::writeClient = nullptr;
9 | WriteClient * WriteClient::getTcpSocketClient(){
10 | if(writeClient == nullptr){
11 | writeClient = new WriteClient;
12 | unordered_map configMap = ConfigReader::readServerConfig();
13 | qDebug() << "从配置文件读取的" << QString::fromStdString(configMap["HOST"]) << "->" << QString::fromStdString(configMap["PORT"]);
14 | writeClient->hostName = QString::fromStdString(configMap["HOST"]);
15 | writeClient->port = stoi(configMap["PORT"]);
16 | qDebug() << "------------正在连接服务器--------------" << writeClient->hostName;
17 | long long start;
18 | time(&start);
19 | writeClient->tcpClient.connectToHost(writeClient->hostName, writeClient->port);
20 | bool connResult = writeClient->tcpClient.waitForConnected(10000);
21 | long long end;
22 | time(&end);
23 | qDebug() << "连接时长" << (end - start);
24 | if(end - start < 8){
25 | qDebug() << "------------连接服务器成功--------------";
26 | }
27 | else {
28 | writeClient = nullptr;
29 | }
30 | }
31 | return writeClient;
32 | }
33 |
34 | void WriteClient:: writeImage(unsigned int account, string imagePath){
35 | ifstream in(imagePath, ios::in | ios::binary);
36 | in.seekg(0, ios::end); //设置文件指针到文件流的尾部
37 | streampos imageSize = in.tellg();
38 | DataEncoder encoder;
39 | string headStr = encoder.encode(SEND, account, IMAGE, imageSize);
40 | in.seekg(0);
41 | qDebug() << "需要发送的图片的字节数-----" << imageSize;
42 | int count = 0;
43 | tcpClient.write(headStr.data(), headStr.length());
44 | while(in.tellg() != -1){
45 | in.read(buffer, TCP_BUFSIZ);
46 | int size = tcpClient.write(buffer, in.gcount());
47 | count += size;
48 | }
49 | qDebug() << "实际发送的字节数---------" << count;
50 | in.close();
51 | }
52 |
53 | WriteClient::~WriteClient(){
54 | delete writeClient;
55 | writeClient = nullptr;
56 | }
57 |
--------------------------------------------------------------------------------
/client/source/writeclient.h:
--------------------------------------------------------------------------------
1 | #ifndef WRITECLIENT_H
2 | #define WRITECLIENT_H
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include"dataparser.h"
14 | #include"dataencoder.h"
15 | #include "client.h"
16 |
17 | using namespace std;
18 | class WriteClient : public QObject, public Client
19 | {
20 | Q_OBJECT
21 | private:
22 | WriteClient();
23 | static WriteClient *writeClient;
24 |
25 | public:
26 | static WriteClient * getTcpSocketClient();
27 |
28 | void writeImage(unsigned int account, string imagePath);
29 |
30 | ~WriteClient();
31 |
32 | };
33 | #endif // WRITECLIENT_H
34 |
--------------------------------------------------------------------------------
/server/Dao/MySQLConnector.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 |
5 | #include "MySQLConnector.h"
6 |
7 | void MySQLConnector::init() {
8 | conn = mysql_init(NULL);
9 | conn = mysql_real_connect(conn, HOST, USERNAME, PASSWORD, DATABASE, PORT, NULL, 0);
10 | if (!conn) {
11 | cout << "mysql_real_connect fail" << endl;
12 | exit(-1);
13 | }
14 | mysql_query(conn, "set names utf8");
15 | }
16 |
17 | MySQLConnector *MySQLConnector::getMySQLConnector() {
18 | if (connector == nullptr) {
19 | connector = new MySQLConnector;
20 | connector->init();
21 | }
22 | return connector;
23 | }
24 |
25 | pair MySQLConnector::queryUser(const string& account, const string& password) {
26 | string querySql =
27 | "select account, username from user where account = " + account + " and password = " + "\"" + password +
28 | "\"";
29 | pair user;
30 | int res = mysql_query(conn, querySql.data());
31 | if (!res) {
32 | MYSQL_RES *result = mysql_store_result(conn);
33 | if (result && mysql_num_rows(result)) {
34 | MYSQL_ROW row = mysql_fetch_row(result);
35 | user.first = atoi(row[0]);
36 | user.second = row[1];
37 | }
38 | mysql_free_result(result);
39 | }
40 | return user;
41 | }
42 |
43 | bool MySQLConnector::queryUser(const string& account) {
44 | string querySql = "select account from user where account = " + account;
45 | int res = mysql_query(conn, querySql.data());
46 | bool flag = false;
47 | if (!res) {
48 | MYSQL_RES *result = mysql_store_result(conn);
49 | if (result && mysql_num_rows(result)) {
50 | flag = true;
51 | }
52 | mysql_free_result(result);
53 | }
54 | return flag;
55 | }
56 |
57 | bool MySQLConnector::insertUser(const string& account, const string& username, const string& password) {
58 | string insertSql = "insert into `user` values(" +
59 | account + ","
60 | "\"" + username + "\","
61 | + "\"" + password + "\","
62 | + "\"" + MyTime::getCurrentFormatTimeStr() + "\")";
63 | int res = mysql_query(conn, insertSql.data());
64 | return res == 0;
65 | }
66 |
67 | MySQLConnector::~MySQLConnector() {
68 | mysql_close(conn);
69 | delete connector;
70 | }
71 | MySQLConnector *MySQLConnector::connector = nullptr;
72 |
--------------------------------------------------------------------------------
/server/Dao/MySQLConnector.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include "../Util/MyTime.h"
11 | #include "../config/mysql_config.h"
12 | using namespace std;
13 |
14 | class MySQLConnector {
15 | private:
16 | MYSQL *conn;
17 | static MySQLConnector *connector;
18 | void init();
19 |
20 | public:
21 | static MySQLConnector *getMySQLConnector();
22 |
23 | pair queryUser(const string& account, const string& password);
24 |
25 | bool queryUser(const string& account);
26 |
27 | bool insertUser(const string& account, const string& username, const string& password);
28 |
29 | ~MySQLConnector();
30 | };
31 |
--------------------------------------------------------------------------------
/server/Makefile:
--------------------------------------------------------------------------------
1 | OBJS = ./out/main.o ./out/MyTime.o ./out/MySQLConnector.o ./out/UserService.o ./out/Online.o ./out/DataEncoder.o ./out/HeadData.o ./out/DataProcesser.o
2 | main: $(OBJS)
3 | g++ -std=c++11 $(OBJS) -o main `mysql_config --cflags --libs` -ljsoncpp
4 | ./out/main.o: main.cpp ./ProtocolHead/HeadData.h ./Service/DataProcesser.h ./Service/UserService.h ./Service/Online.h ./config/server_config.h
5 | g++ -std=c++11 -c main.cpp -o ./out/main.o
6 | ./out/DataProcesser.o: ./Service/DataProcesser.cpp ./ProtocolHead/protocolmsg.h ./ProtocolHead/HeadData.h ./ProtocolHead/DataEncoder.h ./Util/MyTime.h
7 | g++ -std=c++11 -c ./Service/DataProcesser.cpp -o ./out/DataProcesser.o
8 | ./out/HeadData.o: ./ProtocolHead/HeadData.cpp ./ProtocolHead/protocolmsg.h
9 | g++ -std=c++11 -c ./ProtocolHead/HeadData.cpp -o ./out/HeadData.o
10 | ./out/DataEncoder.o: ./ProtocolHead/DataEncoder.cpp ./ProtocolHead/protocolmsg.h
11 | g++ -std=c++11 -c ./ProtocolHead/DataEncoder.cpp -o ./out/DataEncoder.o
12 | ./out/Online.o: ./Service/Online.cpp ./Util/MyTime.h
13 | g++ -std=c++11 -c ./Service/Online.cpp -o ./out/Online.o
14 | ./out/UserService.o: ./Service/UserService.cpp ./Dao/MySQLConnector.h
15 | g++ -std=c++11 -c ./Service/UserService.cpp -o ./out/UserService.o
16 | ./out/MySQLConnector.o: ./Dao/MySQLConnector.cpp ./Util/MyTime.h ./config/mysql_config.h
17 | g++ -std=c++11 -c ./Dao/MySQLConnector.cpp -o ./out/MySQLConnector.o
18 | ./out/MyTime.o: ./Util/MyTime.cpp
19 | g++ -std=c++11 -c ./Util/MyTime.cpp -o ./out/MyTime.o
20 | clean:
21 | rm -rf ./out/*.o main
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/server/ProtocolHead/DataEncoder.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 |
5 | #include "DataEncoder.h"
6 |
7 | void DataEncoder::encodeElement(unsigned int data, unsigned int len) {
8 | char *c = hp + len - 1;
9 | for (int i = len; i > 0; i--) {
10 | *c = (char) (data & 0xff);
11 | c--;
12 | data >>= 8;
13 | }
14 | hp = hp + len;
15 | }
16 |
17 | DataEncoder::DataEncoder() {
18 |
19 | }
20 |
21 | string
22 | DataEncoder::encode(unsigned int protocolId, unsigned int account, unsigned int dataType, unsigned int dataLength) {
23 | hp = head;
24 | encodeElement(protocolId, PROTOCOL_ID_SIZE);
25 | encodeElement(account, ACCOUNT_SIZE);
26 | encodeElement(dataType, DATA_TYPE_SIZE);
27 | encodeElement(dataLength, DATA_SIZE);
28 | return string(head, sizeof(head));
29 | }
30 |
--------------------------------------------------------------------------------
/server/ProtocolHead/DataEncoder.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 |
5 | #pragma once
6 | #include"protocolmsg.h"
7 | #include
8 |
9 | using namespace std;
10 |
11 | class DataEncoder {
12 | private:
13 | char head[BASE_BUFFER_SIZE];
14 | char *hp;
15 |
16 | void encodeElement(unsigned int data, unsigned int len);
17 |
18 | public:
19 | DataEncoder();
20 |
21 | string encode(unsigned int protocolId, unsigned int account, unsigned int dataType, unsigned int dataLength);
22 |
23 | };
24 |
--------------------------------------------------------------------------------
/server/ProtocolHead/HeadData.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 |
5 | #include "HeadData.h"
6 |
7 | bool HeadData::baseParse() {
8 | this->protocolId = parseInt(PROTOCOL_ID_SIZE);
9 | this->account = parseInt(ACCOUNT_SIZE);
10 | this->dataType = parseInt(DATA_TYPE_SIZE);
11 | this->dataLength = parseInt(DATA_SIZE);
12 | }
13 |
14 | unsigned int HeadData::parseInt(int len) {
15 | unsigned int sum = 0;
16 | unsigned int i = 0;
17 | for (const char *end = bp + len - 1; bp <= end; end--) {
18 | sum = sum + (((unsigned int) ((unsigned char) (*end))) << i);
19 | i += 8;
20 | }
21 | bp = bp + len;
22 | return sum;
23 | }
24 |
25 | HeadData::HeadData(int fd) {
26 | read(fd, buffer, BASE_BUFFER_SIZE);
27 | bp = buffer;
28 | baseParse();
29 | }
30 |
31 | HeadData::HeadData() {
32 |
33 | }
34 |
35 | bool HeadData::parse(const char *buffer) {
36 | bp = buffer;
37 | baseParse();
38 | }
39 |
40 | unsigned int HeadData::getProtocolId() {
41 | return this->protocolId;
42 | }
43 |
44 | unsigned int HeadData::getAccount() {
45 | return this->account;
46 | }
47 |
48 | unsigned int HeadData::getDataType() {
49 | return this->dataType;
50 | }
51 |
52 | unsigned int HeadData::getDataLength() {
53 | return this->dataLength;
54 | }
55 |
--------------------------------------------------------------------------------
/server/ProtocolHead/HeadData.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 |
5 | #pragma once
6 | #include"protocolmsg.h"
7 | #include
8 | #include
9 | #include
10 |
11 | using namespace std;
12 |
13 | class HeadData {
14 | //协议号(1B)-账号(2B)-数据类型(1B)-数据长度(4B)-数据
15 | private:
16 | char buffer[BASE_BUFFER_SIZE];
17 | const char *bp;
18 | unsigned int protocolId;
19 | unsigned int account;
20 | unsigned int dataType;
21 | unsigned int dataLength;
22 |
23 | bool baseParse();
24 |
25 | unsigned int parseInt(int len);
26 |
27 | public:
28 | HeadData(int fd);
29 |
30 | HeadData();
31 |
32 | bool parse(const char *buffer);
33 |
34 | unsigned int getProtocolId();
35 |
36 | unsigned int getAccount();
37 |
38 | unsigned int getDataType();
39 |
40 | unsigned int getDataLength();
41 |
42 | };
43 |
44 |
--------------------------------------------------------------------------------
/server/ProtocolHead/protocolmsg.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 | #pragma once
5 | #define BASE_BUFFER_SIZE 8
6 | #define PROTOCOL_ID_SIZE 1
7 | #define ACCOUNT_SIZE 2
8 | #define DATA_TYPE_SIZE 1
9 | #define DATA_SIZE 4
10 | #define LOGIN 1
11 | #define SEND 2
12 | #define READ 3
13 | #define NOTICE 4
14 | #define ACK 5
15 | #define LOGOUT 6
16 | #define REGISTER 7
17 | #define ONLINELIST 8
18 | #define CLOSE 9
19 | #define TEXT 1
20 | #define IMAGE 2
21 | #define LOGIN_SUCCESS 0
22 | #define LOGIN_FAIL 1
23 | #define LOGIN_EXIST 2
24 | #define REGISTER_SUCCESS 0
25 | #define REGISTER_FAIL 1
26 |
--------------------------------------------------------------------------------
/server/Service/DataProcesser.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 |
5 | #include "DataProcesser.h"
6 |
7 | DataProcesser::DataProcesser() {
8 |
9 | }
10 |
11 | int DataProcesser::checkSocketConnected(int sock) {
12 | if (sock <= 0)
13 | return 0;
14 | tcp_info info{};
15 | int len = sizeof(info);
16 | getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *) &len);
17 | if (info.tcpi_state == TCP_ESTABLISHED) {
18 | return 1;
19 | } else {
20 | return 0;
21 | }
22 | }
23 |
24 | string DataProcesser::readTextContent(int fd, unsigned int dataLength) {
25 | unsigned int count = 0;
26 | //注意size一定不能是unsigned型, 因为后面read可能会返回-1,从而造成错误
27 | int size = 0;
28 | unsigned int buffSize = TCP_BUFSIZ;
29 | string content;
30 | while (true) {
31 | if (checkSocketConnected(fd) == 0) {
32 | break;
33 | }
34 | if((size = read(fd, buffer, min(buffSize, dataLength - count))) <= 0){
35 | break;
36 | }
37 | if (size > 0) {
38 | count += size;
39 | content += string(buffer, size);
40 | }
41 | if (count == dataLength) {
42 | break;
43 | }
44 | }
45 | return content;
46 | }
47 |
48 | string DataProcesser::readImageContent(int fd, unsigned int dataLength) {
49 | //获取当前时间戳作为文件名前缀
50 | string imagePath = IMAGE_PATH + MyTime::getTimeStampStr() + ".png";
51 | ofstream os(imagePath, ios::out | ios::binary);
52 | unsigned int count = 0;
53 | int size = 0;
54 | unsigned int buffSize = TCP_BUFSIZ;
55 | while (true) {
56 | if (checkSocketConnected(fd) == 0) {
57 | break;
58 | }
59 | if((size = recv(fd, buffer, min(buffSize, dataLength - count), MSG_WAITALL)) <= 0){
60 | break;
61 | }
62 | count += size;
63 | os.write(buffer, size);
64 | if (count == dataLength) {
65 | break;
66 | }
67 | }
68 | os.close();
69 | return imagePath;
70 | }
71 |
72 | void DataProcesser::writeText(int fd, unsigned int account, string text, unsigned int protocolId) {
73 | DataEncoder de;
74 | string headStr = de.encode(protocolId, account, TEXT, text.length());
75 | if (checkSocketConnected(fd) == 0) {
76 | return;
77 | }
78 | send(fd, headStr.data(), headStr.length(), MSG_NOSIGNAL);
79 | read(fd, buffer, BASE_BUFFER_SIZE);
80 | int count = 0;
81 | unsigned int dataLength = text.length();
82 | const char *data = text.data();
83 | unsigned int buffSize = TCP_BUFSIZ;
84 | HeadData hd;
85 | while (true) {
86 | if (checkSocketConnected(fd) == 0) {
87 | break;
88 | }
89 | ssize_t size = send(fd, data, min(buffSize, dataLength - count), MSG_NOSIGNAL);
90 | count += size;
91 | data = data + size;
92 | //接收客户端的确认信息
93 | unsigned int splitDataLength = 0;
94 | while (true) {
95 | if (checkSocketConnected(fd) == 0) {
96 | break;
97 | }
98 | read(fd, buffer, BASE_BUFFER_SIZE);
99 | hd.parse(buffer);
100 | splitDataLength += hd.getDataLength();
101 | if (splitDataLength == size) {
102 | break;
103 | }
104 | }
105 | if (count == dataLength) {
106 | break;
107 | }
108 | }
109 | }
110 |
111 | void DataProcesser::writeImage(int fd, unsigned int account, const string &imagePath) {
112 | ifstream in(imagePath, ios::in | ios::binary);
113 | if (!in.is_open()) {
114 | cout << "文件打开失败" << endl;
115 | return;
116 | }
117 | int imageSize = getFileLength(imagePath);
118 | DataEncoder de;
119 | string headStr = de.encode(SEND, account, IMAGE, imageSize);
120 | if (imageSize == 0) {
121 | in.close();
122 | return;
123 | }
124 | in.seekg(0);
125 | if (checkSocketConnected(fd) == 0) {
126 | return;
127 | }
128 | send(fd, headStr.data(), headStr.length(), MSG_NOSIGNAL);
129 | read(fd, buffer, BASE_BUFFER_SIZE);
130 | int count = 0;
131 | HeadData hd;
132 | while (in.tellg() != -1) {
133 | if (checkSocketConnected(fd) == 0) {
134 | break;
135 | }
136 | in.read(buffer, TCP_BUFSIZ);
137 | send(fd, buffer, in.gcount(), MSG_NOSIGNAL);
138 | //接收客户端的确认信息
139 | unsigned int splitDataLength = 0;
140 | while (true) {
141 | if (checkSocketConnected(fd) == 0) {
142 | break;
143 | }
144 | read(fd, buffer, BASE_BUFFER_SIZE);
145 | hd.parse(buffer);
146 | splitDataLength += hd.getDataLength();
147 | if (splitDataLength == in.gcount()) {
148 | break;
149 | }
150 | }
151 | count += in.gcount();
152 | }
153 | in.close();
154 | }
155 |
156 | void DataProcesser::writeMsg(int fd, unsigned int account, string text, unsigned int protocolId) {
157 | DataEncoder de;
158 | string headStr = de.encode(protocolId, account, TEXT, text.length());
159 | text = headStr + text;
160 | send(fd, text.data(), text.length(), MSG_NOSIGNAL);
161 | }
162 |
163 | void
164 | DataProcesser::writeTextToAllUser(const vector &fds, int account, const string &text, unsigned int protocolId) {
165 | for (auto &fd : fds) {
166 | writeText(fd, account, text, protocolId);
167 | }
168 | }
169 |
170 | void DataProcesser::writeImageToAllUser(const vector &fds, int account, const string &imagePath) {
171 | for (auto &fd : fds) {
172 | writeImage(fd, account, imagePath);
173 | }
174 | }
175 |
176 | int DataProcesser::getFileLength(const string &fileName) {
177 | ifstream in(fileName, ios::in | ios::binary);
178 | if (!in.is_open()) {
179 | cout << "文件打开失败" << endl;
180 | return 0;
181 | }
182 | in.seekg(0, ios::end); //设置文件指针到文件流的尾部
183 | int fileLength = in.tellg();
184 | in.close();
185 | return fileLength;
186 | }
187 |
188 |
--------------------------------------------------------------------------------
/server/Service/DataProcesser.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 | #pragma once
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include"../ProtocolHead/protocolmsg.h"
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include"../Util/MyTime.h"
15 | #include"../ProtocolHead/HeadData.h"
16 | #include "../ProtocolHead/DataEncoder.h"
17 |
18 | #define IMAGE_PATH "./image/"
19 | #define TCP_BUFSIZ 8192
20 | using namespace std;
21 |
22 | class DataProcesser {
23 | //协议号(1B)-账号(2B)-数据类型(1B)-数据长度(4B)-数据
24 | private:
25 | char buffer[TCP_BUFSIZ];
26 |
27 | int checkSocketConnected(int sock);
28 |
29 | public:
30 | DataProcesser();
31 |
32 | string readTextContent(int fd, unsigned int dataLength);
33 |
34 | string readImageContent(int fd, unsigned int dataLength);
35 |
36 | void writeText(int fd, unsigned int account, string text, unsigned int protocolId = SEND);
37 |
38 | void writeImage(int fd, unsigned int account, const string &imagePath);
39 |
40 | void writeMsg(int fd, unsigned int account, string text, unsigned int protocolId = SEND);
41 |
42 | void writeTextToAllUser(const vector &fds, int account, const string &text, unsigned int protocolId = SEND);
43 |
44 | void writeImageToAllUser(const vector &fds, int account, const string &imagePath);
45 |
46 | int getFileLength(const string &fileName);
47 |
48 | };
49 |
--------------------------------------------------------------------------------
/server/Service/Online.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 |
5 | #include "Online.h"
6 |
7 | string Online::getOnlineListStr() {
8 | Json::Value onlineList;
9 | for (auto &item : userMap) {
10 | Json::Value userJson;
11 | userJson["account"] = item.second.account;
12 | userJson["username"] = item.second.username;
13 | userJson["loginTime"] = item.second.loginTime;
14 | onlineList.append(userJson);
15 | }
16 | return onlineList.toStyledString();
17 | }
18 |
19 | bool Online::appendUser(int account, string username) {
20 | user u = {account, move(username), MyTime::getCurrentFormatTimeStr()};
21 | userMap[account] = u;
22 | return true;
23 | }
24 |
25 | bool Online::removeUser(int account) {
26 | userMap.erase(account);
27 | writeFdToReadFd.erase(accountToFd[account]);
28 | accountToFd.erase(account);
29 | return true;
30 | }
31 |
32 | bool Online::appendUser(pair &user) {
33 | return appendUser(user.first, user.second);
34 | }
35 |
36 | int Online::getReadFd(int writeFd) {
37 | return writeFdToReadFd[writeFd];
38 | }
39 |
40 | vector Online::getAllReadFd() {
41 | vector v;
42 | for (auto &item : writeFdToReadFd) {
43 | v.push_back(item.second);
44 | }
45 | return v;
46 | }
47 |
48 | bool Online::appendWriteFd(int account, int fd) {
49 | accountToFd[account] = fd;
50 | return true;
51 | }
52 |
53 | bool Online::appendReadFd(int account, int fd) {
54 | writeFdToReadFd[accountToFd[account]] = fd;
55 | return true;
56 | }
57 |
58 | string Online::getUserJsonStr(int account) {
59 | Json::Value jsonUser;
60 | jsonUser["account"] = account;
61 | jsonUser["username"] = userMap[account].username;
62 | return jsonUser.toStyledString();
63 | }
64 |
65 | string Online::getUserName(int account) {
66 | return userMap[account].username;
67 | }
68 |
69 | bool Online::isLogin(int account) {
70 | return userMap.count(account) != 0;
71 | }
72 |
--------------------------------------------------------------------------------
/server/Service/Online.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 | #pragma once
5 | #include
6 | #include
7 | #include"../Util/MyTime.h"
8 | #include
9 | #include
10 |
11 | using namespace std;
12 |
13 | class Online {
14 | private:
15 | struct user {
16 | int account;
17 | string username;
18 | string loginTime;
19 | };
20 | unordered_map writeFdToReadFd;
21 | unordered_map accountToFd;
22 | unordered_map userMap;
23 | public:
24 | string getOnlineListStr();
25 |
26 | bool appendUser(int account, string username);
27 |
28 | bool removeUser(int account);
29 |
30 | bool appendUser(pair &user);
31 |
32 | int getReadFd(int writeFd);
33 |
34 | vector getAllReadFd();
35 |
36 | bool appendWriteFd(int account, int fd);
37 |
38 | bool appendReadFd(int account, int fd);
39 |
40 | string getUserJsonStr(int account);
41 |
42 | string getUserName(int account);
43 |
44 | bool isLogin(int account);
45 |
46 | };
47 |
--------------------------------------------------------------------------------
/server/Service/UserService.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 |
5 | #include "UserService.h"
6 |
7 | bool UserService::checkAccount(string &account) {
8 | //账号长度在3-6位
9 | if (account.length() > 6 || account.length() < 3) {
10 | cout << "账号长度有误" << endl;
11 | return false;
12 | }
13 | //账号只能是数字
14 | for (char &c : account) {
15 | if (!(c >= '0' && c <= '9')) {
16 | cout << "账号只能是数字" << endl;
17 | return false;
18 | }
19 | }
20 | //账号不能超过65535
21 | if (stoi(account) > 65535) {
22 | cout << "账号不能超过65535" << endl;
23 | return false;
24 | }
25 | return true;
26 | }
27 |
28 | bool UserService::checkPassword(string &password) {
29 | //密码长度为6-16位
30 | if (password.length() < 6 || password.length() > 16) {
31 | cout << "密码长度有误" << endl;
32 | return false;
33 | }
34 | //密码由字母,数字组成
35 | for (char &c : password) {
36 | if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) {
37 | cout << "密码有误" << endl;
38 | return false;
39 | }
40 | }
41 | return true;
42 | }
43 |
44 | UserService::UserService() {
45 | connector = MySQLConnector::getMySQLConnector();
46 | }
47 |
48 | pair UserService::checkLogin(string account, string password) {
49 | pair user;
50 | if (checkAccount(account) && checkPassword(password)) {
51 | user = connector->queryUser(account, password);
52 | }
53 | return user;
54 | }
55 |
56 | bool UserService::isRegistered(string account) {
57 | if (!checkAccount(account)) {
58 | return true;
59 | }
60 | return connector->queryUser(account);
61 | }
62 |
63 | bool UserService::registerUser(string account, string username, string password) {
64 | return checkAccount(account) && checkPassword(password) && connector->insertUser(account, username, password);
65 | }
66 |
--------------------------------------------------------------------------------
/server/Service/UserService.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 |
5 | #pragma once
6 | #include"../Dao/MySQLConnector.h"
7 |
8 | using namespace std;
9 |
10 | class UserService {
11 | private:
12 | MySQLConnector *connector;
13 |
14 | bool checkAccount(string &account);
15 |
16 | bool checkPassword(string &password);
17 |
18 | public:
19 | UserService();
20 |
21 | pair checkLogin(string account, string password);
22 |
23 | bool isRegistered(string account);
24 |
25 | bool registerUser(string account, string username, string password);
26 | };
27 |
28 |
--------------------------------------------------------------------------------
/server/Util/MyTime.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 |
5 | #include "MyTime.h"
6 | string MyTime::getCurrentFormatTimeStr() {
7 | time_t timep;
8 | time(&timep);
9 | char tmp[64] = { '\0' };
10 | strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", localtime(&timep));
11 | return string(tmp);
12 | }
13 |
14 | string MyTime::getTimeStampStr(){
15 | timeval stamp{};
16 | gettimeofday(&stamp, NULL);
17 | return to_string(stamp.tv_sec) + to_string(stamp.tv_usec);
18 | }
19 |
--------------------------------------------------------------------------------
/server/Util/MyTime.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 |
5 | #pragma once
6 | #include
7 | #include
8 | #include
9 | using namespace std;
10 | class MyTime
11 | {
12 | public:
13 | static string getCurrentFormatTimeStr();
14 | static string getTimeStampStr();
15 | };
16 |
17 |
--------------------------------------------------------------------------------
/server/config/mysql_config.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 | #pragma once
5 | #define HOST "127.0.0.1"
6 | #define USERNAME "root"
7 | #define PASSWORD "123456"
8 | #define PORT 3306
9 | #define DATABASE "chatroom"
10 |
--------------------------------------------------------------------------------
/server/config/server_config.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by cswen on 2020/8/3.
3 | //
4 | #pragma once
5 | #define HOST "127.0.0.1"
6 | #define PORT 8888
7 | #define MAX_CONNECTIONS 1024
8 |
--------------------------------------------------------------------------------
/server/image/README.md:
--------------------------------------------------------------------------------
1 | # 用于保存图片
2 |
--------------------------------------------------------------------------------
/server/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include"ProtocolHead/HeadData.h"
11 | #include"Service/DataProcesser.h"
12 | #include "Service/UserService.h"
13 | #include "Service/Online.h"
14 | #include "config/server_config.h"
15 |
16 | using namespace std;
17 |
18 | int main() {
19 | int lfd = socket(AF_INET, SOCK_STREAM, 0);
20 | sockaddr_in serverAddr{}, clientAddr{};
21 | int opt = 1;
22 | if (-1 == setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
23 | cout << "setsockopt fail" << endl;
24 | exit(-1);
25 | }//设置端口复用
26 | int epfd = epoll_create(MAX_CONNECTIONS);
27 | epoll_event ev{}, events[MAX_CONNECTIONS];
28 | ev.data.fd = lfd;
29 | ev.events = EPOLLIN;
30 | if (-1 == epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev)) {
31 | cout << "epoll_ctl fail" << endl;
32 | exit(-1);
33 | }
34 | serverAddr.sin_port = htons(PORT);
35 | serverAddr.sin_family = AF_INET;
36 | inet_pton(AF_INET, HOST, &serverAddr.sin_addr);
37 | if (-1 == bind(lfd, (sockaddr *) &serverAddr, sizeof(serverAddr))) {
38 | cout << "bind fail" << endl;
39 | exit(-1);
40 | }
41 | if (-1 == listen(lfd, MAX_CONNECTIONS)) {
42 | cout << "listen fail" << endl;
43 | exit(-1);
44 | }
45 | cout << "listening..." << endl;
46 | char ipAddress[BUFSIZ];
47 | UserService us;
48 | Online online;
49 | while (true) {
50 | int nready = epoll_wait(epfd, events, MAX_CONNECTIONS, -1);
51 | if (nready < 0) {
52 | cout << "epoll_wait error" << endl;
53 | exit(-1);
54 | }
55 | cout << "收到" << nready << "个请求" << endl;
56 | for (int i = 0; i < nready; i++) {
57 | int fd = events[i].data.fd;
58 | if (fd == lfd) {
59 | socklen_t len = sizeof(clientAddr);
60 | int cfd = accept(lfd, (sockaddr *) &clientAddr, &len);
61 | ev.data.fd = cfd;
62 | ev.events = EPOLLIN;
63 | epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
64 | inet_ntop(AF_INET, &clientAddr.sin_addr, ipAddress, sizeof(clientAddr));
65 | //设置超时read
66 | struct timeval timeout = {1, 0};
67 | setsockopt(cfd, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(struct timeval));
68 | } else if (events[i].events & EPOLLIN) {
69 | HeadData hd(fd);
70 | unsigned int protocolId = hd.getProtocolId();
71 | unsigned int account = hd.getAccount();
72 | unsigned int dataType = hd.getDataType();
73 | unsigned int dataLength = hd.getDataLength();
74 | DataProcesser dp;
75 | switch (protocolId) {
76 | case LOGIN: {
77 | string loginMsg = dp.readTextContent(fd, dataLength);
78 | Json::Reader jsonReader;
79 | Json::Value msg;
80 | jsonReader.parse(loginMsg, msg);
81 | string account = msg["account"].asString();
82 | string password = msg["password"].asString();
83 | pair user = us.checkLogin(account, password);
84 | Json::Value loginResult;
85 | //登录成功
86 | if (user.first != 0) {
87 | if (online.isLogin(user.first)) {
88 | loginResult["status"] = LOGIN_EXIST;
89 | } else {
90 | online.appendUser(user);
91 | online.appendWriteFd(user.first, fd);
92 | loginResult["status"] = LOGIN_SUCCESS;
93 | loginResult["username"] = user.second;
94 | }
95 | }
96 | //失败
97 | else {
98 | loginResult["status"] = LOGIN_FAIL;
99 | }
100 | string loginResultStr = loginResult.toStyledString();
101 | dp.writeMsg(fd, 0, loginResultStr);
102 | }
103 | break;
104 | case REGISTER: {
105 | string registerMsg = dp.readTextContent(fd, dataLength);
106 | Json::Reader jsonReader;
107 | Json::Value registerResult;
108 | Json::Value msg;
109 | jsonReader.parse(registerMsg, msg);
110 | string account = msg["account"].asString();
111 | string username = msg["username"].asString();
112 | string password = msg["password"].asString();
113 | if (us.isRegistered(account) || !us.registerUser(account, username, password)) {
114 | registerResult["status"] = REGISTER_FAIL;
115 | } else {
116 | registerResult["status"] = REGISTER_SUCCESS;
117 | }
118 | dp.writeMsg(fd, 0, registerResult.toStyledString(), REGISTER);
119 | }
120 | break;
121 |
122 | case SEND: {
123 | string baseMsg = online.getUserName(account) + "(" + to_string(account) + ")说:";
124 | if (dataType == TEXT) {
125 | dp.writeTextToAllUser(online.getAllReadFd(), account, baseMsg);
126 | string content = dp.readTextContent(fd, dataLength);
127 | dp.writeTextToAllUser(online.getAllReadFd(), account, content);
128 | } else if (dataType == IMAGE) {
129 | string imagePath = dp.readImageContent(fd, dataLength);
130 | if (dp.getFileLength(imagePath) == dataLength) {
131 | dp.writeTextToAllUser(online.getAllReadFd(), account, baseMsg);
132 | dp.writeImageToAllUser(online.getAllReadFd(), account, imagePath);
133 | } else {
134 | ev.data.fd = fd;
135 | ev.events = EPOLLIN;
136 | epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev);
137 | close(fd);
138 | close(online.getReadFd(fd));
139 | string logoutMsg =
140 | online.getUserName(account) + "(" + to_string(account) + ")" + "离开了聊天室!";
141 | online.removeUser(account);
142 | vector fds = online.getAllReadFd();
143 | if (!fds.empty()) {
144 | dp.writeTextToAllUser(fds, account, logoutMsg, NOTICE);
145 | dp.writeTextToAllUser(fds, 0, online.getOnlineListStr(), ONLINELIST);
146 | }
147 | }
148 | }
149 | }
150 | break;
151 | case READ: {
152 | ev.data.fd = fd;
153 | ev.events = EPOLLIN;
154 | epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev);
155 | online.appendReadFd(account, fd);
156 | string loginMsg = online.getUserName(account) + "(" + to_string(account) + ")" + "走进了聊天室!";
157 | dp.writeTextToAllUser(online.getAllReadFd(), account, loginMsg, NOTICE);
158 | dp.writeTextToAllUser(online.getAllReadFd(), account, online.getOnlineListStr(), ONLINELIST);
159 | }
160 | break;
161 | case LOGOUT: {
162 | sleep(1);
163 | ev.data.fd = fd;
164 | ev.events = EPOLLIN;
165 | epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev);
166 | close(fd);
167 | close(online.getReadFd(fd));
168 | string logoutMsg = online.getUserName(account) + "(" + to_string(account) + ")" + "离开了聊天室!";
169 | online.removeUser(account);
170 | vector fds = online.getAllReadFd();
171 | cout << "当前在线人数:" << fds.size() << endl;
172 | if (!fds.empty()) {
173 | dp.writeTextToAllUser(fds, account, logoutMsg, NOTICE);
174 | dp.writeTextToAllUser(fds, 0, online.getOnlineListStr(), ONLINELIST);
175 | }
176 | }
177 | break;
178 | case CLOSE: {
179 | sleep(1);
180 | ev.data.fd = fd;
181 | ev.events = EPOLLIN;
182 | epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev);
183 | close(fd);
184 | }
185 | break;
186 | }
187 |
188 | }
189 | }
190 | }
191 |
192 | close(lfd);
193 | return 0;
194 | }
195 |
--------------------------------------------------------------------------------
/server/out/README.md:
--------------------------------------------------------------------------------
1 | # 编译过程中产生的.o文件
2 |
--------------------------------------------------------------------------------
/sql/user.sql:
--------------------------------------------------------------------------------
1 | SET NAMES utf8mb4;
2 | SET FOREIGN_KEY_CHECKS = 0;
3 |
4 | DROP TABLE IF EXISTS `user`;
5 | CREATE TABLE `user` (
6 | `account` int(11) NOT NULL,
7 | `username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
8 | `password` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
9 | `createTime` datetime(0) NOT NULL,
10 | PRIMARY KEY (`account`) USING BTREE
11 | ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
12 |
13 | SET FOREIGN_KEY_CHECKS = 1;
14 |
--------------------------------------------------------------------------------
/聊天室演示.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cswen-scut/chatroom/1f7723e93b6236c474c628891ac2530d4fe9b198/聊天室演示.mp4
--------------------------------------------------------------------------------