MsQuic 是微软开源的 IETF QUIC 协议实现,采用 C 语言编写,跨平台支持 Windows、Linux、macOS、iOS、Android 和 Xbox 。本文将从环境搭建、编译安装到编写第一个 QUIC 程序,带你快速入门。
一、MsQuic 简介与特性
MsQuic 是一个通用的 QUIC 库,具有以下核心特性:
QUIC 协议特性 :
- 所有数据包加密,使用 TLS 1.3 进行握手认证
- 支持可靠和不可靠的并行数据流
- 首次往返即可交换应用数据(0-RTT)
- 改进的拥塞控制和丢包恢复
- 客户端 IP 或端口变化时连接保持
MsQuic 实现特性 :
- 针对客户端和服务器双向优化
- 针对最大吞吐量和最小延迟优化
- 异步 I/O 模型
- 支持接收方缩放和 UDP 发送/接收合并
- 提供 C、C++、Rust、C# 多语言绑定
二、环境准备与编译安装
2.1 系统要求
MsQuic 支持以下平台 :
- Windows:Windows 10/11、Windows Server、Xbox
- Linux:主流发行版(Ubuntu、Debian、CentOS 等)
- 其他:macOS、iOS、Android
2.2 获取源码
bash
git clone https://github.com/microsoft/msquic.git
cd msquic
2.3 Linux 平台编译
安装依赖:
bash
# Ubuntu/Debian
sudo apt install -y cmake ninja-build gcc g++ libssl-dev
# CentOS/RHEL
sudo yum install -y cmake ninja-build gcc-c++ openssl-devel
编译与安装 :
bash
mkdir build && cd build
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release
ninja
sudo ninja install
也可以使用GCC+Make编译
bash
# 克隆仓库,--recursive 参数必不可少,用于下载子模块
git clone --recursive https://github.com/microsoft/msquic.git
cd msquic
# 创建一个单独的构建目录,这有助于保持源码目录的整洁
mkdir build && cd build
# 生成 Makefile
# 指定 CMAKE_BUILD_TYPE 为 Release,生成优化后的高性能二进制文件
cmake .. -DCMAKE_BUILD_TYPE=Release
# 开始编译,这里使用 `-j` 参数可以开启多核编译,例如 `-j 4` 表示使用4个核心
make -j $(nproc)
需要安装前置依赖库
bash
# 安装开发工具组,其中包含 GCC 和 Make
sudo yum groupinstall -y "Development Tools"
# 安装 CMake 和 openssl 开发库
sudo yum install -y cmake openssl-devel
# 注意:CentOS 7 自带的 CMake 版本可能过低,
# 如果编译时提示版本过低(需要 3.16 以上),请参考后面的 Q&A 部分进行升级。
常用 CMake 选项 :
| 选项 | 说明 | 默认值 |
|---|---|---|
-DMSQUIC_BUILD_SAMPLES=ON |
构建示例程序 | ON |
-DMSQUIC_USE_SYSTEM_TLS=ON |
使用系统 TLS 库 | OFF |
-DCMAKE_BUILD_TYPE=Release |
发布版本 | Debug |
编译完成后,你可以选择将库安装到系统中,或者直接在构建目录下使用。
bash
# 安装到系统目录 (默认为 /usr/local)
sudo make install
# 验证动态库是否生成成功
ls ./bin/libmsquic.so
CMake 版本过低, 这是 CentOS 7 最容易遇到的问题。官方要求 CMake 版本 3.16 或更高。
可以通过 Kitware 的官方仓库来安装最新版:
bash
# 添加 Kitware 的 yum 源
sudo yum install -y epel-release
sudo yum install -y wget
wget https://apt.kitware.com/kitware-common.sh
sudo bash kitware-common.sh
# 安装新版 CMake
sudo yum install -y cmake
2.4 Windows 平台编译
使用 Visual Studio :
bash
mkdir build && cd build
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release
ninja
sudo ninja install
使用 NuGet 包(推荐用于 .NET 项目):
bash
dotnet add package Microsoft.Native.Quic.MsQuic.Schannel
三、API 核心概念
MsQuic 采用异步事件驱动模型,包含以下核心对象 :
| 对象 | 说明 | 对应 TCP |
|---|---|---|
| Registration | MsQuic 库的初始化,全局单例 | - |
| Configuration | TLS 证书、设置等配置信息 | - |
| Listener | 服务端监听器,接受新连接 | listen() + accept() |
| Connection | QUIC 连接,承载多个流 | socket + connect() |
| Stream | 数据流,实际传输数据的通道 | read()/write() |
架构关系:
bash
Registration (1) ──┬── Configuration (N)
└── Listener (N) ──┬── Connection (N) ──┬── Stream (N)
└── Connection ──────┴── Stream
3.1 核心对象架构

3.2 对象生命周期时序图

3.3 连接与流

3.4 回调事件驱动模型

4.5 配置与证书体系

4.6 完整类图

4.7 与 TCP Socket 编程的对应关系
| TCP 操作 | MsQuic 对应 |
|---|---|
socket(), bind() |
Configuration + Registration |
listen(), accept() |
Listener + 连接回调 |
connect() |
ConnectionStart() |
read()/write() |
Stream 读写回调 |
四、编写第一个程序:服务端
以下示例实现一个简单的 echo 服务器。
4.1 完整代码(server.c)
bash
#include <msquic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 注册 QUIC API 函数表
const QUIC_API_TABLE* MsQuic;
// 连接回调函数
QUIC_STATUS ConnectionCallback(
_In_ HQUIC Connection,
_In_opt_ void* Context,
_Inout_ QUIC_CONNECTION_EVENT* Event
) {
switch (Event->Type) {
case QUIC_CONNECTION_EVENT_CONNECTED:
printf("[server] Client connected\n");
break;
case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT:
case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER:
case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE:
printf("[server] Connection shutdown\n");
MsQuic->ConnectionClose(Connection);
break;
case QUIC_CONNECTION_EVENT_STREAM_STARTED:
// 接受新流,设置流回调
MsQuic->SetCallbackHandler(Event->STREAM_STARTED.Stream,
(void*)StreamCallback, NULL);
MsQuic->StreamStart(Event->STREAM_STARTED.Stream,
QUIC_STREAM_START_FLAG_NONE);
break;
}
return QUIC_STATUS_SUCCESS;
}
// 流回调函数
QUIC_STATUS StreamCallback(
_In_ HQUIC Stream,
_In_opt_ void* Context,
_Inout_ QUIC_STREAM_EVENT* Event
) {
switch (Event->Type) {
case QUIC_STREAM_EVENT_RECEIVE:
// 接收数据并回显
printf("[server] Received %llu bytes\n", Event->RECEIVE.TotalBufferLength);
MsQuic->StreamSend(Stream, Event->RECEIVE.Buffers,
Event->RECEIVE.BufferCount,
QUIC_SEND_FLAG_ALLOW_0_RTT, NULL);
break;
case QUIC_STREAM_EVENT_SEND_COMPLETE:
// 发送完成
break;
case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN:
case QUIC_STREAM_EVENT_PEER_RECEIVE_ABORTED:
case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE:
MsQuic->StreamClose(Stream);
break;
}
return QUIC_STATUS_SUCCESS;
}
int main() {
HQUIC Registration = NULL;
HQUIC Configuration = NULL;
HQUIC Listener = NULL;
QUIC_STATUS status;
// 1. 打开 MsQuic
if (QUIC_FAILED(MsQuicOpen2(&MsQuic))) {
printf("Failed to open MsQuic\n");
return -1;
}
// 2. 创建注册对象
status = MsQuic->RegistrationOpen(NULL, &Registration);
if (QUIC_FAILED(status)) goto Error;
// 3. 创建配置(自签名证书,生产环境需要正确配置)
QUIC_CREDENTIAL_CONFIG CredConfig;
memset(&CredConfig, 0, sizeof(CredConfig));
CredConfig.Type = QUIC_CREDENTIAL_TYPE_NONE;
CredConfig.Flags = QUIC_CREDENTIAL_FLAG_NONE;
status = MsQuic->ConfigurationOpen(Registration, &CredConfig, 0, NULL, &Configuration);
if (QUIC_FAILED(status)) goto Error;
// 4. 配置监听地址
QUIC_ADDR Address = {0};
QuicAddrSetFamily(&Address, QUIC_ADDRESS_FAMILY_INET);
QuicAddrSetPort(&Address, 4433);
// 5. 创建监听器
status = MsQuic->ListenerOpen(Registration, ConnectionCallback, NULL, &Listener);
if (QUIC_FAILED(status)) goto Error;
// 6. 绑定地址并启动监听
status = MsQuic->ListenerStart(Listener, &Address, QUIC_LISTENER_FLAG_NONE, Configuration);
if (QUIC_FAILED(status)) goto Error;
printf("Echo server listening on port 4433\n");
// 7. 事件循环(简单示例中使用 sleep)
while (1) {
MsQuic->RegistrationWait(Registration, NULL, INFINITE);
}
Error:
if (Listener) MsQuic->ListenerClose(Listener);
if (Configuration) MsQuic->ConfigurationClose(Configuration);
if (Registration) MsQuic->RegistrationClose(Registration);
MsQuicClose(MsQuic);
return -1;
}
五、编写第一个程序:客户端
5.1 完整代码(client.c)
cpp
#include <msquic.h>
#include <stdio.h>
#include <string.h>
const QUIC_API_TABLE* MsQuic;
HQUIC ClientStream = NULL;
int Completed = 0;
// 连接回调
QUIC_STATUS ConnectionCallback(
_In_ HQUIC Connection,
_In_opt_ void* Context,
_Inout_ QUIC_CONNECTION_EVENT* Event
) {
switch (Event->Type) {
case QUIC_CONNECTION_EVENT_CONNECTED:
printf("[client] Connected to server\n");
// 连接成功后创建流
MsQuic->StreamOpen(Connection, QUIC_STREAM_OPEN_FLAG_NONE,
StreamCallback, NULL, &ClientStream);
MsQuic->StreamStart(ClientStream, QUIC_STREAM_START_FLAG_NONE);
break;
case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE:
printf("[client] Connection shutdown\n");
MsQuic->ConnectionClose(Connection);
Completed = 1;
break;
}
return QUIC_STATUS_SUCCESS;
}
// 流回调
QUIC_STATUS StreamCallback(
_In_ HQUIC Stream,
_In_opt_ void* Context,
_Inout_ QUIC_STREAM_EVENT* Event
) {
switch (Event->Type) {
case QUIC_STREAM_EVENT_START_COMPLETE:
{
// 流启动成功,发送数据
const char* message = "Hello, QUIC!";
QUIC_BUFFER Buffer = { strlen(message), (uint8_t*)message };
printf("[client] Sending: %s\n", message);
MsQuic->StreamSend(Stream, &Buffer, 1,
QUIC_SEND_FLAG_ALLOW_0_RTT | QUIC_SEND_FLAG_FIN,
NULL);
}
break;
case QUIC_STREAM_EVENT_RECEIVE:
{
// 接收服务器回显
char buffer[1024];
memcpy(buffer, Event->RECEIVE.Buffers[0].Buffer,
Event->RECEIVE.Buffers[0].Length);
buffer[Event->RECEIVE.Buffers[0].Length] = '\0';
printf("[client] Received echo: %s\n", buffer);
}
break;
case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE:
MsQuic->StreamClose(Stream);
Completed = 1;
break;
}
return QUIC_STATUS_SUCCESS;
}
int main(int argc, char** argv) {
const char* server_ip = "127.0.0.1";
uint16_t server_port = 4433;
if (argc > 1) server_ip = argv[1];
if (argc > 2) server_port = atoi(argv[2]);
HQUIC Registration = NULL;
HQUIC Configuration = NULL;
HQUIC Connection = NULL;
QUIC_STATUS status;
// 1. 打开 MsQuic
if (QUIC_FAILED(MsQuicOpen2(&MsQuic))) {
printf("Failed to open MsQuic\n");
return -1;
}
// 2. 创建注册对象
status = MsQuic->RegistrationOpen(NULL, &Registration);
if (QUIC_FAILED(status)) goto Error;
// 3. 创建配置
QUIC_CREDENTIAL_CONFIG CredConfig;
memset(&CredConfig, 0, sizeof(CredConfig));
CredConfig.Type = QUIC_CREDENTIAL_TYPE_NONE;
CredConfig.Flags = QUIC_CREDENTIAL_FLAG_CLIENT;
status = MsQuic->ConfigurationOpen(Registration, &CredConfig, 0, NULL, &Configuration);
if (QUIC_FAILED(status)) goto Error;
// 4. 解析服务器地址
QUIC_ADDR Address = {0};
QuicAddrSetFamily(&Address, QUIC_ADDRESS_FAMILY_INET);
QuicAddrSetPort(&Address, server_port);
QuicAddrSetIp(&Address, server_ip);
// 5. 创建连接
status = MsQuic->ConnectionOpen(Registration, ConnectionCallback, NULL, &Connection);
if (QUIC_FAILED(status)) goto Error;
// 6. 启动连接
status = MsQuic->ConnectionStart(Connection, Configuration, QUIC_ADDRESS_FAMILY_INET,
server_ip, server_port);
if (QUIC_FAILED(status)) goto Error;
printf("Client connecting to %s:%d\n", server_ip, server_port);
// 7. 等待完成
while (!Completed) {
MsQuic->RegistrationWait(Registration, NULL, INFINITE);
}
Error:
if (Connection) MsQuic->ConnectionClose(Connection);
if (Configuration) MsQuic->ConfigurationClose(Configuration);
if (Registration) MsQuic->RegistrationClose(Registration);
MsQuicClose(MsQuic);
return status;
}
5.2 编译与运行
编译:
cpp
# Linux
gcc -o server server.c -lmsquic -lssl -lcrypto
gcc -o client client.c -lmsquic -lssl -lcrypto
# 或者使用 samples 目录下的示例
cd build/bin
./msquic_basic_server
./msquic_basic_client
运行测试:
cpp
# 终端1 - 启动服务端
./server
# 终端2 - 启动客户端
./client 127.0.0.1 4433
预期输出:
cpp
Client connecting to 127.0.0.1:4433
[server] Client connected
[client] Connected to server
[client] Sending: Hello, QUIC!
[server] Received 12 bytes
[client] Received echo: Hello, QUIC!
六、常见问题与最佳实践
6.1 编译常见问题
| 问题 | 解决方案 |
|---|---|
msquic.h: No such file |
确认已执行 ninja install,检查 /usr/local/include |
undefined reference to MsQuicOpen2 |
链接时添加 -lmsquic |
| OpenSSL 版本不兼容 | Ubuntu 22.04 使用 libssl-dev 3.0;CentOS 7 需升级 OpenSSL |
6.2 开发最佳实践
证书配置:
cpp
QUIC_CREDENTIAL_CONFIG CredConfig;
CredConfig.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE;
CredConfig.CertificateFile = "server.crt";
CredConfig.PrivateKeyFile = "server.key";
CredConfig.Flags = QUIC_CREDENTIAL_FLAG_NONE;
回调中避免耗时操作:
cpp
// 错误:回调中执行耗时操作
QUIC_STATUS StreamCallback(...) {
ProcessLargeData(); // 会阻塞协议处理
return QUIC_STATUS_SUCCESS;
}
// 正确:将耗时任务派发到工作线程
QUIC_STATUS StreamCallback(...) {
SubmitToThreadPool(Event->RECEIVE.Buffers);
return QUIC_STATUS_PENDING; // 表示异步处理
}
连接迁移 (移动网络场景特有):
MsQuic 支持网络切换时连接保持。当客户端从 WiFi 切换到 5G 时,只需客户端调用:
cpp
// 客户端使用新 IP 更新连接地址
MsQuic->ConnectionSetParam(Connection,
QUIC_PARAM_CONN_REMOTE_ADDRESS, ...);
服务端会自动处理无需额外代码 。
七、参考资源
| 资源 | 地址 |
|---|---|
| GitHub 仓库 | https://github.com/microsoft/msquic |
| 官方文档 | https://github.com/microsoft/msquic/wiki |
| 示例代码 | samples/ 目录下 |
| NuGet 包 | Microsoft.Native.Quic.MsQuic.Schannel |
| 性能看板 | https://github.com/microsoft/msquic/wiki/Performance-Dashboard |
MsQuic 是一个功能完备的 QUIC 库,掌握了以上基础后,你可以进一步学习流控、0-RTT、多路径传输等高级特性。建议从 samples/ 目录下的完整示例开始实践,逐步理解其异步事件驱动模型。