0%

「orange」-server

顶层设计

  • 映射表(补:实际上可以用结构体减少映射,不过不够灵活)
  • 线程池、任务
  • 日志系统
  • 聊天日志
  • 同步非阻塞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->表。但实际上这行不通,因为表的参数类型是不同的,并且这也无法通过模板来解决。
    • 可能使用函数模板通过传入表id操作不同的表吗?已尝试,这行不通,可以看我的博客函数模板问题 | 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
//映射表
unordered_map<int, string> conn1_ip;//id0,套接字1->ip,以下都用这样的命名方式
unordered_map<string, int> ip_conn2;//id1
unordered_map<int, int> conn1_2;//id2
unordered_map<int, string> conn1_sid;//id3
unordered_map<int, string> conn1_sname;//id4
unordered_map<string, int> sid_conn2;//id5
unordered_map<string, string> sid_sname;//id6
unordered_map<string, clientState> sid_state;//id7
unordered_map<int, clientState> conn1_state;//id8
unordered_map<int, int> conn1_peer2;//id9
unordered_map<int, string> conn2_user;//id10
unordered_map<int, string> conn1_user;//id11

//每个表的互斥锁
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);

//通过key找value
lock_guard<mutex> locker(mux);
typename unordered_map<T1, T2>::iterator iter = map.find(key);

if (iter == map.end())//这里返回是因为之前想写函数模板,直接调用就进行错误处理等
return T2();//string的空构造是空字符串,int的空构造是0,clientState的空构造是noLogin
else
return iter->second;

//通过value找key
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;

//if (iter == map.end()),否则一定找不到
////这里返回是因为之前想写函数模板,直接调用就进行错误处理等
return T1();//string的空构造是空字符串,int的空构造是0,clientState的空构造是noLogin

写了后面一些内容,发现映射表使用太频繁了,因为没有封装,导致代码极速膨胀(因为每个操作都要互斥,一行语句会因为调用互斥加锁解锁展开成三四行),所以还是打算封装,每个表都配一系列函数好了,代码很多…不过后面调用就舒服很多。注:后面有些补充的表不会填上来。

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,//chat状态
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;//id0,套接字1->ip,以下都用这样的命名方式
unordered_map<string, int> ip_conn2;//id1
unordered_map<int, int> conn1_2;//id2
unordered_map<int, string> conn1_sid;//id3
unordered_map<int, string> conn1_sname;//id4
unordered_map<string, int> sid_conn2;//id5
unordered_map<string, string> sid_sname;//id6
unordered_map<string, clientState> sid_state;//id7
unordered_map<int, clientState> conn1_state;//id8
unordered_map<int, int> conn1_peer2;//id9
unordered_map<int, string> conn2_user;//id10
unordered_map<int, string> conn1_user;//id11

//每个表的互斥锁
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:
//-----------------------------------------------------0
void ins_conn1_ip(int key, string value);
void del_conn1_ip(int key);
string fvalue_conn1_ip(int key);
//不会通过ip找conn1

//-----------------------------------------------------1
void ins_ip_conn2(string key, int value);
void del_ip_conn2(string key);
int fvalue_ip_conn2(string key);
//不会通过conn2找ip

//-----------------------------------------------------2
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);//通过2找1

//-----------------------------------------------------3
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);

//-----------------------------------------------------4
void ins_conn1_sname(int key, string value);
void del_conn1_sname(int key);
string fvalue_conn1_sname(int key);

//-----------------------------------------------------5
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);

//-----------------------------------------------------6
void ins_sid_sname(string key, string value);
void del_sid_sname(string key);
string fvalue_sid_sname(string key);

//-----------------------------------------------------7
void ins_sid_state(string key, clientState value);
void del_sid_state(string key);
clientState fvalue_sid_state(string key);
//不可能通过state找key

//-----------------------------------------------------8
void ins_conn1_state(int key, clientState value);
void del_conn1_state(int key);
clientState fvalue_conn1_state(int key);
//不可能通过state找key

//-----------------------------------------------------9
void ins_conn1_peer2(int key, int value);
void del_conn1_peer2(int key);
int fvalue_conn1_peer2(int key);

//-----------------------------------------------------10
void ins_conn2_user(int key, string value);
void del_conn2_user(int key);
string fvalue_conn2_user(int key);

//-----------------------------------------------------11
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"
//---------------------------------------0
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;
}
//---------------------------------------1
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;
}
//-----------------------------------------------------2
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)//通过2找1
{
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;
}

//-----------------------------------------------------3
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;
}

//-----------------------------------------------------4
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;
}

//-----------------------------------------------------5
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 "";
}

//-----------------------------------------------------6
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;
}

//-----------------------------------------------------7
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;
}
//不可能通过state找key

//-----------------------------------------------------8
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;
}
//不可能通过state找key

//-----------------------------------------------------9
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;
}

//-----------------------------------------------------10
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;
}

//-----------------------------------------------------11
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);//获取一个sql连接句柄,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())) //执行语句,成功返回0,错误返回非0
{
LOG_DEBUG( "Select error: %s",order.c_str());
mysql_free_result(res);//错误的话释放结果集(无论成功与否,查询后总是释放)
send(conn1,sendstr.c_str(),int(sendstr.size()),0);//发送server busy
return;
}
res = mysql_store_result(sql);//完整的结果集

while(MYSQL_ROW row = mysql_fetch_row(res)) //遍历行,没有对应的表项就进不来,进来就只有一行
{
string passwd(row[1]);
// 能select到说明又对应的username,看是登录还是注册
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//注册,说明userid被使用了
{
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);//发送server busy
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(){}//析构函数实际上和构造函数一样,可以private,因为本质上是成员函数调用
public:
Sqlconnpool(const Sqlconnpool&) = delete;
Sqlconnpool& operator=(const Sqlconnpool&) = delete;

void close();
static Sqlconnpool* instance();
MYSQL* getconn(int timeout);
void freeconn(MYSQL* conn);
//无法使用构造函数传参,用init,默认参数写声明中
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);//防止放入nullptr
lock_guard<mutex> locker(mux);
connque.push(conn);
freecount++;
cond.notify_one();//唤醒一个get线程
}
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);//放入的一定不是nullptr
}
}

void Sqlconnpool::close()
{
unique_lock<mutex> locker(mux);
while(freecount != maxconn)//必须要等待所有连接都放回来,直接close再执行查询程序会崩溃
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;//保存连接好的sql
Sqlconnpool* connpool;//保存连接池
public:
SqlRAII(MYSQL** sql,Sqlconnpool* sqlpool,int timeout = 0)//传入sql指针的地址,即&sql,获取连接后传出去
{
assert(sqlpool);//必须先建好连接池

*sql = sqlpool->getconn(timeout);//可能会超时
//为了用户自行getconn和使用sqlraii的统一,这里统一让用户在上层处理sql为nullptr的情况
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},//服务器没有re命令,但是多了一个choosefile,通告选择的文件号码
{"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)//epoll响应conn1,指定工作函数从conn1处接收
{
//接收,ET模式下要一次接收完,该函数在下篇博客epoll中作出。
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;//因为是oneshot,每次重新注册
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)
{
//对于一个关键字的命令,无法用空格分割,考虑到最后一定有个\n是没用的,因此把\n改为空格,一举两得
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;//返回值是右值,外部vector会接收右值,调用移动构造

}

命令工作函数

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);//获取发送方userid
if(conn2 == -1)
{
LOG_ERROR("%s chatmsg error, can't find peer!", myuser.c_str());
return;
}
string peeruser = usermap.fvalue_conn2_user(conn2);//获取对方userid

string mysname = usermap.fvalue_conn1_sname(conn1);//获取发送方名字

LOG_INFO("%s send [msg] to %s",myuser.c_str(),peeruser.c_str());
//还要记录myuser的聊天日志
recvstr = recvstr.substr(1,recvstr.size()-2);//去~和去\n
recvstr = mysname+": "+recvstr;
send(conn2,recvstr.c_str(),recvstr.size(),0);

//记录日志,在下一篇博客中已完成该函数
//前面的recvstr已经记录了发送方名字和发送内容,然后文件名包含了发送者id,因此再补上接收者id即可
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);//获取userid
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);//修改自己状态为waiting
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);//获取发送方userid
string peeruser = usermap.fvalue_conn2_user(conn2);//获取对方userid
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);//获取自己的sid,为了修改状态
usermap.ins_conn1_state(conn1,clientState::isChatting);//修改自己状态为chatting
usermap.ins_sid_state(mysid,clientState::isChatting);//改两次

int peerconn1 = usermap.fkey_conn1_2(conn2);//获取对方套接字1,为了修改状态
usermap.ins_conn1_state(peerconn1,clientState::isChatting);//修改对方状态为chatting
usermap.ins_sid_state(sid,clientState::isChatting);//改两次

usermap.ins_conn1_peer2(conn1,conn2);
usermap.ins_conn1_peer2(peerconn1,myconn2);

//accept不删,为了在break到达前accept后还能找到对方,reject可以删,因为不用找对方
//usermap.del_conn1_req_peer2(peerconn1);//把服务器保存的对方的请求删掉

string myuser = usermap.fvalue_conn1_user(conn1);//获取发送方userid
string peeruser = usermap.fvalue_conn2_user(conn2);//获取对方userid
LOG_INFO("%s [accept] the chat requestion for %s",myuser.c_str(),peeruser.c_str());
}
else//对方已经退出了,或在accept到服务器前break了,或其他情况,让accept方退出
{
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);//获取对方套接字1,为了修改状态
usermap.ins_conn1_state(peerconn1,clientState::cmdLine);//修改对方状态为cmdLine
usermap.ins_sid_state(sid,clientState::cmdLine);//改两次

usermap.del_conn1_req_peer2(peerconn1);//把服务器保存的对方的请求删掉

string myuser = usermap.fvalue_conn1_user(conn1);//获取发送方userid
string peeruser = usermap.fvalue_conn2_user(conn2);//获取对方userid
LOG_INFO("%s [reject] the chat requestion for %s",myuser.c_str(),peeruser.c_str());
}
//其他情况下对于reject不用管


}
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)//加下划线为了避免和break关键字重合,这里不close,在epoll里close
{
string sendstr;
clientState state = usermap.fvalue_conn1_state(conn1);//获取自己状态
string mysid = usermap.fvalue_conn1_sid(conn1);//获取自己的sid,为了修改状态

string myuser = usermap.fvalue_conn1_user(conn1);//获取发送方userid
int conn2 = usermap.fvalue_conn1_req_peer2(conn1);
if(state == clientState::isWaiting)
{
usermap.ins_conn1_state(conn1,clientState::cmdLine);//修改自己状态为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)//在break到达前acceptfile了,让对方退出
{
if(conn2 == -1)//没有请求就不用管,reject会把请求删掉,这与accept区分开来
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)//正在通信,或在break到达前accept了
{
conn2 = usermap.fvalue_conn1_peer2(conn1);//找到正在通信的对方

string mysid = usermap.fvalue_conn1_sid(conn1);//获取自己的sid,为了修改状态
usermap.ins_conn1_state(conn1,clientState::cmdLine);//修改自己状态为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);//获取对方套接字1,为了修改状态
string sid = usermap.fvalue_conn1_sid(peerconn1);//获取对方sid,修改状态
usermap.ins_conn1_state(peerconn1,clientState::cmdLine);//修改对方状态为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);//获取发送方userid
string peeruser = usermap.fvalue_conn2_user(conn2);//获取对方userid
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);//修改自己状态为waiting
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);//获取发送方userid
string peeruser = usermap.fvalue_conn2_user(conn2);//获取对方userid
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);//获取对方套接字1,为了修改状态
usermap.ins_conn1_state(peerconn1,clientState::cmdLine);//修改对方状态为cmdLine
usermap.ins_sid_state(sid,clientState::cmdLine);//改两次


//accept不删,为了在break到达前accept后还能找到对方,reject可以删,因为不用找对方
//usermap.del_conn1_req_peer2(peerconn1);//把服务器保存的对方的请求删掉

string myuser = usermap.fvalue_conn1_user(conn1);//获取发送方userid
string peeruser = usermap.fvalue_conn2_user(conn2);//获取对方userid
LOG_INFO("%s [acceptfile] from %s",myuser.c_str(),peeruser.c_str());
}
else//对方已经退出了,或在accept到服务器前break了,或其他情况,让accept方退出
{
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);//获取对方套接字1,为了修改状态
usermap.ins_conn1_state(peerconn1,clientState::cmdLine);//修改对方状态为cmdLine
usermap.ins_sid_state(sid,clientState::cmdLine);//改两次

usermap.del_conn1_req_peer2(peerconn1);//把服务器保存的对方的请求删掉

string myuser = usermap.fvalue_conn1_user(conn1);//获取发送方userid
string peeruser = usermap.fvalue_conn2_user(conn2);//获取对方userid
LOG_INFO("%s [rejectfile] from %s",myuser.c_str(),peeruser.c_str());
}
//其他情况下对于reject不用管


}
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);//修改自己状态为waiting
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);//获取发送方userid
string peeruser = usermap.fvalue_conn2_user(conn2);//获取对方userid
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);//获取对方套接字1,为了修改状态
usermap.ins_conn1_state(peerconn1,clientState::cmdLine);//修改对方状态为cmdLine
usermap.ins_sid_state(sid,clientState::cmdLine);//改两次


//accept不删,为了在break到达前accept后还能找到对方,reject可以删,因为不用找对方
//usermap.del_conn1_req_peer2(peerconn1);//把服务器保存的对方的请求删掉

string myuser = usermap.fvalue_conn1_user(conn1);//获取发送方userid
string peeruser = usermap.fvalue_conn2_user(conn2);//获取对方userid
LOG_INFO("%s [acceptget] from %s",myuser.c_str(),peeruser.c_str());
}
//其他情况下对于getfile不用管
}
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);//获取对方套接字1,为了修改状态
usermap.ins_conn1_state(peerconn1,clientState::cmdLine);//修改对方状态为cmdLine
usermap.ins_sid_state(sid,clientState::cmdLine);//改两次

usermap.del_conn1_req_peer2(peerconn1);//把服务器保存的对方的请求删掉

string myuser = usermap.fvalue_conn1_user(conn1);//获取发送方userid
string peeruser = usermap.fvalue_conn2_user(conn2);//获取对方userid
LOG_INFO("%s [rejectget] from %s",myuser.c_str(),peeruser.c_str());
}
//其他情况下对于reject不用管


}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void choosefile(int conn1, string number)//服务器没有re命令,但是多了一个choosefile,通告选择的文件号码
{
string sendstr;
int conn2 = usermap.fvalue_conn1_req_peer2(conn1);//从getfile请求里获取对方的套接字
if(conn2 == -1)//意外情况...
{
LOG_ERROR("[choosefile] can't find the file-sender")
return;
}

string myip = usermap.fvalue_conn1_ip(conn1);//告知对方(发送方)本地ip地址
sendstr = "@#choosefile "+number+" "+myip;
send(conn2,sendstr.c_str(),sendstr.size(),0);//发给对方

string myuser = usermap.fvalue_conn1_user(conn1);//获取发送方userid
string peeruser = usermap.fvalue_conn2_user(conn2);//获取对方userid
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);//先获取旧的sid
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);//查看是否已有该sid
string sendstr;
if(check != -1)//该sid已注册
{
sendstr = "The newsid ["+newsid+"] has been used, setsid failed";
send(conn1,sendstr.c_str(),sendstr.size(),0);//发给自己
return;
}

//有些表sid是当key的,这里的修改原则是删除->重新插入
string sname = usermap.fvalue_conn1_sname(conn1);//获取sname,为了改sid_sname表
clientState state = usermap.fvalue_sid_state(oldsid);//获取state,为了改sid_state表
int conn2 = usermap.fvalue_sid_conn2(oldsid);//获取conn2,为了改sid_conn2表

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);//获取userid
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);//先获取旧的sname
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);//获取userid
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+clean map+close
{
break_(conn1);//调用break,让会影响到的对方退出
//break是没有关系的,因为如果普通情况下exit,请求表映射是空,那么break不会做事情

string myuser = usermap.fvalue_conn1_user(conn1);//获取userid
LOG_INFO("%s [exit]",myuser.c_str());//删之前记录一下

cleanmap(conn1);//删表
epoller.delFd(conn1);//从epoll内删除事件
close(conn1);//关套接字
}
void cleanmap(int conn1)//删表,oyasumi和exit都可以使用
{
string myip = usermap.fvalue_conn1_ip(conn1);
string mysid = usermap.fvalue_conn1_sid(conn1);
int conn2 = usermap.fvalue_conn1_2(conn1);

//13个表都删了
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

在命令hisiroyasumi中,服务器会返回一句话,这句话实际上是随机的。理论上,设置一个全局变量每次调用命令就加一,这样在多个用户调用的情况下就能做到对单个用户来说比较随机了。

但是还是用点相对来说高级的东西,c语言的rand和srand就不用了,我们来学一下c++11新实现的库<random>。使用方法很简单:

  • 实例化一个随机数引擎
  • 实例化一个统计分布(可选)
  • 分布对象调用引擎对象即可返回随机数

伪随机数引擎

random库提供了三种常用的随机数生成引擎,都以模版类的方式定义。分别是:

  • linear_congruential_engine:线性同余生成引擎,是最常用也是速度最快的,但随机效果一般
  • mersenne_twister_engine:梅森旋转算法,随机效果最好。比较慢,占用存储空间较大,但是在参数设置合理的情况下,可生成最长的不重复序列,且具有良好的频谱特征。
  • subtract_with_carry_engine:滞后Fibonacci算法。速度最快,占用存储空间较大,频谱特性有时不佳。
  • default_random_engine:就是线性同余引擎,参考https://cplusplus.com/reference/random/default_random_engine/

所有生成器引擎,或者经过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 //负二项分布

// Rate-based distributions:
poisson_distribution //泊松分布
exponential_distribution //指数分布
gamma_distribution //伽马分布
weibull_distribution //威布尔分布
extreme_value_distribution //极值分布

//正态分布相关:
normal_distribution //正态分布
chi_squared_distribution //卡方分布
cauchy_distribution //柯西分布
fisher_f_distribution //费歇尔F分布
student_t_distribution // t分布

//分段分布相关:
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:
//hisir语录和oyasumi语录大小可能不一样,所以对应不一样的随即器
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);//获取userid
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);//调用break,让会影响到的对方退出

string myuser = usermap.fvalue_conn1_user(conn1);//获取userid
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
//threadpool.h
#ifndef THREADPOOL_H
#define THREADPOOL_H

#include<mutex>
#include<thread>
#include<condition_variable>
#include<functional>
#include<queue>
#include <cassert>//使用assert函数
class threadpool
{
private:
struct pool//封装三个资源
{
std::mutex mtx;//互斥锁
std::queue<std::function<void()>> taskQueue;//任务队列,无参数的function,调用时不用传参
std::condition_variable cond;//条件变量
bool isclose = false;//默认值是false
};
std::shared_ptr<pool> pool_;//共享指针,pool_是一个指针指向pool结构体,这个指针用于线程池操作资源

public:
threadpool(int threadnum = 8):pool_(std::make_shared<pool>())//以make_shared的方式new一个对象给pool_指针
{
assert(threadnum > 0);//没有线程就报错
for(int i=0;i<threadnum;i++)//创建线程池
std::thread([pool_t = pool_]{//现在要按值捕获,相当于拷贝构造共享指针,计数+1,且指向相同内容
std::unique_lock<std::mutex> locker(pool_t->mtx);//定义一个locker对象,现在已经锁住了
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();//把thread分离,不用手动join,结束自动回收
}

void addTask(std::function<void()> task)
{
std::lock_guard<std::mutex> locker(pool_->mtx);//定义一个locker对象
pool_->taskQueue.emplace(task);//这种方式,使用emplace和push没啥区别,task本身就是临时对象
//如果要真正使用到emplace调用构造函数,还要配合std::forward完美转发,此时无论构造函数是不是explicit(不能隐式转换),都可以正常工作
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>//sockaddr_in
#include <arpa/inet.h>//in_addr
#include <cstring>
#include <unistd.h>//close
#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);//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;//ipv4
socketaddr.sin_port = htons(port);
socketaddr.sin_addr.s_addr = htonl(INADDR_ANY);

//Port reused
int optval = 1;
int ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void*)&optval, sizeof(int));
if(ret == -1)
{
close(listenfd);
exit(1);
}

//bind
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 + " ";//add a space
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;//move rvalue
}

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);

//The server sends back using a NAT ip
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;//ipv4
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;//ipv4
socketaddr2.sin_port = htons(stoi(port2));
inet_pton(AF_INET, ip2.c_str(), &socketaddr2.sin_addr);

if(ip1 != ip2) //Different intranets
{
ip_port1 = ip1 + " " + port1;
ip_port2 = ip2 + " " + port2;
}
else //In the same NAT
{
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");
//exit(1);
}

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");
//exit(1);
}

}
}

下一阶段

现在可以进入下一阶段了,见下一篇博客(这篇字数多太卡了)