├── 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 --------------------------------------------------------------------------------