顶层设计
映射表(补:实际上可以用结构体减少映射,不过不够灵活)
线程池、任务
日志系统
聊天日志
同步非阻塞IO
为方便描述,我们做如下约定:
客户端主线程是命令行线程,另一个线程叫做接收线程
服务器主线程是接收命令线程,也叫命令线程,它只进行消息中转,命令解析与返回由线程池处理。该线程监听的端口由客户端主线程连接。
服务器另一个监听线程叫发送线程,监听的端口由客户端接收线程连接(这样服务器就能知道客户端两个连接分别属于哪个线程),信息的发送由线程池处理。
映射表 考虑一下用户在服务器里的映射信息:
在连接的时候,服务器会获得套接字描述符connfd和客户端对应的ip地址和端口号;当客户端后续发信息过来时,epoll会得知事件,同时获取的是用户的套接字信息,因此用connfd会比较多。然后,用户有两个线程会与服务器的两个监听线程连接,唯一能得知这两个套接字是一个用户的方法就是使用ip。由此我们大概能设计如下映射:
0)套接字1->ip地址:这个映射是通过客户端主线程连接到服务器命令线程;
1)ip地址->套接字2:这个映射是客户端接收线程连接到服务器发送线程,因为连接时间不可知,因此把映射保存下来;
2)套接字1->套接字2:这个映射通过上面的映射获取,套接字1找到ip然后找到套接字2,这样就建立好了服务器与客户端之间的映射,即能快速找到客户端两个套接字。
本质上因为不知道套接字1先建立还是套接字2先建立,所以不能获得套接字1的映射就立马去找套接字2,最稳健的时机是用户登录时建立两个套接字之间的映射,因为用户能登录说明两个连接都建立完成,并且此时是阻塞返回的,消息发到主线程,不需要立即使用套接字2。
下面建立其他相关的映射:
3)套接字1->sid:最初的sid是用户登录时的userid,因此这个映射在登录时建立,setsid可以更改。
4)套接字1->sname:最初的sname是用户登录时的userid,因此这个映射在登录时建立,setsname可以更改。这两个映射是为了一些请求命令需要让对方得知自己的sid和sname,能快速获取。
5)sid->套接字2:许多命令都是通过指定sid发送到对方的接收线程,这个映射在登录时建立。
6)sid->sname:通过搜索sid获取sname。
7)sid->客户状态:accept之类的命令通过sid检索对方状态。
8)套接字1->客户状态:本机的命令有时也需要与客户状态交互。
chatting双方映射,在聊天的双方经常通过自己的套接字1发送到对方的套接字2,因此要建立该表。accept时服务器可以获取自己的套接字1和对方sid,此时可以建立:
9)[本机套接字1]->[对方套接字2],这个通过对方sid->套接字2获取。
[对方套接字1]->[本机套接字2],这个通过本机套接字1->本机套接字2获取,对方的套接字1通过套接字2搜索映射表获取套接字1,这一步耗点时间,因为对方得知accept也需要时间,不会那么快发消息,所以可以搜索建立。
注意上面两个表本质是一样的,用一个表存储。
补充:
10)conn2->user:获取user
11)conn1->user:获取user
12)conn1_req_peer2:conn1发起chat或send请求,就在这个表记录对方的端口,这样conn1break时才能知道要向谁break
映射表也许还有很多,现在也无法想到底。由于映射表挺多的,可以使用一个类封装,像客户端的请求表一样。但是这个类包含了许多的表,设计就有多种方案了。在讨论这些方案之前,我们可以想到,这些表的操作肯定需要一些互斥处理。对于同一个表,插入查找删除肯定是需要互斥的,但是两个不同的表之间需要互斥吗?不需要,并且如果这被需要的话可能会严重损耗性能,因此一个表就对应一个互斥锁。现在我们来讨论表类的设计:
第一种,小封装,直接暴露这些表,插入查找删除都由外部执行。评价是这个方案肯定是不行 的,接口太难用了。
第二种,完全封装,但每个表配对一个函数,外部调用相应的函数执行。评价是代码冗余 ,代码重复 ,并且无法全部做到函数重载,接口质量太差了。
第三种,完全封装,使用模板编写函数,插入查找删除统一使用。但问题是无法得知对哪个表操作,或许可以使用一个顶层映射表,表id->表。但实际上这行不通,因为表的参数类型是不同的,并且这也无法通过模板来解决。
讨论到现在,实际上发现没什么值得封装的了。。。因此直接不封装好了,设计大概是这样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 unordered_map<int , string> conn1_ip; unordered_map<string, int > ip_conn2; unordered_map<int , int > conn1_2; unordered_map<int , string> conn1_sid; unordered_map<int , string> conn1_sname; unordered_map<string, int > sid_conn2; unordered_map<string, string> sid_sname; unordered_map<string, clientState> sid_state; unordered_map<int , clientState> conn1_state; unordered_map<int , int > conn1_peer2; unordered_map<int , string> conn2_user; unordered_map<int , string> conn1_user; mutex mux_conn1_ip; mutex mux_ip_conn2; mutex mux_conn1_2; mutex mux_conn1_sid; mutex mux_conn1_sname; mutex mux_sid_conn2; mutex mux_sid_sname; mutex mux_sid_state; mutex mux_conn1_state; mutex mux_conn1_peer2; mutex mux_conn2_user; mutex mux_conn1_user; lock_guard<mutex> locker (mux) ;map[key] = value lock_guard<mutex> locker (mux); typename unordered_map<T1, T2>::iterator iter = map.find (key);if (iter == map.end ()) return ; else map.erase (iter); lock_guard<mutex> locker (mux) ;typename unordered_map<T1, T2>::iterator iter = map.find (key);if (iter == map.end ()) return T2 (); else return iter->second; lock_guard<mutex> locker (mux) ;typename unordered_map<T1, T2>::iterator iter = map.begin ();for (; iter != map.end (); iter++) if (iter->second == value) return iter->first; return T1 ();
写了后面一些内容,发现映射表使用太频繁了,因为没有封装,导致代码极速膨胀(因为每个操作都要互斥,一行语句会因为调用互斥加锁解锁展开成三四行),所以还是打算封装,每个表都配一系列函数好了,代码很多…不过后面调用就舒服很多。注:后面有些补充的表不会填上来。
state_c.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #ifndef STATE_C_H #define STATE_C_H enum class clientState { noLogin = 0 , cmdLine, isChatting, isWaiting }; #endif
usermap.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 #ifndef USERMAP_H #define USERMAP_H #include <string> #include <unordered_map> #include <mutex> #include "state_c.h" using namespace std;class UserMap { private : unordered_map<int , string> conn1_ip; unordered_map<string, int > ip_conn2; unordered_map<int , int > conn1_2; unordered_map<int , string> conn1_sid; unordered_map<int , string> conn1_sname; unordered_map<string, int > sid_conn2; unordered_map<string, string> sid_sname; unordered_map<string, clientState> sid_state; unordered_map<int , clientState> conn1_state; unordered_map<int , int > conn1_peer2; unordered_map<int , string> conn2_user; unordered_map<int , string> conn1_user; mutex mux_conn1_ip; mutex mux_ip_conn2; mutex mux_conn1_2; mutex mux_conn1_sid; mutex mux_conn1_sname; mutex mux_sid_conn2; mutex mux_sid_sname; mutex mux_sid_state; mutex mux_conn1_state; mutex mux_conn1_peer2; mutex mux_conn2_user; mutex mux_conn1_user; public : void ins_conn1_ip (int key, string value) ; void del_conn1_ip (int key) ; string fvalue_conn1_ip (int key) ; void ins_ip_conn2 (string key, int value) ; void del_ip_conn2 (string key) ; int fvalue_ip_conn2 (string key) ; void ins_conn1_2 (int key, int value) ; void del_conn1_2 (int key) ; int fvalue_conn1_2 (int key) ; int fkey_conn1_2 (int value) ; void ins_conn1_sid (int key, string value) ; void del_conn1_sid (int key) ; string fvalue_conn1_sid (int key) ; int fkey_conn1_sid (string value) ; void ins_conn1_sname (int key, string value) ; void del_conn1_sname (int key) ; string fvalue_conn1_sname (int key) ; void ins_sid_conn2 (string key, int value) ; void del_sid_conn2 (string key) ; int fvalue_sid_conn2 (string key) ; string fkey_sid_conn2 (int value) ; void ins_sid_sname (string key, string value) ; void del_sid_sname (string key) ; string fvalue_sid_sname (string key) ; void ins_sid_state (string key, clientState value) ; void del_sid_state (string key) ; clientState fvalue_sid_state (string key) ; void ins_conn1_state (int key, clientState value) ; void del_conn1_state (int key) ; clientState fvalue_conn1_state (int key) ; void ins_conn1_peer2 (int key, int value) ; void del_conn1_peer2 (int key) ; int fvalue_conn1_peer2 (int key) ; void ins_conn2_user (int key, string value) ; void del_conn2_user (int key) ; string fvalue_conn2_user (int key) ; void ins_conn1_user (int key, string value) ; void del_conn1_user (int key) ; string fvalue_conn1_user (int key) ; }; #endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 #include "usermap.h" void UserMap::ins_conn1_ip (int key, string value) { lock_guard<mutex> locker (mux_conn1_ip) ; conn1_ip[key] = value; } void UserMap::del_conn1_ip (int key) { lock_guard<mutex> locker (mux_conn1_ip) ; typename unordered_map<int , string>::iterator iter = conn1_ip.find (key); if (iter == conn1_ip.end ()) return ; else conn1_ip.erase (iter); } string UserMap::fvalue_conn1_ip (int key) { lock_guard<mutex> locker (mux_conn1_ip) ; typename unordered_map<int , string>::iterator iter = conn1_ip.find (key); if (iter == conn1_ip.end ()) return "" ; else return iter->second; } void UserMap::ins_ip_conn2 (string key, int value) { lock_guard<mutex> locker (mux_ip_conn2) ; ip_conn2[key] = value; } void UserMap::del_ip_conn2 (string key) { lock_guard<mutex> locker (mux_ip_conn2) ; typename unordered_map<string, int >::iterator iter = ip_conn2.find (key); if (iter == ip_conn2.end ()) return ; else ip_conn2.erase (iter); } int UserMap::fvalue_ip_conn2 (string key) { lock_guard<mutex> locker (mux_ip_conn2) ; typename unordered_map<string, int >::iterator iter = ip_conn2.find (key); if (iter == ip_conn2.end ()) return -1 ; else return iter->second; } void UserMap::ins_conn1_2 (int key, int value) { lock_guard<mutex> locker (mux_conn1_2) ; conn1_2[key] = value; } void UserMap::del_conn1_2 (int key) { lock_guard<mutex> locker (mux_conn1_2) ; typename unordered_map<int , int >::iterator iter = conn1_2.find (key); if (iter == conn1_2.end ()) return ; else conn1_2.erase (iter); } int UserMap::fvalue_conn1_2 (int key) { lock_guard<mutex> locker (mux_conn1_2) ; typename unordered_map<int , int >::iterator iter = conn1_2.find (key); if (iter == conn1_2.end ()) return -1 ; else return iter->second; } int UserMap::fkey_conn1_2 (int value) { lock_guard<mutex> locker (mux_conn1_2) ; typename unordered_map<int , int >::iterator iter = conn1_2.begin (); for (; iter != conn1_2.end (); iter++) if (iter->second == value) return iter->first; return -1 ; } void UserMap::ins_conn1_sid (int key, string value) { lock_guard<mutex> locker (mux_conn1_sid) ; conn1_sid[key] = value; } void UserMap::del_conn1_sid (int key) { lock_guard<mutex> locker (mux_conn1_sid) ; typename unordered_map<int , string>::iterator iter = conn1_sid.find (key); if (iter == conn1_sid.end ()) return ; else conn1_sid.erase (iter); } string UserMap::fvalue_conn1_sid (int key) { lock_guard<mutex> locker (mux_conn1_sid) ; typename unordered_map<int , string>::iterator iter = conn1_sid.find (key); if (iter == conn1_sid.end ()) return "" ; else return iter->second; } int UserMap::fkey_conn1_sid (string value) { lock_guard<mutex> locker (mux_conn1_sid) ; typename unordered_map<int , string>::iterator iter = conn1_sid.begin (); for (; iter != conn1_sid.end (); iter++) if (iter->second == value) return iter->first; return -1 ; } void UserMap::ins_conn1_sname (int key, string value) { lock_guard<mutex> locker (mux_conn1_sname) ; conn1_sname[key] = value; } void UserMap::del_conn1_sname (int key) { lock_guard<mutex> locker (mux_conn1_sname) ; typename unordered_map<int , string>::iterator iter = conn1_sname.find (key); if (iter == conn1_sname.end ()) return ; else conn1_sname.erase (iter); } string UserMap::fvalue_conn1_sname (int key) { lock_guard<mutex> locker (mux_conn1_sname) ; typename unordered_map<int , string>::iterator iter = conn1_sname.find (key); if (iter == conn1_sname.end ()) return "" ; else return iter->second; } void UserMap::ins_sid_conn2 (string key, int value) { lock_guard<mutex> locker (mux_sid_conn2) ; sid_conn2[key] = value; } void UserMap::del_sid_conn2 (string key) { lock_guard<mutex> locker (mux_sid_conn2) ; typename unordered_map<string, int >::iterator iter = sid_conn2.find (key); if (iter == sid_conn2.end ()) return ; else sid_conn2.erase (iter); } int UserMap::fvalue_sid_conn2 (string key) { lock_guard<mutex> locker (mux_sid_conn2) ; typename unordered_map<string, int >::iterator iter = sid_conn2.find (key); if (iter == sid_conn2.end ()) return -1 ; else return iter->second; } string UserMap::fkey_sid_conn2 (int value) { lock_guard<mutex> locker (mux_sid_conn2) ; typename unordered_map<string, int >::iterator iter = sid_conn2.begin (); for (; iter != sid_conn2.end (); iter++) if (iter->second == value) return iter->first; return "" ; } void UserMap::ins_sid_sname (string key, string value) { lock_guard<mutex> locker (mux_sid_sname) ; sid_sname[key] = value; } void UserMap::del_sid_sname (string key) { lock_guard<mutex> locker (mux_sid_sname) ; typename unordered_map<string, string>::iterator iter = sid_sname.find (key); if (iter == sid_sname.end ()) return ; else sid_sname.erase (iter); } string UserMap::fvalue_sid_sname (string key) { lock_guard<mutex> locker (mux_sid_sname) ; typename unordered_map<string, string>::iterator iter = sid_sname.find (key); if (iter == sid_sname.end ()) return "" ; else return iter->second; } void UserMap::ins_sid_state (string key, clientState value) { lock_guard<mutex> locker (mux_sid_state) ; sid_state[key] = value; } void UserMap::del_sid_state (string key) { lock_guard<mutex> locker (mux_sid_state) ; typename unordered_map<string, clientState>::iterator iter = sid_state.find (key); if (iter == sid_state.end ()) return ; else sid_state.erase (iter); } clientState UserMap::fvalue_sid_state (string key) { lock_guard<mutex> locker (mux_sid_state) ; typename unordered_map<string, clientState>::iterator iter = sid_state.find (key); if (iter == sid_state.end ()) return clientState::noLogin; else return iter->second; } void UserMap::ins_conn1_state (int key, clientState value) { lock_guard<mutex> locker (mux_conn1_state) ; conn1_state[key] = value; } void UserMap::del_conn1_state (int key) { lock_guard<mutex> locker (mux_conn1_state) ; typename unordered_map<int , clientState>::iterator iter = conn1_state.find (key); if (iter == conn1_state.end ()) return ; else conn1_state.erase (iter); } clientState UserMap::fvalue_conn1_state (int key) { lock_guard<mutex> locker (mux_conn1_state) ; typename unordered_map<int , clientState>::iterator iter = conn1_state.find (key); if (iter == conn1_state.end ()) return clientState::noLogin; else return iter->second; } void UserMap::ins_conn1_peer2 (int key, int value) { lock_guard<mutex> locker (mux_conn1_peer2) ; conn1_peer2[key] = value; } void UserMap::del_conn1_peer2 (int key) { lock_guard<mutex> locker (mux_conn1_peer2) ; typename unordered_map<int , int >::iterator iter = conn1_peer2.find (key); if (iter == conn1_peer2.end ()) return ; else conn1_peer2.erase (iter); } int UserMap::fvalue_conn1_peer2 (int key) { lock_guard<mutex> locker (mux_conn1_peer2) ; typename unordered_map<int , int >::iterator iter = conn1_peer2.find (key); if (iter == conn1_peer2.end ()) return -1 ; else return iter->second; } void UserMap::ins_conn2_user (int key, string value) { lock_guard<mutex> locker (mux_conn2_user) ; conn2_user[key] = value; } void UserMap::del_conn2_user (int key) { lock_guard<mutex> locker (mux_conn2_user) ; typename unordered_map<int , string>::iterator iter = conn2_user.find (key); if (iter == conn2_user.end ()) return ; else conn2_user.erase (iter); } string UserMap::fvalue_conn2_user (int key) { lock_guard<mutex> locker (mux_conn2_user) ; typename unordered_map<int , string>::iterator iter = conn2_user.find (key); if (iter == conn2_user.end ()) return "" ; else return iter->second; } void UserMap::ins_conn1_user (int key, string value) { lock_guard<mutex> locker (mux_conn1_user) ; conn1_user[key] = value; } void UserMap::del_conn1_user (int key) { lock_guard<mutex> locker (mux_conn1_user) ; typename unordered_map<int , string>::iterator iter = conn1_user.find (key); if (iter == conn1_user.end ()) return ; else conn1_user.erase (iter); } string UserMap::fvalue_conn1_user (int key) { lock_guard<mutex> locker (mux_conn1_user) ; typename unordered_map<int , string>::iterator iter = conn1_user.find (key); if (iter == conn1_user.end ()) return "" ; else return iter->second; }
调用测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include "usermap.h" using namespace std;int main () { UserMap usermap; usermap.ins_conn1_2 (1 , 2 ); int conn2 = usermap.fvalue_conn1_2 (1 ); cout << conn2 << endl; string ip = usermap.fvalue_conn1_ip (1 ); if (ip == "" ) cout << "none" << endl; usermap.ins_conn1_ip (1 , "123" ); ip = usermap.fvalue_conn1_ip (1 ); cout << ip << endl; clientState state = usermap.fvalue_conn1_state (1 ); if (state == clientState::noLogin) cout << "none" << endl; usermap.ins_conn1_state (1 ,clientState::isChatting); state = usermap.fvalue_conn1_state (1 ); if (state == clientState::noLogin) cout << "none" << endl; return 0 ; }
注册登录 这里假设搞定了日志系统和数据库连接池先,数据库连接池代码放在这后面,然后把注册登录的处理函数写了(大概在markdown写一下,有bug后面再改):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 void verify (int conn1, int isLogin, string userid, string password) { MYSQL* sql; SqlRAII myconn (&sql,sqlpool,0 ) ; string sendstr = "Server busy!" ; if (sql==nullptr ) { send (conn1,sendstr.c_str (),int (sendstr.size ()),0 ); return ; } else { MYSQL_FIELD *fields = nullptr ; MYSQL_RES *res = nullptr ; string order = "SELECT userid, password FROM user WHERE userid='" +userid+"'" ; if (mysql_query (sql, order.c_str ())) { LOG_DEBUG ( "Select error: %s" ,order.c_str ()); mysql_free_result (res); send (conn1,sendstr.c_str (),int (sendstr.size ()),0 ); return ; } res = mysql_store_result (sql); while (MYSQL_ROW row = mysql_fetch_row (res)) { string passwd (row[1 ]) ; if (isLogin) { if (password == passwd) { sendstr = "success" ; send (conn1,sendstr.c_str (),int (sendstr.size ()),0 ); string ip = usermap.fvalue_conn1_ip (conn1); LOG_INFO ("%s login successfully with password: %s ip: %s" ,userid.c_str (),password.c_str (),ip.c_str ()); mysql_free_result (res); login_addmap (conn1,userid); return ; } else { sendstr = "password error!" ; send (conn1,sendstr.c_str (),int (sendstr.size ()),0 ); string ip = usermap.fvalue_conn1_ip (conn1); LOG_INFO ("%s login unsuccessfully with password: %s ip: %s" ,userid.c_str (),password.c_str (),ip.c_str ()); mysql_free_result (res); return ; } } else { sendstr = "the user id [" +userid+"] is already in use" ; send (conn1,sendstr.c_str (),int (sendstr.size ()),0 ); LOG_INFO ("register unsuccessfully, [%s] is already in use" ,userid.c_str ()); mysql_free_result (res); return ; } } mysql_free_result (res); if (isLogin) { sendstr = "user id [" +userid+"] not found" ; send (conn1,sendstr.c_str (),int (sendstr.size ()),0 ); LOG_INFO ("%s login but id not found" ,userid.c_str ()); return ; } else { string order_insert = "INSERT INTO user(userid, password) VALUES('" +userid+"','" +password+"')" ; if (mysql_query (sql, order_insert.c_str ())) { LOG_DEBUG ( "Insert error: %s" ,order_insert.c_str ()); send (conn1,sendstr.c_str (),int (sendstr.size ()),0 ); return ; } else { sendstr = "success" ; send (conn1,sendstr.c_str (),int (sendstr.size ()),0 ); string ip = usermap.fvalue_conn1_ip (conn1); LOG_INFO ("register successfully with id: [%s] password: [%s] ip:%s" ,userid.c_str (),password.c_str (),ip.c_str ()); return ; } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void login_addmap (int conn1,string userid) { string ip = usermap.fvalue_conn1_ip (conn1); int conn2 = usermap.fvalue_ip_conn2 (ip); usermap.ins_conn1_2 (conn1,conn2); usermap.ins_conn1_sid (conn1,userid); usermap.ins_conn1_sname (conn1,userid); usermap.ins_sid_conn2 (userid,conn2); usermap.ins_sid_sname (userid,userid); usermap.ins_sid_state (userid,clientState::cmdLine); usermap.ins_conn1_state (conn1,clientState::cmdLine); usermap.ins_conn2_user (conn2,userid); usermap.ins_conn1_user (conn1,userid); }
数据库连接池 在之前的博客已经介绍过了,这里再介绍以下吧:
头文件#include <mysql/mysql.h>
数据库的一个连接句柄的初始化有三步
定义一个sql指针:MYSQL *sql = nullptr;
用这个指针初始化一个sql结构体,返回一个指向这个结构体的指针:sql = mysql_init(sql);
init后就connect,连接数据库,返回一个可用连接sql = mysql_real_connect(sql, host,user, pwd,dbName, port, unix_socket, client_flag);
host是主机名或IP,如果“host”是NULL或字符串”localhost”,连接将被视为与本地主机的连接。
如果unix_socket不是NULL,该字符串描述了应使用的套接字或命名管道。注意,“host”参数决定了连接的类型。
client_flag的值通常为0,其他标志可以实现特定的功能
然后这个sql句柄就可以用来执行语句了,最后在不使用的时候还需要调用mysql_close(sql);
释放连接。并且,为了避免在使用库完成应用程序后发生内存泄漏(例如,在关闭与服务器的连接之后),可以显式调用mysql_library_end()。这样可以执行内存 Management 以清理和释放库使用的资源。
上面就是一个连接的建立,对于多个连接,我们可以把多个连接初始化后放入一个队列里,这样就构成了一个连接池。对于这样一个共享的连接池,就需要互斥操作。而这一个队列又和阻塞队列不同,队列是可能空的但不可能会因为满而阻塞——可用的连接是一定的,push回去不会多。在阻塞队列中使用了两个条件变量管理了空/满缓冲区的阻塞,这里只需要管理空,也即pop操作的阻塞。可以用一个条件变量,也可以用一个信号量。条件变量可以做超时处理,因此用条件变量(信号量就使用sem_post(&semId_)、sem_wait(&semId_)、sem_init(&semId_, 0, MAX_CONN_)
)。
当上层需要登录或注册时,会尝试获取一个连接,然后使用完后释放连接。这里的问题是,当没有连接可用时,是阻塞等待还是直接返回错误。或许使用折中会好一点,即用cond.wait_for()阻塞一段时间。当释放一个连接后就尝试唤醒一个阻塞的线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #ifndef SQLCONNPOOL_H #define SQLCONNPOOL_H #include <mysql/mysql.h> #include <mutex> #include <queue> #include <condition_variable> using namespace std;class Sqlconnpool { private : mutex mux; condition_variable cond; queue<MYSQL*> connque; int maxconn; int freecount; Sqlconnpool (); ~Sqlconnpool (){} public : Sqlconnpool (const Sqlconnpool&) = delete ; Sqlconnpool& operator =(const Sqlconnpool&) = delete ; void close () ; static Sqlconnpool* instance () ; MYSQL* getconn (int timeout) ; void freeconn (MYSQL* conn) ; void init (const char * host,int port,const char * user,const char * pwd,const char * dbname,int connsize=20 ) ; int conncount () ; }; #endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #include "sqlconnpool.h" #include <chrono> #include <cassert> #include "log.h" using namespace std;Sqlconnpool::Sqlconnpool () { maxconn = 0 ; freecount = 0 ; } Sqlconnpool* Sqlconnpool::instance () { static Sqlconnpool instance; return &instance; } MYSQL* Sqlconnpool::getconn (int timeout = 0 ) { assert (timeout>=0 ); unique_lock<mutex> locker (mux) ; while (connque.empty ()) if (cond.wait_for (locker, chrono::seconds (timeout)) == std::cv_status::timeout) { LOG_WARN ("Sqlconnpool busy" ) return nullptr ; } MYSQL* sql = connque.front (); connque.pop (); freecount--; return sql; } void Sqlconnpool::freeconn (MYSQL* conn) { assert (conn); lock_guard<mutex> locker (mux) ; connque.push (conn); freecount++; cond.notify_one (); } int Sqlconnpool::conncount () { lock_guard<mutex> locker (mux) ; return maxconn-freecount; } void Sqlconnpool::init (const char * host,int port,const char * user,const char * pwd,const char * dbname,int connsize) { assert (connsize>0 ); maxconn = connsize; freecount = connsize; for (int i=0 ;i<maxconn;i++) { MYSQL* sql = nullptr ; sql = mysql_init (sql); if (!sql) { LOG_ERROR ("sql number %d init error" ,i); assert (sql); } sql = mysql_real_connect (sql,host,user,pwd,dbname,port,nullptr ,0 ); if (!sql) { LOG_ERROR ("sql number %d connect error" ,i); assert (sql); } connque.push (sql); } } void Sqlconnpool::close () { unique_lock<mutex> locker (mux) ; while (freecount != maxconn) cond.wait (locker); while (!connque.empty ()) { mysql_close (connque.front ()); connque.pop (); } mysql_library_end (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #ifndef SQLRAII_H #define SQLRAII_H #include "sqlconnpool.h" #include <cassert> using namespace std;class SqlRAII { private : MYSQL* conn; Sqlconnpool* connpool; public : SqlRAII (MYSQL** sql,Sqlconnpool* sqlpool,int timeout = 0 ) { assert (sqlpool); *sql = sqlpool->getconn (timeout); conn = *sql; connpool = sqlpool; } ~SqlRAII () { if (conn) connpool->freeconn (conn); } }; #endif
信息接收处理
对于聊天信息,只需要判断第一个字符是不是”~”即可,是的话通过映射表发送给对方
对于命令信息,客户端进行了很多处理了,发送过来的命令一定是正确的且不带@,且首尾没有空格,服务器需要做的就是再解析成向量形式
从客户端发来的信息都带有一个换行符,这是为了服务器刚好能使用客户端的解析函数,我们接着约定,服务器发给客户端的信息都不带有换行符。这样比如发来的聊天信息就要记得去尾部换行符。
命令的映射如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const unordered_map<string, int > cmdmap ={ {"register" ,0 }, {"login" ,1 }, {"search" ,2 }, {"chat" ,3 }, {"accept" ,4 }, {"reject" ,5 }, {"break" ,6 }, {"send" ,7 }, {"sendfile" ,8 }, {"acceptfile" ,9 }, {"rejectfile" ,10 }, {"getfile" ,11 }, {"acceptget" ,12 }, {"rejectget" ,13 }, {"setsid" ,14 }, {"setsname" ,15 }, {"choosefile" ,16 }, {"exit" ,17 }, {"hisir" ,18 }, {"myresource" ,19 }, {"oyasumi" ,20 } };
工作函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 70 71 72 73 74 75 76 77 78 79 80 81 82 void task (int conn1) { string recvstr = recv_str (conn1); if (recvstr == "" ) return ; if (recvstr[0 ]=='~' ) { chatmsg (conn1,recvstr); } else { vector<string> cmdvec = parse (recvstr); int cmdvalue = cmdmap[cmdvec[0 ]]; switch (cmdvalue) { case 0 : verify (conn1, 0 , cmdvec[1 ], cmdvec[2 ]); break ; case 1 : verify (conn1, 1 , cmdvec[1 ], cmdvec[2 ]); break ; case 2 : search (conn1, cmdvec[1 ]); break ; case 3 : chat (conn1, cmdvec[1 ]); break ; case 4 : accept (conn1, cmdvec[1 ]); break ; case 5 : reject (conn1, cmdvec[1 ]); break ; case 6 : break_ (conn1); break ; case 7 : send_ (conn1, cmdvec[1 ], cmdvec[2 ]); break ; case 8 : sendfile (conn1, cmdvec[1 ], cmdvec[2 ]); break ; case 9 : acceptfile (conn1, cmdvec[1 ]); break ; case 10 : rejectfile (conn1, cmdvec[1 ]); break ; case 11 : getfile (conn1, cmdvec[1 ]); break ; case 12 : acceptget (conn1, cmdvec[1 ], cmdvec[2 ]); break ; case 13 : rejectget (conn1, cmdvec[1 ]); break ; case 14 : setsid (conn1, cmdvec[1 ]); break ; case 15 : setsname (conn1, cmdvec[1 ]); break ; case 16 : choosefile (conn1, cmdvec[1 ]); break ; case 17 : exit (conn1); break ; case 18 : hisir (conn1, cmdvec[1 ]); break ; case 20 : oyasumi (conn1); break ; } } uint32_t connEvent = CONNEVENT; epoller.modFd (conn1, connEvent); }
解析命令函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 vector<string> parse (string cmdstr) { cmdstr[cmdstr.size () - 1 ] = ' ' ; vector<string> res; size_t pos = 0 ; size_t pos1; while ((pos1 = cmdstr.find (' ' , pos)) != string::npos) { res.push_back (cmdstr.substr (pos, pos1 - pos)); while (cmdstr[pos1] == ' ' ) pos1++; pos = pos1; } return res; }
命令工作函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void chatmsg (int conn1, string& recvstr) { int conn2 = usermap.fvalue_conn1_peer2 (conn1); string myuser = usermap.fvalue_conn1_user (conn1); if (conn2 == -1 ) { LOG_ERROR ("%s chatmsg error, can't find peer!" , myuser.c_str ()); return ; } string peeruser = usermap.fvalue_conn2_user (conn2); string mysname = usermap.fvalue_conn1_sname (conn1); LOG_INFO ("%s send [msg] to %s" ,myuser.c_str (),peeruser.c_str ()); recvstr = recvstr.substr (1 ,recvstr.size ()-2 ); recvstr = mysname+": " +recvstr; send (conn2,recvstr.c_str (),recvstr.size (),0 ); recvstr = recvstr + " [" +peeruser+"]" ; if (!msg_log (myuser,recvstr)) LOG_ERROR ("%s open log falied!" , myuser.c_str ()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void search (int conn1, string sid) { string sendstr; string sname = usermap.fvalue_sid_sname (sid); string myuser = usermap.fvalue_conn1_user (conn1); LOG_INFO ("%s [search] sid-%s" ,myuser.c_str (),sid.c_str ()); if (sname == "" ) { sendstr = "The peer does not exist!" ; send (conn1,sendstr.c_str (),sendstr.size (),0 ); return ; } clientState state = usermap.fvalue_sid_state (sid); if (state == clientState::isChatting) sendstr = "sid-[" +sid+"]sname-[" +sname+"]state-is chatting" ; else sendstr = "sid-[" +sid+"]sname-[" +sname+"]state-isn't chatting" ; send (conn1,sendstr.c_str (),sendstr.size (),0 ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void chat (int conn1, string sid) { string sendstr; int conn2 = usermap.fvalue_sid_conn2 (sid); int myconn2 = usermap.fvalue_conn1_2 (conn1); string mysid = usermap.fvalue_conn1_sid (conn1); string mysname = usermap.fvalue_conn1_sname (conn1); usermap.ins_conn1_state (conn1,clientState::isWaiting); usermap.ins_sid_state (mysid,clientState::isWaiting); if (conn2 == -1 ) { sendstr = "The peer does not exist, please break!" ; send (myconn2,sendstr.c_str (),sendstr.size (),0 ); return ; } clientState state = usermap.fvalue_sid_state (sid); if (state == clientState::isChatting) { sendstr = "The peer is chatting, you may wait for a long time..." ; send (myconn2,sendstr.c_str (),sendstr.size (),0 ); } sendstr = "@#chatfrom " +mysid+" " +mysname; send (conn2,sendstr.c_str (),sendstr.size (),0 ); usermap.ins_conn1_req_peer2 (conn1,conn2); string myuser = usermap.fvalue_conn1_user (conn1); string peeruser = usermap.fvalue_conn2_user (conn2); LOG_INFO ("%s send a [chat] requestion to %s" ,myuser.c_str (),peeruser.c_str ()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 void accept_ (int conn1, string sid) { string sendstr; clientState state = usermap.fvalue_sid_state (sid); int conn2 = usermap.fvalue_sid_conn2 (sid); int myconn2 = usermap.fvalue_conn1_2 (conn1); if (state == clientState::isWaiting) { sendstr = "@#chat accept" ; send (conn2,sendstr.c_str (),sendstr.size (),0 ); string mysid = usermap.fvalue_conn1_sid (conn1); usermap.ins_conn1_state (conn1,clientState::isChatting); usermap.ins_sid_state (mysid,clientState::isChatting); int peerconn1 = usermap.fkey_conn1_2 (conn2); usermap.ins_conn1_state (peerconn1,clientState::isChatting); usermap.ins_sid_state (sid,clientState::isChatting); usermap.ins_conn1_peer2 (conn1,conn2); usermap.ins_conn1_peer2 (peerconn1,myconn2); string myuser = usermap.fvalue_conn1_user (conn1); string peeruser = usermap.fvalue_conn2_user (conn2); LOG_INFO ("%s [accept] the chat requestion for %s" ,myuser.c_str (),peeruser.c_str ()); } else { sendstr = "@#break now" ; send (myconn2,sendstr.c_str (),sendstr.size (),0 ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void reject (int conn1, string sid) { string sendstr; clientState state = usermap.fvalue_sid_state (sid); int conn2 = usermap.fvalue_sid_conn2 (sid); if (state == clientState::isWaiting) { sendstr = "@#chat reject" ; send (conn2,sendstr.c_str (),sendstr.size (),0 ); int peerconn1 = usermap.fkey_conn1_2 (conn2); usermap.ins_conn1_state (peerconn1,clientState::cmdLine); usermap.ins_sid_state (sid,clientState::cmdLine); usermap.del_conn1_req_peer2 (peerconn1); string myuser = usermap.fvalue_conn1_user (conn1); string peeruser = usermap.fvalue_conn2_user (conn2); LOG_INFO ("%s [reject] the chat requestion for %s" ,myuser.c_str (),peeruser.c_str ()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 void break_ (int conn1) { string sendstr; clientState state = usermap.fvalue_conn1_state (conn1); string mysid = usermap.fvalue_conn1_sid (conn1); string myuser = usermap.fvalue_conn1_user (conn1); int conn2 = usermap.fvalue_conn1_req_peer2 (conn1); if (state == clientState::isWaiting) { usermap.ins_conn1_state (conn1,clientState::cmdLine); usermap.ins_sid_state (mysid,clientState::cmdLine); if (conn2 == -1 ) return ; usermap.del_conn1_req_peer2 (conn1); sendstr = "@#break " +mysid; send (conn2,sendstr.c_str (),sendstr.size (),0 ); LOG_INFO ("%s [break] from waiting" ,myuser.c_str ()); } else if (state == clientState::cmdLine) { if (conn2 == -1 ) return ; usermap.del_conn1_req_peer2 (conn1); sendstr = "@#break now" ; send (conn2,sendstr.c_str (),sendstr.size (),0 ); LOG_INFO ("%s [break] from cmdLine" ,myuser.c_str ()); } else if (state == clientState::isChatting) { conn2 = usermap.fvalue_conn1_peer2 (conn1); string mysid = usermap.fvalue_conn1_sid (conn1); usermap.ins_conn1_state (conn1,clientState::cmdLine); usermap.ins_sid_state (mysid,clientState::cmdLine); LOG_INFO ("%s [break] from chatting" ,myuser.c_str ()); if (conn2 == -1 ) return ; int peerconn1 = usermap.fkey_conn1_2 (conn2); string sid = usermap.fvalue_conn1_sid (peerconn1); usermap.ins_conn1_state (peerconn1,clientState::cmdLine); usermap.ins_sid_state (sid,clientState::cmdLine); usermap.del_conn1_peer2 (conn1); usermap.del_conn1_peer2 (peerconn1); sendstr = "@#break now" ; send (conn2,sendstr.c_str (),sendstr.size (),0 ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void send_ (int conn1, string sid, string& msg) { string sendstr; int conn2 = usermap.fvalue_sid_conn2 (sid); if (conn2 == -1 ) return ; string mysid = usermap.fvalue_conn1_sid (conn1); string mysname = usermap.fvalue_conn1_sname (conn1); sendstr = "sid-[" +mysid+"] sname-[" +mysname+"] send: " +msg; send (conn2,sendstr.c_str (),sendstr.size (),0 ); string myuser = usermap.fvalue_conn1_user (conn1); string peeruser = usermap.fvalue_conn2_user (conn2); LOG_INFO ("%s [send] message to %s: %s" ,myuser.c_str (),peeruser.c_str (),msg.c_str ()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void sendfile (int conn1, string sid, string filename) { size_t pos = filename.find_last_not_of ("/" ); if (pos != string::npos) filename = filename.substr (pos+1 ); string sendstr; int conn2 = usermap.fvalue_sid_conn2 (sid); int myconn2 = usermap.fvalue_conn1_2 (conn1); string mysid = usermap.fvalue_conn1_sid (conn1); string mysname = usermap.fvalue_conn1_sname (conn1); usermap.ins_conn1_state (conn1,clientState::isWaiting); usermap.ins_sid_state (mysid,clientState::isWaiting); if (conn2 == -1 ) { sendstr = "The peer does not exist, please break!" ; send (myconn2,sendstr.c_str (),sendstr.size (),0 ); return ; } sendstr = "@#sendfilefrom " +mysid+" " +mysname+" " +filename; send (conn2,sendstr.c_str (),sendstr.size (),0 ); usermap.ins_conn1_req_peer2 (conn1,conn2); string myuser = usermap.fvalue_conn1_user (conn1); string peeruser = usermap.fvalue_conn2_user (conn2); LOG_INFO ("%s send a [sendfile] requestion to %s" ,myuser.c_str (),peeruser.c_str ()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void acceptfile (int conn1, string sid) { string sendstr; clientState state = usermap.fvalue_sid_state (sid); int conn2 = usermap.fvalue_sid_conn2 (sid); int myconn2 = usermap.fvalue_conn1_2 (conn1); if (state == clientState::isWaiting) { string myip = usermap.fvalue_conn1_ip (conn1); sendstr = "@#sendfile accept " +myip; send (conn2,sendstr.c_str (),sendstr.size (),0 ); int peerconn1 = usermap.fkey_conn1_2 (conn2); usermap.ins_conn1_state (peerconn1,clientState::cmdLine); usermap.ins_sid_state (sid,clientState::cmdLine); string myuser = usermap.fvalue_conn1_user (conn1); string peeruser = usermap.fvalue_conn2_user (conn2); LOG_INFO ("%s [acceptfile] from %s" ,myuser.c_str (),peeruser.c_str ()); } else { sendstr = "@#break now" ; send (myconn2,sendstr.c_str (),sendstr.size (),0 ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void rejectfile (int conn1, string sid) { string sendstr; clientState state = usermap.fvalue_sid_state (sid); int conn2 = usermap.fvalue_sid_conn2 (sid); if (state == clientState::isWaiting) { sendstr = "@#sendfile reject" ; send (conn2,sendstr.c_str (),sendstr.size (),0 ); int peerconn1 = usermap.fkey_conn1_2 (conn2); usermap.ins_conn1_state (peerconn1,clientState::cmdLine); usermap.ins_sid_state (sid,clientState::cmdLine); usermap.del_conn1_req_peer2 (peerconn1); string myuser = usermap.fvalue_conn1_user (conn1); string peeruser = usermap.fvalue_conn2_user (conn2); LOG_INFO ("%s [rejectfile] from %s" ,myuser.c_str (),peeruser.c_str ()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void getfile (int conn1, string sid) { string sendstr; int conn2 = usermap.fvalue_sid_conn2 (sid); int myconn2 = usermap.fvalue_conn1_2 (conn1); string mysid = usermap.fvalue_conn1_sid (conn1); string mysname = usermap.fvalue_conn1_sname (conn1); usermap.ins_conn1_state (conn1,clientState::isWaiting); usermap.ins_sid_state (mysid,clientState::isWaiting); if (conn2 == -1 ) { sendstr = "The peer does not exist, please break!" ; send (myconn2,sendstr.c_str (),sendstr.size (),0 ); return ; } sendstr = "@#getfilefrom " +mysid+" " +mysname; send (conn2,sendstr.c_str (),sendstr.size (),0 ); usermap.ins_conn1_req_peer2 (conn1,conn2); string myuser = usermap.fvalue_conn1_user (conn1); string peeruser = usermap.fvalue_conn2_user (conn2); LOG_INFO ("%s send a [getfile] requestion to %s" ,myuser.c_str (),peeruser.c_str ()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void acceptget (int conn1, string sid, string& src) { string sendstr; clientState state = usermap.fvalue_sid_state (sid); int conn2 = usermap.fvalue_sid_conn2 (sid); if (state == clientState::isWaiting) { sendstr = "@#getfile accept " +src; send (conn2,sendstr.c_str (),sendstr.size (),0 ); int peerconn1 = usermap.fkey_conn1_2 (conn2); usermap.ins_conn1_state (peerconn1,clientState::cmdLine); usermap.ins_sid_state (sid,clientState::cmdLine); string myuser = usermap.fvalue_conn1_user (conn1); string peeruser = usermap.fvalue_conn2_user (conn2); LOG_INFO ("%s [acceptget] from %s" ,myuser.c_str (),peeruser.c_str ()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void rejectget (int conn1, string sid) { string sendstr; clientState state = usermap.fvalue_sid_state (sid); int conn2 = usermap.fvalue_sid_conn2 (sid); if (state == clientState::isWaiting) { sendstr = "@#getfile reject" ; send (conn2,sendstr.c_str (),sendstr.size (),0 ); int peerconn1 = usermap.fkey_conn1_2 (conn2); usermap.ins_conn1_state (peerconn1,clientState::cmdLine); usermap.ins_sid_state (sid,clientState::cmdLine); usermap.del_conn1_req_peer2 (peerconn1); string myuser = usermap.fvalue_conn1_user (conn1); string peeruser = usermap.fvalue_conn2_user (conn2); LOG_INFO ("%s [rejectget] from %s" ,myuser.c_str (),peeruser.c_str ()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void choosefile (int conn1, string number) { string sendstr; int conn2 = usermap.fvalue_conn1_req_peer2 (conn1); if (conn2 == -1 ) { LOG_ERROR ("[choosefile] can't find the file-sender" ) return ; } string myip = usermap.fvalue_conn1_ip (conn1); sendstr = "@#choosefile " +number+" " +myip; send (conn2,sendstr.c_str (),sendstr.size (),0 ); string myuser = usermap.fvalue_conn1_user (conn1); string peeruser = usermap.fvalue_conn2_user (conn2); LOG_INFO ("%s [choosefile] from %s" ,myuser.c_str (),peeruser.c_str ()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 void setsid (int conn1, string& newsid) { string oldsid = usermap.fvalue_conn1_sid (conn1); string sendstr; if (oldsid == newsid) { sendstr = "Your old sid and new sid are the same" ; send (conn1,sendstr.c_str (),sendstr.size (),0 ); return ; } int check = usermap.fvalue_sid_conn2 (newsid); string sendstr; if (check != -1 ) { sendstr = "The newsid [" +newsid+"] has been used, setsid failed" ; send (conn1,sendstr.c_str (),sendstr.size (),0 ); return ; } string sname = usermap.fvalue_conn1_sname (conn1); clientState state = usermap.fvalue_sid_state (oldsid); int conn2 = usermap.fvalue_sid_conn2 (oldsid); usermap.ins_conn1_sid (conn1,newsid); usermap.del_sid_sname (oldsid); usermap.ins_sid_sname (newsid,sname); usermap.del_sid_state (oldsid); usermap.ins_sid_state (newsid,state); usermap.del_sid_conn2 (oldsid); usermap.ins_sid_conn2 (newsid,conn2); sendstr = "setsid successfullly, new sid is [" +newsid+"]" ; send (conn1,sendstr.c_str (),sendstr.size (),0 ); string myuser = usermap.fvalue_conn1_user (conn1); LOG_INFO ("%s [setsid] from %s to [%s]" ,myuser.c_str (),oldsid.c_str (),newsid.c_str ()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void setsname (int conn1, string& newsname) { string oldsname = usermap.fvalue_conn1_sname (conn1); string sendstr; if (oldsname == newsname) { sendstr = "Your old sname and new sname are the same" ; send (conn1,sendstr.c_str (),sendstr.size (),0 ); return ; } string sid = usermap.fvalue_conn1_sid (conn1); usermap.ins_conn1_sname (conn1,newsname); usermap.ins_sid_sname (sid,newsname); sendstr = "setsname successfullly, new sname is [" +newsname+"]" ; send (conn1,sendstr.c_str (),sendstr.size (),0 ); string myuser = usermap.fvalue_conn1_user (conn1); LOG_INFO ("%s [setsname] from %s to [%s]" ,myuser.c_str (),oldsname.c_str (),newsname.c_str ()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void exit_ (int conn1) { break_ (conn1); string myuser = usermap.fvalue_conn1_user (conn1); LOG_INFO ("%s [exit]" ,myuser.c_str ()); cleanmap (conn1); epoller.delFd (conn1); close (conn1); } void cleanmap (int conn1) { string myip = usermap.fvalue_conn1_ip (conn1); string mysid = usermap.fvalue_conn1_sid (conn1); int conn2 = usermap.fvalue_conn1_2 (conn1); usermap.del_conn1_ip (conn1); usermap.del_ip_conn2 (myip); usermap.del_conn1_2 (conn1); usermap.del_conn1_sid (conn1); usermap.del_conn1_sname (conn1); usermap.del_sid_conn2 (mysid); usermap.del_sid_sname (mysid); usermap.del_sid_state (mysid); usermap.del_conn1_state (conn1); usermap.del_conn1_peer2 (conn1); usermap.del_conn2_user (conn2); usermap.del_conn1_user (conn1); usermap.del_conn1_req_peer2 (conn1); }
随机数库random 在命令hisir
和oyasumi
中,服务器会返回一句话,这句话实际上是随机的。理论上,设置一个全局变量每次调用命令就加一,这样在多个用户调用的情况下就能做到对单个用户来说比较随机了。
但是还是用点相对来说高级的东西,c语言的rand和srand就不用了,我们来学一下c++11新实现的库<random>
。使用方法很简单:
实例化一个随机数引擎
实例化一个统计分布(可选)
分布对象调用引擎对象即可返回随机数
伪随机数引擎 random库提供了三种常用的随机数生成引擎,都以模版类的方式定义。分别是:
所有生成器引擎,或者经过adapter修饰后的类实例,都提供如下接口供使用
min:返回最小值,静态函数
max:返回最大值,静态函数
seed:设置随机数生成的种子
operator():产生随机数
void discard (unsigned long long z):调用z次operator()函数
另外,都定义了输入输出操作符和关系运算的非成员函数。随机数引擎接收一个整数作为种子,不提供就会使用默认值 。
使用大概像这样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int min,max; default_random_engine e; uniform_int_distribution<unsigned > u (min,max) ; int randNum=u (e);int randNum=e ();
预定义算法 定义了算法的最佳实践,避免了参数的选择,可以直接选择引擎,即不用使用上面的引擎然后设置一些参数,可以直接用算法。
算法包括minstd_rand0、minstd_rand、mt19937(32位)、mt19937_64(产生64位随机数)、ranlux24_base、ranlux48_base等。 mt19937
是目前 C++ 标准中最实用的引擎
分布 分布用于进一步加载引擎,使随机数具有分布性质,提供的分布有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 uniform_int_distribution uniform_real_distribution bernoulli_distribution binomial_distribution geometry_distribution negative_biomial_distribution poisson_distribution exponential_distribution gamma_distribution weibull_distribution extreme_value_distribution normal_distribution chi_squared_distribution cauchy_distribution fisher_f_distribution student_t_distribution discrete_distribution piecewise_constant_distribution piecewise_linear_distribution
random_device 区别与其他生成算法的伪随机数,通过硬件生成真正的不确定随机数(如果硬件不支持,标准也允许使用伪随机生成方法实现此函数),返回一个unsigned int,通常作为前述引擎的seeds。
前面使用分布加载引擎后,如果不设置种子,每次运行的随机数序列都是一样的,因为:引擎使用默认值->引擎序列相同->分布产生的序列相同。
因此如果要每次运行都产生不一样的序列,就要用种子,以往的做法可以用time(NULL),但这实际上是个糟糕的做法。这就可以用到random_device。
random_device一般只用来作为其他伪随机数算法的种子,参考:C++11随机数的正确打开方式 - 别再闹了 - 博客园 (cnblogs.com) 。大概就是说:
random_device最大最小值不可以改
linux下是用熵池获取随机数的,当熵池用尽后,许多random_device的具体实现的性能会急速下降
多次调用random_device要花费比其他伪随机数算法更多的时间。在Linux中,每次调用random_device都需要读urandom这个文件再关闭,而在WIndom中我们需要调用操作系统的API,再销毁实例化对象,这个时间花费显然比设置好种子就能一直产生的其他伪随机数算法要慢得多。
而作为种子:只调用一次,用于给引擎一个随机的初始值就完成了。
1 2 3 4 5 6 7 8 9 10 11 #include <random> using namespace std;int main () { int min = 0 ,max = 100 ; random_device seed; ranlux48 engine (seed()) ; uniform_int_distribution<int > distrib (min, max) ; int random = distrib (engine); }
随机数考量 前面大概熟悉了一下随机数的生成,但还没想好用户怎么用随机数,是所有用户共享一个随机数分布呢?还是用户来了就创建分布对象然后只生成一个数字呢?还是一个用户对应一个分布对象呢?
简单点就可以用第二种,因为这两个命令用的频率应该很少,缺点是多次调用hisir的话,随机得不够均匀;并且这种方法更耗时、耗内存,为了生成单个数字而完整地实例化了一个分布对象。
第一种的话也简单点,从统计的角度上看,这对于全体用户是均匀分布,但用户体验好肯定是对于单个用户来说均匀分布一些。
第三种貌似比较折中,但是就需要多一个映射表,挺麻烦的,映射表已经不想改了,否决否决。
基本上是决定用第一种共享的方式,加一个互斥锁访问。因为如果从概率角度上看,每个用户访问时,每个随机数的概率都是相同的(均匀分布),这对用户来说就也都是均匀的了。当然,我也没研究底层的实现是基于统计的还是基于概率的。不过已经足够了。
严格来说,全局的方式都不用随机数种子 ,因为一直运行的话,也就不会有相同的序列了。因为有互斥锁,封装一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> #include <random> #include <mutex> using namespace std;class Randint { private : mutex mux; random_device seed; ranlux48 engine; uniform_int_distribution<int > distrib; public : Randint (int min, int max) :engine (seed ()), distrib (min, max){} int operator () () { lock_guard<mutex> locker (mux) ; return distrib (engine); } }; int main () { Randint rand_of_hisir (0 , 10 ) ; for (int i = 0 ; i < 100 ; i++) cout << rand_of_hisir () << " " ; return 0 ;
可以使用多个对象变成一个vector形式的随机化对象池,不过从分布来看没什么意义,因此就分别对两个命令各使用一个对象即可。
语录对象 语录对象使用const的vector<string>
,通过下标访问,不用互斥。形式如下,具体就不在博客里放了。
1 2 const vector<string> hisir_sentence = [...];const vector<string> oyasumi_sentence = [...];
1 2 3 4 5 6 7 8 void hisir (int conn1, string& msg) { string sendstr = hisir_sentence[rand_of_hisir ()]; send (conn1,sendstr.c_str (),sendstr.size (),0 ); string myuser = usermap.fvalue_conn1_user (conn1); LOG_INFO ("%s [hisir]: %s" ,myuser.c_str (),msg.c_str ()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 void oyasumi (int conn1) { string sendstr = oyasumi_sentence[rand_of_oyasumi ()]; send (conn1,sendstr.c_str (),sendstr.size (),0 ); break_ (conn1); string myuser = usermap.fvalue_conn1_user (conn1); LOG_INFO ("%s [oyasumi]~" ,myuser.c_str ()); cleanmap (conn1); close (conn1); }
线程池 线程池就是传入task工作,可以参考之前的博客:WebServer模块单元测试 | JySama
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 #ifndef THREADPOOL_H #define THREADPOOL_H #include <mutex> #include <thread> #include <condition_variable> #include <functional> #include <queue> #include <cassert> class threadpool { private : struct pool { std::mutex mtx; std::queue<std::function<void ()>> taskQueue; std::condition_variable cond; bool isclose = false ; }; std::shared_ptr<pool> pool_; public : threadpool (int threadnum = 8 ):pool_ (std::make_shared <pool>()) { assert (threadnum > 0 ); for (int i=0 ;i<threadnum;i++) std::thread ([pool_t = pool_]{ std::unique_lock<std::mutex> locker (pool_t ->mtx); while (true ) { if (!pool_t ->taskQueue.empty ()) { auto task = pool_t ->taskQueue.front (); pool_t ->taskQueue.pop (); locker.unlock (); task (); locker.lock (); } else if (pool_t ->isclose) break ; else pool_t ->cond.wait (locker); } }).detach (); } void addTask (std::function<void ()> task) { std::lock_guard<std::mutex> locker (pool_->mtx) ; pool_->taskQueue.emplace (task); pool_->cond.notify_one (); } void close () { pool_->isclose = true ; pool_->cond.notify_all (); } ~threadpool () { } }; #endif
工作函数补充——内网穿透 于2022/12补充
当进行文件传输时,公网服务器在工作线程里顺便实现内网穿透(基于udp)服务即可,该部分的工作在这篇博客:UDP hole punching | JySama 。为了封装到项目中,使用命名空间。
为了简单,不考虑高并发的情况,默认连续的两个向服务器请求的内网主机就是要进行内网穿透的主机。
udp_hole_punch.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #ifndef UDP_HOLE_PUNCHING_H #define UDP_HOLE_PUNCHING_H #include <string> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <cstring> #include <unistd.h> #include <vector> #include "log.h" using namespace std;namespace UDP_HP{ void init_udp_Socket (int & listenfd, const int port) ; string udp_hole_punching (int listenfd) ; vector<string> parse (string str) ; void work (int & listenudp) ; } #endif
udp_hole_punch.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 #include "udp_hole_punch.h" namespace UDP_HP{ void init_udp_Socket (int & listenfd, const int port) { listenfd = socket (AF_INET,SOCK_DGRAM,IPPROTO_UDP); if (listenfd < 0 ) { LOG_ERROR ("create listen socket error, port-%d\n" ,port); exit (1 ); } struct sockaddr_in socketaddr; socketaddr.sin_family = AF_INET; socketaddr.sin_port = htons (port); socketaddr.sin_addr.s_addr = htonl (INADDR_ANY); int optval = 1 ; int ret = setsockopt (listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof (int )); if (ret == -1 ) { close (listenfd); exit (1 ); } if (bind (listenfd,(struct sockaddr *)&socketaddr,sizeof (socketaddr))==-1 ) { close (listenfd); exit (1 ); } } string udp_hole_punching (int listenfd) { struct sockaddr_in gateway; socklen_t addr_len = sizeof (gateway); memset (&gateway, 0 , sizeof (gateway)); char recvbuf[128 ]; memset (&recvbuf, 0 , sizeof (recvbuf)); int res = recvfrom (listenfd, recvbuf, 128 , 0 , (struct sockaddr *)&gateway, &addr_len); if (res < 0 ) { LOG_WARN ("udp hole punching receive error!\n" ); return "" ; } else { string ip = string (inet_ntoa (gateway.sin_addr)); string port = to_string (ntohs (gateway.sin_port)); string host = string (recvbuf); LOG_INFO ("udp hole punching NAT ip: %s, port: %s; host:%s\n" ,ip.c_str (),port.c_str (),host.c_str ()); return ip+" " +port+" " +host; } return "" ; } vector<string> parse (string str) { str = str + " " ; vector<string> res; size_t pos = 0 ; size_t pos1; while ((pos1 = str.find (' ' , pos)) != string::npos) { res.push_back (str.substr (pos, pos1 - pos)); while (str[pos1] == ' ' ) pos1++; pos = pos1; } return res; } void work (int & listenudp) { string ip_port1 = udp_hole_punching (listenudp); string ip_port2 = udp_hole_punching (listenudp); if (ip_port1 == "" || ip_port2 == "" ) return ; vector<string> host1 = parse (ip_port1); vector<string> host2 = parse (ip_port2); string ip1 = host1[0 ]; string port1 = host1[1 ]; string ip2 = host2[0 ]; string port2 = host2[1 ]; struct sockaddr_in socketaddr1; socketaddr1.sin_family = AF_INET; socketaddr1.sin_port = htons (stoi (port1)); inet_pton (AF_INET, ip1.c_str (), &socketaddr1.sin_addr); struct sockaddr_in socketaddr2; socketaddr2.sin_family = AF_INET; socketaddr2.sin_port = htons (stoi (port2)); inet_pton (AF_INET, ip2.c_str (), &socketaddr2.sin_addr); if (ip1 != ip2) { ip_port1 = ip1 + " " + port1; ip_port2 = ip2 + " " + port2; } else { ip_port1 = host1[2 ] + " " + host1[3 ]; ip_port2 = host2[2 ] + " " + host2[3 ]; } int res = sendto (listenudp, ip_port1.c_str (), ip_port1.size (), 0 , (struct sockaddr*)&socketaddr2, sizeof (socketaddr2)); if (res < 0 ) { LOG_WARN ("udp sendto error!\n" ); } res = sendto (listenudp, ip_port2.c_str (), ip_port2.size (), 0 , (struct sockaddr*)&socketaddr1, sizeof (socketaddr1)); if (res < 0 ) { LOG_WARN ("udp sendto error!\n" ); } } }
下一阶段 现在可以进入下一阶段了,见下一篇博客(这篇字数多太卡了)