一个可以把玩的针对WebSocket分段的处理方案

devtools/2025/1/17 16:33:13/

市场上各种高级语言的WebSocket Echo的测试方案不少,但找来找去,愣是没有一个现成的可以针对分段(fragmetation)处理的Echo服务端。分段处理在一些对实时性要求较高的场合非常重要,比如流媒体,实时监控等场景,不能等待所有数据都齐备了再发送回客户端。基于此,从零开始着手打造第一款针对分段需求的Echo服务端,提供各种测试所适用的场景需要。

服务端是基于IIS提供的WebSocket框架,这样可以专注于商业逻辑的开发。对于客户端,我也提供了二种不同的方案——同步和异步的客户端方案。同步和异步方案使用的都是WinHTTP提供的API,同步比较简单直接,适合基本应用。异步比较复杂,可以构建较为大型的商业应用,更符合直觉上的使用习惯。下面介绍一下同步方案的应用。

HINTERNET hConnectionHandle = NULL, hRequestHandle = NULL, hWebSocketHandle = NULL;BYTE rgbBuffer[1024]{ }, rgbCommand[] = u8"How are you doing/你好吗/Cómo estás/お元気ですか/어떻게 지내세요", rgbClose[] = u8"Bye/再见/Adiós/さよなら/안녕";BOOL fStatus = FALSE;DWORD dwError = ERROR_SUCCESS;SetConsoleOutputCP(CP_UTF8);// Create session, connection and request handles.HINTERNET hSessionHandle = WinHttpOpen(L"WebSocket sample", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0);if (hSessionHandle == NULL){dwError = GetLastError();goto quit;}hConnectionHandle = WinHttpConnect(hSessionHandle, L"localhost", INTERNET_DEFAULT_HTTP_PORT, 0);if (hConnectionHandle == NULL){dwError = GetLastError();goto quit;}hRequestHandle = WinHttpOpenRequest(hConnectionHandle, L"GET", L"/interactws/main.iws", NULL, NULL, NULL, 0);if (hRequestHandle == NULL){dwError = GetLastError();goto quit;}

WebSocket的文本协议是基于UTF8的,和UNICODE比节省传输上的开销,所以在开头也定义了二个使用UTF8的字符串,一个是用于测试用的数据,一个是关闭连接时的状态内容。接下是命令行的输出方法,按UTF8格式输出。第一步是建立个新的会话session,然后是和服务端建立连接。有了连接后,访问特定的WebSocket应用所在的路径。

 // Request protocol upgrade from http to websocket.
#pragma prefast(suppress:6387, "WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET does not take any arguments.")fStatus = WinHttpSetOption(hRequestHandle, WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, NULL, 0);if (!fStatus){dwError = GetLastError();goto quit;}fStatus = WinHttpSendRequest(hRequestHandle, WINHTTP_NO_ADDITIONAL_HEADERS, 0, NULL, 0, 0, 0);if (!fStatus){dwError = GetLastError();goto quit;}fStatus = WinHttpReceiveResponse(hRequestHandle, 0);if (!fStatus){dwError = GetLastError();goto quit;}hWebSocketHandle = WinHttpWebSocketCompleteUpgrade(hRequestHandle, NULL);if (hWebSocketHandle == NULL){dwError = GetLastError();goto quit;}

接着是准备更新协议,从HTTP更新到WebSocket,完成协议头的准备和发送,接受服务端的返回,最后完成这步协议更新。这里可以看到,对于WebSocket,不是说必须在浏览器下才能使用,任何普通的程序只要有支持HTTP的库,都可以使用WebSocket。

// Get connected message from serverreceive(hWebSocketHandle, rgbBuffer);printf("%s\n", rgbBuffer);memset(rgbBuffer, NULL, sizeof rgbBuffer);printf("\n*****   Set server in Binary mode and use fragmentation with size 1 byte in response   *****\n\n");// Set server in Binary mode in responsedwError = WinHttpWebSocketSend(hWebSocketHandle, WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE, (PVOID)"Mode: Binary", (DWORD)strlen("Mode: Binary"));if (dwError != ERROR_SUCCESS){goto quit;}// Set server to use fragmentation in responsedwError = WinHttpWebSocketSend(hWebSocketHandle, WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE, (PVOID)"Fragment: 1", (DWORD)strlen("Fragment: 1"));if (dwError != ERROR_SUCCESS){goto quit;}Sleep(1000);// Send data in Binary mode from clientdwError = WinHttpWebSocketSend(hWebSocketHandle, WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE, rgbCommand, sizeof rgbCommand);if (dwError != ERROR_SUCCESS){goto quit;}receive(hWebSocketHandle, rgbBuffer);printf("%s\n", rgbBuffer);memset(rgbBuffer, NULL, sizeof rgbBuffer);

和服务端连接成功后,服务端会返回一个连接成功的信息,客户端需要接收此信息,这是receive()承担的功能,具体在后分析。下面需要通知服务端,让它处于BINARY模式,同时采用分段返回,每次返回一个BYTE。这也是本方案的独特之处,可以动态地调整服务端,即可用BINARY模式,也可以使用UTF8模式,还可以进行分段返回。

 printf("\n*****   Set server in UTF8 mode and use configured fragmentation as before   *****\n\n");// Set server in UTF8 mode in responsedwError = WinHttpWebSocketSend(hWebSocketHandle, WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE, (PVOID)"Mode: UTF8", (DWORD)strlen("Mode: UTF8"));if (dwError != ERROR_SUCCESS){goto quit;}// Send data in Binary mode from clientdwError = WinHttpWebSocketSend(hWebSocketHandle, WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE, rgbCommand, sizeof rgbCommand);if (dwError != ERROR_SUCCESS){goto quit;}receive(hWebSocketHandle, rgbBuffer);printf("%s\n", rgbBuffer);memset(rgbBuffer, NULL, sizeof rgbBuffer);

这段程序调整服务端进入UTF8模式,同时原来的分段设置还有效,所以接收的都是以UTF8模式返回的一个BYTE分段信息。

printf("\n*****   Turn off fragmentation and use UTF8 as set before in server response   *****\n\n");// Turn off fragmentation in server responsedwError = WinHttpWebSocketSend(hWebSocketHandle, WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE, (PVOID)"Fragment: 0", (DWORD)strlen("Fragment: 0"));if (dwError != ERROR_SUCCESS){goto quit;}// Send data in UTF8 mode from clientdwError = WinHttpWebSocketSend(hWebSocketHandle, WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE, rgbCommand, sizeof rgbCommand);if (dwError != ERROR_SUCCESS){goto quit;}receive(hWebSocketHandle, rgbBuffer);printf("%s\n", rgbBuffer);memset(rgbBuffer, NULL, sizeof rgbBuffer);

上面这段关闭了服务端的分段特性,返回的是以UTF8模式的完整文本,而不是一个BYTE的分段返回。

quit:printf("\n*****   Close connection with reason description   *****\n\n");// Gracefully close the connection.WinHttpWebSocketClose(hWebSocketHandle, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, rgbClose, sizeof rgbClose);BYTE rgbCloseReasonBuffer[123]{};DWORD dwCloseReasonLength = 0;USHORT usStatus = 0;WinHttpWebSocketQueryCloseStatus(hWebSocketHandle, &usStatus, rgbCloseReasonBuffer, ARRAYSIZE(rgbCloseReasonBuffer), &dwCloseReasonLength);printf("The server closed the connection with status code: '%d' and reason: '%s'\n", (int)usStatus, rgbCloseReasonBuffer);if (hRequestHandle != NULL){WinHttpCloseHandle(hRequestHandle);hRequestHandle = NULL;}if (hWebSocketHandle != NULL){WinHttpCloseHandle(hWebSocketHandle);hWebSocketHandle = NULL;}if (hConnectionHandle != NULL){WinHttpCloseHandle(hConnectionHandle);hConnectionHandle = NULL;}if (hSessionHandle != NULL){WinHttpCloseHandle(hSessionHandle);hSessionHandle = NULL;}return 0;

最后是关闭连接,并从服务端获取客户端发送的关闭原因,清除相关的资源占用。下面看下文中用到的receive()函数,

VOID receive(HINTERNET hWebSocketHandle, BYTE* pbCurrentBufferPointer) {BYTE* pbInitial = pbCurrentBufferPointer;DWORD dwBytesTransferred = 0;WINHTTP_WEB_SOCKET_BUFFER_TYPE bufferType;DWORD dwBufferLength = 1024;;do{DWORD dwError = WinHttpWebSocketReceive(hWebSocketHandle, pbCurrentBufferPointer, dwBufferLength, &dwBytesTransferred, &bufferType);if (dwError != ERROR_SUCCESS){return;}if (bufferType == WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE || bufferType == WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE)printf("%c\n", *pbCurrentBufferPointer);pbCurrentBufferPointer += dwBytesTransferred;dwBufferLength -= dwBytesTransferred;} while (bufferType == WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE || bufferType == WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE);pbCurrentBufferPointer = pbInitial;
}

这个是同步调用的核心,在一个大的循环中等待服务端的返回。如果返回的是分段信息,读完后移动指针到下一个等待位置,这样下个返回可以把读取的值正确复制到相应的位置。等返回是最后一个分段信息时,此时不满足while的条件因此循环跳出,把指针复原到原始位置,这样一个完整的分段信息就算重新组装完成了。运行以上程序的输出如下,

异步程序比较复杂,在此不再分析,直接上图,大家尽情把玩,

总之,有了这一套服务端和客户端,对于WebSocket的开发是如虎添翼。服务端和客户端可以联合使用,也可以分别使用,其它常用的第三方Echo服务端都支持。若觉得实用,好用,请到它的Github仓库加个星,本人也提供这方面的咨询服务,欢迎点赞评论和收藏!


http://www.ppmy.cn/devtools/151310.html

相关文章

如何选择合适的服务器?服务器租赁市场趋势分析

服务器租赁市场概览 服务器租赁 market可以分为两种类型:按小时、按月和按年,每种模式都有其特点和适用场景,按小时租赁是最经济实惠的选择,适用于短期需求;按月租赁则适合中长期使用;而按年租赁则是最灵活…

uniapp button 去除边框

在找去除边框的办法时试了好久 css里设置了 border: none; /* 去掉边框 */outline: none; /* 确保点击时不出现轮廓 */压根不行,按钮还是浮在页面上有明显轮廓 最后看到了大佬的文章 https://www.cnblogs.com/menxiaojin/p/13752916.html button::after{border: no…

对比学习方法(1)——SimCLR

SimCLR(Simple Contrastive Learning of Representations)是由Google Research提出的一种基于对比学习的无监督学习方法,特别用于学习图像的表示。它的核心思想是通过对比学习来构建有意义的特征表示,使得模型可以在没有标签数据的…

线上工单引发的思考:Spring Boot 中 @Autowired 与 @Resource 的区别

最近接手了离职同事负责的业务,在处理一个线上工单的时候,看了下历史逻辑,在阅读他们写的代码时,发现他们竟然把Autowired和Resource注解混用。今天就借此机会聊聊SpringBoot项目中这两者之间的区别。 1. 注解来源 Autowired&am…

【华为路由/交换机的telnet远程设置】

华为路由/交换机的telnet远程设置 R1:10.1.1.254 R2:10.1.1.1 R3:10.1.1.2 S1:空配 在R2上设置密码为huawei 用R1和R3远程后,在R2上可以查看已经登陆的用户信息 但是这种远程方式的权限非常低,vty用户界…

机器学习之PCA主成分分析法降维及测试

PCA主成分分析法降维及测试 目录 PCA主成分分析法降维及测试PCA主成分分析法概念PCA的基本思想PCA的步骤PCA的优缺点优点缺点 PCA函数函数导入参数方法返回值 实际测试数据理解代码测试 PCA主成分分析法 概念 PCA 主成分分析(PCA)是一种常用的数据分析…

HarmonyOS 鸿蒙 ArkTs(5.0.1 13)实现Scroll下拉到顶刷新/上拉触底加载,Scroll滚动到顶部

HarmonyOS 鸿蒙 ArkTs(5.0.1 13)实现Scroll下拉到顶刷新/上拉触底加载 效果展示 使用方法 import LoadingText from "../components/LoadingText" import PageToRefresh from "../components/PageToRefresh" import FooterBar from "../components/…

从零到一:构建高效稳定的电商数据API接口

在当今的数字化时代,数据已成为企业决策的关键驱动力。对于电商企业而言,数据不仅是衡量业务表现的重要指标,更是优化用户体验、提升运营效率、制定市场策略的重要依据。构建高效稳定的电商数据API接口,是电商企业实现数据驱动决策…