MsQuic 开发入门教程

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/ 目录下的完整示例开始实践,逐步理解其异步事件驱动模型。

相关推荐
SoStraw11 天前
基于P2P开发一个聊天桌面软件
p2p·quic·文件共享·打洞·通信·文件传输·聊天软件
SoStraw12 天前
基于p2p通信开发一个聊天通信软件
p2p·加密·quic·打洞·穿透·传输·聊天
yueqc12 个月前
计算机网络(二):HTTPDNS、IPv6、QUIC
计算机网络·quic·ipv6·httpdns
七夜zippoe4 个月前
HTTP协议深度解析与实现:从请求响应到HTTP/3的完整指南
python·网络协议·http·quic·帧结构
蜂蜜黄油呀土豆4 个月前
深入了解计算机网络中的传输层:TCP 和 UDP
tcp/ip·计算机网络·quic·拥塞控制
haibindev4 个月前
【终极踩坑指南】Windows 10上MsQuic证书加载失败?坑不在证书,而在Schannel!
直播·http3·quic·流媒体
liulilittle8 个月前
HTTP/3.0:网络通信的技术革新与性能飞跃
网络·网络协议·http·https·quic·流媒体·通信
DogDaoDao9 个月前
深入解析quiche开源项目:从QUIC协议到云原生实践
音视频·实时音视频·tcp·quic·视频直播·流媒体协议·quiche
xzkyd outpaper10 个月前
QUIC协议如何在UDP基础上解决网络切换问题
网络·计算机网络·udp·quic