├── CMakeLists.txt ├── MessageJson.cpp ├── MessageJson.h ├── MessageQueue.cpp ├── MessageQueue.h ├── README.md ├── Redis.cpp ├── Redis.h ├── Server.cpp ├── Server.h ├── bug修复日志.md ├── event_infor.h ├── json.hpp ├── main.cpp ├── qt ├── .gitignore ├── CMakeLists.txt ├── client.cpp ├── client.h ├── client.ui ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── register.cpp ├── register.h ├── register.ui ├── socket.cpp └── socket.h ├── test ├── client.cpp └── test.cpp └── 笔记.md /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.01) 2 | project(chat_project) 3 | 4 | set(CMAKE_CXX_STANDARD 14) 5 | set(Boost_INCLUDE_DIR /usr/local/include/boost) 6 | set(Boost_LIBRARY_DIR /usr/local/lib) 7 | 8 | link_libraries(/usr/local/lib/libhiredis.a) 9 | link_libraries(/usr/local/lib/libglog.so) 10 | include_directories(${Boost_INCLUDE_DIR}) 11 | 12 | add_executable(chat_server main.cpp Server.cpp MessageQueue.cpp MessageQueue.h Redis.cpp Redis.h MessageJson.cpp MessageJson.h event_infor.h) 13 | add_executable(chat_test test/test.cpp Redis.cpp MessageJson.cpp) 14 | add_executable(client_test test/client.cpp) 15 | 16 | target_link_libraries(chat_server pthread hiredis glog) 17 | -------------------------------------------------------------------------------- /MessageJson.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by daimiaopeng on 2019/12/12. 3 | // 4 | #include "MessageJson.h" 5 | 6 | MessageJson::MessageJson(const Redis &redis, const string &str) : redis(redis) { 7 | try { 8 | _json = json::parse(str); 9 | _isParseSuccess = true; 10 | } 11 | //test 12 | catch (std::exception &e) { 13 | _isParseSuccess = false; 14 | LOG(ERROR) << "消息格式错误: " << e.what(); 15 | } 16 | } 17 | 18 | string MessageJson::res() { 19 | try { 20 | code = _json["code"]; 21 | switch (code) { 22 | case 0: 23 | type = "login"; 24 | return code0(); 25 | case 1: 26 | type = "registered"; 27 | return code1(); 28 | case 2: 29 | type = "getOnile"; 30 | return code2(); 31 | case 3: 32 | type = "sendPeople"; 33 | return code3(); 34 | case 4: 35 | type = "getRegisterNums"; 36 | return code4(); 37 | case 5: 38 | type = "sendFlie"; 39 | return code5(); 40 | default: 41 | return ""; 42 | } 43 | } 44 | catch (std::exception &e) { 45 | return "错误"; 46 | } 47 | } 48 | 49 | string MessageJson::code0() { 50 | //登录接口 51 | // {"code":0,"data":{"passwd":"123456","name":"daimiaopeng"}} 52 | // {"code":0,"data":{"passwd":"qwe","name":"qwe"}} 53 | string name = _json["data"]["name"]; 54 | string passwd = _json["data"]["passwd"]; 55 | string message; 56 | json reJsonStr; 57 | 58 | string token = redis.login(name, passwd); 59 | if (!token.empty()) { 60 | message = "登录成功"; 61 | } else { 62 | message = "登录失败"; 63 | } 64 | reJsonStr = {{"code", code}, 65 | {"token", token}, 66 | {"data", {{"message", message}}}}; 67 | return reJsonStr.dump(); 68 | } 69 | 70 | string MessageJson::code1() { 71 | //注册接口 72 | // {"code":1,"data":{"passwd":"123412356","name":"qwieuy"}} 73 | string name = _json["data"]["name"]; 74 | string passwd = _json["data"]["passwd"]; 75 | int res = redis.registered(name, passwd); 76 | string message; 77 | json reJsonStr; 78 | int isCode; 79 | if (res == 1) { 80 | isCode = 1; 81 | message = "注册成功"; 82 | } else { 83 | isCode = 0; 84 | message = "注册失败,该用户名被已注册"; 85 | } 86 | reJsonStr = {{"code", code}, 87 | {"data", {{"message", message}}}}; 88 | reJsonStr["isCode"] = isCode; 89 | return reJsonStr.dump(); 90 | } 91 | 92 | string MessageJson::code2() { 93 | //获取在线人数和列表 94 | //{"code":2,"token":"qwe"} 95 | // return {"code":2,"data":["qwe"],"nums":1} 96 | auto all = redis.geOnile(); 97 | json reJsonStr; 98 | reJsonStr["code"] = code; 99 | int nums=0; 100 | json j_array{}; 101 | for(auto i:all){ 102 | nums++; 103 | j_array.push_back(i); 104 | } 105 | reJsonStr["data"] = j_array; 106 | reJsonStr["nums"] = nums; 107 | return reJsonStr.dump(); 108 | } 109 | 110 | string MessageJson::code3() { 111 | // 发送消息给某个人接口 112 | // {"code":3,"token":"qwe","receiver":"daimiaopeng","data":"hello"} 113 | // {"code":3,"token":"daimiaopeng","receiver":"qwe","data":"hello"} 114 | // write {"code":3,"data":{"message":"qwe"},"sender":"qwe"} 115 | json reJsonStr; 116 | reJsonStr["code"] = code; 117 | reJsonStr["data"]["message"] = _json["data"]; 118 | reJsonStr["sender"] = _json["sender"]; 119 | return reJsonStr.dump(); 120 | } 121 | 122 | string MessageJson::code4() { 123 | //获取注册人数接口 124 | // {"code":4,"token":"daimiaopeng"} 125 | int nums = redis.getRegisterNums(); 126 | string token = _json["token"]; 127 | string message = "None"; 128 | json reJsonStr; 129 | reJsonStr["code"] = code; 130 | reJsonStr["data"]["message"] = message; 131 | reJsonStr["nums"] = nums; 132 | return reJsonStr.dump(); 133 | } 134 | 135 | 136 | 137 | string MessageJson::messageNew(event_infor *infor) { 138 | //认证后为消息设置其他信息 139 | // {"code":0,"token":"qwe","data":{"passwd":"qwe","name":"qwe"}} 140 | // {"code":2,"token":"qwe","data":{"passwd":"qwe","name":"qwe"}} 141 | // {"code":0,"data":{"passwd":"qwe","name":"qwe"}} 142 | // {"code":1,"data":{"passwd":"qwe","name":"qwqe"}} 143 | // {"code":1,"data":{"passwd":"qwse","name":"qwqe"}} 144 | // {"code":4,"token":"daimiaopeng"} 145 | // {"code":4,"token":"daimiaopeng1"} 146 | auto token = _json.find("token"); 147 | string name; 148 | int code = _json["code"]; 149 | if (token != _json.end()) { 150 | string t = *token; 151 | name = redis.getName(t); 152 | LOG(INFO) <fd, name); 159 | _json["sender"] = name; 160 | } 161 | }else{ 162 | if (code == 0 || code == 1) { 163 | _json["token"] = "None"; 164 | _json["sender"] = "None"; 165 | }else{ 166 | return "giveUp"; 167 | } 168 | } 169 | _json["ip"] = infor->ip; 170 | _json["fd"] = infor->fd; 171 | _json["port"] = infor->port; 172 | _json["status"] = infor->status; 173 | 174 | auto receiver = _json.find("receiver"); 175 | if (receiver == _json.end()) { 176 | _json["receiver"] = _json["sender"]; 177 | } 178 | 179 | LOG(INFO) << "set sender name=" << name << " ip=" << infor->ip << " port=" << infor->port; 180 | return _json.dump(); 181 | } 182 | 183 | bool MessageJson::isParseSuccess() { 184 | return _isParseSuccess; 185 | } 186 | 187 | json MessageJson::getJson() { 188 | return _json; 189 | } 190 | 191 | string MessageJson::code5() { 192 | json reJsonStr; 193 | reJsonStr["code"] = code; 194 | reJsonStr["data"]["message"] = _json["data"]; 195 | reJsonStr["size"] = _json["size"]; 196 | reJsonStr["part"] = _json["part"]; 197 | return reJsonStr.dump(); 198 | } 199 | 200 | 201 | 202 | 203 | 204 | 205 | -------------------------------------------------------------------------------- /MessageJson.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by daimiaopeng on 2019/12/12. 3 | // 4 | 5 | #ifndef CHAT_PROJECT_MESSAGEJSON_H 6 | #define CHAT_PROJECT_MESSAGEJSON_H 7 | 8 | #include "json.hpp" 9 | #include "Redis.h" 10 | #include "event_infor.h" 11 | 12 | using json = nlohmann::json; 13 | using namespace std; 14 | struct event_infor; 15 | 16 | class Redis; 17 | 18 | class MessageQueue; 19 | 20 | class MessageJson { 21 | public: 22 | bool _isParseSuccess; 23 | int code; 24 | string type; 25 | json _json; 26 | Redis redis; 27 | 28 | public: 29 | MessageJson(const Redis &redis, const string &str); 30 | 31 | string messageNew(event_infor *infor); 32 | 33 | bool isParseSuccess(); 34 | 35 | string res(); 36 | 37 | json getJson(); 38 | protected: 39 | string code0(); 40 | 41 | string code2(); 42 | 43 | string code1(); 44 | 45 | string code3(); 46 | 47 | string code4(); 48 | 49 | string code5(); 50 | }; 51 | 52 | 53 | #endif //CHAT_PROJECT_MESSAGEJSON_H 54 | -------------------------------------------------------------------------------- /MessageQueue.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by daimiaopeng on 2019/11/4. 3 | // 4 | 5 | #include "MessageQueue.h" 6 | 7 | 8 | MessageQueue::MessageQueue(Redis &redis) : redis(redis) {} 9 | 10 | void MessageQueue::push(const string &str, event_infor *infor) { 11 | lock_guard lock(_mutex); 12 | MessageJson messageJson(redis, str); 13 | if (messageJson.isParseSuccess()) { 14 | string messageNew = messageJson.messageNew(infor); 15 | if (messageNew == "giveUp") { 16 | return; 17 | } 18 | redis.pushMessageQueue(messageNew); 19 | } 20 | } 21 | 22 | void MessageQueue::run() { 23 | thread _thread(&MessageQueue::sendstr, this); 24 | _thread.detach(); 25 | } 26 | 27 | void MessageQueue::sendstr() { 28 | for (;;) { 29 | this_thread::sleep_for(chrono::milliseconds(10)); 30 | lock_guard lock(_mutex); 31 | int messageLen = redis.lenMessage(); 32 | if (messageLen != 0) { 33 | string str = redis.popMessageQueue(); 34 | sendMessage(str); 35 | } 36 | } 37 | } 38 | 39 | void MessageQueue::sendMessage(const string &str) { 40 | MessageJson messageJson(redis, str); 41 | if (!messageJson.isParseSuccess()) { 42 | return; 43 | } 44 | string writeData = messageJson.res(); 45 | int fd; 46 | json tempJson = messageJson.getJson(); 47 | string receiver = tempJson["receiver"]; 48 | if (receiver == "None") { 49 | fd = tempJson["fd"]; 50 | } else { 51 | fd = redis.getFd(tempJson["receiver"]); 52 | } 53 | _sendPeople(fd, writeData); 54 | } 55 | 56 | void MessageQueue::_sendPeople(int fd, const string &writeData) { 57 | for (int i = 0; i < _len; i++) { 58 | if (_g_events[i].status == 0 || _g_events[i].fd != fd) continue; 59 | LOG(INFO) << "发送fd:" << _g_events[i].fd << "数据:" << writeData; 60 | // send(_g_events[i].fd, writeData.c_str(), writeData.size(), 0); 61 | finalSend(_g_events[i].fd, writeData.c_str(), writeData.size(), 0); 62 | } 63 | } 64 | 65 | void MessageQueue::init(event_infor *g_events, int len) { 66 | this->_g_events = g_events; 67 | this->_len = len; 68 | } 69 | 70 | void MessageQueue::finalSend(int fd,string message,int size,int flages){ 71 | int len = sizeof(MessageStruct) + message.size(); 72 | MessageStruct *messageStruct = static_cast(malloc(len)); 73 | messageStruct->jsonLen = message.size(); 74 | strncpy(messageStruct->json, message.c_str(), message.size()); 75 | send(fd, messageStruct, len, flages); 76 | free(messageStruct) ; 77 | } 78 | -------------------------------------------------------------------------------- /MessageQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by daimiaopeng on 2019/11/4. 3 | // 4 | 5 | #ifndef CHAT_MESSAGEQUEUE_H 6 | #define CHAT_MESSAGEQUEUE_H 7 | 8 | #include "hiredis/hiredis.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "Redis.h" 15 | #include "MessageJson.h" 16 | #include "event_infor.h" 17 | 18 | using namespace std; 19 | 20 | class MessageQueue { 21 | private: 22 | event_infor *_g_events; 23 | int _len; 24 | mutex _mutex; 25 | 26 | public: 27 | Redis redis; 28 | 29 | public: 30 | explicit MessageQueue(Redis &redis); 31 | 32 | MessageQueue() = delete; 33 | 34 | void init(event_infor *g_events, int len); 35 | 36 | void push(const string &str, event_infor *infor); 37 | 38 | void run(); 39 | 40 | void sendstr(); 41 | 42 | void sendMessage(const string &str); 43 | 44 | protected: 45 | void _sendPeople(int fd, const string &writeData); 46 | 47 | void finalSend(int fd, string message, int size, int flages); 48 | }; 49 | 50 | 51 | #endif //CHAT_MESSAGEQUEUE_H 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chat 2 | ## 实现的功能: 3 | - [X] 单独聊天 4 | - [ ] 群聊 5 | - [X] 文件传输 (2020年02月27日) 6 | - [ ] 好友相关功能,例如:好友关系,好友上下线通知等等 7 | - [ ] 优化服务器性能 8 | 9 | ## QT客户端预览(截图更新2020年01月05日18:18:15): 10 | [![lDaKfJ.png](https://s2.ax1x.com/2020/01/05/lDaKfJ.png)](https://imgchr.com/i/lDaKfJ) 11 | [![lDallR.png](https://s2.ax1x.com/2020/01/05/lDallR.png)](https://imgchr.com/i/lDallR) 12 | [![lDauY4.png](https://s2.ax1x.com/2020/01/05/lDauY4.png)](https://imgchr.com/i/lDauY4) 13 | [![lDankF.png](https://s2.ax1x.com/2020/01/05/lDankF.png)](https://imgchr.com/i/lDankF) 14 | [![lDa3Ox.png](https://s2.ax1x.com/2020/01/05/lDa3Ox.png)](https://imgchr.com/i/lDa3Ox) 15 | [![lDaJ0K.png](https://s2.ax1x.com/2020/01/05/lDaJ0K.png)](https://imgchr.com/i/lDaJ0K) 16 | -------------------------------------------------------------------------------- /Redis.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by daimiaopeng on 2019/11/17. 3 | // 4 | 5 | #include "Redis.h" 6 | 7 | Redis::Redis(string ip, u_int port, string passwd) : ip(ip), port(port) { 8 | conn = redisConnect(ip.c_str(), port); 9 | if (conn->err) printf("connection error:%s\n", conn->errstr); 10 | redisReply *reply = static_cast(redisCommand(conn, "AUTH %s", passwd.c_str())); 11 | if (reply->type == REDIS_REPLY_ERROR) { 12 | LOG(INFO) << "Redis认证失败!"; 13 | } else { 14 | LOG(INFO) << "Redis认证成功!"; 15 | } 16 | clearToken(); 17 | freeReplyObject(reply); 18 | }; 19 | 20 | Redis::Redis(string ip, u_int port) : ip(ip), port(port) { 21 | conn = redisConnect(ip.c_str(), port); 22 | if (conn->err) LOG(ERROR) << "connection error: " << conn->errstr; 23 | clearToken(); 24 | }; 25 | 26 | 27 | auto Redis::redisReply_ptr(void *reply) { 28 | auto delRedisReply = [](redisReply *reply) { 29 | freeReplyObject(reply); 30 | }; 31 | unique_ptr reply_unique_ptr(reinterpret_cast(reply), 32 | delRedisReply); 33 | return reply_unique_ptr; 34 | } 35 | 36 | void Redis::pushMessageQueue(const string &message) { 37 | auto reply = redisReply_ptr(redisCommand(conn, "RPUSH messageQueue %s", message.c_str())); 38 | LOG(WARNING) << "添加到消息队列 :" << message; 39 | } 40 | 41 | int Redis::lenMessage() { 42 | string str = "LLEN messageQueue"; 43 | auto reply = redisReply_ptr(redisCommand(conn, str.c_str())); 44 | int len = reply->integer; 45 | return len; 46 | } 47 | 48 | string Redis::popMessageQueue() { 49 | auto reply = redisReply_ptr(redisCommand(conn, "LPOP messageQueue")); 50 | string str = reply->str; 51 | LOG(INFO) << "从消息队列取出:" << str; 52 | return str; 53 | } 54 | 55 | 56 | string Redis::getName(const string &token) { 57 | auto reply = redisReply_ptr(redisCommand(conn, "HMGET token %s", token.c_str())); 58 | string str; 59 | auto element = reply->element; 60 | if ((*element)->str == nullptr) { 61 | str = ""; 62 | return str; 63 | } 64 | str = (*element)->str; 65 | LOG(INFO) << token + "(token->name)" << str; 66 | return str; 67 | } 68 | 69 | 70 | int Redis::getFd(const string &name) { 71 | auto reply = redisReply_ptr(redisCommand(conn, "HMGET fdName %s", name.c_str())); 72 | string str; 73 | auto element = reply->element; 74 | if ((*element)->str == nullptr) { 75 | str = "-1"; 76 | } else { 77 | str = (*element)->str; 78 | } 79 | return stoi(str); 80 | } 81 | 82 | void Redis::setName(int fd, const string &name) { 83 | auto reply = redisReply_ptr(redisCommand(conn, "HMSET fdName %s %d", name.c_str(), fd)); 84 | } 85 | 86 | string Redis::login(const string &name, const string &passwd) { 87 | auto reply = redisReply_ptr(redisCommand(conn, "HMGET login %s", name.c_str())); 88 | auto element = reply->element; 89 | if ((*element)->str == nullptr) { 90 | LOG(INFO) << name + " 该用户没有注册"; 91 | return ""; 92 | } 93 | string str = (*element)->str; 94 | if (passwd == str) { 95 | LOG(INFO) << name + " 登录成功"; 96 | string token = name; 97 | setToken(token, name); 98 | return token; 99 | } else { 100 | LOG(INFO) << name + " 登录密码错误"; 101 | return ""; 102 | } 103 | } 104 | 105 | int Redis::registered(const string &name, const string &passwd) { 106 | auto reply = redisReply_ptr(redisCommand(conn, "HMGET login %s", name.c_str())); 107 | auto element = reply->element; 108 | if ((*element)->str != nullptr) { 109 | LOG(INFO) << name + " 该用户已注册"; 110 | return 0; 111 | } 112 | redisCommand(conn, "HMSET login %s %s", name.c_str(), passwd.c_str()); 113 | LOG(INFO) << name + " 注册成功"; 114 | return 1; 115 | } 116 | 117 | 118 | void Redis::setToken(const string &token, const string &name) { 119 | auto reply = redisReply_ptr(redisCommand(conn, "HMSET token %s %s", token.c_str(), name.c_str())); 120 | LOG(INFO) << name + " (name->token)" << token; 121 | } 122 | 123 | void Redis::init() { 124 | 125 | } 126 | 127 | 128 | 129 | int Redis::getRegisterNums() { 130 | auto reply = redisReply_ptr(redisCommand(conn, "HLEN login")); 131 | LOG(INFO) << "当前注册人数:"<integer; 132 | return reply->integer; 133 | } 134 | 135 | set Redis::geOnile() { 136 | set all; 137 | auto reply = redisReply_ptr(redisCommand(conn, "HGETALL token")); 138 | auto element = reply->element; 139 | for (int i =0;ielements;i= i+2){ 140 | //取name而不是token 141 | all.insert(move(string(element[i+1]->str))); 142 | } 143 | 144 | // LOG(INFO) << "当前在线人: "<<(*element)->; 145 | return all; 146 | } 147 | 148 | 149 | 150 | void Redis::clearToken() { 151 | redisReply_ptr(redisCommand(conn, "DEL token")); 152 | LOG(INFO) <<"token 清空初始化成功"; 153 | } 154 | 155 | 156 | -------------------------------------------------------------------------------- /Redis.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by daimiaopeng on 2019/11/17. 3 | // 4 | 5 | #ifndef CHAT_PROJECT_REDIS_H 6 | #define CHAT_PROJECT_REDIS_H 7 | 8 | #include "json.hpp" 9 | #include 10 | #include "glog/logging.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using json = nlohmann::json; 17 | using namespace std; 18 | 19 | class Redis { 20 | private: 21 | string ip; 22 | u_int port; 23 | redisContext *conn; 24 | 25 | public: 26 | Redis(string ip, u_int port, string passwd); 27 | 28 | Redis(string ip, u_int port); 29 | 30 | void init(); 31 | 32 | void pushMessageQueue(const string &message); 33 | 34 | string getName(int fd); 35 | 36 | string getName(const string &token); 37 | 38 | int getFd(const string &name); 39 | 40 | void setName(int fd, const string &name); 41 | 42 | void delName(int fd); 43 | 44 | set geOnile(); 45 | 46 | redisContext *getConn() { return conn; }; 47 | 48 | auto redisReply_ptr(void *reply); 49 | 50 | string login(const string &name, const string &passwd); 51 | 52 | int registered(const string &name, const string &passwd); 53 | 54 | int lenMessage(); 55 | 56 | string popMessageQueue(); 57 | 58 | int getRegisterNums(); 59 | 60 | void clearToken(); 61 | 62 | void setFd(int fd, const string &name); 63 | 64 | void setToken(const string &token, const string &name); 65 | 66 | 67 | }; 68 | 69 | 70 | #endif //CHAT_PROJECT_REDIS_H 71 | -------------------------------------------------------------------------------- /Server.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by daimiaopeng on 2019/10/31. 3 | // 4 | 5 | #include "Server.h" 6 | 7 | using namespace std; 8 | 9 | Server::Server(string ip, u_int port, const MessageQueue *messageQueue) : _ip(ip), _port(port), _messageQueue( 10 | const_cast(messageQueue)) { 11 | lfd = socket(AF_INET, SOCK_STREAM, 0); 12 | bzero(&serv_addr, sizeof(serv_addr)); 13 | serv_addr.sin_addr.s_addr = inet_addr(_ip.c_str()); 14 | serv_addr.sin_port = htons(_port); 15 | serv_addr.sin_family = AF_INET; 16 | LOG(INFO) << "Using ip: " << _ip << " port: " << _port; 17 | bind(lfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); 18 | listen(lfd, 128); 19 | epoll_fd = epoll_create(128); 20 | _messageQueue->init(g_events, MAX_EVENTS); 21 | _messageQueue->run(); 22 | } 23 | 24 | 25 | void Server::run() { 26 | initsocket(); 27 | 28 | struct epoll_event events[20]; 29 | for (;;) { 30 | int nums_epoll = epoll_wait(epoll_fd, events, 128, -1); 31 | if (nums_epoll == -1) { 32 | perror("epoll_wait() error"); 33 | } 34 | for (int i = 0; i < nums_epoll; i++) { 35 | event_infor *infor = static_cast(events[i].data.ptr); 36 | if (events[i].events & EPOLLIN) { 37 | infor->readCallback(infor); 38 | } 39 | if (events[i].events & EPOLLOUT) { 40 | infor->writeCallback(infor); 41 | } 42 | // auto res = async(launch::async, infor->eventCallback,infor); 43 | //线程不应该在这里创建,同一个fd反复创建线程 44 | // thread thread1(infor->eventCallback,infor); 45 | // thread1.detach(); 46 | //当这个future对象被析构时,会等待线程执行完成 47 | // printf("123\n"); 48 | } 49 | } 50 | } 51 | 52 | 53 | void Server::initsocket() { 54 | event_infor *l_infor = &g_events[MAX_EVENTS]; 55 | l_infor->fd = lfd; 56 | l_infor->status = 1; 57 | l_infor->ip = inet_ntoa(serv_addr.sin_addr); 58 | l_infor->port = _port; 59 | l_infor->readCallback = [&](event_infor *infor) { acceptconn(infor); }; 60 | l_infor->writeCallback = nullptr; 61 | eventadd(EPOLLIN, l_infor); 62 | 63 | } 64 | 65 | void Server::acceptconn(event_infor *infor) { 66 | //有请求时 cfd的Callback()调用acceptconn(),accept()一个客户端,然后添加到epoll 67 | //设置Callback为自定义的read函数 68 | // 初始化一个socket结构 69 | sockaddr_in socket_addr{}; 70 | socklen_t client_addr_len = sizeof(socket_addr); 71 | int cfd = accept(infor->fd, (struct sockaddr *) &socket_addr, &client_addr_len); 72 | if (cfd == -1) { 73 | if (errno != EAGAIN && errno != EINTR) { 74 | sleep(1); 75 | } 76 | // LOG(ERROR)<<("%s:accept,%s\n", __func__, strerror(errno)); 77 | LOG(ERROR) << "连接失败"; 78 | return; 79 | } 80 | int i = 0; 81 | for (i = 0; i < MAX_EVENTS; i++) { 82 | if (g_events[i].status == 0) break; 83 | if (i == MAX_EVENTS) // 超出连接数上限 84 | { 85 | LOG(INFO) << __func__ << "max connect limit " << MAX_EVENTS; 86 | break; 87 | } 88 | } 89 | event_infor *cli_event_infor = &g_events[i]; 90 | cli_event_infor->fd = cfd; 91 | cli_event_infor->status = true; 92 | cli_event_infor->port = socket_addr.sin_port; 93 | cli_event_infor->ip = inet_ntoa(socket_addr.sin_addr); 94 | cli_event_infor->readCallback = [&](event_infor *infor) { recvdata(infor); }; 95 | cli_event_infor->writeCallback = [&](event_infor *infor) { senddata(infor); }; 96 | LOG(INFO) << cli_event_infor->ip << " 连接成功\n"; 97 | // eventadd(EPOLLOUT, cli_event_infor); 98 | eventadd(EPOLLIN, cli_event_infor); 99 | 100 | } 101 | 102 | void Server::recvdata(event_infor *infor) { 103 | MessageStruct messageStruct; 104 | //读取结构体 105 | int n = recv(infor->fd, &messageStruct, sizeof(MessageStruct), 0); 106 | if (n > 0) { //收到的数据 107 | //获取结构体中的json长度字段 108 | char *jsonData = static_cast(malloc(messageStruct.jsonLen)); 109 | //再读json数据 110 | if (jsonData== nullptr) 111 | return; 112 | recv(infor->fd, jsonData, messageStruct.jsonLen, 0); 113 | // LOG(INFO) << infor->ip << " 收到一条消息 jsonLen = " << messageStruct.jsonLen<<" jsonData = "<push(string(jsonData, messageStruct.jsonLen), infor); 115 | free(jsonData); 116 | } else if (n == 0) { 117 | LOG(INFO) << "fd: " << infor->fd << " 连接关闭"; 118 | close(infor->fd); 119 | infor->status = false; 120 | } else { 121 | close(infor->fd); 122 | infor->status = false; 123 | // LOG(ERROR) << "接收数据失败"; 124 | LOG(INFO) << "fd: " << infor->fd << " 连接关闭"; 125 | // printf("recv[fd=%d] error[%d]:%s\n", infor->fd, errno, strerror(errno)); 126 | } 127 | // while ((n = read(infor->fd, buff, BUFF_MAX-1)) > 1) { 128 | // printf("%s 发来一条消息: %s\n", infor->ip.c_str(), buff); 129 | // bzero(buff, sizeof(buff)); 130 | // } 131 | //如果退出的话要delete 132 | 133 | //这里只专注与读数据 134 | //多线程下面这个delete有问题,recvdata会执行 135 | //delete(infor); 136 | //int res = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, infor->fd, nullptr); // 这里返回-1 137 | // close(infor->fd); 138 | } 139 | 140 | void Server::senddata(event_infor *infor) { 141 | // read(infor1->fd, buff,BUFF_MAX); 142 | if (infor->buff[0] == '\0') { 143 | return; 144 | } 145 | int nums = send(infor->fd, infor->buff, BUFF_MAX - 1, 0); 146 | bzero(infor->buff, BUFF_MAX - 1); 147 | // epoll_ctl(epoll_fd, EPOLL_CTL_DEL, infor->fd, nullptr); 148 | // int ep = EPOLLIN; 149 | // infor->events = ep; 150 | // eventadd(ep, infor); 151 | 152 | } 153 | 154 | void Server::eventadd(int events, event_infor *infor) { 155 | epoll_event event{0, {nullptr}}; 156 | event.events = events; 157 | infor->events = events; 158 | event.data.ptr = static_cast(infor); 159 | epoll_ctl(epoll_fd, EPOLL_CTL_ADD, infor->fd, &event); 160 | } 161 | 162 | 163 | void Server::eventset() { 164 | 165 | } 166 | 167 | -------------------------------------------------------------------------------- /Server.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by daimiaopeng on 2019/10/31. 3 | // 4 | 5 | 6 | #ifndef CHAT_SERVER_H 7 | #define CHAT_SERVER_H 8 | 9 | #include "MessageQueue.h" 10 | 11 | #include "event_infor.h" 12 | #include "glog/logging.h" 13 | #include 14 | #include 15 | 16 | class Server { 17 | private: 18 | string _ip; 19 | int lfd; 20 | u_int _port; 21 | struct sockaddr_in serv_addr{}; 22 | int epoll_fd; 23 | struct event_infor g_events[MAX_EVENTS + 1]{}; 24 | MessageQueue *_messageQueue; 25 | public: 26 | Server(string ip, u_int port, const MessageQueue *messageQueue); 27 | 28 | void run(); 29 | 30 | void eventset(); 31 | 32 | void eventdel(); 33 | 34 | void initsocket(); 35 | 36 | void recvdata(event_infor *infor); 37 | 38 | void acceptconn(event_infor *infor); 39 | 40 | void eventadd(int events, event_infor *infor); 41 | 42 | void senddata(event_infor *infor); 43 | }; 44 | 45 | 46 | #endif //CHAT_SERVER_H 47 | -------------------------------------------------------------------------------- /bug修复日志.md: -------------------------------------------------------------------------------- 1 | ## 黏包分包 2020年1月5日09:44:53 : 2 | 3 | ```bash 4 | I0105 09:41:18.455325 16923 Server.cpp:95] 127.0.0.1 发来一条消息: {"code":2,"token":"qwe"}{"code":4,"token":"qwe"} 5 | E0105 09:41:18.456482 16923 MessageJson.h:39] 消息格式错误: [json.exception.parse_error.101] parse error at line 1, column 25: syntax error while parsing value - unexpected '{'; expected end of input 6 | ``` 7 | 8 | ### 分析: 9 | 客户端发送缓冲区把两条消息一同发送,服务器没有对tcp进行分包,不是导致缓冲区积压,而是客户端把两条消息变成一条发送。 10 | ### 解决办法: 11 | 服务器对消息进行分包处理。 12 | 13 | ### 修复状态: 14 | 15 | ​ 无 16 | 17 | ------ 18 | -------------------------------------------------------------------------------- /event_infor.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by daimiaopeng on 2019/12/16. 3 | // 4 | 5 | #ifndef CHAT_PROJECT_EVENT_INFOR_H 6 | #define CHAT_PROJECT_EVENT_INFOR_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #define BUFF_MAX 1024 22 | #define MAX_EVENTS 1280 23 | struct event_infor; 24 | typedef std::function EventCallback; 25 | struct event_infor { 26 | std::string ip; 27 | u_int port; 28 | int fd; 29 | bool status; 30 | EventCallback writeCallback; 31 | EventCallback readCallback; 32 | char buff[BUFF_MAX]; 33 | int events; 34 | epoll_event *epoll_infor; 35 | std::string id; 36 | }; 37 | 38 | struct MessageStruct{ 39 | int jsonLen; 40 | char json[0]; 41 | }; 42 | 43 | #endif //CHAT_PROJECT_EVENT_INFOR_H 44 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "Server.h" 2 | #include "Redis.h" 3 | #include "glog/logging.h" 4 | 5 | 6 | using namespace std; 7 | 8 | //INFO: 0, WARNING: 1, ERROR: 2 FATAL: 3; 9 | void initLog() { 10 | google::InitGoogleLogging("serverLog"); //初始化log的名字为daqing 11 | google::SetLogDestination(google::GLOG_INFO, "../log/"); //设置输出日志的文件夹,文件夹必须已经存在 12 | google::SetStderrLogging(google::GLOG_INFO); 13 | google::SetLogFilenameExtension("log_"); 14 | FLAGS_colorlogtostderr = true; // Set log color 15 | FLAGS_logbufsecs = 0; // Set log output speed(s) 16 | FLAGS_max_log_size = 1024; // Set max log file size 17 | FLAGS_stop_logging_if_full_disk = true; // If disk is ful 18 | } 19 | 20 | int main() { 21 | initLog(); 22 | Redis redis("127.0.0.1", 6379); 23 | // Redis redis("120.77.204.248", 6379,""); 24 | MessageQueue messageQueue(redis); 25 | Server server("0.0.0.0", 6667, &messageQueue); 26 | 27 | server.run(); 28 | 29 | return 0; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /qt/.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | -------------------------------------------------------------------------------- /qt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(untitled LANGUAGES CXX) 4 | 5 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 6 | 7 | set(CMAKE_AUTOUIC ON) 8 | set(CMAKE_AUTOMOC ON) 9 | set(CMAKE_AUTORCC ON) 10 | 11 | set(CMAKE_CXX_STANDARD 17) 12 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 13 | 14 | #set(Boost_INCLUDE_DIR /usr/local/include/boost) 15 | #set(Boost_LIBRARY_DIR /usr/local/lib) 16 | 17 | #set(Qt5Widgets_DIR /home/daimiaopeng/Qt5.14.0/5.14.0/gcc_64/include/Qt5Widgets_DIR) 18 | #set(Qt5_DIR /home/daimiaopeng/Qt5.14.0/5.14.0/gcc_64/lib/cmake/Qt5) 19 | 20 | 21 | find_package(Qt5 COMPONENTS Widgets REQUIRED Network) 22 | 23 | if(WIN32) 24 | if(MSVC) 25 | set_target_properties(${PROJECT_NAME} PROPERTIES 26 | WIN32_EXECUTABLE YES 27 | LINK_FLAGS "/ENTRY:mainCRTStartup" 28 | ) 29 | elseif(CMAKE_COMPILER_IS_GNUCXX) 30 | # SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mwindows") # Not tested 31 | else() 32 | message(SEND_ERROR "You are using an unsupported Windows compiler! (Not MSVC or GCC)") 33 | endif(MSVC) 34 | elseif(APPLE) 35 | set_target_properties(${PROJECT_NAME} PROPERTIES 36 | MACOSX_BUNDLE YES 37 | ) 38 | elseif(UNIX) 39 | # Nothing special required 40 | else() 41 | message(SEND_ERROR "You are on an unsupported platform! (Not Win32, Mac OS X or Unix)") 42 | endif(WIN32) 43 | 44 | add_executable(untitled 45 | main.cpp 46 | mainwindow.cpp 47 | mainwindow.h 48 | mainwindow.ui 49 | client.cpp 50 | client.h 51 | client.ui 52 | socket.h 53 | socket.cpp 54 | register.cpp 55 | register.h 56 | register.ui 57 | ) 58 | 59 | target_link_libraries(untitled PRIVATE Qt5::Widgets Qt5::Core Qt5::Network) 60 | -------------------------------------------------------------------------------- /qt/client.cpp: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | #include "ui_client.h" 3 | #include 4 | 5 | Client::Client(QWidget *parent, Socket *socket) : 6 | QMainWindow(parent), 7 | ui(new Ui::Client) 8 | { 9 | _socket = socket; 10 | connect(_socket, SIGNAL(code5(json)), this, SLOT(code5(json))); 11 | connect(_socket, SIGNAL(code4(json)), this, SLOT(code4(json))); 12 | connect(_socket, SIGNAL(code2(json)), this, SLOT(code2(json))); 13 | connect(_socket, SIGNAL(code3(json)), this, SLOT(code3(json))); 14 | connect(this, SIGNAL(getRegisterNums()), _socket, SLOT(getRegisterNums())); 15 | connect(this, SIGNAL(getOnile()), _socket, SLOT(getOnile())); 16 | timer = new QTimer(this); 17 | //关联定时器溢出信号和相应的槽函数 18 | connect(timer, SIGNAL(timeout()), this, SLOT(timerUpdate())); 19 | timer->start(2000); 20 | ui->setupUi(this); 21 | ui->textBrowser->setReadOnly(true); 22 | setWindowTitle(tr("聊天客户端")); 23 | bufsize = 3000; 24 | } 25 | 26 | Client::~Client() { 27 | delete ui; 28 | } 29 | 30 | void Client::code5(json _json){ 31 | qDebug()<<"code5"; 32 | int size =_json["size"]; 33 | int part = _json["part"]; 34 | if(ui->progressBar->value()==0){ 35 | ui->progressBar->setRange(0,size); 36 | } 37 | ui->progressBar->setValue(part); 38 | if(ui->progressBar->value()==100){ 39 | QMessageBox::warning(this, tr("Waring"), "接收文件成功", QMessageBox::Yes); 40 | ui->progressBar->setValue(0); 41 | } 42 | QFile file1("test.png"); 43 | file1.open(QIODevice::Append|QIODevice::ReadWrite); 44 | QDataStream out(&file1); 45 | out.setVersion(QDataStream::Qt_5_7); 46 | QByteArray data = QByteArray::fromBase64(string(_json["data"]["message"]).c_str(), QByteArray::Base64Encoding); 47 | out.writeRawData(data,data.size()); 48 | file1.close(); 49 | } 50 | 51 | void Client::code4(json _json){ 52 | int nums =_json["nums"]; 53 | qDebug()<label_7->setText(QString(num.c_str())); 56 | } 57 | 58 | void Client::code3(json _json){ 59 | ui->textBrowser->moveCursor(QTextCursor::End); 60 | QString message = QString::fromStdString(_json["data"]["message"]).toLocal8Bit(); 61 | QString sender = QString::fromStdString(_json["sender"]); 62 | qDebug()<<"message: "<"+sender+": "+message+"\n"+""; 66 | 67 | ui->textBrowser->append(mess); 68 | 69 | } 70 | 71 | void Client::code2(json _json){ 72 | ui->listWidget->clear(); 73 | int nums =_json["nums"]; 74 | auto all = _json["data"]; 75 | qDebug()<label_8->setText(QString(num.c_str())); 78 | for(const auto &i:all){ 79 | if(i == _socket->userName.toStdString()) continue; 80 | ui->listWidget->addItem(QString::fromStdString(string(i))); 81 | } 82 | } 83 | void Client::timerUpdate(){ 84 | emit getOnile(); 85 | // emit getRegisterNums(); 86 | ui->label_14->setText(_socket->userName); 87 | } 88 | 89 | 90 | void Client::on_pushButton_clicked() 91 | { 92 | QString input_data = ui->textEdit->toPlainText(); 93 | if(input_data=="") return; 94 | if(_socket->receiver==""){ 95 | QMessageBox::warning(this, tr("Waring"), "请选择一个聊天用户", QMessageBox::Yes); 96 | return; 97 | } 98 | json _json; 99 | _json["token"] = _socket->token.toStdString(); 100 | _json["code"] = 3; 101 | _json["receiver"] = _socket->receiver.toStdString(); 102 | _json["data"] = input_data.toStdString(); 103 | 104 | _socket->writeData(QString::fromStdString(_json.dump())); 105 | ui->textBrowser->moveCursor(QTextCursor::End); 106 | QString mess = "you: "+input_data+"\n"; 107 | ui->textBrowser->append(""+mess+""); 108 | // ui->textBrowser->append("

123

"); 109 | 110 | // ui->textBrowser->insertPlainText(mess); 111 | 112 | ui->textEdit->clear(); 113 | } 114 | 115 | void Client::on_listWidget_itemActivated(QListWidgetItem *item) 116 | { 117 | _socket->receiver = item->text(); 118 | ui->label_13->setText(_socket->receiver); 119 | } 120 | 121 | void Client::on_pushButton_2_clicked() 122 | { 123 | 124 | 125 | QString curPath=QDir::currentPath();//获取系统当前目录 126 | //获取应用程序的路径 127 | QString dlgTitle="选择一个文件"; //对话框标题 128 | QString filter="所有文件(*.*)"; //文件过滤器 129 | // QString filter="文本文件(*.txt);;图片文件(*.jpg *.gif *.png);;所有文件(*.*)"; //文件过滤器 130 | QString aFileName=QFileDialog::getOpenFileName(this,dlgTitle,curPath,filter); 131 | QProgressDialog process(this); 132 | if (!aFileName.isEmpty()){ 133 | QFile file(aFileName); 134 | file.open(QIODevice::ReadOnly); 135 | QByteArray fileData = file.readAll(); 136 | string fileDataString(fileData.toBase64().toStdString()); 137 | 138 | qDebug()<<"fileData.size()"<token.toStdString(); 148 | _json["code"] = 5; 149 | _json["size"] = fileDataString.size(); 150 | _json["receiver"] = _socket->receiver.toStdString(); 151 | for(long long i=0;iwriteData(QString::fromStdString(_json.dump())); 155 | process.setValue(i); 156 | } 157 | file.close(); 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /qt/client.h: -------------------------------------------------------------------------------- 1 | #ifndef CLIENT_H 2 | #define CLIENT_H 3 | 4 | #include 5 | #include"json.hpp" 6 | #include"socket.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | using json = nlohmann::json; 17 | 18 | namespace Ui { 19 | class Client; 20 | } 21 | 22 | class Client : public QMainWindow { 23 | Q_OBJECT 24 | 25 | public: 26 | explicit Client(QWidget *parent = nullptr, Socket *socket = nullptr); 27 | ~Client(); 28 | signals: 29 | void getRegisterNums(); 30 | void getOnile(); 31 | private slots: 32 | void code5(json _json); 33 | void code4(json _json); 34 | void code2(json _json); 35 | void code3(json _json); 36 | void on_pushButton_clicked(); 37 | void timerUpdate(); 38 | 39 | void on_listWidget_itemActivated(QListWidgetItem *item); 40 | 41 | void on_pushButton_2_clicked(); 42 | 43 | private: 44 | QTimer *timer; 45 | Ui::Client *ui; 46 | Socket *_socket; 47 | int bufsize; 48 | }; 49 | 50 | #endif // CLIENT_H 51 | -------------------------------------------------------------------------------- /qt/client.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Client 4 | 5 | 6 | 7 | 0 8 | 0 9 | 672 10 | 513 11 | 12 | 13 | 14 | Client 15 | 16 | 17 | 18 | 19 | 20 | 470 21 | 190 22 | 171 23 | 201 24 | 25 | 26 | 27 | 28 | 29 | 30 | 480 31 | 0 32 | 175 33 | 46 34 | 35 | 36 | 37 | 38 | 39 | 40 | font: 18pt "微软雅黑"; 41 | 42 | 43 | 注册人数 44 | 45 | 46 | 47 | 48 | 49 | 50 | font: 18pt "微软雅黑"; 51 | 52 | 53 | 0 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 480 63 | 60 64 | 145 65 | 46 66 | 67 | 68 | 69 | 70 | 71 | 72 | font: 18pt "微软雅黑"; 73 | 74 | 75 | 在线人数 76 | 77 | 78 | 79 | 80 | 81 | 82 | font: 18pt "微软雅黑"; 83 | 84 | 85 | 0 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 40 95 | 310 96 | 261 97 | 71 98 | 99 | 100 | 101 | 102 | 103 | 104 | 310 105 | 310 106 | 121 107 | 71 108 | 109 | 110 | 111 | 发送 112 | 113 | 114 | 115 | 116 | 117 | 480 118 | 110 119 | 161 120 | 31 121 | 122 | 123 | 124 | font: 12pt "微软雅黑"; 125 | 126 | 127 | 当前选择聊天用户为: 128 | 129 | 130 | 131 | 132 | 133 | 480 134 | 150 135 | 161 136 | 31 137 | 138 | 139 | 140 | font: 12pt "微软雅黑"; 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 40 150 | 60 151 | 391 152 | 241 153 | 154 | 155 | 156 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 157 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 158 | p, li { white-space: pre-wrap; } 159 | </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> 160 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu';"><br /></p></body></html> 161 | 162 | 163 | false 164 | 165 | 166 | 167 | 168 | 169 | 470 170 | 390 171 | 162 172 | 54 173 | 174 | 175 | 176 | 请双击用户列表选择 177 | 需要聊天的用户 178 | 179 | 180 | 181 | 182 | 183 | 40 184 | 10 185 | 188 186 | 32 187 | 188 | 189 | 190 | 191 | 192 | 193 | font: 12pt "微软雅黑"; 194 | 195 | 196 | 欢迎 197 | 198 | 199 | 200 | 201 | 202 | 203 | font: 12pt "微软雅黑"; 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 40 216 | 400 217 | 391 218 | 41 219 | 220 | 221 | 222 | 223 | 224 | 225 | 发送文件 226 | 227 | 228 | 229 | 230 | 231 | 232 | 0 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 0 243 | 0 244 | 672 245 | 32 246 | 247 | 248 | 249 | 250 | Chat Room 251 | 252 | 253 | 254 | 255 | 256 | 257 | toolBar 258 | 259 | 260 | TopToolBarArea 261 | 262 | 263 | false 264 | 265 | 266 | 267 | 268 | toolBar_2 269 | 270 | 271 | TopToolBarArea 272 | 273 | 274 | false 275 | 276 | 277 | 278 | 279 | 280 | 281 | -------------------------------------------------------------------------------- /qt/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "client.h" 3 | #include 4 | #include 5 | #include "socket.h" 6 | 7 | int main(int argc, char *argv[]) { 8 | QApplication app(argc, argv); 9 | Socket socket; 10 | Client c(nullptr,&socket); 11 | MainWindow w(nullptr, &socket,&c); 12 | w.show(); 13 | 14 | return app.exec(); 15 | } 16 | -------------------------------------------------------------------------------- /qt/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "./ui_mainwindow.h" 3 | #include 4 | #include 5 | 6 | 7 | 8 | MainWindow::MainWindow(QWidget *parent, Socket *socket,Client *c) 9 | : QMainWindow(parent), ui(new Ui::MainWindow) { 10 | ui->setupUi(this); 11 | _socket = socket; 12 | _c = c; 13 | QObject::connect(_socket, SIGNAL(code0(json)), this, SLOT(code0(json))); 14 | QObject::connect(this, SIGNAL(login(QString, QString)), _socket, SLOT(login(QString, QString))); 15 | connect(_socket->tcpSocket, SIGNAL(connected()), this, SLOT(connected())); 16 | //connect(_socket->tcpSocket, SIGNAL(disconnected()), this, SLOT(linkError())); 17 | ui->usrLineEdit->setFocus(); 18 | on_linkBtn_clicked(); 19 | setWindowTitle(tr("登录")); 20 | } 21 | 22 | MainWindow::~MainWindow() { 23 | delete ui; 24 | } 25 | void MainWindow::linkError(){ 26 | QMessageBox::warning(this, tr("Waring"), tr("连接失败"), QMessageBox::Yes); 27 | } 28 | void MainWindow::code0(json _json) { 29 | try { 30 | string message = _json["data"]["message"]; 31 | token = QString::fromStdString(_json["token"]); 32 | } catch (std::exception &e) { 33 | qDebug() << e.what(); 34 | } 35 | _socket->token = token; 36 | qDebug() << "set token:" << token; 37 | goCilent( _json); 38 | } 39 | 40 | void MainWindow::goCilent(json jsonMessage) { 41 | string message = jsonMessage["data"]["message"]; 42 | if (token != "") { 43 | QMessageBox::warning(this, tr("Waring"), message.c_str(), QMessageBox::Yes); 44 | } else { 45 | ui->pwdLineEdit->clear(); 46 | ui->pwdLineEdit->setFocus(); 47 | QMessageBox::warning(this, tr("Waring"), message.c_str(), QMessageBox::Yes); 48 | } 49 | } 50 | 51 | void MainWindow::on_exitBtn_clicked() { 52 | // accept(); 53 | } 54 | 55 | 56 | void MainWindow::on_loginBtn_clicked() { 57 | QString name = ui->usrLineEdit->text(); 58 | QString passwd = ui->pwdLineEdit->text(); 59 | if (name == "") { 60 | QMessageBox::warning(this, tr("Waring"), tr("用户名为空"), QMessageBox::Yes); 61 | return; 62 | } 63 | if (passwd == "") { 64 | QMessageBox::warning(this, tr("Waring"), tr("密码为空"), QMessageBox::Yes); 65 | return; 66 | } 67 | _socket->userName = name; 68 | emit login(name, passwd); 69 | } 70 | 71 | void MainWindow::connected(){ 72 | QMessageBox::warning(this, tr("Waring"), tr("连接成功"), QMessageBox::Yes); 73 | } 74 | void MainWindow::on_registered_clicked() { 75 | Register reg(nullptr, _socket); 76 | reg.exec(); 77 | qDebug() << "yes"; 78 | } 79 | 80 | void MainWindow::on_linkBtn_clicked() 81 | { 82 | QString ip = ui->ipLineEdit->text(); 83 | int port = ui->portLineEdit->text().toInt(); 84 | _socket->init(ip,port); 85 | } 86 | 87 | void MainWindow::on_pushButton_clicked() 88 | { 89 | this->close(); 90 | _c->show(); 91 | 92 | } 93 | -------------------------------------------------------------------------------- /qt/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include "socket.h" 6 | #include 7 | #include 8 | #include "register.h" 9 | #include "client.h" 10 | QT_BEGIN_NAMESPACE 11 | namespace Ui { class MainWindow; } 12 | QT_END_NAMESPACE 13 | 14 | class MainWindow : public QMainWindow { 15 | Q_OBJECT 16 | 17 | public: 18 | MainWindow(QWidget *parent = nullptr, Socket *socket = nullptr, Client *c = nullptr); 19 | 20 | ~MainWindow(); 21 | 22 | signals: 23 | 24 | void 25 | login(QString 26 | name, 27 | QString passwd 28 | ); 29 | 30 | public 31 | slots: 32 | 33 | void on_exitBtn_clicked(); 34 | 35 | void on_loginBtn_clicked(); 36 | 37 | void code0(json _json); 38 | void linkError(); 39 | void connected(); 40 | private 41 | slots: 42 | 43 | void 44 | 45 | on_registered_clicked(); 46 | 47 | void on_linkBtn_clicked(); 48 | 49 | void on_pushButton_clicked(); 50 | 51 | private: 52 | QString token; 53 | Socket *_socket; 54 | Client *_c; 55 | Ui::MainWindow *ui; 56 | bool w; 57 | void goCilent(json jsonMessage); 58 | }; 59 | 60 | #endif // MAINWINDOW_H 61 | -------------------------------------------------------------------------------- /qt/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 678 10 | 513 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 310 21 | 180 22 | 159 23 | 29 24 | 25 | 26 | 27 | QLineEdit::Password 28 | 29 | 30 | 请输入密码 31 | 32 | 33 | 34 | 35 | 36 | 220 37 | 250 38 | 80 39 | 36 40 | 41 | 42 | 43 | 登录 44 | 45 | 46 | 47 | 48 | 49 | 210 50 | 360 51 | 261 52 | 31 53 | 54 | 55 | 56 | 退出 57 | 58 | 59 | 60 | 61 | 62 | 210 63 | 120 64 | 54 65 | 27 66 | 67 | 68 | 69 | 用户名 70 | 71 | 72 | 73 | 74 | 75 | 210 76 | 180 77 | 36 78 | 27 79 | 80 | 81 | 82 | 密码 83 | 84 | 85 | 86 | 87 | 88 | 310 89 | 120 90 | 159 91 | 29 92 | 93 | 94 | 95 | 请输入用户名 96 | 97 | 98 | 99 | 100 | 101 | 380 102 | 250 103 | 80 104 | 36 105 | 106 | 107 | 108 | 注册 109 | 110 | 111 | 112 | 113 | 114 | 110 115 | 40 116 | 495 117 | 38 118 | 119 | 120 | 121 | 122 | 123 | 124 | ip 125 | 126 | 127 | 128 | 129 | 130 | 131 | 127.0.0.1 132 | 133 | 134 | ip 135 | 136 | 137 | 138 | 139 | 140 | 141 | port 142 | 143 | 144 | 145 | 146 | 147 | 148 | 6667 149 | 150 | 151 | QLineEdit::Normal 152 | 153 | 154 | port 155 | 156 | 157 | 158 | 159 | 160 | 161 | 连接服务器 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 210 171 | 310 172 | 261 173 | 31 174 | 175 | 176 | 177 | 进入主界面 178 | 179 | 180 | 181 | 182 | 183 | 184 | 0 185 | 0 186 | 678 187 | 32 188 | 189 | 190 | 191 | 192 | 193 | toolBar 194 | 195 | 196 | TopToolBarArea 197 | 198 | 199 | false 200 | 201 | 202 | 203 | 204 | toolBar_2 205 | 206 | 207 | TopToolBarArea 208 | 209 | 210 | false 211 | 212 | 213 | 214 | 215 | 216 | 217 | exitBtn 218 | clicked() 219 | MainWindow 220 | close() 221 | 222 | 223 | 325 224 | 363 225 | 226 | 227 | 278 228 | 252 229 | 230 | 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /qt/register.cpp: -------------------------------------------------------------------------------- 1 | #include "register.h" 2 | #include "ui_register.h" 3 | 4 | 5 | Register::Register(QWidget *parent, Socket *socket) : 6 | QDialog(parent), 7 | ui(new Ui::Register) { 8 | _socket = socket; 9 | QObject::connect(_socket, SIGNAL(code1(json)), this, SLOT(code1(json))); 10 | QObject::connect(this, SIGNAL(reg(QString, QString)), _socket, SLOT(reg(QString, QString))); 11 | ui->setupUi(this); 12 | setWindowTitle(tr("注册")); 13 | } 14 | 15 | 16 | Register::~Register() { 17 | delete ui; 18 | } 19 | 20 | void Register::code1(json _json) { 21 | string message; 22 | try { 23 | message = _json["data"]["message"]; 24 | isCode = _json["isCode"]; 25 | } catch (std::exception &e) { 26 | qDebug() << e.what(); 27 | } 28 | if (isCode == 0) { 29 | QMessageBox::warning(this, tr("Waring"), message.c_str(), QMessageBox::Yes); 30 | return; 31 | } 32 | QMessageBox::warning(this, tr("Waring"), tr("注册成功"), QMessageBox::Yes); 33 | exit(); 34 | qDebug() << "set isCode:" << isCode; 35 | } 36 | 37 | void Register::exit() { 38 | this->close(); 39 | } 40 | 41 | void Register::on_registerBut_clicked() { 42 | QString name = ui->user->text(); 43 | QString passwd1 = ui->pwdLineEdit_2->text(); 44 | QString passwd2 = ui->pwdLineEdit_3->text(); 45 | if (name == "") { 46 | QMessageBox::warning(this, tr("Waring"), tr("用户名为空"), QMessageBox::Yes); 47 | return; 48 | } 49 | if (passwd1 == "" || passwd2 == "") { 50 | QMessageBox::warning(this, tr("Waring"), tr("密码为空"), QMessageBox::Yes); 51 | return; 52 | } 53 | if (passwd1 != passwd2) { 54 | QMessageBox::warning(this, tr("Waring"), tr("两次输入密码不一致"), QMessageBox::Yes); 55 | return; 56 | } 57 | emit reg(name, passwd1); 58 | qDebug() << "yes"; 59 | 60 | } 61 | -------------------------------------------------------------------------------- /qt/register.h: -------------------------------------------------------------------------------- 1 | #ifndef REGISTER_H 2 | #define REGISTER_H 3 | 4 | #include 5 | #include 6 | #include "mainwindow.h" 7 | #include 8 | 9 | namespace Ui { 10 | class Register; 11 | } 12 | 13 | class Register : public QDialog { 14 | Q_OBJECT 15 | 16 | public: 17 | explicit Register(QWidget *parent = nullptr, Socket *socket = nullptr); 18 | 19 | ~Register(); 20 | 21 | 22 | signals: 23 | 24 | void reg(QString name,QString passwd); 25 | 26 | 27 | private slots: 28 | 29 | void on_registerBut_clicked(); 30 | 31 | void code1(json _json); 32 | 33 | private: 34 | void exit(); 35 | int isCode; 36 | Ui::Register *ui; 37 | Socket *_socket; 38 | }; 39 | 40 | #endif // REGISTER_H 41 | -------------------------------------------------------------------------------- /qt/register.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Register 4 | 5 | 6 | 7 | 0 8 | 0 9 | 530 10 | 402 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 120 20 | 140 21 | 36 22 | 27 23 | 24 | 25 | 26 | 密码 27 | 28 | 29 | 30 | 31 | 32 | 220 33 | 140 34 | 159 35 | 29 36 | 37 | 38 | 39 | QLineEdit::Password 40 | 41 | 42 | 请输入密码 43 | 44 | 45 | 46 | 47 | 48 | 120 49 | 80 50 | 54 51 | 27 52 | 53 | 54 | 55 | 用户名 56 | 57 | 58 | 59 | 60 | 61 | 120 62 | 200 63 | 72 64 | 27 65 | 66 | 67 | 68 | 确认密码 69 | 70 | 71 | 72 | 73 | 74 | 220 75 | 200 76 | 159 77 | 29 78 | 79 | 80 | 81 | QLineEdit::Password 82 | 83 | 84 | 请输入密码 85 | 86 | 87 | 88 | 89 | 90 | 120 91 | 280 92 | 261 93 | 31 94 | 95 | 96 | 97 | 注册 98 | 99 | 100 | 101 | 102 | 103 | 220 104 | 80 105 | 159 106 | 29 107 | 108 | 109 | 110 | 请输入用户名 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /qt/socket.cpp: -------------------------------------------------------------------------------- 1 | #include "socket.h" 2 | #include 3 | Socket::Socket(QObject *parent) : QObject(parent) { 4 | tcpSocket = new QTcpSocket(); 5 | connect(tcpSocket, SIGNAL(connected()), this, SLOT(connected())); 6 | connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(readData())); 7 | 8 | } 9 | void Socket::init(QString ip,int port){ 10 | tcpSocket->connectToHost(ip, port, QTcpSocket::ReadWrite); 11 | } 12 | 13 | void Socket::connected() { 14 | qDebug() << "connected" << "\n"; 15 | } 16 | 17 | void Socket::readData() { 18 | qDebug() << "readMessage"; 19 | MessageStruct messageStruct; 20 | tcpSocket->read((char*)&messageStruct,sizeof(MessageStruct)); 21 | char *jsonData = static_cast(malloc(messageStruct.jsonLen)); 22 | tcpSocket->read(jsonData,messageStruct.jsonLen); 23 | string readMessage(jsonData,messageStruct.jsonLen); 24 | free(jsonData); 25 | qDebug() << "readMessage: " << QString::fromStdString(readMessage)<< "\n"; 26 | try { 27 | _json = json::parse(readMessage); 28 | } catch (std::exception &e) { 29 | qDebug() << e.what(); 30 | return; 31 | } 32 | int code = _json["code"]; 33 | switch (code) { 34 | case 0: 35 | emit code0(_json); 36 | qDebug() << "login"; 37 | break; 38 | case 1: 39 | emit code1(_json); 40 | qDebug() << "registered"; 41 | break; 42 | case 2: 43 | emit code2(_json); 44 | qDebug() << "registered"; 45 | break; 46 | case 3: 47 | emit code3(_json); 48 | break; 49 | case 4: 50 | emit code4(_json); 51 | qDebug() << "getRegisterNums"; 52 | break; 53 | case 5: 54 | emit code5(_json); 55 | qDebug() << "getFile"; 56 | break; 57 | default: 58 | qDebug() << "default"; 59 | break; 60 | } 61 | } 62 | void Socket::getOnile(){ 63 | json message; 64 | message["code"] = 2; 65 | message["token"] = token.toStdString(); 66 | QString writeMessage = QString::fromStdString(message.dump()); 67 | qDebug() << "writeData: " << writeMessage; 68 | writeData(writeMessage); 69 | } 70 | void Socket::login(QString name, QString passwd) { 71 | json message; 72 | message["code"] = 0; 73 | message["data"]["name"] = name.toStdString(); 74 | message["data"]["passwd"] = passwd.toStdString(); 75 | QString writeMessage = QString::fromStdString(message.dump()); 76 | qDebug() << "writeData: " << writeMessage; 77 | writeData(writeMessage); 78 | } 79 | 80 | void Socket::reg(QString name, QString passwd) { 81 | json message; 82 | message["code"] = 1; 83 | message["data"]["name"] = name.toStdString(); 84 | message["data"]["passwd"] = passwd.toStdString(); 85 | QString writeMessage = QString::fromStdString(message.dump()); 86 | qDebug() << "writeData: " << writeMessage; 87 | writeData(writeMessage); 88 | } 89 | 90 | 91 | void Socket::getRegisterNums(){ 92 | json message; 93 | message["code"] = 4; 94 | message["token"] = token.toStdString(); 95 | QString writeMessage = QString::fromStdString(message.dump()); 96 | qDebug() << "getRegisterNums: " << writeMessage; 97 | writeData(writeMessage); 98 | } 99 | 100 | void Socket::writeData(QString message) { 101 | string data = message.toStdString(); 102 | int len = sizeof(MessageStruct) + data.size(); 103 | MessageStruct *messageStruct = static_cast(malloc(len)); 104 | messageStruct->jsonLen = data.size(); 105 | strncpy(messageStruct->json, data.c_str(), data.size()); 106 | tcpSocket->write((char*) messageStruct,len); 107 | tcpSocket->flush(); 108 | delete [] messageStruct; 109 | } 110 | 111 | 112 | -------------------------------------------------------------------------------- /qt/socket.h: -------------------------------------------------------------------------------- 1 | #ifndef SOCKET_H 2 | #define SOCKET_H 3 | 4 | #include 5 | #include 6 | #include"json.hpp" 7 | #include 8 | 9 | using json = nlohmann::json; 10 | using namespace std; 11 | struct MessageStruct{ 12 | int jsonLen; 13 | char json[0]; 14 | }; 15 | class Socket : public QObject { 16 | Q_OBJECT 17 | public: 18 | explicit Socket(QObject *parent = nullptr); 19 | QTcpSocket *tcpSocket; 20 | void writeData(QString message); 21 | void init(QString ip,int port); 22 | QString receiver; 23 | QString userName; 24 | QString token; 25 | signals: 26 | void code0(json _json); 27 | void code1(json _json); 28 | void code2(json _json); 29 | void code3(json _json); 30 | void code4(json _json); 31 | void code5(json _json); 32 | public 33 | slots: 34 | void readData(); 35 | void connected(); 36 | void login(QString name, QString passwd); 37 | void reg(QString name, QString passwd); 38 | void getRegisterNums(); 39 | void getOnile(); 40 | protected: 41 | json _json; 42 | 43 | 44 | 45 | }; 46 | 47 | #endif // SOCKET_H 48 | -------------------------------------------------------------------------------- /test/client.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by daimiaopeng on 2019/12/19. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | struct MessageStruct { 20 | int jsonLen; 21 | char json[0]; 22 | }; 23 | int main(int argc, char *argv[]) { 24 | int sockfd, numbytes; 25 | char buf[BUFSIZ]; 26 | struct sockaddr_in their_addr; 27 | printf("break!"); 28 | while ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1); 29 | printf("We get the sockfd~\n"); 30 | their_addr.sin_family = AF_INET; 31 | their_addr.sin_port = htons(6667); 32 | their_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 33 | bzero(&(their_addr.sin_zero), 8); 34 | 35 | while (connect(sockfd, (struct sockaddr *) &their_addr, sizeof(struct sockaddr)) == -1); 36 | printf("Get the Server~Cheers!\n"); 37 | int i = 0; 38 | 39 | // string data = "{\"code\":0,\"data\":{\"passwd\":\"123456\",\"name\":\"daimiaopeng\"}}"; 40 | // int len = sizeof(MessageStruct) + data.size(); 41 | // MessageStruct *messageStruct = static_cast(malloc(len)); 42 | // messageStruct->jsonLen = data.size(); 43 | // strncpy(messageStruct->json, data.c_str(), data.size()); 44 | // cout << messageStruct->jsonLen; 45 | 46 | string data = "{\"code\":0,\"data\":{\"passwd\":\"123456\",\"name\":\"daimiaopeng\"}}"; 47 | int len = sizeof(MessageStruct) + data.size(); 48 | MessageStruct *messageStruct = static_cast(malloc(len)); 49 | messageStruct->jsonLen = data.size(); 50 | strncpy(messageStruct->json, data.c_str(), data.size()); 51 | 52 | for (int i = 0; i < 1; i++) { 53 | send(sockfd, messageStruct, len, 0); 54 | MessageStruct revcStruct; 55 | int n = recv(sockfd, &messageStruct, sizeof(revcStruct), 0); 56 | if (n > 0) { //收到的数据 57 | //获取结构体中的json长度字段 58 | char *jsonData = static_cast(malloc(revcStruct.jsonLen)); 59 | //再读json数据 60 | recv(sockfd, jsonData, revcStruct.jsonLen, 0); 61 | cout << " 收到一条消息 jsonLen = " << revcStruct.jsonLen << " jsonData = " << jsonData; 62 | sleep(3); 63 | delete[](jsonData); 64 | } 65 | } 66 | return 0; 67 | } -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by daimiaopeng on 2019/12/12. 3 | // 4 | 5 | #ifndef CHAT_TEST 6 | #define CHAT_TEST 7 | #define _GLIBCXX_USE_CXX11_ABI 1 8 | 9 | #include 10 | #include "../Redis.h" 11 | #include "../MessageJson.h" 12 | 13 | using namespace std; 14 | 15 | void test0(Redis &redis) { 16 | // string name = "1223"; 17 | // string passwd = name; 18 | // redis.login(name, passwd); 19 | // redis.registered(name, passwd); 20 | // string token = redis.login(name, passwd); 21 | redis.getName("daimiaopeng"); 22 | // redis.getRegisterNums(); 23 | // redis.getOnlineNums(); 24 | } 25 | 26 | 27 | int main() { 28 | // 29 | Redis redis("127.0.0.1", 6379); 30 | // string a; 31 | // string b; 32 | // cin >> a; 33 | // {"code":0,"data":{"passwd":"123","name":"daimiaopeng"}} 34 | // MessageJson messageJson(redis, a); 35 | // string writeData = messageJson.res(); 36 | // cout << writeData; 37 | test0(redis); 38 | 39 | return 0; 40 | } 41 | 42 | #endif -------------------------------------------------------------------------------- /笔记.md: -------------------------------------------------------------------------------- 1 | # linux chat 开发笔记 2 | 3 | 4 | 5 | ## 回调函数的实现: 6 | 7 | ### function<> 初步认识 8 | 9 | 用`std::function<>`实现 10 | 头文件在`#include ` 通过`typedef std::function EventCallback;` 宏定义一个`EventCallback`回调函数 `` 返回值为void ,传入参数为自定义的事件结构体,目前暂定为下: 11 | 12 | ```c++ 13 | struct event_infor { 14 | std::string ip; //ip地址 15 | u_int port; //端口 16 | int fd; //文件描述符 17 | EventCallback eventCallback; // 回调函数 18 | int events; //事件的性质 比如EPOLLIN 19 | //其实应该还要有其它的定义,日后有需求再说 20 | }; 21 | ``` 22 | 把`event_infor`的`eventCallback`函数参数为`event_infor`这样就可以在函数中获取这些信息,不管用没用到。 23 | 24 | `epoll_event.events`和`event_infor.events`一样、`epoll_event.data.ptr`指针指向`event_infor`,注意在写代码中一定要用new分配内存来定义一个`event_infor`,在定义`epoll_event`时可以是局部变量因为在执行`epoll_ctl()`后会把`epoll_event`拷贝添加进内部的红黑树,会复制`epoll_event.data.ptr`但不会复制所指向的`event_infor`,所以用new开辟一块内存空间存放`event_infor`,然后返回指针把`epoll_event.data.ptr`指向它。这个坑我弄了一整天才发现,如果是局部变量的话在外面调用回调函数会出现段错误,或者结构体里面的变量默认初始化为0,这样的话当回调函数用这个里面的默认`fd = 0`,结果导致很多错误,比如`read()`一个`fd = 0`而本来客户端的`fd = 5`,那么程序会永远卡死在这里等待标准输入stdin(因为标准输入文件描述符为0)。图一在`event_infor`的生命周期有效,当执行回调函数把`*event_infor`传进去后,地址没变但内容全部为0,因为在这里它生命周期已经结束。 25 | 26 | 图1: 27 | 28 | ![KXuuut.png](https://s2.ax1x.com/2019/11/03/KXuuut.png) 29 | 图2: 30 | 31 | ![KXuKDP.png](https://s2.ax1x.com/2019/11/03/KXuKDP.png) 32 | 33 | ### 绑定回调函数 34 | 35 | 传统的绑定方法: 36 | 37 | 1.C语言的函数指针 38 | 39 | 2.`std::bind()` 40 | 41 | 3.lambda表达式 42 | 43 | 第一种就不深入探讨了,在C语言中只能用这个,而在c++中函数的绑定更为简单`std::bind()`为传统做法,lambda是c++11后才出现的,用这两个区别《effective modern c++ 》中有详细的讨论,作者是建议用lambda的,但是用之前要完全了解lambda的方方面面避免错误。 44 | 45 | lambda使用示例: 46 | 47 | `l_infor.eventCallback = [&](event_infor *infor){ acceptconn(infor);};` 48 | 49 | 通过[&]来额外引入其它变量,最后在调用实际功能函数,实际功能函数参数可以随便你,lambda的作用就是两者的粘合剂,优点简洁。 50 | 51 | ## TCP粘包解决方案 52 | 53 | 黏包的在客户端发送频率低的情况下粘包不明显,下面是原来的服务器中epoll一个事件可读的回调函数`recvdata(event_infor *infor)`,当调用这个函数然后`recv()`函数读缓冲区的数据,指定大小为`BUFF_MAX-1`,而`BUFF_MAX`我设置为1024,一般单次接收数据小于这个值,所以基本上就是读取了所有缓冲区数据,接收过程:收到数据->epoll可读->调用回调函数->读取缓冲区所有数据->空缓冲区等待下一次接收。在处理客户端发来的聊天消息时还没有发生粘包现象,处理文件传输时一直粘包,问题分析是客户端高速循环调用`send()`发送数据,也就是说服务器其实`recv()`多条数据,比如说发送`{"code":1}`和`{"code":2}`服务器读一次可能是`{"code":1}{"co`。 54 | 原服务器代码: 55 | 56 | ```c++ 57 | void Server::recvdata(event_infor *infor) { 58 | int n = recv(infor->fd, infor->buff, BUFF_MAX - 1, 0); 59 | if (n > 0) { //收到的数据 60 | infor->buff[n] = '\0'; 61 | LOG(INFO) << infor->ip << " 发来一条消息: " << infor->buff; 62 | _messageQueue->push(infor->buff, infor); 63 | } else if (n == 0) { 64 | LOG(INFO) << "fd: " << infor->fd << " 连接关闭"; 65 | close(infor->fd); 66 | infor->status = false; 67 | } else { 68 | close(infor->fd); 69 | infor->status = false; 70 | LOG(INFO) << "fd: " << infor->fd << " 连接关闭"; 71 | } 72 | bzero(infor->buff, BUFF_MAX - 1); 73 | } 74 | ``` 75 | 76 | 解决办法见下: 77 | 78 | ```c++ 79 | //结构体定义 80 | struct MessageStruct{ 81 | int jsonLen; 82 | char json[0]; //不能换成char *json 83 | }; 84 | ``` 85 | 86 | 客户端代码: 87 | 88 | ```c++ 89 | string data = "{\"code\":0,\"data\":{\"passwd\":\"123456\",\"name\":\"daimiaopeng\"}}"; 90 | int len = sizeof(MessageStruct) + data.size(); 91 | MessageStruct *messageStruct = static_cast(malloc(len)); 92 | messageStruct->jsonLen = data.size(); 93 | strncpy(messageStruct->json, data.c_str(), data.size()); 94 | //也可以用memcpy(),这里是string->char* 而不是char*->char* 95 | for (int i = 0; i < 1000; i++) { 96 | send(sockfd, messageStruct, len, 0); 97 | } 98 | free(messageStruct); 99 | ``` 100 | 101 | 改进后的服务器代码: 102 | 103 | ```c++ 104 | void Server::recvdata(event_infor *infor) { 105 | MessageStruct messageStruct; 106 | //读取结构体 107 | int n = recv(infor->fd, &messageStruct, sizeof(MessageStruct), 0); 108 | if (n > 0) { //收到的数据 109 | //获取结构体中的json长度字段 110 | char *jsonData = static_cast(malloc(messageStruct.jsonLen)); 111 | //再读json数据 112 | if (jsonData== nullptr) 113 | return; 114 | recv(infor->fd, jsonData, messageStruct.jsonLen, 0); 115 | _messageQueue->push(string(jsonData, messageStruct.jsonLen), infor); 116 | free(jsonData); 117 | } else if (n == 0) { 118 | LOG(INFO) << "fd: " << infor->fd << " 连接关闭"; 119 | close(infor->fd); 120 | infor->status = false; 121 | } else { 122 | close(infor->fd); 123 | infor->status = false; 124 | LOG(INFO) << "fd: " << infor->fd << " 连接关闭"; 125 | } 126 | } 127 | ``` 128 | 129 | 总的来说是通过定义一个结构体然后二进制发送,读`messageStruct`的大小数据,`sizeof(messageStruct)`大小就是`int`类型的大小,为4,实际上后面还跟了数据,`char json[0]`不占用空间,而`char *json`是占用8个字节的,所以不能用`char *json`代替`char json[0]`这里是个坑注意一下,转换完成读取jsonLen的值,再读jsonLen大小的数据这样就完成分包了。这里比较难理解,建议网上多看几篇通过结构体解决粘包的文章,结合本文的代码理解,注意在windows平台下的vs编译器会出现“非法的大小为零的数组”错误提示,因为不支持char json[0];写法。 130 | --------------------------------------------------------------------------------