Msquic用起来还是很方便很直观的
因为微软喜欢玩句柄 所以很多对象都由如下形式提供
Tips:关于微软为啥喜欢句柄请自行百度
HQUIC Registration{};
我们来看看github官网 微软给出的对象有哪些 下图来自Msquic github
下面这段解释来自微软github msquic/API.md at main · microsoft/msquic · GitHub 这里一起贴出
The API supports both server and client applications. All functionality is exposed primarily via a set of different objects:
Api - The top level handle and function table for all other API calls.
Registration – Manages the execution context for all child objects. An app may open multiple registrations but ideally should only open one.
Configuration – Abstracts the configuration for a connection. This generally consists both of security related and common QUIC settings.
Listener – Server side only, this object provides the interface for an app to accept incoming connections from clients. Once the connection has been accepted, it is independent of the listener. The app may create as many of these as necessary.
Connection – Represents the actual QUIC connection state between the client and server. The app may create (and/or accept) as many of these as necessary.
Stream – The layer at which application data is exchanged. Streams may be opened by either peer of a connection and may be unidirectional or bidirectional. For a single connection, as many streams as necessary may be created.
Tips:App不是微软的对象哦 他指的是你自己的应用程序
那么如上图所示 根据常识 咋们的客户端代码肯定是不需要Listener的 那么在代码中需要用到的HQUIC对象就有
//顶层句柄对象
HQUIC Registration{};
//Configuration句柄对象
HQUIC Configuration{};
//Connection句柄对象
HQUIC Connection{};
//stream句柄对象
HQUIC Stream[MaxStreamSize]{};
整个Msquic的流程图如下
可以看到啊 非常的清晰 根据流程图 下面是代码 注释同样也是写的非常详细了 如果这都看不懂可以给我留言 我手把手教你
Tips:
CreatNewStream并不是Msquic的函数 而是我自己封装的文章来源:https://www.toymoban.com/news/detail-461967.html
主要操作包括 StreamOpen StreamStart 详情看代码吧文章来源地址https://www.toymoban.com/news/detail-461967.html
#include <iostream>
#include <fstream>
extern "C"
{
#include <msquic.h>
}
//验证安全证书?
#define DontValidate 0
//拥有服务器的Ticket?
#define HasResumptionTicket 0
//流的最大数量
#define MaxStreamSize 5
//函数表
const QUIC_API_TABLE* MsQuic{};
//注册配置 总体配置
const QUIC_REGISTRATION_CONFIG RegConfig = { "MsquicClient",QUIC_EXECUTION_PROFILE_LOW_LATENCY };
//顶层句柄对象
HQUIC Registration{};
//Configuration句柄对象
HQUIC Configuration{};
//Connection句柄对象
HQUIC Connection{};
//stream句柄对象
HQUIC Stream[MaxStreamSize]{};
//流的索引
uint32_t Stream_index{ 0 };
//应用层协议选择 客户端服务端两边匹配即可
const QUIC_BUFFER Alpn = { sizeof("test") - 1,(uint8_t*)"test" };
//服务器IP和端口
const char* ServerIp = "127.0.0.1";
uint16_t ServerPort = 8808;
//这些参数不再做解释 在ConnectionCallBack中已经有解释
_IRQL_requires_max_(DISPATCH_LEVEL)
_Function_class_(QUIC_STREAM_CALLBACK)
QUIC_STATUS
QUIC_API
ClientStreamCallback(
_In_ HQUIC Stream,
_In_opt_ void* Context,
_Inout_ QUIC_STREAM_EVENT* Event
)
{
UNREFERENCED_PARAMETER(Context);
switch (Event->Type) {
case QUIC_STREAM_EVENT_SEND_COMPLETE:
//调用streamSend完成后触发
free(Event->SEND_COMPLETE.ClientContext);
std::cout << "Data Send Complete" << std::endl;
break;
case QUIC_STREAM_EVENT_RECEIVE:
std::cout << "Data Receive from server" << std::endl;
//从对端收到数据 数据保存在联合体中 打印数据
for (uint32_t i = 0; i < Event->RECEIVE.BufferCount; i++)
{
std::cout << "Buffer" << i << "=" << std::endl;
for (uint32_t j = 0; j < Event->RECEIVE.Buffers->Length; j++)
std::cout << *((uint8_t*)(Event->RECEIVE.Buffers->Buffer + j)) << " ";
std::cout << std::endl;
}
break;
case QUIC_STREAM_EVENT_PEER_SEND_ABORTED:
//对端终止
std::cout << "Server Aborted" << std::endl;
break;
case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN:
// 对端终止
std::cout << "Server Aborted" << std::endl;
break;
case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE:
//客户端服务端均中止
std::cout << "Both ShutDown" << std::endl;
if (!Event->SHUTDOWN_COMPLETE.AppCloseInProgress) {
MsQuic->StreamClose(Stream);
}
break;
default:
break;
}
return QUIC_STATUS_SUCCESS;
}
QUIC_STATUS CreateNewStream(HQUIC Connection)
{
QUIC_STATUS ret = QUIC_STATUS_SUCCESS;
//Open与Start之后都没发送任何数据 这里只是简单的分配内存注册对象 因为是0RTT建连 所以要发送数据的时候才真正的开始发送数据 该函数与Connection同理 也是注册回调
ret = MsQuic->StreamOpen(Connection, QUIC_STREAM_OPEN_FLAG_NONE, ClientStreamCallback, NULL, &Stream[Stream_index]);
if (QUIC_FAILED(ret))
{
std::cout << "StreamOpen failed" << std::endl;
return ret;
}
//这一步也不会发送任何数据 可以设置在这一步通知对端 也可以不设置 默认不通知 这里会分配流的标识符 后续通过StreamSend来发送数据
ret = MsQuic->StreamStart(Stream[Stream_index], QUIC_STREAM_START_FLAG_NONE);
if (QUIC_FAILED(ret))
{
std::cout << "StreamStart failed" << std::endl;
return ret;
}
//接下来发送数据
QUIC_BUFFER SendBuffer{};
SendBuffer.Length = 6;
SendBuffer.Buffer = (uint8_t*)malloc(6);
memcpy(SendBuffer.Buffer, "Hello", 6);
if (HasResumptionTicket)
{
ret = MsQuic->StreamSend(Stream[Stream_index], &SendBuffer, 1, QUIC_SEND_FLAG_ALLOW_0_RTT, &SendBuffer);
if (QUIC_FAILED(ret))
{
std::cout << "StreamSend failed" << std::endl;
return ret;
}
}
else
{
ret = MsQuic->StreamSend(Stream[Stream_index], &SendBuffer, 1, QUIC_SEND_FLAG_NONE, &SendBuffer);
if (QUIC_FAILED(ret))
{
std::cout << "StreamSend failed" << std::endl;
return ret;
}
}
free(SendBuffer.Buffer);
Stream_index++;
return ret;
}
_IRQL_requires_max_(DISPATCH_LEVEL)//微软独有的玩意不用管 指定中断级别 只有中断级别比这个高的才有资格中断他 不然一直占着CPU执行
_Function_class_(QUIC_CONNECTION_CALLBACK)//解释
QUIC_STATUS//返回值
QUIC_API//函数调用约定
ClientConnectionCallback(
_In_ HQUIC Connection,//微软传给你的Connetion对象
_In_opt_ void* Context,//自己传的透传指针
_Inout_ QUIC_CONNECTION_EVENT* Event//微软传给你的事件对象
)
{
QUIC_STATUS ret = QUIC_STATUS_SUCCESS;
//(微软推荐的做法)根据事件的类型写回调
//其实随便写啥都行
switch (Event->Type)
{
case QUIC_CONNECTION_EVENT_CONNECTED:
{
std::cout << "Good!Connection complete" << std::endl;
//连接成功后就可以创建stream流对象了
ret = CreateNewStream(Connection);
if (QUIC_FAILED(ret))
{
std::cout << "CreateNewStream failed" << std::endl;
return ret;
}
break;
}
case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT:
{
//连接即将被关闭 有很多可能原因 需要自行判断
if (Event->SHUTDOWN_INITIATED_BY_TRANSPORT.Status == QUIC_STATUS_CONNECTION_IDLE)
{
//超时关闭
std::cout << "TimeOut! The Connection will close immediately" << std::endl;
}
else
{
std::cout << "err! check the error code the code is" << Event->SHUTDOWN_INITIATED_BY_TRANSPORT.Status << std::endl;
}
break;
}
case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER:
{
//服务端关闭连接
std::cout << "the server close the connection" << std::endl;
break;
}
case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE:
{
//连接关闭完成
std::cout << "the connection has been closed" << std::endl;
break;
}
case QUIC_CONNECTION_EVENT_RESUMPTION_TICKET_RECEIVED:
{
//收到重连的ticket 用来进行0RTT建连
//下次再进行建立连接的时候可以将这个ticket作为参数传入
//然后就可以不进行任何握手进行通信
std::cout << "Resumption ticket received" << std::endl;
std::fstream tmp("ResumptionTicket.txt", std::ios::trunc | std::ios::binary | std::ios::out);
for (uint32_t i = 0; i < Event->RESUMPTION_TICKET_RECEIVED.ResumptionTicketLength; i++) {
printf("%.2X", (uint8_t)Event->RESUMPTION_TICKET_RECEIVED.ResumptionTicket[i]);
tmp << (uint8_t)Event->RESUMPTION_TICKET_RECEIVED.ResumptionTicket[i];
}
std::cout << std::endl;
tmp.close();
break;
}
default:
break;
}
return QUIC_STATUS_SUCCESS;
}
void Clientmain()
{
QUIC_STATUS ret = QUIC_STATUS_SUCCESS;
//打开库和拿到函数表(拿到函数地址)
ret = MsQuicOpen2(&MsQuic);
if (QUIC_FAILED(ret))
{
std::cout << "open Msquic API table failed" << std::endl;
return;
}
//创建打开顶层registration对象(所有操作必须先打开这个才能继续) 别问 问就是微软要求的
ret = MsQuic->RegistrationOpen(&RegConfig, &Registration);
if (QUIC_FAILED(ret))//这个QUIC_FAILED其实就是判断是否小于0 微软官方例子这么写 我也沿用了
{
std::cout << "RegistrationOpen failed" << std::endl;
return;
}
//设置QUIC各种参数 如0RTT建连等等
QUIC_SETTINGS Settings{};
//设置超时时间 单位ms 其他设置请参考成员变量
Settings.IdleTimeoutMs = 1000;
//设置好了之后要开启该设置
Settings.IsSet.IdleTimeoutMs = TRUE;
//配置验证安全证书等选项 客户端不需要有证书 除非服务器要求 故设置为无
QUIC_CREDENTIAL_CONFIG CredConfig{};
CredConfig.Type = QUIC_CREDENTIAL_TYPE_NONE;
CredConfig.Flags = QUIC_CREDENTIAL_FLAG_CLIENT;
//当你不想验证服务器的安全证书时
if (DontValidate)
{
CredConfig.Flags |= QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION;
}
//[Alpn]是你要使用的应用层协议(客户端和服务器必须相同 但是注意只是声明 但实际上就算你填入了http msquic也不会帮你实现这个应用层协议 只要两边匹配实际上你随便填一个莫名其妙的协议也行)
//打开Configuration句柄对象
ret = MsQuic->ConfigurationOpen(Registration, &Alpn, 1, &Settings, sizeof(Settings), NULL, &Configuration);
if (QUIC_FAILED(ret))
{
std::cout << "ConfigurationOpen failed" << std::endl;
return;
}
//打开后还要单独把我们刚刚设置好的安全证书选项填入(除非服务器特殊要求 不然quic使用的tls1.3版本是不需要客户端拥有安全证书的)
ret = MsQuic->ConfigurationLoadCredential(Configuration, &CredConfig);
if (QUIC_FAILED(ret))
{
std::cout << "ConfigurationLoadCredential failed" << std::endl;
return;
}
//Registraion和configuration都打开了 剩下就可以直接connect后然后保存stream对象了 注意Msquic要求你注册回调 回调我写在上面的 然后他内部开线程去调用 内部工作线程的数量在
//绝大多数情况下保持默认即可
//第三个参数透传指针
ret = MsQuic->ConnectionOpen(Registration, ClientConnectionCallback, NULL, &Connection);
if (QUIC_FAILED(ret))
{
std::cout << "ConfigurationLoadCredential failed" << std::endl;
return;
}
//如果已经建立过连接并且拥有了服务器的ticket 此处可以将ticket作为参数传入 然后可以进行0RTT建连 非常快速
//详情看调研测试报告
if (HasResumptionTicket)
{
uint8_t ResumptionTicket[1024]{};//这里填入你之前收到的
uint16_t RealLength{};//实际的Ticket长度
ret = MsQuic->SetParam(Connection, QUIC_PARAM_CONN_RESUMPTION_TICKET, RealLength, ResumptionTicket);
if (QUIC_FAILED(ret))
{
std::cout << "SetParam failed" << std::endl;
return;
}
}
//如果允许0-rtt 则直接0rtt建连 不用经过下面的ConnectionStart
if (HasResumptionTicket)
{
CreateNewStream(Connection);
}
else
{
//打开连接对象后 就可以直接连接了 客户端主流程到此结束 逻辑非常清晰 接下来就是你之前注册过的回调在起作用了 回调会把流创建好
ret = MsQuic->ConnectionStart(Connection, Configuration, QUIC_ADDRESS_FAMILY_UNSPEC, ServerIp, ServerPort);
if (QUIC_FAILED(ret))
{
std::cout << "ConnectionStart failed" << std::endl;
return;
}
}
}
到了这里,关于Msquic客户端详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!