当前位置: 首页 > news >正文

Msquic客户端详解

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的函数 而是我自己封装的

主要操作包括 StreamOpen StreamStart 详情看代码吧

#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;
		}
	}

}

相关文章:

  • Eclipse Theia技术揭秘——构建桌面IDE
  • 交换机的构成以及其工作原理
  • 想换壁纸找不到高质量的?来这里用python采集上万张壁纸
  • Mybatis-Plus(核心功能篇 ==> 条件构造器
  • vue2.X+Cesium1.93Demo
  • 适配器模式【Java设计模式】
  • 聊一下接口幂等性
  • springboot源码理解十二、springMVC功能
  • 论文写作教程之学术论文中需要做好的10 件事
  • AutoAugment 学习
  • 产品经理认证(NPDP)—备考错题集二
  • R语言ggplot2可视化:使用ggpubr包的ggmaplot函数可视化MA图(MA-plot)、genenames参数配置点标签对应的基因名称
  • 程序兼容性的定义 windows7的兼容性
  • 【C#】接口的基本概念
  • 四嗪 PEG 接头 下篇:Tetrazine-PEG5-NH-CH2CH2-4-Phenol试剂
  • 【译】JS基础算法脚本:字符串结尾
  • #Java异常处理
  • 78. Subsets
  • Markdown 语法简单说明
  • Python_网络编程
  • Python十分钟制作属于你自己的个性logo
  • python学习笔记-类对象的信息
  • Redux 中间件分析
  • sessionStorage和localStorage
  • vue-router 实现分析
  • windows下使用nginx调试简介
  • 湖南卫视:中国白领因网络偷菜成当代最寂寞的人?
  • 快速体验 Sentinel 集群限流功能,只需简单几步
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 使用 QuickBI 搭建酷炫可视化分析
  • 听说你叫Java(二)–Servlet请求
  • 小李飞刀:SQL题目刷起来!
  • 智能情侣枕Pillow Talk,倾听彼此的心跳
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • !!【OpenCV学习】计算两幅图像的重叠区域
  • #Spring-boot高级
  • (AngularJS)Angular 控制器之间通信初探
  • (bean配置类的注解开发)学习Spring的第十三天
  • (C语言)求出1,2,5三个数不同个数组合为100的组合个数
  • (四)JPA - JQPL 实现增删改查
  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • (算法)前K大的和
  • (五)c52学习之旅-静态数码管
  • (转)树状数组
  • .Net中ListT 泛型转成DataTable、DataSet
  • .考试倒计时43天!来提分啦!
  • :如何用SQL脚本保存存储过程返回的结果集
  • @RequestBody与@ModelAttribute
  • @德人合科技——天锐绿盾 | 图纸加密软件有哪些功能呢?
  • [ C++ ] STL---仿函数与priority_queue
  • [ Linux Audio 篇 ] 音频开发入门基础知识
  • [ vulhub漏洞复现篇 ] struts2远程代码执行漏洞 S2-005 (CVE-2010-1870)
  • [20171102]视图v$session中process字段含义
  • [BUUCTF]-Reverse:reverse3解析
  • [bzoj1901]: Zju2112 Dynamic Rankings