#连接 #连接管理器 #zeromq #入站连接 #应用 #协议

bin+lib condure

HTTP/WebSocket 连接管理器

15个稳定版本

1.10.0 2023年6月29日
1.9.2 2023年2月6日
1.9.1 2023年1月20日
1.8.0 2022年11月3日
1.0.1 2020年7月24日

#35 in WebSocket

Apache-2.0

1.5MB
35K SLoC

Condure

Condure 是一种管理网络连接的服务,以便从多个进程中控制连接。它可以管理入站连接和出站连接。应用程序通过 ZeroMQ 与 Condure 通信。

Condure 只能管理它所知道的协议的连接。目前这是 HTTP/1 和 WebSocket。请参阅 支持的协议

该项目受到 Mongrel2 的启发。

用例

  • 将连接所有权从一个进程传递到另一个进程。
  • 重新启动应用程序而不会断开其连接。
  • 在多个进程之间平衡连接所有权。

基本用法

启动服务器

$ condure --listen 8000 --zclient-stream ipc://client

连接一个处理程序,例如这个简单的 Python 程序

# this handler responds to every request with "hello world"

import os
import time
import tnetstring
import zmq

instance_id = 'basichandler.{}'.format(os.getpid()).encode()

ctx = zmq.Context()
in_sock = ctx.socket(zmq.PULL)
in_sock.connect('ipc://client-out')
out_sock = ctx.socket(zmq.PUB)
out_sock.connect('ipc://client-in')

# await subscription
time.sleep(0.01)

while True:
    m_raw = in_sock.recv()
    req = tnetstring.loads(m_raw[1:])
    print('IN {}'.format(req))

    resp = {}
    resp[b'from'] = instance_id
    resp[b'id'] = req[b'id']
    resp[b'code'] = 200
    resp[b'reason'] = b'OK'
    resp[b'headers'] = [[b'Content-Type', b'text/plain']]
    resp[b'body'] = b'hello world\n'

    print('OUT {}'.format(resp))
    out_sock.send(req[b'from'] + b' T' + tnetstring.dumps(resp))

客户端请求

$ curl -i https://127.0.0.1:8000
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 12

hello world

接收请求的进程不需要与响应的进程相同!例如,这里有一个将请求 ID 输出到 stdout 的程序

# this handler just outputs the request ID

import tnetstring
import zmq

ctx = zmq.Context()
sock = ctx.socket(zmq.PULL)
sock.connect('ipc://client-out')

while True:
    m = sock.recv_multipart()
    req = tnetstring.loads(m[0][1:])
    print('{} {}'.format(req[b'from'].decode(), req[b'id'].decode()))

当客户端发起请求时,我们可以看到请求 ID 信息

$ python examples/printreq.py
condure 0-0-0

在另一个 shell 中,我们可以使用像这样的程序进行响应

# this program sends a response to a certain request ID

import sys
import time
import tnetstring
import zmq

body = sys.argv[1]
addr = sys.argv[2].encode()
rid = sys.argv[3].encode()

ctx = zmq.Context()
sock = ctx.socket(zmq.PUB)
sock.connect('ipc://client-in')

# await subscription
time.sleep(0.01)

resp = {}
resp[b'from'] = b'sendresp'
resp[b'id'] = rid
resp[b'code'] = 200
resp[b'reason'] = b'OK'
resp[b'headers'] = [[b'Content-Type', b'text/plain']]
resp[b'body'] = '{}\n'.format(body).encode()

m = [addr + b' T' + tnetstring.dumps(resp)]

sock.send_multipart(m)

例如

$ python examples/sendresp.py "responding from another process" condure 0-0-0

客户端看到

$ curl -i https://127.0.0.1:8000
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 32

responding from another process

为了便于测试,可以将程序连接起来

$ python -u examples/printreq.py | xargs -n 2 python examples/sendresp.py "responding from another process"

挂起和恢复连接

在将连接控制权从一个进程传递到另一个进程时,首先需要挂起连接。这通过发送 handoff-start 消息并等待 handoff-proceed 消息来完成。到那时,可以将连接信息提供给另一个进程,并通过发送任何消息(例如 keep-alive)来恢复连接。请参阅 ZHTTP 规范

REQ 模式

除了使用 PUSH/ROUTER/SUB 套接字的流模式之外,还有一个可用的 "REQ" 模式,它使用 DEALER 套接字。要启用它,将 req 设置为监听端口的模式。这种模式对于使用 ZeroMQ 实现简单的请求/响应服务器很有用。

支持的协议

Condure 支持 HTTP/1 和 WebSocket。

Condure在第七层管理连接,并且只支持它所了解的协议。这是为了简化其使用。处理任意协议需要应用程序构建能够在TCP流中任意字节位置暂停/恢复会话的协议栈,这使得Condure的使用变得不可行。相反,Condure是协议感知的,并为应用程序提供解析后的帧,这样应用程序只需要支持在帧边界处暂停/恢复会话。

性能

Condure是为高性能而构建的。它使用了多种优化技术,包括最小堆分配、环形缓冲区、向量I/O、分层时间轮和快速数据结构(例如slabs)。在一个实例上,仅使用2个工作者(总共4个线程)就测试了超过1M个并发连接。请参阅https://blog.fanout.io/2020/08/11/rewriting-pushpins-connection-manager-in-rust/

与Mongrel2的比较

  • Condure支持作为服务器和客户端。
  • Condure支持多核。
  • Condure支持在不需要多个进程的情况下监听多个端口。
  • Condure不支持多个路由,也不打算作为共享服务器。每个希望将连接保持在单独进程中的应用程序都应该启动自己的Condure实例。
  • Condure没有配置文件。配置使用命令行参数提供。
  • Condure使用基于ZeroMQ的协议ZHTTP,比Mongrel2的协议更容易使用且更可靠。

未来计划

  • HTTP/2
  • HTTP/3

依赖项

~8–18MB
~279K SLoC