我正在使用C ++ Builder 10.1 Berlin编写一个简单的WebSocket服务器应用程序,该应用程序在端口上侦听从Web浏览器(如Google 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个WebSocket连接到' ws:// localhost:55555 /'失败:一个或多个保留位开启:reserved1 = 1,reserved2 = 0,reserved3 = 0
我希望WebSocket连接成功,然后我可以在Web浏览器和我的服务器应用程序之间发送数据。
也许有人已经知道出了什么问题,并且可以展示如何实现这个目标的完整示例?
以下是我的应用程序的Memo1显示的内容:
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
答案 0 :(得分:3)
TIdHTTPServer
你犯了两大错:
您的OnConnect
事件处理程序正在读取客户端的初始HTTP请求行(GET
行)。它根本不应该从客户端读取任何内容,因为这样做会干扰TIdHTTPServer
对HTTP协议的处理。
在事件处理程序读取请求行并退出后,TIdHTTPServer
然后读取下一行(Host
标题)并将其解释为请求行,这就是原因:
ARequestInfo->Command
属性为"HOST:"
而不是"GET"
。
ARequestInfo->Host
,ARequestInfo->Document
,ARequestInfo->Version
,ARequestInfo->VersionMajor
,ARequestInfo->VersionMinor
属性都错了。
当您应该使用OnCommandOther
事件时,最终不得不使用OnCommandGet
事件。
您正在访问TMemo
事件中的TIdHTTPServer
,而不与主UI线程同步。 TIdHTTPServer
是一个多线程组件。它的事件在工作线程的上下文中被触发。 VCL / FMX UI控件不是线程安全的,因此您必须与主UI线程正确同步。
您的服务器没有验证WebSocket协议要求服务器验证的握手中的所有内容(这适用于测试,但请确保您为生产执行此操作)。
但更重要的是,TIdHTTPServer
不适合实现WebSockets(即TODO item)。涉及HTTP的WebSocket协议唯一的问题是握手。握手完成后,其他一切都是WebSocket框架,而不是HTTP。要在TIdHTTPServer
中处理它,需要在OnCommandGet
事件内部实现整个 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库可用。您应该使用其中一个而不是手动实现WebSockets。有些图书馆甚至建立在Indy之上。