Msquic用起來還是很方便很直觀的
因為微軟喜歡玩句柄 所以很多對象都由如下形式提供
Tips:關(guān)于微軟為啥喜歡句柄請自行百度
HQUIC Registration{};
?我們來看看github官網(wǎng) 微軟給出的對象有哪些 下圖來自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不是微軟的對象哦?他指的是你自己的應(yīng)用程序
那么如上圖所示 根據(jù)常識 咋們的客戶端代碼肯定是不需要Listener的 那么在代碼中需要用到的HQUIC對象就有
//頂層句柄對象
HQUIC Registration{};
//Configuration句柄對象
HQUIC Configuration{};
//Connection句柄對象
HQUIC Connection{};
//stream句柄對象
HQUIC Stream[MaxStreamSize]{};
整個Msquic的流程圖如下
?可以看到啊 非常的清晰 根據(jù)流程圖 下面是代碼 注釋同樣也是寫的非常詳細(xì)了 如果這都看不懂可以給我留言 我手把手教你
Tips:
CreatNewStream并不是Msquic的函數(shù) 而是我自己封裝的文章來源:http://www.zghlxwxcb.cn/news/detail-461967.html
主要操作包括 StreamOpen StreamStart 詳情看代碼吧文章來源地址http://www.zghlxwxcb.cn/news/detail-461967.html
#include <iostream>
#include <fstream>
extern "C"
{
#include <msquic.h>
}
//驗證安全證書?
#define DontValidate 0
//擁有服務(wù)器的Ticket?
#define HasResumptionTicket 0
//流的最大數(shù)量
#define MaxStreamSize 5
//函數(shù)表
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 };
//應(yīng)用層協(xié)議選擇 客戶端服務(wù)端兩邊匹配即可
const QUIC_BUFFER Alpn = { sizeof("test") - 1,(uint8_t*)"test" };
//服務(wù)器IP和端口
const char* ServerIp = "127.0.0.1";
uint16_t ServerPort = 8808;
//這些參數(shù)不再做解釋 在ConnectionCallBack中已經(jīng)有解釋
_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:
//調(diào)用streamSend完成后觸發(fā)
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;
//從對端收到數(shù)據(jù) 數(shù)據(jù)保存在聯(lián)合體中 打印數(shù)據(jù)
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:
//客戶端服務(wù)端均中止
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之后都沒發(fā)送任何數(shù)據(jù) 這里只是簡單的分配內(nèi)存注冊對象 因為是0RTT建連 所以要發(fā)送數(shù)據(jù)的時候才真正的開始發(fā)送數(shù)據(jù) 該函數(shù)與Connection同理 也是注冊回調(diào)
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;
}
//這一步也不會發(fā)送任何數(shù)據(jù) 可以設(shè)置在這一步通知對端 也可以不設(shè)置 默認(rèn)不通知 這里會分配流的標(biāo)識符 后續(xù)通過StreamSend來發(fā)送數(shù)據(jù)
ret = MsQuic->StreamStart(Stream[Stream_index], QUIC_STREAM_START_FLAG_NONE);
if (QUIC_FAILED(ret))
{
std::cout << "StreamStart failed" << std::endl;
return ret;
}
//接下來發(fā)送數(shù)據(jù)
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執(zhí)行
_Function_class_(QUIC_CONNECTION_CALLBACK)//解釋
QUIC_STATUS//返回值
QUIC_API//函數(shù)調(diào)用約定
ClientConnectionCallback(
_In_ HQUIC Connection,//微軟傳給你的Connetion對象
_In_opt_ void* Context,//自己傳的透傳指針
_Inout_ QUIC_CONNECTION_EVENT* Event//微軟傳給你的事件對象
)
{
QUIC_STATUS ret = QUIC_STATUS_SUCCESS;
//(微軟推薦的做法)根據(jù)事件的類型寫回調(diào)
//其實隨便寫啥都行
switch (Event->Type)
{
case QUIC_CONNECTION_EVENT_CONNECTED:
{
std::cout << "Good!Connection complete" << std::endl;
//連接成功后就可以創(chuàng)建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:
{
//連接即將被關(guān)閉 有很多可能原因 需要自行判斷
if (Event->SHUTDOWN_INITIATED_BY_TRANSPORT.Status == QUIC_STATUS_CONNECTION_IDLE)
{
//超時關(guān)閉
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:
{
//服務(wù)端關(guān)閉連接
std::cout << "the server close the connection" << std::endl;
break;
}
case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE:
{
//連接關(guān)閉完成
std::cout << "the connection has been closed" << std::endl;
break;
}
case QUIC_CONNECTION_EVENT_RESUMPTION_TICKET_RECEIVED:
{
//收到重連的ticket 用來進(jìn)行0RTT建連
//下次再進(jìn)行建立連接的時候可以將這個ticket作為參數(shù)傳入
//然后就可以不進(jìn)行任何握手進(jìn)行通信
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;
//打開庫和拿到函數(shù)表(拿到函數(shù)地址)
ret = MsQuicOpen2(&MsQuic);
if (QUIC_FAILED(ret))
{
std::cout << "open Msquic API table failed" << std::endl;
return;
}
//創(chuàng)建打開頂層registration對象(所有操作必須先打開這個才能繼續(xù)) 別問 問就是微軟要求的
ret = MsQuic->RegistrationOpen(&RegConfig, &Registration);
if (QUIC_FAILED(ret))//這個QUIC_FAILED其實就是判斷是否小于0 微軟官方例子這么寫 我也沿用了
{
std::cout << "RegistrationOpen failed" << std::endl;
return;
}
//設(shè)置QUIC各種參數(shù) 如0RTT建連等等
QUIC_SETTINGS Settings{};
//設(shè)置超時時間 單位ms 其他設(shè)置請參考成員變量
Settings.IdleTimeoutMs = 1000;
//設(shè)置好了之后要開啟該設(shè)置
Settings.IsSet.IdleTimeoutMs = TRUE;
//配置驗證安全證書等選項 客戶端不需要有證書 除非服務(wù)器要求 故設(shè)置為無
QUIC_CREDENTIAL_CONFIG CredConfig{};
CredConfig.Type = QUIC_CREDENTIAL_TYPE_NONE;
CredConfig.Flags = QUIC_CREDENTIAL_FLAG_CLIENT;
//當(dāng)你不想驗證服務(wù)器的安全證書時
if (DontValidate)
{
CredConfig.Flags |= QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION;
}
//[Alpn]是你要使用的應(yīng)用層協(xié)議(客戶端和服務(wù)器必須相同 但是注意只是聲明 但實際上就算你填入了http msquic也不會幫你實現(xiàn)這個應(yīng)用層協(xié)議 只要兩邊匹配實際上你隨便填一個莫名其妙的協(xié)議也行)
//打開Configuration句柄對象
ret = MsQuic->ConfigurationOpen(Registration, &Alpn, 1, &Settings, sizeof(Settings), NULL, &Configuration);
if (QUIC_FAILED(ret))
{
std::cout << "ConfigurationOpen failed" << std::endl;
return;
}
//打開后還要單獨把我們剛剛設(shè)置好的安全證書選項填入(除非服務(wù)器特殊要求 不然quic使用的tls1.3版本是不需要客戶端擁有安全證書的)
ret = MsQuic->ConfigurationLoadCredential(Configuration, &CredConfig);
if (QUIC_FAILED(ret))
{
std::cout << "ConfigurationLoadCredential failed" << std::endl;
return;
}
//Registraion和configuration都打開了 剩下就可以直接connect后然后保存stream對象了 注意Msquic要求你注冊回調(diào) 回調(diào)我寫在上面的 然后他內(nèi)部開線程去調(diào)用 內(nèi)部工作線程的數(shù)量在
//絕大多數(shù)情況下保持默認(rèn)即可
//第三個參數(shù)透傳指針
ret = MsQuic->ConnectionOpen(Registration, ClientConnectionCallback, NULL, &Connection);
if (QUIC_FAILED(ret))
{
std::cout << "ConfigurationLoadCredential failed" << std::endl;
return;
}
//如果已經(jīng)建立過連接并且擁有了服務(wù)器的ticket 此處可以將ticket作為參數(shù)傳入 然后可以進(jìn)行0RTT建連 非??焖? //詳情看調(diào)研測試報告
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建連 不用經(jīng)過下面的ConnectionStart
if (HasResumptionTicket)
{
CreateNewStream(Connection);
}
else
{
//打開連接對象后 就可以直接連接了 客戶端主流程到此結(jié)束 邏輯非常清晰 接下來就是你之前注冊過的回調(diào)在起作用了 回調(diào)會把流創(chuàng)建好
ret = MsQuic->ConnectionStart(Connection, Configuration, QUIC_ADDRESS_FAMILY_UNSPEC, ServerIp, ServerPort);
if (QUIC_FAILED(ret))
{
std::cout << "ConnectionStart failed" << std::endl;
return;
}
}
}
到了這里,關(guān)于Msquic客戶端詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!