I’ve written a WebSocket client in Javascript that connects and shakes hands with C# my server. Currently, upon ‘Flushing’ my output stream ( only after I have sent the header BACK to the client and affirmed my connection ), the client crashes and generates a server-side exception when I try to write the next time around. The latter behavior is expected, however what I can’t figure out is why the flush would drop the connection. I’m using a TcpListener and a Socket with a StreamWriter on the server end, and a plain WebSocket on the client.
What is truly perplexing about this scenario is that during transmission of the ‘handshake’, text can be transmitted both ways and a Flush is executed after each line is sent, but as soon as the handshake is complete, a flush terminates the connection.
Please tell me if there is not enough information in this revision; as it has been modified a few times now.
Thanks in advance.
Client 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("TestOutput\r\n\r");
}
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>
The handshake offer I recieve from my client is as follows:
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
I interpret the handshake like so:
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;
}
}
}
Send my Header Response:
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("\r\n");
}
And get the following output from the client ( at this point ):
#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
At this point, if I attempt to execute the following server-side method, the client disconnects like so:
#WebSocket Connection Aborted:
Reason: 1006
Reason:
Clean: false
Message Code:
-Taken from something I found on the net, modified a bit…
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] == '\0')
{
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();
}
UPDATE:
I just replaced all of the code in this document with my most current.
To summarize:
- 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.
One point I have only noticed while editing this, is the last few lines of my WriteMessage method. After doing all of my framing, I convert my byte array to a character array and use a StreamReader.Write to send it. I’m not sure if this is a viable solution or not, so please put me in check if it’s not.
Otherwise I am baffled. This seems to comply to all of the standards that I have read anything about, but still fails me miserably. This is quite the deal-maker if I can get it working, so I really do appreciate anyone’s help.
Thank you.
-DigitalJedi facepalm
The problem is caused by use of Sec-WebSocket-Protocol in the handshake response. The client did not request a subprotocol so the only valid response from the server is to complete the handshake without specifying a subprotocol. If the server responds with an unexpected subprotocol, the client is required to close the connection. See the /subprotocol/ section in section 4.2.2 of RFC 6455 for details.
The easiest fix is to remove the Sec-WebSocket-Protocol header from your response. If you want to retain it, you need to pass a subprotocol name as the second argument to the client’s WebSocket constructor and use this subprotocol in your server’s response. See the client API docs for details.
EDIT:
Once you’ve completed your handshake, the server will quite possibly fail trying to read the "TestOutput" message from the client’s onOpen. WebSocket messages aren’t plain text and don’t use HTTP so the line
this.ReadLine()is hugely unlikely to find a \r\n to terminate on. See the data framing section of the spec for details. This wiki post has some useful psuedo-code for websocket reads/writes. Or, you could try my C++ server. SeeWsProtocol80::Read()for how to read messages. Or look at one of the open source C# servers, such as Fleck (code to read/write messages is linked).There are some other small changes you could consider which would make your code more robust but won’t make the difference between an immediate pass and fail: