当前位置: 首页 > 知识库问答 >
问题:

WebSocket连接到TIdHTTPServer,握手问题

段干华皓
2023-03-14

我正在使用C Builder 10.1柏林编写一个简单的WebSocket服务器应用程序,它在端口上侦听从网络浏览器发送的一些命令,如谷歌Chrome。

在我的表单上,我有一个TMemo、TButton和TIdHTTPServer,我有以下代码:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 IdHTTPServer1->Bindings->DefaultPort = 55555;
 IdHTTPServer1->Active = true;
}

void __fastcall TForm1::IdHTTPServer1Connect(TIdContext *AContext)
{

  Memo1->Lines->Add(AContext->Binding->PeerIP);
  Memo1->Lines->Add( AContext->Connection->IOHandler->ReadLn(enUTF8));
  Memo1->Lines->Add( AContext->Data->ToString());

}


void __fastcall TForm5::IdHTTPServer1CommandOther(TIdContext *AContext, TIdHTTPRequestInfo *ARequestInfo,
          TIdHTTPResponseInfo *AResponseInfo)
{

UnicodeString svk,sValue;
TIdHashSHA1 *FHash;
TMemoryStream *strmRequest;

FHash = new TIdHashSHA1;
strmRequest  =  new TMemoryStream;

strmRequest->Position = 0;
svk = ARequestInfo->RawHeaders->Values["Sec-WebSocket-Key"];

Memo1->Lines->Add("Get:"+svk);
      AResponseInfo->ResponseNo         = 101;
      AResponseInfo->ResponseText       = "Switching Protocols";
      AResponseInfo->CloseConnection    = False;
      //Connection: Upgrade
      AResponseInfo->Connection         = "Upgrade";
      //Upgrade: websocket
      AResponseInfo->CustomHeaders->Values["Upgrade"] = "websocket";

      sValue = svk + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
      sValue = TIdEncoderMIME::EncodeBytes( FHash->HashString(sValue) );
      AResponseInfo->CustomHeaders->Values["Sec-WebSocket-Accept"] = sValue;
      AResponseInfo->ContentText = "Welcome here!";

      AResponseInfo->WriteHeader();

 UnicodeString URLstr = "http://"+ARequestInfo->Host+ARequestInfo->Document;
      if (ARequestInfo->UnparsedParams != "") URLstr = URLstr+"?"+ARequestInfo->UnparsedParams;
      Memo1->Lines->Add(URLstr);
      Memo1->Lines->Add(ARequestInfo->Command );
      Memo1->Lines->Add("--------");
      Memo1->Lines->Add(ARequestInfo->RawHeaders->Text );
      Memo1->Lines->Add(AContext->Data->ToString() );
}

从Chrome,我执行这个Javascript代码:

var connection = new WebSocket('ws://localhost:55555');

connection.onopen = function () {
  connection.send('Ping');
};

但是我从Chrome上得到了这个错误:

VM77:1到“ws://localhost:55555/”的WebSocket连接失败:一个或多个保留位打开:reserved1=1、reserved2=0、reserved3=0

我希望WebSocket连接成功,然后我可以在web浏览器和服务器应用程序之间发送数据。

也许有人已经知道什么是错误的,并且可以展示一个如何实现这一点的完整示例?

以下是我的应用程序备忘录1显示的内容:

192.168.0.25
GET / HTTP/1.1
Get:TnBN9qjOJiwka2eJe7mR0A==
http://
HOST:
--------
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://bcbjournal.org
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Sec-WebSocket-Key: TnBN9qjOJiwka2eJe7mR0A==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

以下是Chrome显示的:

响应请求:

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 13
Date: Thu, 08 Jun 2017 15:04:00 GMT
Upgrade: websocket
Sec-WebSocket-Accept: 2coLmtu++HmyY8PRTNuaR320KPE=

请求报头

GET ws://192.168.0.25:55555/ HTTP/1.1
Host: 192.168.0.25:55555
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://bcbjournal.org
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Sec-WebSocket-Key: TnBN9qjOJiwka2eJe7mR0A==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

共有1个答案

慕容兴贤
2023-03-14

你犯了两个大错误:

>

事件处理程序读取请求行并退出后,TIdHTTPServer然后读取下一行(主机Host头)并将其解释为请求行,这就是为什么:

>

ARequestInfo-

当您应该使用OnCommandGet事件时,您最终不得不使用OnCommandOther事件。

您正在访问TIdHTTPServer事件中的TMemo,而不与主UI线程同步。TIdHTTPServer是一个多线程组件。它的事件是在工作线程的上下文中触发的。VCL/FMX UI控件不是线程安全的,因此必须与主UI线程正确同步。

您的服务器没有验证WebSocket协议要求服务器验证的握手中的所有内容(这对于测试来说很好,但请确保您是为了生产而验证的)。

但更重要的是,TIdHTTPServer不太适合实现WebSocket(这是一个TODO项)。WebSocket协议唯一涉及HTTP的是握手。握手完成后,其他一切都是WebSocket框架,而不是HTTP。要在TIdHTTPServer中处理该问题,需要在On命令获取事件中实现整个WebSocket会话,读取并发送所有WebSocket帧,防止事件处理程序退出,直到连接关闭。对于这种逻辑,我建议直接使用TIdTCPServer,只需在其OnExecute事件的开头手动处理HTTP握手,然后循环处理WebSocket帧的其余事件。

握手完成后,您的OnCommandOther事件处理程序当前未执行任何WebSocket I/O。它将控制权返回给TIdHTTPServer,然后该服务器将尝试读取新的HTTP请求。一旦客户端向服务器发送WebSocket帧,TIdHTTPServer将无法处理它,因为它不是HTTP,并且可能会将HTTP响应发送回客户端,这将被误解,导致客户端无法完成WebSocket会话并关闭套接字连接。

话虽如此,请尝试类似的方法:

#include ...
#include <IdSync.hpp>

class TLogNotify : public TIdNotify
{
protected:
    String FMsg;

    void __fastcall DoNotify()
    {
        Form1->Memo1->Lines->Add(FMsg);
    }

public:
    __fastcall TLogNotify(const String &S) : TIdNotify(), FMsg(S) {}
};

__fastcall TForm1::TForm1(TComponent *Owner)
    : TForm(Owner)
{
    IdHTTPServer1->DefaultPort = 55555;
}

void __fastcall TForm1::Log(const String &S)
{
    (new TLogNotify(S))->Notify();
}

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    IdHTTPServer1->Active = true;
}

void __fastcall TForm1::IdHTTPServer1Connect(TIdContext *AContext)
{
    Log(_D("Connected: ") + AContext->Binding->PeerIP);
}

void __fastcall TForm1::IdHTTPServer1Disconnect(TIdContext *AContext)
{
    Log(_D("Disconnected: ") + AContext->Binding->PeerIP);
}

void __fastcall TForm5::IdHTTPServer1CommandGet(TIdContext *AContext, TIdHTTPRequestInfo *ARequestInfo, TIdHTTPResponseInfo *AResponseInfo)
{
    Log(ARequestInfo->RawHTTPCommand);

    if (ARequestInfo->Document != _D("/"))
    {
        AResponseInfo->ResponseNo = 404;
        return;
    }

    if ( !(ARequestInfo->IsVersionAtLeast(1, 1) &&
          TextIsSame(ARequestInfo->RawHeaders->Values[_D("Upgrade")], _D("websocket")) &&
          TextIsSame(ARequestInfo->Connection, _D("Upgrade")) ) )
    {
        AResponseInfo->ResponseNo         = 426;
        AResponseInfo->ResponseText       = _D("upgrade required");
        return;
    }

    String svk = ARequestInfo->RawHeaders->Values[_D("Sec-WebSocket-Key")];

    if ( (ARequestInfo->RawHeaders->Values[_D("Sec-WebSocket-Version")] != _D("13")) ||
          svk.IsEmpty() )
    {
        AResponseInfo->ResponseNo = 400;
        return;
    }

    // validate Origin, Sec-WebSocket-Protocol, and Sec-WebSocket-Extensions as needed...

    Log(_D("Get:") + svk);

    AResponseInfo->ResponseNo         = 101;
    AResponseInfo->ResponseText       = _D("Switching Protocols");
    AResponseInfo->CloseConnection    = false;
    AResponseInfo->Connection         = _D("Upgrade");
    AResponseInfo->CustomHeaders->Values[_D("Upgrade")] = _D("websocket");

    TIdHashSHA1 *FHash = new TIdHashSHA1;
    try {
        String sValue = svk + _D("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
        sValue = TIdEncoderMIME::EncodeBytes( FHash->HashString(sValue) );
        AResponseInfo->CustomHeaders->Values[_D("Sec-WebSocket-Accept")] = sValue;
    }
    __finally {
        delete FHash;
    }

    AResponseInfo->WriteHeader();

    String URLstr = _D("http://") + ARequestInfo->Host + ARequestInfo->Document;
    if (!ARequestInfo->UnparsedParams.IsEmpty()) URLstr = URLstr + _D("?") + ARequestInfo->UnparsedParams;
    Log(URLstr);
    Log(_D("--------"));
    Log(ARequestInfo->RawHeaders->Text);

    // now send/receive WebSocket frames here as needed,
    // using AContext->Connection->IOHandler directly...
}

也就是说,有很多第三方WebSocket库可用。您应该使用其中之一,而不是手动实现WebSocket。一些图书馆甚至建在印地的顶部。

 类似资料:
  • 问题内容: 我在连接到套接字服务器时遇到问题。 我有两个不同的云服务器(1)用于客户端项目,(2)用于套接字服务器。 (1)客户项目: (2)套接字服务器 1. nginx laravel-echo-server.json 下面是laravel-echo-server配置。 主管 这是主管配置。 2. Laravel回声服务器 注意:我使用和,并且效果很好。 有没有人有办法解决吗? 问题答案: 本

  • 我正试图将socket.io与Angular集成,但在建立从客户端到服务器的连接时遇到了困难。我已经查看了其他相关问题,但我的问题发生在本地,所以中间没有web服务器。 这就是我的服务器代码的样子: 我使用Grunt按以下顺序加载客户端JavaScript文件: 我刚刚意识到websockets正在部分工作。我在Chrome开发者控制台看到一个101交换协议请求。但是,我看到的帧只有Engine.

  • 我正在写一个c websocket服务器,chrome上的开发工具说SecWebSocket接受头值不正确。我已经测试了好几天了,一切似乎都很好。客户端使用readystate 3关闭,而不调用websocket onopen,尽管它在chrome开发工具中显示为101。 这是我计算钥匙的代码 铬反应 Chrome请求 它表示"WebSocket握手过程中出错:不正确的'Sec-WebSocket

  • 我们很难与远程机器(如PayPal vb)建立https连接谁从我们的系统中禁用了SSL3协议。Net应用程序。HttpWebRequest实例的GetResponse方法出现以下异常。 请求被中止:无法创建SSL/TLS安全通道。 当我们使用WireShark深入并跟踪网络日志时,我们看到远程机器返回以下错误 TLSv1。2警报(级别:致命,描述:握手失败)握手失败40 更有趣的情况是,当我尝试

  • 我正在做我的第一个测试,尝试使用phpwebsocket。 我将其握手标题值设置为以下值: 所以我运行web套接字PHP文件(创建连接),一切正常。 因此,我运行测试html,其中包含一个脚本,该脚本试图连接到我正在使用的phpwebsocket,在浏览器控制台中,我收到以下错误: 但是我发现这个错误在WebSocket握手:不正确的'Sec-WebSocket接受'头值与PHP,这是通过定义缓冲

  • ASP.NET 5 RC1上的信号器3-RC1用户 尝试运行websocket时出现以下错误。 使用Chrome50.0.2661.87 标题如下: 请求标题: 日期:2016年4月29日星期五13:51:54 GMT 服务器:Kestrel 传输编码:分块 升级:websocket X-Content-Type-Options:nosniff X-Powered-By:ASP.NET 响应头: