Msquic客户端详解

这篇具有很好参考价值的文章主要介绍了Msquic客户端详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Msquic用起来还是很方便很直观的

因为微软喜欢玩句柄 所以很多对象都由如下形式提供

Tips:关于微软为啥喜欢句柄请自行百度

HQUIC Registration{};

 我们来看看github官网 微软给出的对象有哪些 下图来自Msquic github

Msquic客户端详解

 下面这段解释来自微软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的流程图如下

Msquic客户端详解

 可以看到啊 非常的清晰 根据流程图 下面是代码 注释同样也是写的非常详细了 如果这都看不懂可以给我留言 我手把手教你

Tips:

CreatNewStream并不是Msquic的函数 而是我自己封装的

主要操作包括 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模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • kafka客户端应用参数详解

    Kafka提供了非常简单的客户端API。只需要引入一个Maven依赖即可: 1、消息发送者主流程  然后可以使用Kafka提供的Producer类,快速发送消息。 ​ 整体来说,构建Producer分为三个步骤: 设置Producer核心属性  :Producer可选的属性都可以由ProducerConfig类管理。比如ProducerConfig.BOOTST

    2024年02月07日
    浏览(52)
  • Git客户端Sourcetree工具安装使用详解&命令

    目录 一、Git工作流程 二、Git安装 三、Sourcetree工具安装 四、Sourcetree配置 五、Sourcetree配置SSH 六、GIT常用命令 1、获取安装程序 a、官网地址:https://git-scm.com/downloads b、网盘地址:百度网盘 请输入提取码    xc5d 2、双击按照默认配置安装即可,安装目录:D:softwareGit、安装完

    2024年02月07日
    浏览(60)
  • 【HDFS】ResponseProcessor线程详解以及客户端backoff反压

    ResponseProcessor如何处理datanode侧发过来的packet ack的 客户端侧backoff逻辑。 ResponseProcessor:主要功能是处理来自datanode的响应。当一个packet的响应到达时,会把这个packet从ackQueue里移除。

    2024年02月11日
    浏览(67)
  • NFS服务器简介、在Linux上搭建NFS服务器和客户端,使用autofs进行NFS客户端自动挂载和卸载详解

    目录 一.NFS服务器简介 1.含义简介: 2.工作原理简介: 3.RPC服务与NFS服务配合使用 二.NFS配置文件参数命令介绍 1.主配置文件/etc/exports 2.日志文件/var/lib/nfs/ 3.showmount命令 三.主配置文件/etc/exports挂载写法 1.配置nfs服务端和客户端 2.windows客户端挂载测试 3.权限介绍 (1)rw/ro,服

    2024年02月04日
    浏览(52)
  • Spring 教程—REST 客户端详解(WebClient 、RestTemplate、HTTP 接口)

    Spring框架为调用REST端点提供了以下选择: WebClient - 非阻塞、响应式客户端和 fluent API。 RestTemplate - 带有模板方法API的同步客户端。 HTTP 接口 - 注解式接口,并生成动态代理实现。 WebClient  是一个非阻塞的、响应式的客户端,用于执行HTTP请求。它在5.0中引入,提供了  Re

    2024年02月07日
    浏览(42)
  • 【C++】TCP通信服务端与客户端代码实现及详解

    上述代码使用Winsock库实现了简单的TCP服务器,它监听指定端口并与客户端进行通信。下面对代码进行详细分析: #pragma comment(lib, \\\"ws2_32.lib\\\") 是一个特殊的 编译器指令 ,用于告诉编译器在链接阶段将 ws2_32.lib 库文件添加到最终的可执行文件中。无需在编译命令行或IDE中显式指

    2024年02月03日
    浏览(35)
  • 利用idea生成webservice客户端--详解步骤--(wsdl文件的使用)

    目录 一、idea安装webservice 1.点击左上file,选中settings​编辑 2.下载Web Service 3.给此项目添加webservice 4.添加webservice的依赖 二、利用idea根据wsdl文件自动生成webService客户端代码(然后比照着生成的测试类进行接口或方法的调用) 1.打开tools - WebServices - Generate Java Code From Wsdl,按照图中

    2024年02月08日
    浏览(54)
  • Ubuntu配置NFS客户端和服务端详解——手把手配置

    如果您想实现远程访问并修改 ROS 主机中 Ubuntu 上的文件,可以通过 NFS挂载的方式。虚拟机上的 Ubuntu 系统可以通过 NFS 的方式来访问 ROS 主机中Ubuntu 系统的文件,NFS 分为服务器挂载和客户端访问。这里虚拟机上的 Ubuntu作为客户端,ROS 主机上的 Ubuntu 作为服务端,虚拟机的

    2024年02月01日
    浏览(41)
  • <Linux>《OpenSSH 客户端配置文件ssh_config详解》

    除非另有说明,对于每个参数,将使用第一个获得的值。配置文件包含由 Host 规范分隔的部分,该部分仅应用于与规范中给出的模式之一匹配的主机。匹配的主机名通常是命令行中给出的名称(请参阅 CanonicalizeHostname 选项以了解异常情况)。 由于使用了每个参数的第一个获得的

    2024年02月07日
    浏览(71)
  • NFS服务器简介、在Linux上搭建NFS服务器和客户端,Linux上使用auto(autofs)进行NFS客户端自动挂载和卸载详解

    目录 一.NFS服务器简介 1.含义简介: 2.工作原理简介: 3.RPC服务与NFS服务配合使用 二.NFS配置文件参数命令介绍 1.主配置文件/etc/exports 2.日志文件/var/lib/nfs/ 3.showmount命令 三.主配置文件/etc/exports挂载写法 1.配置nfs服务端和客户端 2.windows客户端挂载测试 3.权限介绍 (1)rw/ro,服

    2024年02月04日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包