我用Javascript编写了一个WebSocket客户端,它可以与我的服务器C#连接并握手。目前,在"刷新"我的输出流时(仅在我将标头发送回客户端并确认我的连接之后),当我下次尝试写入时,客户端崩溃并生成服务器端异常。后一种行为是意料之中的,但是我无法弄清楚为什么刷新会断开连接。我在服务器端使用带有StreamWriter的TcpListener和Socket,在客户端使用普通WebSocket。
这种情况真正令人困惑的是,在"握手"传输期间,文本可以双向传输,并且在发送每行后执行刷新,但是一旦握手完成,刷新就会终止连接。
如果这次修订中没有足够的信息,请告诉我;因为它现在已经修改了几次。
提前谢谢。
客户端 Javascript:
<!DOCTYPE html>
<meta charset="utf-8" />
<html>
<head>
<script language="javascript" type="text/javascript">
var wsUri = "ws://127.0.0.1:9002/cc";
var output;
var websocket = null;
function init()
{
StartWebSocket();
}
function StartWebSocket()
{
output = document.getElementById("output");
writeToScreen("#WebSocket Starting");
websocket = new WebSocket(wsUri,"lorem.ipsum.com");
writeToScreen("#WebSocket Instantiated");
websocket.removeEventListener("open",onOpen,false);
websocket.addEventListener("open",onOpen,false);
websocket.removeEventListener("close",onClose,false);
websocket.addEventListener("close",onClose,false);
websocket.removeEventListener("message",onMessage,false);
websocket.addEventListener("message",onMessage,false);
websocket.removeEventListener("error",onError,false);
websocket.addEventListener("error",onError,false);
writeToScreen("#WebSocket Events Attached");
}
function onOpen(evt)
{
try
{
writeToScreen("#WebSocket Connection Established");
writeToScreen("#WebSocket BinaryType: " + websocket.binaryType);
writeToScreen("#WebSocket Protocol: " + websocket.protocol);
writeToScreen("#WebSocket Extensions: " + websocket.extensions);
doSend("TestOutputrnr");
}
catch( e )
{
writeToScreen(e);
}
}
function onClose(evt)
{
writeToScreen("#WebSocket Connection Aborted:");
writeToScreen(" Reason: " + evt.code );
writeToScreen(" Reason: " + evt.reason );
writeToScreen(" Clean: " + evt.wasClean);
}
function onMessage(evt)
{
writeToScreen("#WebSocket Message Event");
try
{
writeToScreen("<span style="color: blue;">#WebSocket Server Message: " + evt.data+"</span>");
}
catch( e )
{
writeToScreen(e);
}
}
function onError(evt)
{
writeToScreen("<span style="color: red;">#WebSocket Error:</span> " + evt.data);
}
function doSend(message)
{
try
{
websocket.send(message);
writeToScreen("#WebSocket Output Written to Server: " + message);
}
catch( e )
{
writeToScreen(e);
}
}
function writeToScreen(message)
{
try
{
var pre = document.createElement("a");
pre.style.wordWrap = "break-word";
pre.innerHTML = message + "<br>";
output.appendChild(pre);
}
catch( e )
{
writeToScreen(e);
}
}
window.addEventListener("load", init, false);
</script>
</head>
<body>
<div id="output"></div>
</body>
</html>
我从客户那里收到的握手提议如下:
GET /cc HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 127.0.0.1:9002
Origin: http://localhost
Sec-WebSocket-Key: icajBpkAfgA+YbVheBpDsQ==
Sec-WebSocket-Version: 13
我这样解释握手:
public override void Interpret(string Argument)
{
if (String.IsNullOrEmpty(Argument))
{
return;
}
else
{
if( !HeaderFinished )
{
if (!HeaderStarted)
{
if (Argument.StartsWith("GET /"))
{
this.Role = "client";
HeaderStarted = true;
this.Server.Print("Connection at " + this.Address + " set to client.");
}
else
{
return;
}
}
else
{
if (Argument.StartsWith("Sec-WebSocket-Key:"))
{
this.Key = Argument.Split(' ')[1].TrimEnd().TrimStart();
return;
}
else if (Argument.StartsWith("Sec-WebSocket-Version:"))
{
this.HeaderFinished = true;
this.WriteHeaderResponse();
HeaderSent = true;
return;
}
}
}
else
{
this.InterpretMessage(DecodeMessage(Argument));
return;
}
}
}
发送我的标头响应:
public void WriteHeaderResponse()
{
this.WriteLine("HTTP/1.1 101 Switching Protocols");
this.WriteLine("Upgrade: websocket");
this.WriteLine("Connection: Upgrade");
String NewKey = ComputeResponseKey(this.Key);
this.WriteLine("Sec-WebSocket-Accept: " + NewKey);
this.WriteLine("Sec-WebSocket-Protocol: lorem.ipsum.com");
this.WriteLine("rn");
}
并从客户端获取以下输出(此时):
#WebSocket Starting
#WebSocket Instantiated
#WebSocket Events Attached
#WebSocket Connection Established
#WebSocket BinaryType: blob
#WebSocket Protocol: lorem.ipsum.com
#WebSocket Extensions:
#WebSocket Output Written to Server: TestOutput
此时,如果我尝试执行以下服务器端方法,客户端将断开连接,如下所示:
#WebSocket Connection Aborted:
Reason: 1006
Reason:
Clean: false
消息代码:-取自我在网上找到的东西,修改了一下...
public void WriteMessage(byte[] Payload)
{
byte[] Message;
int Length = Payload.Length;
int MaskLength = 4;
if (Length < 126)
{
Message = new byte[2 + MaskLength + Length];
Message[1] = (byte)Length;
}
else if (Length < 65536)
{
Message = new byte[4 + MaskLength + Length];
Message[1] = (byte)126;
Message[2] = (byte)(Length / 256);
Message[3] = (byte)(Length % 256);
}
else
{
Message = new byte[10 + MaskLength + Length];
Message[1] = (byte)127;
int left = Length;
int unit = 256;
for (int i = 9; i > 1; i--)
{
Message[i] = (byte)(left % unit);
left = left / unit;
if (left == 0)
break;
}
}
//Set FIN
Message[0] = (byte)129;// (0 | 0x80);
//Set mask bit
//Message[1] = (byte)(Message[1] | 0x80);
//GenerateMask(Message, Message.Length - MaskLength - Length);
//if (Length > 0)
//MaskData(Payload, 0, Length, Message, Message.Length - Length, Message, Message.Length - MaskLength - Length);
char[] output = new char[Message.Length-4];
for( int i = 0, y = 0, z = 0; i < Message.Length; i++ )
{
if (Message[z] == ' ')
{
if (Payload.Length > i-z)
output[i] = (char)Payload[y++];
}
else
{
output[i] = (char)Message[z++];
}
}
this.OutputWriter.Write(output, 0, output.Length);
this.OutputWriter.Flush();
}
更新:我刚刚用我最新的代码替换了本文档中的所有代码。
总结一下:
- The client-server handshake has been matched on both sides.
- A path has been defined in the URI for the WebSocket.
- Data packets are now being 'properly' framed.
我只在编辑时注意到的一点是我的 WriteMessage 方法的最后几行。完成所有成帧后,我将字节数组转换为字符数组并使用 StreamReader.Write 发送它。我不确定这是否是一个可行的解决方案,所以如果不是,请让我检查一下。
否则我会感到困惑。这似乎符合我读过的所有标准,但仍然让我惨遭失败。如果我能让它发挥作用,这是相当的交易撮合者,所以我真的很感谢任何人的帮助。
谢谢。-数字绝地脸掌
该问题是由在握手响应中使用 Sec-WebSocket-Protocol 引起的。 客户端未请求子协议,因此来自服务器的唯一有效响应是在不指定子协议的情况下完成握手。 如果服务器使用意外的子协议进行响应,则需要客户端关闭连接。 有关详细信息,请参阅 RFC 6455 第 4.2.2 节中的/subprotocol/部分。
最简单的解决方法是从响应中删除 Sec-WebSocket-Protocol 标头。 如果要保留它,则需要将子协议名称作为第二个参数传递给客户端的 WebSocket 构造函数,并在服务器的响应中使用此子协议。 有关详细信息,请参阅客户端 API 文档。
编辑:
一旦你完成了握手,服务器很可能会失败,试图从客户端的onOpen读取"TestOutput"消息。 WebSocket 消息不是纯文本,也不使用 HTTP,因此this.ReadLine()
行不太可能找到要终止的 \r。 有关详细信息,请参阅规范的数据框架部分。 这个维基帖子有一些有用的伪代码用于websocket读取/写入。 或者,您可以尝试我的C++服务器。 请参阅WsProtocol80::Read()
,了解如何阅读邮件。 或者看看开源的C#服务器之一,如Fleck(读/写消息的代码是链接的)。
您可以考虑其他一些小的更改,这些更改会使您的代码更加健壮,但不会在立即通过和失败之间产生区别:
- 理想情况下,您指定的任何子协议都应包含您的域名,以最大程度地减少意外匹配任何不兼容协议请求的机会。 RFC 6455 的早期部分解释了原因。 在使用
- 支持的子协议进行响应之前,值得考虑检查请求中 Sec-WebSocket-Protocol 标头的存在和值。
- 无法保证客户端请求中标头的顺序,因此可以延迟响应,直到读取空行。