views:

2671

answers:

4

I'm trying to implement a C# web socket server, but its giving me a few troubles. I'm running a webserver(ASP.NET) to host the page with the javascript and the web socket server is implemented as a C# console application.

I'm able to detect the connection attempt from the client (chrome running the javascript) and also to retrieve the handshake from the client. But the client doesn't seem to accept the handshake I'm sending back (the onopen function on the web socket is never called).

I've been reading the The Web Socket protocol and I can't see what I'm doing wrong. Heres a bit of the server code:

Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
IPEndPoint ep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8181);
listener.Bind(ep);
listener.Listen(100);
Console.WriteLine("Wainting for connection...");
Socket socketForClient = listener.Accept();
if (socketForClient.Connected)
{
    Console.WriteLine("Client connected");
    NetworkStream networkStream = new NetworkStream(socketForClient);
    System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(networkStream);
    System.IO.StreamReader streamReader = new System.IO.StreamReader(networkStream);

    //read handshake from client:
    Console.WriteLine("HANDSHAKING...");
    char[] shake = new char[255];
    streamReader.Read(shake, 0, 255);

    string handshake =
       "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" +
       "Upgrade: WebSocket\r\n" +
       "Connection: Upgrade\r\n" +
       "WebSocket-Origin: http://localhost:8080\r\n" +
       "WebSocket-Location: ws://localhost:8181\r\n" +
       "\r\n";

    streamWriter.Write(handshake);
    streamWriter.Flush();

I'm running to web server on port 8080 and the web socket server on port 8181, both on my localhost.

I've tried sending the handshake in different encodings (ASCII, bytes and Hex) but this doesn't seem to make a difference. The connection is never fully established. The javascript looks like this:

var ws;
var host = 'ws://localhost:8181';
debug("Connecting to " + host + " ...");
try {
 ws = new WebSocket(host);
} catch (err) {
 debug(err, 'error');
}
ws.onopen = function () {
 debug("connected...", 'success');
};
ws.onclose = function () {
 debug("Socket closed!", 'error');
};
ws.onmessage = function (evt) {
 debug('response: ' + evt, 'response');
};

I'm guessing that the error lies in the C# server as chrome is sending the information as it should, but as a said the onopen function is never called.

So my question in short: Has any of you ever accomplished this - and if yes, how did you do it? And of cause: Do you see any apparent errors in the code (hope thats not to much to ask)

+10  A: 

Probably it's an encoding issue. Here's a working C# server I wrote:

class Program
{
    static void Main(string[] args)
    {
        var listener = new TcpListener(IPAddress.Loopback, 8181);
        listener.Start();
        using (var client = listener.AcceptTcpClient())
        using (var stream = client.GetStream())
        using (var reader = new StreamReader(stream))
        using (var writer = new StreamWriter(stream))
        {
            writer.WriteLine("HTTP/1.1 101 Web Socket Protocol Handshake");
            writer.WriteLine("Upgrade: WebSocket");
            writer.WriteLine("Connection: Upgrade");
            writer.WriteLine("WebSocket-Origin: http://localhost:8080");
            writer.WriteLine("WebSocket-Location: ws://localhost:8181/websession");
            writer.WriteLine("");
        }
        listener.Stop();
    }
}

And the corresponding client hosted on localhost:8080:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
<html xmlns="http://www.w3.org/1999/xhtml"&gt;
<head>
    <script type="text/javascript">
        var socket = new WebSocket('ws://localhost:8181/websession');
        socket.onopen = function() {
            alert('handshake successfully established. May send data now...');
        };
        socket.onclose = function() {
            alert('connection closed');
        };
    </script>
</head>
<body>
</body>
</html>

This example only establishes the handshake. You will need to tweak the server in order to continue accepting data once the handshake has been established.

Darin Dimitrov
Thanks! Turns out it was the WebSocket-Location part of the handshake that was giving me trouble. Apparently it can't be just 'ws://localhost:8181'.After changing it to 'ws://localhost:8181/something' and changing the host in the javascript to the same thing - it worked.
Jon List
Thanks for the post. I was attempting this with Chrome + a static html file and had to change the origin to prevent the browser from immediately disconnecting:writer.WriteLine("WebSocket-Origin: file://");
CaffeineFueled
+6  A: 

After getting the web socket server working, i created a small sample appilcation, and posted and article about it on codeproject: Web Socket Server

Go take a look and download the source if you are interested.

Jon List
That's a great article. Thanks Jon. But unfortunately, Chrome changed its web sockets to the -76 version of the protocol (http://blog.chromium.org/2010/06/websocket-protocol-updated.html), so your example no longer works. Any chance you can update your example? Thanks in advance.
JohnnyO
Hi, thanks for the feedback!I have changed the example quite a bit, and put it up on codeplex: http://nugget.codeplex.com/.I have implemented the basic parts of the new protocol (76 aka 00), so go take a look there.
Jon List
Wow Jon, very impressive. I would have never been able to figure this out myself. It works really well in Chrome 6 and Safari 5. Thanks for sharing your work.
JohnnyO
A: 

Hi Jon, I can't test out your example because I don't have c#, but I am trying to do the same thing in C++ and I also haven't been able to get socket.onopen to trigger. I am receiving the GET from the browser and am writing this return handshake:

HTTP/1.1 101 Web Socket Protocol Handshake\r\n
Upgrade: WebSocket\r\n
Connection: Upgrade\r\n
Sec-WebSocket-Origin: null\r\n
Sec-WebSocket-Location: ws://localhost:8080/test\r\n
\r\n
1{ñF¨[‹ÁùVƒ@'Ù

The null origin is correct (the console warns me of a mismatch if I set it to anything else)
I am certain the challenge code is correct (it works in the protocol example)
I have tried making WebSocket one word (which it should be)
Chromium neither warns of any problems in the console, nor does it trigger the onopen... but it does trigger an onclose a little while later.

Does anyone know how I can debug further to discover why chromium won't open the websocket? Thanks, I'm totally stuck on this one!

Tom
Is onclose called? I had a problem where neither onclose, or onopen was called, the solution was to NOT encode the answer to the challenge.I make an array just large enough to hold the handshake plus the 16 bytes for the answer, i stuff the handshake into the array (UTF8-encoded) and then fill the remaining 16 bytes with the unencoded MD5 answer.
Jon List
Thanks Jon for your comment, I did notice that you UTF8-encoded the message part, but I looked up UTF8 encoding and it seems that it is exactly equivalent to ascii for the first 128 characters (which encompasses every character used in the reply), so I decided I didn't need to encode it.It makes sense that is didn't work when you encoded the challenge answer.onclose does get called, but not straight away and not always, I haven't worked that one out yet.Do you think the only hope I have is to download the source to chromium and debug it? I would really like to avoid going down that route.
Tom
debugging the chromium source seems like a lot of work. Could it be that the .NET sockets are doing some magic for me? They might be sending an end-of-stream char or something similar?
Jon List
Thanks for your help Jon, I got it to work! I had tested the challenge answer was correct from the example, but had made a mistake in reading the final 8 bytes, I was reading from before the final \r\n\r\n rather than after, of course it looked right as it was still scrambled data from the end of the key-2!So all working now
Tom
A: 

I just discovered this websocket stuff and explored and I am running into the same issue with OnOpen never been fired and getting INVALID_STATE_ERR when trying to Send a string to the server. I am using the phpwebsocket server example are google code but its all is pretty simple as a socket server. What exactly did you guys figure out with the server being an error causing Chrome to not set the readyState to 1 (thus firing onOpen)? I mixed the fix above. Thanks

Hector