0

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("&nbsp;&nbsp;&nbsp;&nbsp;Reason: " + evt.code );
        writeToScreen("&nbsp;&nbsp;&nbsp;&nbsp;Reason: " + evt.reason );
        writeToScreen("&nbsp;&nbsp;&nbsp;&nbsp;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

DigitalJedi805
  • 1,486
  • 4
  • 16
  • 41
  • So you wrote client and serverside? can you provide the code or what kind of handshake you perform and how you do it? – Legy Apr 06 '12 at 22:31
  • Absolutely - I'll edit it in right now. – DigitalJedi805 Apr 06 '12 at 22:46
  • If there's anything inside of that that you need refined upon, please let me know. – DigitalJedi805 Apr 06 '12 at 22:50
  • what about the TcpListener? you catch up a new incoming conection, receive the new tcpclient... You do that in a new Thread? and what about try and catch there, you ask the stream for data... How did you handle it, when the client refuses the conection? and where do you calculate and du the handshake on the clientside? – Legy Apr 06 '12 at 22:55
  • I can paste the connector code if you like, but the listener gets its own thread, and each new connection gets its own thread. I do try/catch inside the acceptance code, and have yet to experience an exception there. As per the clientside, the handshake is sent when the object is instantiated, and as soon as I receive the last line of the handshake, I pass back my response. I'll post another edit. – DigitalJedi805 Apr 06 '12 at 22:59
  • can you provide some more information on what errors or exceptions you get on both sidesß – Legy Apr 06 '12 at 23:21
  • The connection throws an IOException from the server end as soon as I perform a flush. 'Connection was reset by the remote host' or some such. Client kicks back a code 1006, which I can't find much of a definition for. Thanks again, and if this still isn't enough I am sure I can dig into the server exception a little deeper. – DigitalJedi805 Apr 11 '12 at 16:19
  • No more thoughts @Kerwindena? – DigitalJedi805 Apr 25 '12 at 16:34

1 Answers1

1

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. See WsProtocol80::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:

  • Any subprotocol you specify should ideally include your domain name to minimise the chances of accidentally matching any incompatible protocol request. An early section of RFC 6455 explains why.
  • It'd be worth considering checking for the presence and value of a Sec-WebSocket-Protocol header in a request before responding with your supported subprotocol.
  • The order of headers in a client request isn't guaranteed so you could delay your response until you read an empty line.
Community
  • 1
  • 1
simonc
  • 41,632
  • 12
  • 85
  • 103
  • May be my end solution. I'm going to employ this and pass this as an answer. Thank you. This may have just saved me quite a headache. – DigitalJedi805 Apr 17 '12 at 20:07
  • Sadly - even after commenting out that line of my response I still end with the same result. If you don't have any further advice, I'll likely bump this to an answer due to the fact that it seems to be as close as I am going to get, but sadly, I am still in the same boat. – DigitalJedi805 Apr 17 '12 at 20:10
  • @DigitalJedi805 Ah, I've just spotted another problem. I'll update my answer in a sec – simonc Apr 17 '12 at 20:55
  • Much appreciated Simon. I'll take a look into this and be sure to let you know how it goes. I'm not sure that I'll have a chance before the bounty on this expires so it's yours; just stay on the line with me for a couple more days in-case I don't nail it down; if you don't mind. – DigitalJedi805 Apr 17 '12 at 21:10
  • Hey Simon, I've made a couple of changes as prescribed, but still haven't managed to get this worked out. Any advice at this point would be great. You've been a big help already, either way. I modified the greater portion of my original post to include changes. – DigitalJedi805 Apr 25 '12 at 16:25
  • What changes have you made and at which point does it now fail? If the changes are extensive enough to justify a new question, I'll spot it so long as it has the websocket keyword. – simonc Apr 25 '12 at 16:27
  • Well I removed the protocol response from the server, framed my data, and included a path in my websocket uri, but it still breaks in the same place. – DigitalJedi805 Apr 25 '12 at 16:30
  • I already edited my whole original. I just made another modification and got the protocols to match; client-to-server and back - still get a connection and crash as soon as I write. – DigitalJedi805 Apr 25 '12 at 16:43
  • Messages from (javascript) client to your server will be framed/masked. Does your InterpretMessage function cope with this? (I can't find code for it.) Also, messages from server to client must not be masked so your WriteMessage function needs a little work. (The framing here is still valid though; don't throw all the code away!) – simonc Apr 25 '12 at 18:50
  • I do have a 'DecodeMessage' method on the server end that I haven't actually nailed down yet, but in this circumstance it is relatively unimportant as I don't intend to even have an input field on the client side. I stole the WriteMessage method from the net, along with the accompanying two methods. I'll extract the masking and see what happens. Thanks for sticking with me. – DigitalJedi805 Apr 25 '12 at 20:33
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/10516/discussion-between-simonc-and-digitaljedi805) – simonc Apr 25 '12 at 20:57