C# 实现WebSocket服务端实例

C# 实现WebSocket服务端实例

WebSocket

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

WebSocket是一种双向通信协议,在建立连接后,WebSocket服务器和Browser/UA都能主动的向对方发送或接收数据,就像Socket一样,不同的是WebSocket是一种建立在Web基础上的一种简单模拟Socket的协议;
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 :

/// <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 &amp;&amp; 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;
        }
    }
}

}

发表评论

邮箱地址不会被公开。

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据