这篇翻译不完整。请帮忙从英语翻译这篇文章

介绍

如果你想学习如何使用 WebSocket API,那么有一台服务器将会是非常有用的。在本文中,我将向你展示如何使用C#来写后端。你可以使用任何可用于后端开发的语言来做这个事, 但是,要为了使例子简明易懂,我选择微软的C#。

此服务器符合 RFC 6455 因此,因此它只处理来自 Chrome16,Firefox 11,IE 10 及更高版本的连接。

第一步

WebSockets 通过 TCP (传输控制协议) 连接进行通信.。幸运的是, C# 中有一个 TcpListener 类。 它位于 System.Net.Sockets 的命名空间。

最好使用 using 关键字来包含命名空间,这样在你写代码的时候就不需要指定详细的命名空间。

TcpListener

构造函数:

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

localaddr 是监听地址, and port 是监听端口.

To create an IPAddress object from a string, use the Parse static method of IPAddress.

Methods:

  • Start()
  • System.Net.Sockets.TcpClient AcceptTcpClient()
    Waits for a Tcp connection, accepts it and returns it as a TcpClient object.

Here's a barebones server implementation:

​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. Both sides of the channel have reading and writing capability.

Properties:

  • int Available
    This Property indicates how many bytes of data have been sent. The value is zero until NetworkStream.DataAvailable is true.

NetworkStream

Methods:

  • Write(Byte[] buffer, int offset, int size)
    Writes bytes from buffer, offset and size determine length of message.
  • Read(Byte[] buffer, int offset, int size)
    Reads bytes to buffer. offset and size determine the length of the message.

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, it sends a GET request to upgrade the connection to a WebSocket from a simple HTTP request. This is known as handshaking.

This sample code can detect a GET from the client. Note that this will block until the first 3 bytes of a message are available. Alternative solutions should be investigated for production environments.

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

while(client.Available < 3)
{
   // wait for enough bytes to be available
}

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

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

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

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

} else {

}

The response is easy to build, but might be a little bit difficult to understand. The full explanation of the Server handshake can be found in RFC 6455, section 4.2.2. For our purposes, we'll just build a simple response.

You must:

  1. Obtain the value of the "Sec-WebSocket-Key" request header without any leading or trailing whitespace
  2. Concatenate it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (a special GUID specified by RFC 6455)
  3. Compute SHA-1 and Base64 hash of the new value
  4. Write the hash back as the value of "Sec-WebSocket-Accept" response header in an HTTP response

if (new System.Text.RegularExpressions.Regex("^GET").IsMatch(data))
{
    const string eol = "\r\n"; // HTTP/1.1 defines the sequence CR LF as the end-of-line marker

    Byte[] response = Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" + eol
        + "Connection: Upgrade" + eol
        + "Upgrade: websocket" + eol
        + "Sec-WebSocket-Accept: " + Convert.ToBase64String(
            System.Security.Cryptography.SHA1.Create().ComputeHash(
                Encoding.UTF8.GetBytes(
                    new System.Text.RegularExpressions.Regex("Sec-WebSocket-Key: (.*)").Match(data).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
                )
            )
        ) + eol
        + eol);

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

Decoding messages

After a successful handshake, the client will send encoded messages to the server.

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

129 131 61 84 35 6 112 16 109

Let's take a look at what these bytes mean.

The first byte, which currently has a value of 129, is a bitfield that breaks down as such:

FIN (Bit 0) RSV1 (Bit 1) RSV2 (Bit 2) RSV3 (Bit 3) Opcode (Bit 4:7)
1 0 0 0 0x1=0001
  • FIN bit: This bit indicates whether the full message has been sent from the client. Messages may be sent in frames, but for now we will keep things simple.
  • RSV1, RSV2, RSV3: These bits must be 0 unless an extension is negotiated which supplies a nonzero value to them.
  • Opcode: These bits describe the type of message received. Opcode 0x1 means this is a text message. Full list of Opcodes

The second byte, which currently has a value of 131, is another bitfield that breaks down as such:

MASK (Bit 0) Payload Length (Bit 1:7)
1 0x83=0000011
  • MASK bit: Defines whether the "Payload data" is masked.  If set to 1, a masking key is present in Masking-Key, and this is used to unmask the "Payload data". All messages from the client to the server have this bit set.
  • Payload Length: If this value is between 0 and 125, then it is the length of message. If it is 126, the following 2 bytes (16-bit unsigned integer) are the length. If it is 127, the following 8 bytes (64-bit unsigned integer) are the length.

Because the first bit is always 1 for client-to-server messages, you can subtract 128 from this byte to get rid of the MASK bit.

Note that the MASK bit is set in our message. This means that the next four bytes (61, 84, 35, and 6) are the mask bytes used to decode the message. These bytes change with every message.

The remaining bytes are the encoded message payload.

Decoding algorithm

Di = Ei XOR M(i mod 4)

where D is the decoded message array, E is the encoded message array, M is the mask byte array, and i is the index of the message byte to decode.

Example in C#:

Byte[] decoded = new Byte[3];
Byte[] encoded = new Byte[3] {112, 16, 109};
Byte[] mask = new Byte[4] {61, 84, 35, 6};

for (int i = 0; i < encoded.Length; i++) {
    decoded[i] = (Byte)(encoded[i] ^ mask[i % 4]);
}
 

文档标签和贡献者

此页面的贡献者: NutAcorn
最后编辑者: NutAcorn,