Writing a WebSocket server in C#

  • Revision slug: WebSockets/Writing_WebSocket_server
  • Revision title: Writing WebSocket server
  • Revision id: 352823
  • Created:
  • Creator: lumia
  • Is current revision? No
  • Comment

Revision Content

Introduction

If you would like to use the WebSocket API, it is useful if you have a server. :-) In this article I will show you how to write one in C#.

In lots of server side programming language you can do it, but to keep things simple and more understandable, I chose Microsoft's language.

This server gives which is required by RFC 6455 so it will only handle connections from Chrome version 16, Firefox 11, IE 10 and over.

First steps

WebSocket is communicating over a TCP (Transmission Control Protocol) connection, luckily C# has a TcpListener class which name tells everything. It is in System.Net.Sockets namespace.

It is a good idea to use using keyword to write less. It means you have not to retype the name of namespace if you use classes from it.

TcpListener

Constructor:

TcpListener(System.Net.IPAddress localaddr, int port)

You set here, where the server will be reachable.

To easily give the expected type to first parameter use the Parse static method of IPAddress.

Methods:

  • Start()
  • System.Net.Sockets.TcpClient AcceptTcpClient()
    Waiting for a connection and if it is over TCP, accepts it and returns it as a TcpClient object.

Let us use up what we have learnt.

​using System.Net.Sockets;
using System.Net;
using System;

class Server {
    public static void Main() {
        TcpListener server = new TcpListener(IPAddress.Parse("127.0.0.1"), 80);

        server.Start();
        Console.WriteLine("Server has started on 127.0.0.1:80.{0}Waiting for a connection...", Environment.NewLine);

        TcpClient client = server.AcceptTcpClient();

        Console.WriteLine("A client connected.");
    }
}

TcpClient

Methods:

  • System.Net.Sockets.NetworkStream GetStream()
    Gets the stream which is the communication channel. Write- and readable by both sides.

Properties:

  • int Available
    Number of bytes of data has been sent. Value of it is 0 until NetworkStream.DataAvailable is false.

NetworkStream

Methods:

Write(Byte[] buffer, int offset, int size)

Writes bytes from buffer from offset to size to stream.

Read(Byte[] buffer, int offset, int size)

Reads bytes to buffer from offset to size from stream.

Let us extend our example.

TcpClient client = server.AcceptTcpClient();

Console.WriteLine("A client connected.");

NetworkStream stream = client.GetStream();

//enter to an infinite cycle to be able to handle every change in stream
while (true) {
    while (!stream.DataAvailable);

    Byte[] bytes = new Byte[client.Available];

    stream.Read(bytes, 0, bytes.Length);
}

Handshaking

When a client connects to a server, sends a GET request to upgrade their connection to WebSocket from a simple HTTP request. This is known as handshake.

It is sent as plain text so we can easily seperate from other requests.

using System.Text;
using System.Text.RegularExpressions;

Byte[] bytes = new Byte[client.Available];

stream.Read(bytes, 0, bytes.Length);

//translate bytes of request to string
String request = Encoding.UTF8.GetString(bytes);

if (new Regex("^GET").IsMatch(data)) {

}

Creating the response is easier than understanding why you must do this way.

You must

  1. obtain the value of Sec-WebSocket-Key request header without any leading and trailing whitespace
  2. concatenate it with 258EAFA5-E914-47DA-95CA-C5AB0DC85B11
  3. compute SHA-1 and Base64 code of it
  4. write it back as value of Sec-WebSocket-Accept response header as part of a HTTP response.
if (new Regex("^GET").IsMatch(data)) {
    Byte[] response = Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" + Environment.NewLine
        + "Connection: Upgrade" + Environment.NewLine
        + "Upgrade: websocket" + Environment.NewLine
        + "Sec-WebSocket-Accept: " + Convert.ToBase64String (
            SHA1.Create().ComputeHash (
                Encoding.UTF8.GetBytes (
                    new Regex("Sec-WebSocket-Key: (.*)").Match(data).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
                )
            )
        ) + Environment.NewLine
        + Environment.NewLine);

    stream.Write(response, 0, response.Length);
}

Decoding messages

After a successful handshake client can send messages to the server, but now these are encoded.

If we sends "MDN", we get these bytes:

129 131 61 84 35 6 112 16 109

- 129:

FIN (Is this the whole message?) RSV1 RSV2 RSV3 Opcode
1 0 0 0 0x1=0001

You can send your message in frames, but now keep things simple.
Opcode 0x1 means this is a text. Full list of Opcodes

- 131:

If the second byte minus 128 is between 0 and 125, this is the length of message. If it is 126, the following 2 bytes, if 127, the following 3 bytes are the length.

I can take 128, because the first bit is always 1.

- 61, 84, 35 and 6 are the bytes of key to decode. Changes every time.

The remaining bytes are the encoded message.

Revision Source

<h2 id="Introduction">Introduction</h2>
<p>If you would like to use the WebSocket API, it is useful if you have a server. :-) In this article I will show you how to write one in C#.</p>
<div class="note">
  <p>In lots of server side programming language you can do it, but to keep things simple and more understandable, I chose Microsoft's language.</p>
</div>
<p>This server gives which is required by <a href="http://tools.ietf.org/html/rfc6455" title="http://tools.ietf.org/html/rfc6455">RFC 6455</a> so it will only handle connections from Chrome version 16, Firefox 11, IE 10 and over.</p>
<h2 id="First_steps">First steps</h2>
<p>WebSocket is communicating over a <a href="http://en.wikipedia.org/wiki/Transmission_Control_Protocol" title="http://en.wikipedia.org/wiki/Transmission_Control_Protocol">TCP (Transmission Control Protocol)</a>&nbsp;connection, luckily C# has a <a href="http://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener.aspx" title="http://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener.aspx">TcpListener</a> class which name tells everything. It is in <em>System.Net.Sockets</em> namespace.</p>
<div class="note">
  <p><span style="line-height: 1.572;">It is a good idea to use </span><em style="line-height: 1.572;"><span style="line-height: 1.572;">u</span><span style="line-height: 1.572;">sing</span></em><span style="line-height: 1.572;"> keyword to write less. It means you have not to retype the name of namespace if you use classes from it.</span></p>
</div>
<h3 id="TcpListener">TcpListener</h3>
<p>Constructor:</p>
<pre>
TcpListener(System.Net.IPAddress localaddr, int port)</pre>
<p>You set here, where the server will be reachable.</p>
<div class="note">
  <p><span style="line-height: 1.572;">To easily give the expected type to first parameter use the&nbsp;</span><em style="line-height: 1.572;">Parse</em><span style="line-height: 1.572;">&nbsp;static method of&nbsp;</span><em style="line-height: 1.572;">IPAddress.</em></p>
</div>
<p><span style="line-height: 1.572;">Methods</span><span style="line-height: 1.572;">:</span></p>
<ul>
  <li><span style="line-height: 1.572;">Start()</span></li>
  <li><span style="line-height: 1.572;">System.Net.Sockets.<a href="http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.aspx" title="http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.aspx">TcpClient</a> AcceptTcpClient()<br />
    Waiting for a connection and if it is over TCP, accepts it and returns it as a TcpClient object.</span></li>
</ul>
<p><span style="line-height: 1.572;">Let us use up what we have learnt.</span></p>
<pre>
<span style="line-height: 1.572;">​using System.Net.Sockets;
using System.Net;
using System;

class Server {
    public static void Main() {
        TcpListener server = new TcpListener(IPAddress.Parse("127.0.0.1"), 80);

        server.Start();
        Console.WriteLine("Server has started on 127.0.0.1:80.{0}Waiting for a connection...", Environment.NewLine);

        TcpClient client = server.AcceptTcpClient();

        </span><span style="line-height: 1.572;">Console.WriteLine("A client connected.");
</span><span style="line-height: 1.572;">    }
}</span></pre>
<h3 id="TcpClient"><span style="line-height: 1.572;">TcpClient</span></h3>
<p>Methods:</p>
<ul>
  <li>System.Net.Sockets.<a href="http://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream.aspx" title="http://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream.aspx">NetworkStream</a> GetStream()<br />
    Gets the stream which is the communication channel. Write- and readable by both sides.</li>
</ul>
<p>Properties:</p>
<ul>
  <li>int Available<br />
    Number of bytes of data has been sent. Value of it is 0 until <em>NetworkStream.DataAvailable</em> is <em>false</em>.</li>
</ul>
<h3 id="NetworkStream">NetworkStream</h3>
<p>Methods:</p>
<pre>
Write(Byte[] buffer, int offset, int size)</pre>
<p>Writes bytes from <em>buffer</em> from <em>offset</em>&nbsp;to <em>size</em> to stream.</p>
<pre>
<span style="line-height: 1.572;">Read(Byte[] buffer, int offset, int size)</span></pre>
<p>Reads bytes to <em>buffer</em> from <em>offset&nbsp;</em>to <em>size</em> from stream.</p>
<p>Let us extend our example.</p>
<pre>
TcpClient client = server.AcceptTcpClient();

Console.WriteLine("A client connected.");

NetworkStream stream = client.GetStream();

//enter to an infinite cycle to be able to handle every change in stream
while (true) {
    while (!stream.DataAvailable);

    Byte[] bytes = new Byte[client.Available];

    stream.Read(bytes, 0, bytes.Length);
}</pre>
<h2 id="Handshaking">Handshaking</h2>
<div class="note">
  <p>When a client connects to a server, sends a GET request to upgrade their connection to WebSocket from a simple HTTP request. This is known as handshake.</p>
</div>
<p>It is sent as plain text so we can easily seperate from other requests.</p>
<pre>
using System.Text;
using System.Text.RegularExpressions;

Byte[] bytes = new Byte[client.Available];

stream.Read(bytes, 0, bytes.Length);

//translate bytes of request to string
String request = Encoding.UTF8.GetString(bytes);

if (new Regex("^GET").IsMatch(data)) {

}</pre>
<p>Creating the response is easier than understanding why you must do this way.</p>
<p>You must</p>
<ol>
  <li>obtain the value of <em>Sec-WebSocket-Key</em> request header without any leading and trailing whitespace</li>
  <li>concatenate it with&nbsp;258EAFA5-E914-47DA-95CA-C5AB0DC85B11</li>
  <li>compute SHA-1 and Base64 code of it</li>
  <li>write it back as value of <em>Sec-WebSocket-Accept</em> response header as part of a HTTP response.</li>
</ol>
<pre>
if (new Regex("^GET").IsMatch(data)) {
    Byte[] response = Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" + Environment.NewLine
        + "Connection: Upgrade" + Environment.NewLine
        + "Upgrade: websocket" + Environment.NewLine
        + "Sec-WebSocket-Accept: " + Convert.ToBase64String (
            SHA1.Create().ComputeHash (
                Encoding.UTF8.GetBytes (
                    new Regex("Sec-WebSocket-Key: (.*)").Match(data).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
                )
            )
        ) + Environment.NewLine
        + Environment.NewLine);

    stream.Write(response, 0, response.Length);
}</pre>
<h2 id="Decoding_data">Decoding messages</h2>
<p>After a successful handshake client can send messages to the server, but now these are encoded.</p>
<p>If we sends "MDN", we get these bytes:</p>
<table>
  <tbody>
    <tr>
      <td>129</td>
      <td>131</td>
      <td>61</td>
      <td>84</td>
      <td>35</td>
      <td>6</td>
      <td>112</td>
      <td>16</td>
      <td>109</td>
    </tr>
  </tbody>
</table>
<p>- 129:</p>
<table>
  <thead>
    <tr>
      <th scope="col">FIN (Is this the whole message?)</th>
      <th scope="col">RSV1</th>
      <th scope="col">RSV2</th>
      <th scope="col">RSV3</th>
      <th scope="col">Opcode</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
      <td>0x1=0001</td>
    </tr>
  </tbody>
</table>
<p>You can send your message in frames, but now keep things simple.<br />
  <span style="line-height: 1.572;">Opcode </span><em style="line-height: 1.572;">0x1</em><span style="line-height: 1.572;"> means this is a text. </span><a href="http://tools.ietf.org/html/rfc6455#section-5.2" style="line-height: 1.572;" title="http://tools.ietf.org/html/rfc6455#section-5.2">Full list of Opcodes</a></p>
<p>- 131:</p>
<p>If the second byte minus 128 is between 0 and 125, this is the length of message. If it is 126, the following 2 bytes, if 127, the following 3 bytes are the length.</p>
<div class="note">
  <p>I can take 128, because the first bit is always 1.</p>
</div>
<p>- 61, 84, 35 and 6 are the bytes of key to decode. Changes every time.</p>
<p>The remaining bytes are the<span style="line-height: 1.572;">&nbsp;</span><span style="line-height: 1.572;">encoded</span><span style="line-height: 1.572;">&nbsp;message.</span></p>
Revert to this revision