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

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

市场上各种高级语言的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 server
    receive(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 response
    dwError = 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 response
    dwError = 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 client
	dwError = 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 response
	dwError = 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 client
    dwError = 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 response
    dwError = 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 client
    dwError = 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.kler.cn/a/502210.html

相关文章:

  • 备战蓝桥杯:树的存储与遍历(dfs和bfs)
  • js前序遍历等
  • 基于 FastExcel 与消息队列高效生成及导入机构用户数据
  • 计算机网络 (40)域名系统DNS
  • sql server cdc漏扫数据
  • HTML5 网站模板
  • 浅谈云计算07 | 云安全机制
  • 蓝桥杯历届真题 # 数字诗意(C++,Java)
  • React面试常见题目
  • C++中 为什么要把基类指针指向子类对象?
  • STM32 FreeRTOS的任务创建和删除
  • 2_CSS3 背景 --[CSS3 进阶之路]
  • vue集成导出 txt文本文档 和 excel文档 的方法
  • jsdom爬虫程序中eBay主页内容爬取的异步处理
  • 从epoll事件的视角探讨TCP:三次握手、四次挥手、应用层与传输层之间的联系
  • 【论文笔记】Sign Language Video Retrieval with Free-Form Textual Queries
  • IDEA的Git界面(ALT+9)log选项不显示问题小记
  • 基于Java的推箱子游戏设计与实现
  • 31_搭建Redis分片集群
  • React中Fiber树构建过程详解——react中render一个App组件(包含子组件)的流程详解
  • 深度学习中的常见初始化方法:原理、应用与比较
  • C语言结构体如何实现位段
  • async++源码阅读——task模块
  • 【HM-React】08. Layout模块
  • 树状数组与线段树简单讲解与习题
  • SQLite 语法快速入门