07. 基础服务器:实现get、set、del功能
# 07. 基础服务器:实现get、set、del功能
有了上一章的事件循环代码,咱们终于能给服务器加点命令功能啦!在咱们的设计里,“命令”就是一串字符串,比如set key val
。我们会用下面这种方式对“命令”进行编码:
+------+-----+------+-----+------+-----+-----+------+
| nstr | len | str1 | len | str2 | ... | len | strn |
+------+-----+------+-----+------+-----+-----+------+
1
2
3
2
3
这里的nstr
是字符串的数量,len
是紧跟其后的字符串的长度,它们都是32位整数。
而响应呢,则是一个32位的状态码,后面跟着响应字符串:
+-----+---------+
| res | data... |
+-----+---------+
1
2
3
2
3
一切从try_one_request
函数开始。
static bool try_one_request(Conn *conn) {
// 尝试从缓冲区解析一个请求
if (conn->rbuf_size < 4) {
// 缓冲区数据不够,下次循环再试试
return false;
}
uint32_t len = 0;
memcpy(&len, &conn->rbuf[0], 4);
if (len > k_max_msg) {
msg("too long");
conn->state = STATE_END;
return false;
}
if (4 + len > conn->rbuf_size) {
// 缓冲区数据不够,下次循环再试试
return false;
}
// 拿到一个请求,生成响应
uint32_t rescode = 0;
uint32_t wlen = 0;
int32_t err = do_request(
&conn->rbuf[4], len,
&rescode, &conn->wbuf[4 + 4], &wlen
);
if (err) {
conn->state = STATE_END;
return false;
}
wlen += 4;
memcpy(&conn->wbuf[0], &wlen, 4);
memcpy(&conn->wbuf[4], &rescode, 4);
conn->wbuf_size = 4 + wlen;
// 从缓冲区移除请求
// 注意:频繁使用memmove效率不高
// 注意:生产环境代码需要更好的处理方式
size_t remain = conn->rbuf_size - 4 - len;
if (remain) {
memmove(conn->rbuf, &conn->rbuf[4 + len], remain);
}
conn->rbuf_size = remain;
// 改变状态
conn->state = STATE_RES;
state_res(conn);
// 如果请求已完全处理,继续外层循环
return (conn->state == STATE_REQ);
}
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
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
do_request
函数负责处理请求,目前只识别3种命令(get
、set
、del
)。
static int32_t do_request(
const uint8_t * req, uint32_t reqlen,
uint32_t * rescode, uint8_t * res, uint32_t * reslen)
{
std::vector<std::string> cmd;
if (0 != parse_req(req, reqlen, cmd)) {
msg("bad req");
return -1;
}
if (cmd.size() == 2 && cmd_is(cmd[0], "get")) {
*rescode = do_get(cmd, res, reslen);
} else if (cmd.size() == 3 && cmd_is(cmd[0], "set")) {
*rescode = do_set(cmd, res, reslen);
} else if (cmd.size() == 2 && cmd_is(cmd[0], "del")) {
*rescode = do_del(cmd, res, reslen);
} else {
// 不识别的命令
*rescode = RES_ERR;
const char *msg = "Unknown cmd";
strcpy((char * ) res, msg);
*reslen = strlen(msg);
return 0;
}
return 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
命令的解析过程很直接:
static int32_t parse_req(
const uint8_t *data, size_t len, std::vector<std::string> &out)
{
if (len < 4) {
return -1;
}
uint32_t n = 0;
memcpy(&n, &data[0], 4);
if (n > k_max_args) {
return -1;
}
size_t pos = 4;
while (n--) {
if (pos + 4 > len) {
return -1;
}
uint32_t sz = 0;
memcpy(&sz, &data[pos], 4);
if (pos + 4 + sz > len) {
return -1;
}
out.push_back(std::string((char * )&data[pos + 4], sz));
pos += 4 + sz;
}
if (pos != len) {
return -1; // 有多余的无用数据
}
return 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
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
下面是3种命令的“具体实现”:
enum {
RES_OK = 0,
RES_ERR = 1,
RES_NX = 2,
};
// 键值空间的数据结构。在我们下一章实现哈希表之前,这只是个占位的
static std::map<std::string, std::string> g_map;
static uint32_t do_get(
const std::vector<std::string> &cmd, uint8_t * res, uint32_t * reslen)
{
if (!g_map.count(cmd[1])) {
return RES_NX;
}
std::string &val = g_map[cmd[1]];
assert(val.size() <= k_max_msg);
memcpy(res, val.data(), val.size());
*reslen = (uint32_t)val.size();
return RES_OK;
}
static uint32_t do_set(
const std::vector<std::string> &cmd, uint8_t * res, uint32_t * reslen)
{
(void)res;
(void)reslen;
g_map[cmd[1]] = cmd[2];
return RES_OK;
}
static uint32_t do_del(
const std::vector<std::string> &cmd, uint8_t * res, uint32_t * reslen)
{
(void)res;
(void)reslen;
g_map.erase(cmd[1]);
return RES_OK;
}
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
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
现在,是时候用我们的客户端测试一下啦:
static int32_t send_req(int fd, const std::vector<std::string> &cmd) {
uint32_t len = 4;
for (const std::string &s : cmd) {
len += 4 + s.size();
}
if (len > k_max_msg) {
return -1;
}
char wbuf[4 + k_max_msg];
memcpy(&wbuf[0], &len, 4); // 假设是小端序
uint32_t n = cmd.size();
memcpy(&wbuf[4], &n, 4);
size_t cur = 8;
for (const std::string &s : cmd) {
uint32_t p = (uint32_t)s.size();
memcpy(&wbuf[cur], &p, 4);
memcpy(&wbuf[cur + 4], s.data(), s.size());
cur += 4 + s.size();
}
return write_all(fd, wbuf, 4 + len);
}
static int32_t read_res(int fd) {
// 代码省略...
// 打印结果
uint32_t rescode = 0;
if (len < 4) {
msg("bad response");
return -1;
}
memcpy(&rescode, &rbuf[4], 4);
printf("server says: [%u] %.*s\n", rescode, len - 4, &rbuf[8]);
return 0;
}
int main(int argc, char **argv) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
die("socket()");
}
// 代码省略...
std::vector<std::string> cmd;
for (int i = 1; i < argc; ++i) {
cmd.push_back(argv[i]);
}
int32_t err = send_req(fd, cmd);
if (err) {
goto L_DONE;
}
err = read_res(fd);
if (err) {
goto L_DONE;
}
L_DONE:
close(fd);
return 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
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
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
测试命令:
$ ./client get k
server says: [2]
$ ./client set k v
server says: [0]
$ ./client get k
server says: [0] v
$ ./client del k
server says: [0]
$ ./client get k
server says: [2]
$ ./client aaa bbb
server says: [1] Unknown cmd
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
07_client.cpp
07_server.cpp
上次更新: 2025/03/25, 00:48:42