使用HTML5下WebSocket搭建简易聊天室

一、Html5 WebSocket介绍

WebSocket protocol 是HTML5一种新的协议(protocol)。它是实现了浏览器与服务器全双工通信(full-duplex)。

现在,很多网站为了实现即时通讯(real-time),所用的技术都是轮询(polling)。轮询是在特定的的时间间隔(time interval)(如每1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给客服端的浏览器。这种传统的HTTP request d的模式带来很明显的缺点 – 浏览器需要不断的向服务器发出请求(request),然而HTTP request 的header是非常长的,里面包含的数据可能只是一个很小的值,这样会占用很多的带宽。

而最比较新的技术去做轮询的效果是Comet – 用了AJAX。但这种技术虽然可达到全双工通信,但依然需要发出请求(reuqest)。

在 WebSocket API,浏览器和服务器只需要要做一个握手的动作(实际上是tcp),然后,浏览器和服务器之间就形成了一条快速通道(这里走的是新的协议)。两者之间就直接可以数据互相传送。

二、IM系统的几种通信方式

1.点对点通信(对等通信方式):客户端A想要与客户端B进行通信,首页会与IM服务器进行一次握手,然后从IM服务器拿到客户端B的地址。然后直接向客户端B发送消息,然后客户端B获取到A客户端的地址,也直接向客户端A回复消息,这样就不通过IM服务器来中转,这样双方的即时文字消息就不通过 IM服务器中转,而是通过网络进行点对点的直接通讯,这称为对等通讯方式(Peer To Peer) 。PS:这种方式需要做内网穿透或代理,不然无法获取到对方的地址等信息。

2.代理通信:当客户端A与客户端B之间存在防火墙,网速很慢等原因,IM服务器可以提供消息中专的服务,客户端A先把消息发送到IM服务器,然后再通过IM服务器把消息转发给客户端B,这样无需得到客户端的地址信息就能实现消息送达,这种方式叫做代理通信。

3.离线代理通信:当客户端A想要与客户端B通信的时候,发现客户端B不在线,这样IM服务器会把消息存起来,等到下一次客户端B上线的时候,由客户端B主动获取到离线消息(这样做好像可以降低服务器的压力)。

三、利用Html5的WebSocket实现简单的聊天室

1.服务端代码如下,注释那些都挺全的,就不一一多说:

private async Task WebSocketContext(AspNetWebSocketContext context)
        {
            try
            {
                WebSocket socket = context.WebSocket;

                //获取连接信息
                string user_name = TDCMS.Common.TD_Request.GetQueryStringValue("user_name", "");

                //第一次open时,添加到连接池中
                if (_userPool.Find(c => c.User_name== user_name) == null)
                {
                    _userPool.Add(new UserPool() {  User_name = user_name , Socket = socket });
                }
                else
                {
                    UserPool p = _userPool.Find(c => c.User_name == user_name);
                    if (socket != p.Socket)//当前对象不一致,更新
                    {
                        p.Socket = socket;
                    }
                }

                UserPool sourcePool= _userPool.Find(c => c.User_name == user_name);//获取到发送者连接池

                #region 对所有连接池中广播 我上线了
                foreach (var item in _userPool)
                {
                    MessageModel model = new MessageModel()
                    {
                        Aim = item.User_name,
                        Contents = user_name + "上线了",
                        Source = sourcePool.User_name,
                        Status = 1
                    };
                    await item.Socket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(JsonHelper.ObjectToJson(model))), WebSocketMessageType.Text, true, CancellationToken.None);
                }
                #endregion

                bool isNext = true;
                while (isNext)
                {
                    if (socket.State == WebSocketState.Open)
                    {
                        ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);
                        WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None);

                        #region 关闭Socket处理,删除连接池
                        if (socket.State != WebSocketState.Open)//连接关闭
                        {
                            if (_userPool.Find(c => c.User_name == user_name) != null)
                                _userPool.Remove(_userPool.Find(c => c.User_name == user_name));//删除连接池
                            //广播当前在线的用户 我下线了
                            foreach (var item in _userPool)
                            {
                                MessageModel offline = new MessageModel()
                                {
                                    Aim = item.User_name,
                                    Contents = user_name + "下线了",
                                    Source = sourcePool.User_name,
                                    Status = 1
                                };
                                await item.Socket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(JsonHelper.ObjectToJson(offline))), WebSocketMessageType.Text, true, CancellationToken.None);
                            }
                            break;
                        }
                       
                        #endregion

                        #region 如果连接没有关闭,处理发送过来的消息

                        MessageModel model=new MessageModel();
                        int messageCount = result.Count;
                        string messageStr= Encoding.UTF8.GetString(buffer.Array, 0, messageCount);
                        model = JsonHelper.JsonToObject<MessageModel>(messageStr);//这个是解析好的 消息

                        //发送消息到每个客户端
                        foreach (var item in _userPool)
                        {
                            await item.Socket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(JsonHelper.ObjectToJson(model))), WebSocketMessageType.Text, true, CancellationToken.None);
                        }

                        #endregion
                    }
                }

            }
            catch(Exception ex)
            {
                throw ex;
            }
        }

2.客户端JS代码如下:

<script>
        var im;//WebSocket对象
        function initIm() {
            var user=$('#txtUserName').val();
            im = new MyIm(window.location.hostname, window.location.port, user);
            $('.online').show();
            $('.offline').hide();
            im.Init();
        }

3.客户端HTML:

<div class="mainIm">
        <div>
            <ul class="mainBox">
            </ul>
            <ul class="messageBox">
            </ul>
        </div>
        <div class="online" style="display:none;">
            <input type="text" id="txtContents" placeholder="输入要发送的内容" />
            <input type="button" value="发送" onclick="sendMsg()" /><input type="button" value="断开连接" onclick="offLine()" />
        </div>
        <div class="offline">
            <input type="text" id="txtUserName" placeholder="请输入一个用户名" />
            <input type="button" value="连接" onclick="initIm()" />
        </div>
    </div>

4.最后的效果如下:

使用HTML5下WebSocket搭建简易聊天室

三、总结

目前IE并不支持WebSocket,就目前来说,这种方式并不适用于大范围使用。

这里只是实现了简单的聊天室,如果想要一对一,只需找到用户的连接池,向该连接池发送消息即可,如果用户不存在,可以创建一个全局变量离线消息池来储存离线消息。

如有大神发现写的不会的地方,请多多指教!!!

相关推荐