用C/C++构建自己的Redis——第一章、TCP Server和Client

用C/C++构建自己的Redis——第一章、TCP Server和Client



前言

数据结构,很多初学者对它的实际用处了解较少,最近看了一本书《Build Your Own Redis with C/C++》,它讲述了如何从0使用C/C++,运用基本的数据结构,构建属于自己的Redis。今天,我们一起学习TCP Server和Client这一部分。
原书链接:https://build-your-own.org/redis/


一、什么是Socket

Socket 是两个实体通过网络进行通信的通道。通过Socket,实现Client给Server发送请求,Server发送响应。

二、创建TCP Server

1.获取Socket Handle

int fd = scoket(AF_INET, SOCK_STREAM, 0);

其中AF_INET表示使用IPv4,SOCK_STREAM表示使用面向连接的TCP协议。

2.配置Socket

有许多选项可以改变Socket的行为。例如TCP生命周期等。
然而,socket()系统调用并不能传递这些选项,因此需要使用另一个系统调用setsockopt()对其进行配置。

int val = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));

SOL_SOCKET意思为正在使用的socket选项。SO_REUSEADDR表示是否允许重用地址。

3.绑定到地址

// bind, this is the syntax that deals with IPv4 addresses
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = ntohs(1234);
addr.sin_addr.s_addr = ntohl(0);    // wildcard address 0.0.0.0
int rv = bind(fd, (const sockaddr *)&addr, sizeof(addr));
if (rv) {
	die("bind()");
}

struct sockaddr_in用于保存一个IPv4地址和端口。对于IPv6,使用struct sockaddr_in6。ntohs() 和 ntohl() 函数会将数字转换为所需的 big endian 格式。

4.Listen

// listen
rv = listen(fd, SOMAXCONN);
if (rv) {
    die("listen()");
}

在执行listen()系统调用后,操作系统会自动处理TCP握手,并将已建立的连接放入队列。然后,应用程序就可以通过accept()提取连接。SOMAXCONN参数就是队列的大小。在Linux上,其被定义为128。

5.接受连接

Server进入循环,接受并处理每个client的连接。

while (true) {
    // accept
    struct sockaddr_in client_addr = {};
    socklen_t addrlen = sizeof(client_addr);
    int connfd = accept(fd, (struct sockaddr *)&client_addr, &addrlen);
    if (connfd < 0) {
        continue;   // error
    }

    do_something(connfd);
    close(connfd);
}

accept()系统调用会返回地址。addrlen既是输入大小,也是输出大小。

6.读和写

我们只处一个read()和write()

static void do_something(int connfd) {
    char rbuf[64] = {};
    ssize_t n = read(connfd, rbuf, sizeof(rbuf) - 1);
    if (n < 0) {
        msg("read() error");
        return;
    }
    printf("client says: %s\n", rbuf);

    char wbuf[] = "world";
    write(connfd, wbuf, strlen(wbuf));
}

read()和write()返回的都是字节数,这里我们只演示如何使用Socket的API,不进行细致编程。

三、创建TCP Client

Client要做的是:写东西,从Server读回,然后关闭连接

1.连接

这里没什么好说的,直接看代码吧。

int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
    die("socket()");
}

struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = ntohs(1234);
addr.sin_addr.s_addr = ntohl(INADDR_LOOPBACK);  // 127.0.0.1
int rv = connect(fd, (const struct sockaddr *)&addr, sizeof(addr));
if (rv) {
    die("connect");
}

char msg[] = "hello";
write(fd, msg, strlen(msg));

char rbuf[64] = {};
ssize_t n = read(fd, rbuf, sizeof(rbuf) - 1);
if (n < 0) {
    die("read");
}
printf("server says: %s\n", rbuf);
close(fd);

2.编译和运行

编译:

g++ -Wall -Wextra -O2 -g 03_server.cpp -o server
g++ -Wall -Wextra -O2 -g 03_client.cpp -o client

运行:

$ ./server
client says: hello
$ ./client
server says: world

四、其中用到的结构体定义

struct sockaddr {
    unsigned short   sa_family;     // AF_INET, AF_INET6
    char             sa_data[14];   // useless
};

struct sockaddr_in {
    short           sin_family;     // AF_INET
    unsigned short  sin_port;       // port number, big endian
    struct in_addr  sin_addr;       // IPv4 address
    char            sin_zero[8];    // useless
};

struct sockaddr_in6 {
    uint16_t        sin6_family;    // AF_INET6
    uint16_t        sin6_port;      // port number, big endian
    uint32_t        sin6_flowinfo;
    struct in6_addr sin6_addr;      // IPv6 address
    uint32_t        sin6_scope_id;
};

struct sockaddr_storage {
    sa_family_t     ss_family;      // AF_INET, AF_INET6
    // enough space for both IPv4 and IPv6
    char    __ss_pad1[_SS_PAD1SIZE];
    int64_t __ss_align;
    char    __ss_pad2[_SS_PAD2SIZE];
};

总结

这一章主要了解Client-Server的通信过程,代码总和如下:
client.cpp:https://build-your-own.org/redis/03/03_client.cpp.htm
server.cpp:https://build-your-own.org/redis/03/03_server.cpp.htm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值