571
社区成员




通过图片可以清楚地了解 HTTP 协议的发展和其中结构上的差异。
自从 2015 年 HTTP/2 标准发布后,大多数主流浏览器已经支持该标准。从图中可以看出,虽然 HTTP/2 对传输层协议做了极大的改动和重新设计,但底层仍是基于 TCP 协议,然而与之相比,UDP 协议在效率上存在天然优势。因此,Google 开发了基于 UDP 的名为 QUIC 的传输层协议,QUIC 全称 Quick UDP Internet Connections。后经提议,互联网工程任务组正式将基于 QUIC 协议的 HTTP (HTTP over QUIC)重命名为 HTTP/3。
QUIC 又称快速 UDP 网络连接,它基于 UDP 协议,是用来替代 HTTP/2 中 TCP 、TLS 的传输层协议,运行在 QUIC 协议上的 HTTP 协议即 HTTP/3。
QUIC 的优势主要体现在以下方面:
另外,QUIC将网络模型中控制传输行为的传输层,从内核态迁移到了用户态来实现。这将带来2个巨大的优势:
(1) 迭代优化效率大大提升。从服务端角度看,大型在线系统的内核升级成本非常高,因此通常的升级周期较长。从客户端角度看,设备操作系统版本升级同样由相应厂商控制,升级周期难以统一。调整为用户态实现后,不管是服务端还是用户端,都将加速迭代而不受系统软件的限制。
(2) 灵活适应不同业务场景的网络需求。随着直播、短视频等不同业务需求不断涌现,实现在内核态的TCP,不能采用同一套拥塞控制算法或参数适应各类业务场景。5G条件下这种缺陷变得更加显著。QUIC可将拥塞控制算法/参数调控到连接的粒度,甚至可针对同一个APP内的不同业务场景灵活适配。
XQUIC是IETF QUIC草案版本的一个C协议库实现,整体链路架构设计如图所示。XQUIC内部包含了QUIC-Transport(传输层)、QUIC-TLS(加密层)和HTTP/3.0(应用层)的实现。在外部依赖方面,TLS/1.3依赖了开源boringssl或openssl实现,除此之外无其他外部依赖。
XQUIC传输层内部流程设计如下图,XQUIC内部维护了每条连接的状态机、Stream状态机,在Stream级别实现可靠传输,并通过读事件通知的方式将数据投递给应用层。传输层Stream与应用层HTTP/3的Request Stream有一一映射关系,通过这样的方式解决HTTP/2 over TCP的头部阻塞问题。
由于IETF QUIC传输层的设计可以独立剥离,并作为TCP的替代方案对接其他应用层协议。XQUIC内部实现同样基于这样的分层方式,并对外提供两套原生接口:HTTP/3请求应答接口 和 传输层独立接口(类似TCP),使得例如RTMP、上传协议等可以较为轻松地接入。
OS: Ubuntu 20.04 focal(on the Windows Subsystem for Linux)
Kernel: x86_64 Linux 5.10.60.1-micros
CPU: AMD Ryzen 5 3550H with Radeon Vega Mobile Gfx @ 8x 2.096GHz
RAM: 8GB
# 获取 XQUIC 源码
git clone https://github.com/alibaba/xquic.git
cd xquic
# 编译 BabaSSL
git clone https://github.com/BabaSSL/BabaSSL.git ./third_party/babassl
cd ./third_party/babassl/
./config --prefix=/usr/local/babassl
make -j
SSL_TYPE_STR="babassl"
SSL_PATH_STR="${PWD}"
SSL_INC_PATH_STR="${PWD}/include"
SSL_LIB_PATH_STR="${PWD}/libssl.a;${PWD}/libcrypto.a"
cd -
# 使用 BabaSSL 编译 XQUIC
git submodule update --init --recursive
mkdir -p build; cd build
cmake -DGCOV=on -DCMAKE_BUILD_TYPE=Debug -DXQC_ENABLE_TESTING=1 -DXQC_SUPPORT_SENDMMSG_BUILD=1 -DXQC_ENABLE_EVENT_LOG=1 -DXQC_ENABLE_BBR2=1 -DXQC_DISABLE_RENO=0 -DSSL_TYPE=${SSL_TYPE_STR} -DSSL_PATH=${SSL_PATH_STR} -DSSL_INC_PATH=${SSL_INC_PATH_STR} -DSSL_LIB_PATH=${SSL_LIB_PATH_STR} ..
make -j4
注意要准备好libevent-dev包,以避免'<event2/event.h>' undefined
的错误。
Ubuntu下使用以下命令安装:
sudo apt install libevent-dev
分别在不同的终端窗口运行'build'文件夹下的test_server和test_client。
server :
denk@DESKTOP-B0LS0SD:~/xquic/build$ ./test_server
congestion control flags: 0
/home/denk/xquic/tests/test_server.c:906 (xqc_server_accept)
/home/denk/xquic/tests/test_server.c:369 (xqc_server_h3_conn_create_notify)
sending rate: 0.000 Kbps
recvfrom size:32256
timer wakeup now:1642594182366290
timer wakeup now:1642594182389419
/home/denk/xquic/tests/test_server.c:400 (xqc_server_h3_conn_handshake_finished)
0rtt_flag:2
recvfrom size:16761
/home/denk/xquic/tests/test_server.c:564 (xqc_server_request_create_notify)
:method = POST
:scheme = https
:path = /path/resource
host = test.xquic.com
content-type = text/plain
content-length = 1048576
xqc_h3_request_recv_body read:4096, offset:4096, fin:0
xqc_h3_request_recv_body read:4096, offset:8192, fin:0
xqc_h3_request_recv_body read:4096, offset:12288, fin:0
recvfrom size:21341
xqc_h3_request_recv_body read:4096, offset:16384, fin:0
xqc_h3_request_recv_body read:4096, offset:20480, fin:0
...
xqc_h3_request_recv_body read:4096, offset:1048576, fin:1
xqc_h3_request_send_headers success size=8
xqc_h3_request_send_body sent:1048576, offset=1048576
recvfrom size:34
recvfrom size:36
recvfrom size:37
sending rate: 34629.855 Kbps
recvfrom size:222
recvfrom size:37
recvfrom size:37
recvfrom size:37
recvfrom size:37
timer wakeup now:1642594183040102
/home/denk/xquic/tests/test_server.c:577 (xqc_server_request_close_notify)
sending rate: 1125.921 Kbps
recvfrom size:33
timer wakeup now:1642594184538073
/home/denk/xquic/tests/test_server.c:386 (xqc_server_h3_conn_close_notify)
send_count:1045, lost_count:0, tlp_count:4, recv_count:1090, srtt:17255 early_data_flag:2, conn_err:0, ack_info:#1078-1068#0-0#0-0#v0429
client :
denk@DESKTOP-B0LS0SD:~/xquic/build$ ./test_client
congestion control flags: 0
read token size 9
/home/denk/xquic/tests/test_client.c:685 (xqc_client_h3_conn_create_notify)
xqc_h3_conn_is_ready_to_send_early_data:1
xqc_h3_request_send_headers success size=39
xqc_h3_request_send_body sent:28672, offset=28672
save_tp_cb use server domain as the key. h3[1]
/home/denk/xquic/tests/test_client.c:736 (xqc_client_h3_conn_handshake_finished)
0rtt_flag:2
====>DCID:381014683905b7dd
====>SCID:0bcb285e3aa7fcac
xqc_h3_request_send_body sent:1019904, offset=1048576
save_session_cb use server domain as the key. h3[1]
save_session_cb use server domain as the key. h3[1]
/home/denk/xquic/tests/test_client.c:752 (xqc_client_h3_conn_ping_acked_notify)
====>ping_id:1
/home/denk/xquic/tests/test_client.c:752 (xqc_client_h3_conn_ping_acked_notify)
====>no ping_id
recvfrom size:3991
timer wakeup now:1642594182428293
...
:method = POST
:scheme = https
:path = /path/resource
:status = 200
host = test.xquic.com
content-type = text/plain
xqc_h3_request_recv_body read:4096, offset:4096, fin:0
xqc_h3_request_recv_body read:4096, offset:8192, fin:0
xqc_h3_request_recv_body read:4096, offset:12288, fin:0
...
>>>>>>>> request time cost:471444 us, speed:4448 K/s
>>>>>>>> send_body_size:1048576, recv_body_size:1048576
timer wakeup now:1642594182957815
/home/denk/xquic/tests/test_client.c:1474 (xqc_client_request_close_notify)
send_body_size:1048576, recv_body_size:1048576, send_header_size:103, recv_header_size:92, recv_fin:1, err:0
xqc_client_timeout_callback now 1642594183328903
xqc_client_timeout_callback now 1642594184330054
recving rate: 0.174 Kbps
recvfrom size:33
timer wakeup now:1642594184467099
/home/denk/xquic/tests/test_client.c:720 (xqc_client_h3_conn_close_notify)
conn errno:256
send_count:1068, lost_count:0, tlp_count:0, recv_count:1073, srtt:5478 early_data_flag:2, conn_err:0, ack_info:#1061-15#0-0#0-0#v0429
从外到内展示 xquic client 封装的功能。
const xqc_cid_t *
xqc_connect(xqc_engine_t *engine, const xqc_conn_settings_t *conn_settings,
const unsigned char *token, unsigned token_len, const char *server_host, int no_crypto_flag,
const xqc_conn_ssl_config_t *conn_ssl_config, const struct sockaddr *peer_addr,
socklen_t peer_addrlen, const char *alpn, void *user_data)
{
xqc_connection_t *conn;
/* 判断ALPN */
if (NULL == alpn || strlen(alpn) > XQC_MAX_ALPN_LEN) {
return NULL;
}
/* 创建连接(调用xqc_client_connect) */
conn = xqc_client_connect(engine, conn_settings, token, token_len, server_host, no_crypto_flag,
conn_ssl_config, alpn, peer_addr, peer_addrlen, user_data);
if (conn) {
return &conn->scid_set.user_scid;
}
return NULL;
}
xqc_connection_t *
xqc_client_connect(xqc_engine_t *engine, const xqc_conn_settings_t *conn_settings,
const unsigned char *token, unsigned token_len, const char *server_host, int no_crypto_flag,
const xqc_conn_ssl_config_t *conn_ssl_config, const char *alpn,
const struct sockaddr *peer_addr, socklen_t peer_addrlen, void *user_data)
{
xqc_cid_t dcid;
xqc_cid_t scid;
/* 判断ssl config是否配置 */
if (NULL == conn_ssl_config) {
xqc_log(engine->log, XQC_LOG_ERROR,
"|xqc_conn_ssl_config is NULL|");
return NULL;
}
/* 判断token长度是否越界 */
if (token_len > XQC_MAX_TOKEN_LEN) {
xqc_log(engine->log, XQC_LOG_ERROR,
"|%ud exceed XQC_MAX_TOKEN_LEN|", token_len);
return NULL;
}
/* 判断scid、dcid是否正确 */
if (xqc_generate_cid(engine, NULL, &scid, 0) != XQC_OK
|| xqc_generate_cid(engine, NULL, &dcid, 0) != XQC_OK)
{
xqc_log(engine->log, XQC_LOG_ERROR,
"|generate dcid or scid error|");
return NULL;
}
/* 创建连接(调用xqc_client_create_connection) */
xqc_connection_t *xc = xqc_client_create_connection(engine, dcid, scid, conn_settings,
server_host, no_crypto_flag,
conn_ssl_config, alpn, user_data);
if (xc == NULL) {
xqc_log(engine->log, XQC_LOG_ERROR,
"|create connection error|");
return NULL;
}
if (token && token_len > 0) {
xc->conn_token_len = token_len;
memcpy(xc->conn_token, token, token_len);
}
if (peer_addr && peer_addrlen > 0) {
xc->peer_addrlen = peer_addrlen;
memcpy(xc->peer_addr, peer_addr, peer_addrlen);
}
xqc_log(engine->log, XQC_LOG_DEBUG, "|xqc_connect|");
xqc_log_event(xc->log, CON_CONNECTION_STARTED, xc, XQC_LOG_REMOTE_EVENT);
/* conn_create 回调 */
if (xc->app_proto_cbs.conn_cbs.conn_create_notify) {
if (xc->app_proto_cbs.conn_cbs.conn_create_notify(xc, &xc->scid_set.user_scid, user_data)) {
xqc_conn_destroy(xc);
return NULL;
}
xc->conn_flag |= XQC_CONN_FLAG_UPPER_CONN_EXIST;
}
/* xqc_conn_destroy 必须在连接插入 conns_active_pq 后调用 */
if (!(xc->conn_flag & XQC_CONN_FLAG_TICKING)) {
if (xqc_conns_pq_push(engine->conns_active_pq, xc, 0)) {
return NULL;
}
xc->conn_flag |= XQC_CONN_FLAG_TICKING;
}
xqc_engine_main_logic_internal(engine, xc);
/* 当连接被主函数逻辑destroy后,应该把错误返回到上层 */
if (xqc_engine_conns_hash_find(engine, &scid, 's') == NULL) {
return NULL;
}
return xc;
}
xqc_connection_t *
xqc_client_create_connection(xqc_engine_t *engine, xqc_cid_t dcid, xqc_cid_t scid,
const xqc_conn_settings_t *settings, const char *server_host, int no_crypto_flag,
const xqc_conn_ssl_config_t *conn_ssl_config, const char *alpn, void *user_data)
{
xqc_int_t ret;
xqc_transport_params_t tp;
xqc_trans_settings_t *local_settings;
xqc_connection_t *xc = xqc_conn_create(engine, &dcid, &scid, settings, user_data,
XQC_CONN_TYPE_CLIENT);
if (xc == NULL) {
return NULL;
}
/* 保存初始dcid */
xqc_cid_copy(&(xc->original_dcid), &(xc->dcid_set.current_dcid));
/* 创建初始加密流,必须在tls之前创建,以存储ClientHello */
xc->crypto_stream[XQC_ENC_LEV_INIT] = xqc_create_crypto_stream(xc, XQC_ENC_LEV_INIT, user_data);
if (!xc->crypto_stream[XQC_ENC_LEV_INIT]) {
goto fail;
}
/* 设置无加密选项 */
local_settings = &xc->local_settings;
if (no_crypto_flag == 1) {
local_settings->no_crypto = XQC_TRUE; /* no_crypto 1 means do not crypto*/
} else {
local_settings->no_crypto = XQC_FALSE;
}
/* 创建并初始化tls,启动ClientHello */
if (xqc_client_create_tls(xc, conn_ssl_config, server_host, no_crypto_flag, alpn) != XQC_OK) {
goto fail;
}
/* 复原服务器传输的参数 */
if (conn_ssl_config->transport_parameter_data
&& conn_ssl_config->transport_parameter_data_len > 0)
{
xqc_memzero(&tp, sizeof(xqc_transport_params_t));
ret = xqc_read_transport_params(conn_ssl_config->transport_parameter_data,
conn_ssl_config->transport_parameter_data_len, &tp);
if (ret == XQC_OK) {
xqc_conn_set_early_remote_transport_params(xc, &tp);
}
}
if (xqc_conn_client_on_alpn(xc, alpn, strlen(alpn)) != XQC_OK) {
goto fail;
}
return xc;
/* 失败则删除连接 */
fail:
xqc_conn_destroy(xc);
return NULL;
}
xqc_int_t
xqc_client_create_tls(xqc_connection_t *conn, const xqc_conn_ssl_config_t *conn_ssl_config,
const char *hostname, int no_crypto_flag, const char *alpn)
{
xqc_int_t ret;
xqc_tls_config_t cfg = {0};
uint8_t tp_buf[XQC_MAX_TRANSPORT_PARAM_BUF_LEN] = {0};
uint8_t *session_ticket_buf;
uint8_t *alpn_buf;
size_t alpn_cap;
unsigned char *hostname_buf;
size_t host_cap;
/* 初始化 tls 配置 */
cfg.cert_verify_flag = conn_ssl_config->cert_verify_flag;
cfg.no_crypto_flag = no_crypto_flag;
/* 复制 session ticket */
cfg.session_ticket = xqc_malloc(conn_ssl_config->session_ticket_len + 1);
if (NULL == cfg.session_ticket) {
xqc_log(conn->log, XQC_LOG_ERROR, "|malloc for session ticket fail|");
ret = -XQC_EMALLOC;
goto end;
}
xqc_memcpy(cfg.session_ticket, conn_ssl_config->session_ticket_data,
conn_ssl_config->session_ticket_len);
cfg.session_ticket_len = conn_ssl_config->session_ticket_len;
/* 复制 alpn */
alpn_cap = strlen(alpn) + 1;
cfg.alpn = xqc_malloc(alpn_cap);
if (NULL == cfg.alpn) {
xqc_log(conn->log, XQC_LOG_ERROR, "|malloc for alpn fail|");
ret = -XQC_EMALLOC;
goto end;
}
strncpy(cfg.alpn, alpn, alpn_cap);
/* 复制主机名 hostname */
host_cap = strlen(hostname) + 1;
cfg.hostname = xqc_malloc(host_cap);
if (NULL == cfg.alpn) {
xqc_log(conn->log, XQC_LOG_ERROR, "|malloc for alpn fail|");
ret = -XQC_EMALLOC;
goto end;
}
strncpy(cfg.hostname, hostname, host_cap);
/* 编码本地传输参数,传递给 tls config */
cfg.trans_params = tp_buf;
ret = xqc_conn_encode_local_tp(conn, cfg.trans_params,
XQC_MAX_TRANSPORT_PARAM_BUF_LEN, &cfg.trans_params_len);
if (ret != XQC_OK) {
xqc_log(conn->log, XQC_LOG_ERROR, "|encode transport parameter error|ret:%d", ret);
goto end;
}
/* 创建 tls 实例 */
conn->tls = xqc_tls_create(conn->engine->tls_ctx, &cfg, conn->log, conn);
if (NULL == conn->tls) {
xqc_log(conn->log, XQC_LOG_ERROR, "|create tls instance error");
ret = -XQC_EMALLOC;
goto end;
}
/* 开始握手 */
ret = xqc_tls_init(conn->tls, conn->version, &conn->original_dcid);
if (ret != XQC_OK) {
xqc_log(conn->log, XQC_LOG_ERROR, "|init tls error");
goto end;
}
end:
if (cfg.session_ticket) {
xqc_free(cfg.session_ticket);
}
if (cfg.alpn) {
xqc_free(cfg.alpn);
}
if (cfg.hostname) {
xqc_free(cfg.hostname);
}
return ret;
}
QUIC 丢掉了 TCP、TLS 的包袱,基于 UDP 来实现,并根据以往的经验加以借鉴、改进,实现了一个安全高效可靠的 HTTP/3 通信协议。凭借着 0 RTT 建立连接、平滑的连接迁移、基本消除了队头阻塞、改进的拥塞控制和流量控制等优秀的特性,QUIC 在绝大多数场景下获得了比 HTTP/2 更好的效果。同时,XQUIC完成了用户态的传输层实现,对各类场景进行精细优化,而非单一针对弱网。
随着IETF工作组的不断努力和推进,QUIC标准化进程不断加快,QUIC协议未来可期。
https://github.com/alibaba/xquic
https://www.jianshu.com/p/d58da09e43f8
https://www.cnblogs.com/zhangxu1998cn/p/14342545.html
https://zhuanlan.zhihu.com/p/143464334
https://www.jianshu.com/p/dd9719c4c2c1
https://docs.google.com/document/d/1WJvyZflAO2pq77yOLbp9NsGjC1CHetAXV8I0fQe-B_U/edit
作者:NP 119