WebSocket(一):Implement Simple Server and Client

一. WebSocket Server

首先选择安装nonocast实现的Nonocast.Http,详情可参见:http://nohttp.codeplex.com

可以通过VS自身的PackageManager下载,在Tools->Library Package Manager->Package Manager Console中输入“Install-Package Nonocast.Http”即可。

1. 创建Service类

public class RemoteService : SmallHTTPServiceBase {

    public ActionResult Default() {

        ActionResult result = new HtmlResult("wwwroot/Default.xhtml", DateTime.Now);

        return result;

    }



    public ActionResult Update() {

        return new EmptyActionResult();

    }



    public override void Receive(TcpClient client, string data) {

        Console.WriteLine("[Client: ]{0}", data);

    }
}

我们可以看到其中有一个奇怪的地方“wwwroot/Default.xhtml”,这是一个Html的模板页面,通过访问http://127.0.0.1:7005/Action/Default,用户可以看到Default模板中的网页内容。并且对于模板的编程支持了Razor。参数DateTime.Now则是传给模板的Model。

所以Default.xhtml可以是如下形式:

<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>

    <meta charset="utf-8" />

    <title>Default HTML</title>
</head>
@using System;
@{

    DateTime time = (DateTime)Model;
}
<body>

    <h2>@time</h2>
</body>
</html>

2. Open & Close

RemoteService service = new RemoteService();
service.Open();
...
service.Close();

3. 向所有客户端连接广播消息

service.Notify(“Hello World!”);

这样一个简单的WebSocket Server完成了。下面看看Client如何进行。

二. WebSocket Client

在.Net 4.5中引入了ClientWebSocket,但是可惜的是它只能在Win8 和 Server 2008平台使用。

那么对于广大的Win7和XP用户就只能自己实现或者使用其他第三方类库。

下面我们就来自己写一个简单的Client。

public class WebSocket {

        public event Action OnOpen;

        public event Action OnClose;

        public event WebSocketEventHandler OnMessage;



        public WebSocket(string address) {

            this.uri = new Uri(address);

            string protocol = uri.Scheme;

            if (!protocol.Equals("ws") && !protocol.Equals("wss")) {

                throw new ArgumentException("Unsupported protocol: " + protocol);

            }

        }



        public void Open() {

            string host = uri.DnsSafeHost;

            string path = uri.PathAndQuery;

            string origin = "http://" + host;



            client = CreateSocket(uri);

            stream = client.GetStream();



            int port = ((IPEndPoint)client.Client.RemoteEndPoint).Port;

            if (port != 80) { host = host + ":" + port; }



            string request =

@"GET {0} HTTP/1.1

Upgrade: WebSocket

Connection: Upgrade

Host: {1}

Origin: {2}

Sec-WebSocket-Key: 000



";

            request = string.Format(request, path, host, origin);



            byte[] sendBuffer = Encoding.UTF8.GetBytes(request);

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



            StreamReader reader = new StreamReader(stream);

            // Handshake

            string header = reader.ReadLine();

            header = reader.ReadLine();

            header = reader.ReadLine();

            header = reader.ReadLine();

            header = reader.ReadLine();



            hasHandshake = true;

            stream.BeginRead(readBuffer, 0, readBuffer.Length, new AsyncCallback(OnReadAsyncCallback), null);

            if (OnOpen != null) { OnOpen(); }

        }



        public void Close() {

            stream.Close();

            client.Close();

            if (OnClose != null) { OnClose(); }

        }



        public void Send(string message) {

            if (!hasHandshake) { throw new InvalidOperationException("Handshake not complete"); }

            byte[] buf = Encoding.UTF8.GetBytes(message);



            List<byte> sendBuffer = new List<byte>();

            sendBuffer.Add(0x80);

            sendBuffer.Add((byte)(Convert.ToByte(buf.Length) | 0x80));

            sendBuffer.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 });

            sendBuffer.AddRange(buf);

            stream.Write(sendBuffer.ToArray(), 0, sendBuffer.Count);

            stream.Flush();

        }



        private void OnReadAsyncCallback(IAsyncResult ar) {

            stream.Read(readBuffer, 0, 1);

            int len = Convert.ToInt32(readBuffer[0]);

            len = stream.Read(readBuffer, 0, len);

            var s = Encoding.UTF8.GetString(readBuffer, 0, len);

            if (OnMessage != null) {

                OnMessage(this, new WebSocketEventArgs { TextData = s });

            }



            stream.BeginRead(readBuffer, 0, readBuffer.Length, new AsyncCallback(OnReadAsyncCallback), null);

        }



        private static TcpClient CreateSocket(Uri arg) {

            string scheme = arg.Scheme;

            string host = arg.DnsSafeHost;



            int port = arg.Port;

            if (port <= 0) {

                if (scheme.Equals("wss"))

                    port = 443;

                else if (scheme.Equals("ws"))

                    port = 80;

                else

                    throw new ArgumentException("Unsupported scheme");

            }



            if (scheme.Equals("wss"))

                throw new NotImplementedException("SSL support not implemented yet");

            else

                return new TcpClient(host, port);

        }



        private Uri uri;

        private bool hasHandshake;

        private TcpClient client;

        private NetworkStream stream;

        private byte[] readBuffer = new byte[4096];

    }



    public delegate void WebSocketEventHandler(object sender, WebSocketEventArgs e);



    public class WebSocketEventArgs : EventArgs {

        public WebSocketEventArgs() { }

        public string TextData { get; set; }

    }

其中WebSocketServer的URI地址为如下形式:ws://127.0.0.1:7005/x

在通过TcpClient创建的Socket连接服务器,以上面的URI地址为例,IP为127.0.0.1,Port为7005,连接成功之后需要和WebSocket服务进行握手协议,向服务器发送如下内容:

GET /x HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: 127.0.0.1:7005
Origin: http://127.0.0.1
Sec-WebSocket-Key: 000

这时服务器端会反馈如下内容:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: h3596CBGNqC8QQzCKFeESSgpLBA=
 

Client将反馈信息接收完整之后方可进行正常的收发通信。

首先从接收说起:

服务端发送过来的数据包结构是如下形式的:

[length(1字节)][content(length字节)]

所以在接收数据的时候要先收length长度,再收content内容,如下:

private void OnReadAsyncCallback(IAsyncResult ar) {

        stream.Read(readBuffer, 0, 1);

        int len = Convert.ToInt32(readBuffer[0]);

        len = stream.Read(readBuffer, 0, len);

        var s = Encoding.UTF8.GetString(readBuffer, 0, len);

        if (OnMessage != null) {

            OnMessage(this, new WebSocketEventArgs { TextData = s });

        }



        stream.BeginRead(readBuffer, 0, readBuffer.Length, new AsyncCallback(OnReadAsyncCallback), null);

    }

其次是发送,这就有些复杂了,根据发送的数据包大小不同,所使用的数据协议也会不同,小于128字节是一种协议,大于128字节又是一种协议。我们先简单些考虑小于128字节的情况,今后再进行扩展。

小于128字节时的数据包接口如下形式:

[0x80(1字节)][length(1字节)][mask(4字节)][content(length字节)]
其中length还需要和0x80进行OR操作

所以发送小型数据的时候如下方式实现:

public void Send(string message) {

        if (!hasHandshake) { throw new InvalidOperationException("Handshake not complete"); }

        byte[] buf = Encoding.UTF8.GetBytes(message);



        List<byte> sendBuffer = new List<byte>();

        sendBuffer.Add(0x80);

        sendBuffer.Add((byte)(Convert.ToByte(buf.Length) | 0x80));

        sendBuffer.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 });

        sendBuffer.AddRange(buf);

        stream.Write(sendBuffer.ToArray(), 0, sendBuffer.Count);

        stream.Flush();

    }

通过代码可以看到,发送前先判断是否和服务器进行过握手协议,然后将内容UTF8编码,然后根据协议结构依次发送fin,length,mask,content.

三. 结尾

一个简单的WebSocket C/S实现了,虽然还有些缺陷,但是基本可以满足小数据量的网络通信,可以算是一个小型的IIS。

下面附上Example代码:code

你可能感兴趣的:(websocket)