Jason Pan

进程间传递网络连接(文件描述符)

潘忠显 / 2022-05-29


本文介绍通过实验(代码仓库)模拟两个 Server 之间传输网络连接。做个实验的主要背景:

项目使用 Bazel 构建,依赖 libevent 事件引擎库,也用到 APUE 示例代码(以库的方式使用)。

0. 场景说明

启动两个 Server,分别监听 1001 和 1002 端口,用于建立 Client 来的连接。每个 Server 处理完一条消息便将 网络连接/文件描述符 传递给另外一个服务进程,继续后续处理:

pass_fd_between_processes_1

为了模拟实际中最常用的"带Buffer"的连接情况,也就是我们常说的 Stream RPC,这里还额外模拟一个客户端,将请求批量发送出去,然后服务只要能正确的切包,也能够支持每条消息交替由不同服务进程处理:

pass_fd_between_processes_2

观察连接以及日志,可以观察到连接被转移的过程。下图中 Server1 (pid:32396) 建立的 fd 被传送到了 Server2 (pid:32397)

1. Server

调用 sh start_server.sh 会构建 Server 程序,并拉起两个进程,分别监听 1000 和 1001 端口,同时会创建并监听对应端口的一个命名 UDS。两个 Server进程的逻辑是完全一样的,只是监听的端口不同:

额外解释一下上边流程:TCP listen 和 UDS listen 的结果都能得到一个新的连接,而之后的处理则是完全相同的,即收-收-发-转移fd

【接收协议类型】 为了能够恰好去切包。针对不能确切的切包的协议,后边会有讨论。

// 仅为协议描述
struct proto {
char len;
char data[len];
};

【返回内容】 为带端口的纯文本:

rsp from port: ${服务监听端口}

【日志文件】 每个server都会写对应端口的log文件,1000.log 和 1001.log 会接收到相同条数的内容。

【监听端口】 这里为了简便,所以让他们监听了不同的端口,其实 Linux 允许多个进程监听同一个端口——真是场景中才有意义。

2. 一元 RPC Client

调用 sh start_unary_client.sh 会构建 Unary RPC 请求的 Client,其基本逻辑:

【校验内容】 校验每个 Client 的每次获得的返回与前一次返回来自于不同的处理进程:

assert((port ^ client->last_server_port) == 1);
client->last_server_port = port;

【屏幕输出】 让一个 Client 打印每次处理服务的进程端口信息:

if (client->index == 0) {
  printf("port: %d, last_port: %d\n", port, client->last_server_port);
}

可以直观的看到每个消息处理完都有发生连接的传送:

client_print_server_port

3. 流 RPC Client

使用流 Client 测试是为了验证当操作系统已经接收到完整消息之后,即内容在内核缓存时,是否能够正常的进行连接传递。

调用 sh start_stream_client.sh 会构建 Stream RPC 请求的 Client。其处理逻辑是:

【校验内容】 校验总返回条数为 1000,且每次返回来源于两个不同的 Server 且严格交替:

while (pos != std::string::npos) {
  port = get_current_port(); // 省略处理过程
  assert((port ^ last_port) == 1);
  last_port = port;
}
assert(counter == kRepeateTime);

4. 其他思考