问题描述
我正在使用C ++ Builder 10.1 Berlin编写一个简单的WebSocket服务器应用程序,该应用程序在端口上侦听从Web浏览器(例如Google Chrome)发送的一些命令。
I'm using C++Builder 10.1 Berlin to write a simple WebSocket server application, which listens on a port for some commands sent from a web browser, like Google Chrome.
在我的表单上,我有一个TMemo,TButton和TIdHTTPServer,并且我有以下代码:
On my Form, I have a TMemo, TButton and TIdHTTPServer, and I have this code:
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代码:
From Chrome, I execute this Javascript code:
var connection = new WebSocket('ws://localhost:55555');
connection.onopen = function () {
connection.send('Ping');
};
但是我从Chrome浏览器中收到此错误:
But I get this error from Chrome:
与'ws:// localhost:55555 /'的VM77:1 WebSocket连接失败:一个或多个保留位打开:reserved1 = 1,reserved2 = 0,reserved3 = 0
我希望WebSocket连接成功,然后可以在Web浏览器和服务器应用程序之间发送数据。
I expect the WebSocket connection to be successful, and then I can send data between the web browser and my server application.
也许有人已经知道出了什么问题,并且可以举一个完整的例子来说明如何实现这一目标?
Maybe somebody already knows what is wrong and can show a full example of how to achieve this?
这是我的应用程序的Memo1显示的内容:
Here is what my application's Memo1 shows:
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会显示以下内容:
Here is what Chrome shows:
响应请求:
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
推荐答案
您正在滥用 TIdHTTPServer
您犯了两个大错误:
You are misusing TIdHTTPServer
You are making two big mistakes:
-
您的
OnConnect
事件处理程序正在读取客户端的初始HTTP请求行(GET
行)。它完全不应该从客户端读取任何内容,因为这样做会干扰TIdHTTPServer
对HTTP协议的处理。
Your
OnConnect
event handler is reading the client's initial HTTP request line (theGET
line). It should not be reading anything from the client at all, as doing so interfers withTIdHTTPServer
's handling of the HTTP protocol.
事件处理程序读取请求行并退出后, TIdHTTPServer
然后读取下一行( Host
标头),而是将其解释为请求行,这就是原因:
After the event handler reads the request line and exits, TIdHTTPServer
then reads the next line (the Host
header) and interprets that as the request line instead, which is why:
-
ARequestInfo-> ; Command
属性是HOST:
而不是GET
。
ARequestInfo->主机
, ARequestInfo->文档
, ARequestInfo->版本
, ARequestInfo-> VersionMajor
, ARequestInfo- > VersionMinor
属性都是错误的。
the ARequestInfo->Host
, ARequestInfo->Document
, ARequestInfo->Version
, ARequestInfo->VersionMajor
, ARequestInfo->VersionMinor
properties are all wrong.
您最终不得不使用 OnCommandOther
事件,而应该使用 OnCommandGet
事件。
you end up having to use the OnCommandOther
event when you should be using the OnCommandGet
event instead.
您正在访问 TMemo
c $ c> TIdHTTPServer 事件,而不与主UI线程同步。 TIdHTTPServer
是一个多线程组件。它的事件在辅助线程的上下文中触发。 VCL / FMX UI控件不是线程安全的,因此您必须与主UI线程正确同步。
You are accessing the TMemo
in your TIdHTTPServer
events without synchronizing with the main UI thread. TIdHTTPServer
is a multi-threaded component. Its events are fired in the context of worker threads. VCL/FMX UI controls are not thread-safe, so you must synchronize properly with the main UI thread.
您没有正确实现WebSocket协议
您的服务器没有验证WebSocket协议要求服务器进行验证的握手中的所有内容(这对于测试很合适,但请确保
You are not implementing the WebSocket protocol correctly
Your server is not validating everything in the handshake that the WebSocket protocol requires a server to validate (which is fine for testing, but make sure you do it for production).
但更重要的是, TIdHTTPServer
不适合实现WebSockets(即是)。关于WebSocket协议的唯一涉及HTTP的事情就是握手。握手完成后,其他所有内容都是WebSocket框架,而不是HTTP。要在 TIdHTTPServer
中处理该问题,您需要在 OnCommandGet
内实现 entire WebSocket会话。事件,读取并发送所有WebSocket框架,防止事件处理程序退出,直到关闭连接。对于这种逻辑,我建议直接使用 TIdTCPServer
,并在其 OnExecute $ c开始时手动处理HTTP握手。 $ c>事件,然后循环处理事件的其余部分以处理WebSocket框架。
But more importantly, TIdHTTPServer
is not well-suited for implementing WebSockets (that is a TODO item). The only thing about the WebSocket protocol that involves HTTP is the handshake. After the handshake is finished, everything else is WebSocket framing, not HTTP. To handle that in TIdHTTPServer
requires you to implement the entire WebSocket session inside of the OnCommandGet
event, reading and sending all WebSocket frames, preventing the event handler from exiting, until the connection is closed. For that kind of logic, I would suggest using TIdTCPServer
directly instead, and just handle the HTTP handshake manually at the beginning of its OnExecute
event, and then loop the rest of the event handling the WebSocket frames.
您的 OnCommandOther
事件处理程序握手完成后,当前未执行任何WebSocket I / O。它将控制权返回给 TIdHTTPServer
,然后它将尝试读取新的HTTP请求。客户端将WebSocket框架发送到服务器后,由于 TIdHTTPServer
不是HTTP,因此将无法对其进行处理,并且可能会将HTTP响应发送回客户端,
Your OnCommandOther
event handler is not currently performing any WebSocket I/O after the handshake is finished. It is returning control to TIdHTTPServer
, which will then attempt to read a new HTTP request. As soon as the client sends a WebSocket frame to the server, TIdHTTPServer
will fail to process it since it is not HTTP, and will likely send an HTTP response back to the client, which will get misinterpreted, causing the client to fail the WebSocket session and close the socket connection.
话虽如此,请尝试使客户端无法通过WebSocket会话并关闭套接字连接。而是类似以下内容:
With that said, try something more like this instead:
#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。有些图书馆甚至建立在Indy之上。
That being said, there are plenty of 3rd party WebSocket libraries available. You should use one of them instead of implementing WebSockets manually. Some libraries even build on top of Indy.
这篇关于WebSocket连接到TIdHTTPServer,握手问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!