C# Socket实现Websocket服务端协议

因为要用到websocket与前台通信,整理了一部分c#使用 socket实现的代码

原文连接 
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_server#handshaking


 

Writing a WebSocket server in C#

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#. You can do it in any server-side language, but to keep things simple and more understandable, I chose Microsoft's language.

This server conforms to RFC 6455, so it will only handle connections from Chrome version 16, Firefox 11, IE 10 and over.

First steps

WebSockets communicate over a TCP (Transmission Control Protocol) connection. Luckily, C# has a TcpListener class which does as the name suggests. It is in the System.Net.Sockets namespace.

Note: It is a good idea to include the namespace with the using keyword in order to write less. It allows usage of a namespace's classes without typing the full namespace every time.

TcpListener

Constructor:

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

Note: 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)Copy to Clipboard

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

    Reads bytes to bufferoffset 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 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)
10000x1=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)
10x83=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.

Note: 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

D_i = E_i 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]);

}




 

//

// csc wsserver.cs

// wsserver.exe

using System;

using System.Net;

using System.Net.Sockets;

using System.Text;

using System.Text.RegularExpressions;

class Server {

    public static void Main() {

        string ip = "127.0.0.1";

        int port = 80;

        var server = new TcpListener(IPAddress.Parse(ip), port);

        server.Start();

        Console.WriteLine("Server has started on {0}:{1}, Waiting for a connection…", ip, port);

        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);

            while (client.Available < 3); // match against "get"

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

            stream.Read(bytes, 0, client.Available);

            string s = Encoding.UTF8.GetString(bytes);

            if (Regex.IsMatch(s, "^GET", RegexOptions.IgnoreCase)) {

                Console.WriteLine("=====Handshaking from client=====\n{0}", s);

                // 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

                string swk = Regex.Match(s, "Sec-WebSocket-Key: (.*)").Groups[1].Value.Trim();

                string swka = swk + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

                byte[] swkaSha1 = System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(swka));

                string swkaSha1Base64 = Convert.ToBase64String(swkaSha1);

                // 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\r\n" +

                    "Connection: Upgrade\r\n" +

                    "Upgrade: websocket\r\n" +

                    "Sec-WebSocket-Accept: " + swkaSha1Base64 + "\r\n\r\n");

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

            } else {

                bool fin = (bytes[0] & 0b10000000) != 0,

                    mask = (bytes[1] & 0b10000000) != 0; // must be true, "All messages from the client to the server have this bit set"

                int opcode = bytes[0] & 0b00001111, // expecting 1 - text message

                    offset = 2;

                ulong msglen = bytes[1] & 0b01111111;

                if (msglen == 126) {

                    // bytes are reversed because websocket will print them in Big-Endian, whereas

                    // BitConverter will want them arranged in little-endian on windows

                    msglen = BitConverter.ToUInt16(new byte[] { bytes[3], bytes[2] }, 0);

                    offset = 4;

                } else if (msglen == 127) {

                    // To test the below code, we need to manually buffer larger messages — since the NIC's autobuffering

                    // may be too latency-friendly for this code to run (that is, we may have only some of the bytes in this

                    // websocket frame available through client.Available).

                    msglen = BitConverter.ToUInt64(new byte[] { bytes[9], bytes[8], bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2] },0);

                    offset = 10;

                }

                if (msglen == 0)

                    Console.WriteLine("msglen == 0");

                else if (mask) {

                    byte[] decoded = new byte[msglen];

                    byte[] masks = new byte[4] { bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3] };

                    offset += 4;

                    for (ulong i = 0; i < msglen; ++i)

                        decoded[i] = (byte)(bytes[offset + i] ^ masks[i % 4]);

                    string text = Encoding.UTF8.GetString(decoded);

                    Console.WriteLine("{0}", text);

                } else

                    Console.WriteLine("mask bit not set");

                Console.WriteLine();

            }

        }

    }

}


 




 

<!doctype html>

<style>

    textarea { vertical-align: bottom; }

    #output { overflow: auto; }

    #output > p { overflow-wrap: break-word; }

    #output span { color: blue; }

    #output span.error { color: red; }

</style>

<h2>WebSocket Test</h2>

<textarea cols=60 rows=6></textarea>

<button>send</button>

<div id=output></div>

<script>

  // WebSocket Echo Server | WebSocket.org

  const button = document.querySelector("button");

  const output = document.querySelector("#output");

  const textarea = document.querySelector("textarea");

  const wsUri = "ws://127.0.0.1/";

  const websocket = new WebSocket(wsUri);

  button.addEventListener("click", onClickButton);

  websocket.onopen = (e) => {

    writeToScreen("CONNECTED");

    doSend("WebSocket rocks");

  };

  websocket.onclose = (e) => {

    writeToScreen("DISCONNECTED");

  };

  websocket.onmessage = (e) => {

    writeToScreen(`<span>RESPONSE: ${e.data}</span>`);

  };

  websocket.onerror = (e) => {

    writeToScreen(`<span class=error>ERROR:</span> ${e.data}`);

  };

  function doSend(message) {

    writeToScreen(`SENT: ${message}`);

    websocket.send(message);

  }

  function writeToScreen(message) {

    output.insertAdjacentHTML("afterbegin", `<p>${message}</p>`);

  }

  function onClickButton() {

    const text = textarea.value;

    text && doSend(text);

    textarea.value = "";

    textarea.focus();

  }

</script>

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Unity 是一款强大的游戏开发引擎,但本身并不直接支持 WebSocket 服务端。但是,我们可以借助 Unity 的网络功能和一些第三方库来实现 WebSocket 服务端。 要实现 WebSocket 服务端,我们可以使用 .NET 库中的 HttpListener 类。首先,我们需要创建一个新的 C# 脚本,并在其中引入 System.Net 命名空间。然后,我们可以创建一个 HttpListener 对象,设置监听的地址和端口。例如: HttpListener httpListener = new HttpListener(); httpListener.Prefixes.Add("http://localhost:8080/"); 接下来,我们需要开始监听这个地址和端口: httpListener.Start(); 然后,我们可以使用异步方式等待客户端的连接: IAsyncResult result = httpListener.BeginGetContext(new AsyncCallback(ListenerCallback), httpListener); 在 ListenerCallback 回调函数中,我们可以获取客户端的连接和请求信息: void ListenerCallback(IAsyncResult result) { HttpListener listener = (HttpListener)result.AsyncState; HttpListenerContext context = listener.EndGetContext(result); // 处理客户端的请求 } 在处理客户端请求的过程中,我们可以根据不同的请求类型,实现相应的 WebSocket 逻辑。使用 .NET 中的 WebSocket 类,我们可以从 HttpListenerContext 中获取 WebSocket 对象,并执行诸如发送消息、接收消息等操作。 需要注意的是,为了实现完整的 WebSocket 逻辑,我们可能还需要处理握手过程中的协议判断、消息编码解码等细节。 综上所述,要在 Unity 中实现 WebSocket 服务端,我们可以利用 .NET 中的 HttpListener 类来监听客户端连接,并在处理请求过程中实现 WebSocket 的相关逻辑。这样就可以通过 Unity 实现 WebSocket 服务端了。 ### 回答2: Unity 是一款游戏开发引擎,通常用于开发各种类型的游戏。虽然 Unity 自身不支持直接实现 WebSocket 服务端,但我们可以通过使用插件或自定义脚本来实现。 首先,我们可以选择使用第三方插件或库,如 Best HTTP、WebSocket-Sharp 等来在 Unity 中实现 WebSocket 服务端。这些插件可以通过提供的 API 来创建、监听和处理 WebSocket 连接。我们可以根据项目需求选择最适合的插件,然后按照其文档进行配置和使用。 另外,如果我们希望自己编写 WebSocket 服务端,可以通过使用 Unity 提供的网络相关 API 来实现。首先,我们可以通过 Unity 的 NetworkTransport 类来创建一个基于 UDP 或 TCP 的网络连接。然后,我们可以使用 NetworkTransport.ReceiveFromHost() 方法来接收来自客户端的消息,并使用 NetworkTransport.SendToHost() 方法向客户端发送消息。使用这些 API,我们可以在 Unity 中实现一个简单的 WebSocket 服务端。 不过需要注意的是,WebSocket 是一种基于 TCP 的双向通信协议,与 HTTP 协议不同。在实现 WebSocket 服务端时,我们需要遵循 WebSocket协议规范,并正确处理握手、数据帧等操作。此外,我们还需要考虑并发连接、消息分发等问题,以确保 WebSocket 服务端的稳定性和可靠性。 总结来说,Unity 虽然不直接支持实现 WebSocket 服务端,但我们可以通过使用第三方插件或自定义脚本来实现。无论选择哪种方式,我们都需要理解 WebSocket协议规范,并根据需求正确配置和使用相关的插件或 API。 ### 回答3: 使用Unity实现WebSocket服务器可以通过Unity自带的Networking组件以及C#中的WebSocketSharp库来实现。下面是一个简单的步骤: 1. 在Unity中创建一个空的场景。然后创建一个空的游戏对象,并添加一个脚本来处理WebSocket服务器的逻辑。 2. 在脚本中导入WebSocketSharp库。你可以通过下载WebSocketSharp库的源代码,然后将其导入到Unity项目中,或者使用其他方法(如NuGet)从包管理器中引入。 3. 在脚本中添加WebSocket服务器的逻辑。你需要创建一个WebSocket服务器对象,并指定监听的端口号。例如: ``` using WebSocketSharp; using UnityEngine; public class WebSocketServer : MonoBehaviour { private WebSocketServer wsServer; private void Start() { wsServer = new WebSocketServer(12345); // 指定端口号 wsServer.Start(); wsServer.OnMessage += (sender, e) => { Debug.Log("Received message: " + e.Data); // 处理接收到的消息 }; wsServer.OnClose += (sender, e) => { Debug.Log("Connection closed"); // 处理连接关闭的逻辑 }; } private void OnDestroy() { if (wsServer != null) { wsServer.Stop(); wsServer = null; } } } ``` 4. 将脚本挂载到空的游戏对象上。然后按下Play按钮以在Unity中启动WebSocket服务器。 5. 在客户端上使用WebSocket连接到服务器。你可以使用浏览器的WebSocket API或其他WebSocket库来实现。提供服务器的IP地址和端口号,然后进行连接。 这样,你就可以通过Unity实现基本的WebSocket服务器。你可以根据具体的需求在OnMessage和OnClose事件中添加更多逻辑来处理消息和连接的关闭。同时需要注意,Unity的Networking组件也提供了一些网络功能,你也可以尝试使用这些组件来实现WebSocket服务器。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值