• C# 实现WebSocket服务端实例


    WebSocket

    WebSocket协议是一种双向通信协议,它建立在TCP之上,同http一样通过TCP来传输数据,但是它和http最大的不同有两点:

    1. WebSocket是一种双向通信协议,在建立连接后,WebSocket服务器和Browser/UA都能主动的向对方发送或接收数据,就像Socket一样,不同的是WebSocket是一种建立在Web基础上的一种简单模拟Socket的协议;
    2. WebSocket需要通过握手连接,类似于TCP它也需要客户端和服务器端进行握手连接,连接成功后才能相互通信。

    当Web应用程序调用new WebSocket(url)接口时,Browser就开始了与地址为url的WebServer建立握手连接的过程。

    客户端向服务器发送请求:
    GET /chat HTTP/1.1  
    Host: server.example.com  
    Upgrade: websocket  
    Connection: Upgrade  
    Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==  
    Origin: http://example.com  
    Sec-WebSocket-Protocol: chat,superchat  
    Sec-WebSocket-Version: 13  

     

    服务器端返回内容:
    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: xsOSgr30aKL2GNZKNHKmeT1qYjA=

     

    请求中的【Sec-WebSocket-Key】是随机发送的。而服务器返回的【Sec-WebSocket-Accept】是将【Sec-WebSocket-Key】加上一个固定字符串【258EAFA5-E914-47DA-95CA-C5AB0DC85B11】,并使用SHA-1加密后,再进行BASE-64编码生成的。

    服务端简单实例

    新建一个Web MVC项目:

    .net4.5中实现了对websocket的支持,所以直接在一个项目中新建一个一般处理程序 Handler1

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.WebSockets;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Web;
    using System.Web.WebSockets;
    
    
    namespace WebApplicationWebsocketHandler
    {
        /// <summary>
        /// 离线消息
        /// </summary>
        public class MessageInfo
        {
            public MessageInfo(DateTime _MsgTime, ArraySegment<byte> _MsgContent)
            {
                MsgTime = _MsgTime;
                MsgContent = _MsgContent;
            }
            public DateTime MsgTime { get; set; }
            public ArraySegment<byte> MsgContent { get; set; }
        }
    
    
    
    
        /// <summary>
        /// Handler1 的摘要说明
        /// </summary>
        public class Handler1 : IHttpHandler
        {
            private static Dictionary<string, WebSocket> CONNECT_POOL = new Dictionary<string, WebSocket>();//用户连接池
            private static Dictionary<string, List<MessageInfo>> MESSAGE_POOL = new Dictionary<string, List<MessageInfo>>();//离线消息池
            public void ProcessRequest(HttpContext context)
            {
                //context.Response.ContentType = "text/plain";
                //context.Response.Write("Hello World");
                if (context.IsWebSocketRequest)
                {
                    context.AcceptWebSocketRequest(ProcessChat);
                }
            }
    
            private async Task ProcessChat(AspNetWebSocketContext context)
            {
                WebSocket socket = context.WebSocket;
                string user = context.QueryString["user"].ToString();
    
                try
                {
                    #region 用户添加连接池
                    //第一次open时,添加到连接池中
                    if (!CONNECT_POOL.ContainsKey(user))
                        CONNECT_POOL.Add(user, socket);//不存在,添加
                    else
                        if (socket != CONNECT_POOL[user])//当前对象不一致,更新
                            CONNECT_POOL[user] = socket;
                    #endregion
    
                    #region 离线消息处理
                    if (MESSAGE_POOL.ContainsKey(user))
                    {
                        List<MessageInfo> msgs = MESSAGE_POOL[user];
                        foreach (MessageInfo item in msgs)
                        {
                            await socket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(item.MsgTime + ":" + item.MsgContent)), WebSocketMessageType.Text, true, CancellationToken.None);
                        }
                        MESSAGE_POOL.Remove(user);//移除离线消息
                    }
                    #endregion
    
                    string descUser = string.Empty;//目的用户
                    while (true)
                    {
                        if (socket.State == WebSocketState.Open)
                        {
                            ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);
                            WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None);
    
                            #region 消息处理(字符截取、消息转发)
                            try
                            {
                                #region 关闭Socket处理,删除连接池
                                if (socket.State != WebSocketState.Open)//连接关闭
                                {
                                    if (CONNECT_POOL.ContainsKey(user)) CONNECT_POOL.Remove(user);//删除连接池
                                    break;
                                }
                                #endregion
    
                                string userMsg = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);//发送过来的消息
                                string[] msgList = userMsg.Split('|');
                                if (msgList.Length == 2)
                                {
                                    if (msgList[0].Trim().Length > 0)
                                        descUser = msgList[0].Trim();//记录消息目的用户
                                    buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(msgList[1]));
    
                                    if (CONNECT_POOL.ContainsKey(descUser))//判断客户端是否在线
                                    {
                                        WebSocket destSocket = CONNECT_POOL[descUser];//目的客户端
                                        if (destSocket != null && destSocket.State == WebSocketState.Open)
                                            await destSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
                                    }
                                    else
                                    {
                                        Task.Run(() =>
                                        {
                                            if (!MESSAGE_POOL.ContainsKey(descUser))//将用户添加至离线消息池中
                                                MESSAGE_POOL.Add(descUser, new List<MessageInfo>());
                                            MESSAGE_POOL[descUser].Add(new MessageInfo(DateTime.Now, buffer));//添加离线消息
                                        });
                                    }
    
                                }
                                else
                                {
                                    buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(userMsg));
    
                                    foreach (KeyValuePair<string, WebSocket> item in CONNECT_POOL)
                                    {
                                        await item.Value.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
                                    }
    
                                }
    
                            }
                            catch (Exception exs)
                            {
                                //消息转发异常处理,本次消息忽略 继续监听接下来的消息
                            }
                            #endregion
                        }
                        else
                        {
                            break;
                        }
                    }//while end
                }
                catch (Exception ex)
                {
                    //整体异常处理
                    if (CONNECT_POOL.ContainsKey(user)) CONNECT_POOL.Remove(user);
                }
            }
    
    
            public bool IsReusable
            {
                get
                {
                    return false;
                }
            }
        }
    }

     

    客户端测试代码如下:

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/>
        <title></title>
        <script src="http://code.jquery.com/jquery-1.4.1.min.js"></script>
        <script>
            var ws;
            $().ready(function () {
                $('#conn').click(function () {
                    //ws = new WebSocket('ws://' + window.location.hostname + ':' + window.location.port + '/Handler1.ashx?user=' + $("#user").val());
                    ws = new WebSocket('ws://127.0.0.1:80/Handler1.ashx?user=' + $("#user").val());
                    //var host = 'ws://192.168.85.128:8085/api/WSChat?user='+$("#user").val();
                    //var host = "ws://192.168.85.128:8085/api/WSChat";
                    //webSocket = new WebSocket(host);
    
                    $('#msg').append('<p>正在连接</p>');
    
                    ws.onopen = function () {
                        $('#msg').append('<p>已经连接</p>');
                    }
                    ws.onmessage = function (evt) {
                        $('#msg').append('<p>' + evt.data + '</p>');
                    }
                    ws.onerror = function (evt) {
                        $('#msg').append('<p>' + JSON.stringify(evt) + '</p>');
                    }
                    ws.onclose = function () {
                        $('#msg').append('<p>已经关闭</p>');
                    }
                });
    
                $('#close').click(function () {
                    ws.close();
                });
    
                $('#send').click(function () {
                    if (ws.readyState == WebSocket.OPEN) {
                        ws.send($("#to").val() + "|" + $('#content').val());
                    }
                    else {
                        $('#tips').text('连接已经关闭');
                    }
                });
    
            });
        </script>
    </head>
    <body>
        <div>
            <input id="user" type="text" />
            <input id="conn" type="button" value="连接" />
            <input id="close" type="button"  value="关闭"/><br />
            <span id="tips"></span>
            <input id="content" type="text" />
            <input id="send" type="button"  value="发送"/><br />
            <input id="to" type="text" />目的用户
            <div id="msg">
            </div>
        </div>
    </body>
    </html>

    测试需要iis配置支持WebSocket协议,win7暂不支持。。。

  • 相关阅读:
    QT中的定时器使用
    range()函数常和len()函数一起用于字符串索引。在这里我们要显示每一个元素及其索引值。
    range()的print 《P核》P30
    2.13 带逗号的print语句输出的元素之间自动带个空格
    Python3.x和Python2.x的区别
    print语句默认每行添加一个换行符 来自2.13
    2.12 while循环 print与计数器先后顺序对结果的影响
    函数本地绑定与全局绑定的区别
    字典映射{ :}
    《Python核心编程》P21输入数值字符串→转整型
  • 原文地址:https://www.cnblogs.com/cuihongyu3503319/p/15224167.html
Copyright © 2020-2023  润新知