zoukankan      html  css  js  c++  java
  • WebSocket 网页聊天室的实现(服务器端:.net + windows服务,前端:Html5)

    websocket是HTML5中的比较有特色一块,它使得以往在客户端软件中常用的socket在web程序中也能轻松的使用,较大的提高了效率。废话不多说,直接进入题。

    网页聊天室包括2个部分,后端服务器+前端页面。 
    1、后端服务部分:.net4.0 + windows服务。相比寄宿在iis中,寄宿在进程中的windows服务更加的稳定可靠(文章中的例子用windows控制台程序演示,后面给出完整的windows服务的代码)。 
    2、前端部分:html5 + jQuery + bootstrap。基本的前端快速开发利器。

    一、分析一下聊天室的场景需求,以便构建合适的数据结构

    1、在线用户类 OnlineUser 用户的基本特征为姓名Name(性别年龄啥的先忽略),当然在系统设计中,姓名并不能很好的区分不同用户,所以得需要一个唯一标识符Id。另外,由于可能存在多个聊天室,因此聊天室的编号RoomId也是用户的特征之一。综上,可得在线用户类OnlineUser的结构为:

    ///  <summary>
    ///  在线用户信息
    ///  <summary>
    public class OnlineUser
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string RoomId { get; set; }
        public string SessionId { get; set; }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    PS:SessionId是后续加入的属性,此处可先忽略。

    2、消息类 Message 消息是聊天室最核心的部分,我最简单的消息机构应包含如下成员,消息发送者FromUserId,消息接受者ToUserId,消息类型Type,消息内容Content,消息时间Time。

    public class Message
    {
        public int FromUserId { get; set; }
        public int ToUserId { get; set; }
        public int Type { get; set; }
        public string Time { get; set; }
        public string Content { get; set; }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    PS:这里Type用的是int类型表示的,是出于后续消息传递时,以便于将其包裹成Json格式传递。但在判断时,会将其转换成MessageType格式的枚举型,MessageType暂定包含以下几种状态,

    public enum MessageType
    {
        /// <summary>
        /// 新用户进入
        /// <summary>
        NewUserIn = 1,
    
        /// <summary>
        /// 用户离开
        /// <summary>
        UserExit = 2,
    
        /// <summary>
        /// 新用户提供自身信息
        /// <summary>
        ReprotUserInfo = 3,
    
        /// <summary>
        /// 新文字消息
        /// </summary>
        NewTextMessage = 4,
    
        /// <summary>
        /// 广播基本信息
        /// </summary>
        BroadcastBasicInfo = 5
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    二、项目结构

    1、服务端项目结构如下: 
    ChatRoom项目结构视图

    这是一个windows控制台程序,Program.cs是入口,ChatWebSocket.cs是核心代码快,外部引用的dll包括Json操作类库和Socket操作类库。 
    其中,Model文件夹下的是上面提到的一些基本数据结构,这里看一下核心的代码ChatWebSocket。这当中用到的SuperSocket已经将socket的主要操作封装的很完备了,使用方法如下:

    /* 侦听地址(注意,此处的地址一定要和前端js中的地址一致!!) */
    const string IP = "127.0.0.1";
    /* 侦听端口 */
    const int PORT = 2016;
    
    /* SuperWebSocket中的WebSocketServer对象 */
    WebSocketServer wsServer = null;
    
    /* 当前在线用户列表 */
    List<OnlineUser> olUserList = new List<OnlineUser>();
    
    /* 定时通知客户端线程 */
    BackgroundWorker bkWork = null;
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这里,WebSocketServer是SuperSocket中封装好的Socket服务端类,olUserList 是在线用户列表,bkWork 是后台线程,负责定时向客户端发送一些系统信息。

    构造函数中:

    public ChatWebSocket()
    {
        /* 初始化 以及 相关事件注册 */
        wsServer = new WebSocketServer();
    
        /* 有新会话握手并连接成功 */
        wsServer.NewSessionConnected += WsServer_NewSessionConnected;
        /* 有会话被关闭 可能是服务端关闭 也可能是客户端关闭 */
        wsServer.SessionClosed += WsServer_SessionClosed;
        /* 有新文本消息被接收 */
        wsServer.NewMessageReceived += WsServer_NewMessageReceived;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    WebSocketServer有几个比较重要的事件, 
    1、NewSessionConnected 有新会话握手并连接成功 
    2、SessionClosed 有会话被关闭 可能是服务端关闭 也可能是客户端关闭 
    3、NewMessageReceived 有新文本消息被接收 
    其中NewMessageReceived事件是消息传递的重要事件,也是我们重点处理的事件,下面将3个事件的方法体列出:

    // 有新会话握手并连接成功
    private void WsServer_NewSessionConnected(WebSocketSession session)
    {
        LogHelper.Write(session.SessionID.ToString() + " Connect!");
    }
    
    // 有新文本消息被接收
    private void WsServer_NewMessageReceived(WebSocketSession session, string value)
    {
        LogHelper.Write("Receive Message:" + value);
    
        var msg = JsonConvert.DeserializeObject<Message>(value);
        MessageType mt = (MessageType)msg.Type;
        switch (mt)
        {
            /* 用户报告自己信息,将UserId与SessionId关联 */
            case MessageType.ReprotUserInfo:
                olUserList.Add(new OnlineUser
                {
                    SessionId = session.SessionID, 
                    Id = msg.FromUserId, 
                    RealName = msg.FromUserName, 
                    RoomId = msg.RoomId 
                });
                /* 通知其他用户 */
                SendMessage(session, new Message
                {
                    FromUserId = msg.FromUserId,
                    FromUserName = msg.FromUserName,
                    ToUserId = 0,// 同一房间的人
                    Type = (int)MessageType.NewUserIn,
                    Content = "",
                    Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                    RoomId = msg.RoomId
                });
                break;
    
            /* 用户文字(图片)消息,服务器进行转发 */
            case MessageType.NewTextMessage:
                /* 通知其他用户 */
                msg.Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                SendMessage(session, msg);
                break;
    
            default: break;
        }
    }
    
    // 有会话被关闭 可能是服务端关闭 也可能是客户端关闭
    private void WsServer_SessionClosed(WebSocketSession session, CloseReason value)
    {
        LogHelper.Write(session.SessionID.ToString() + " Exit!");
    
        var u = olUserList.Find(m => m.SessionId == session.SessionID);
        if (u == null)
        {
            return;
        }
        olUserList.Remove(u);
    
        // 通知其他用户
        SendMessage(session, new Message
        {
            FromUserId = u.Id,
            FromUserName = u.RealName,
            ToUserId = 0,// 同一房间的人
            Type = (int)MessageType.UserExit,
            Content = "",
            RoomId = u.RoomId
        });
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    其中,SendMessage(WebSocketSession session, Message msg)方法如下:

    private void SendMessage(WebSocketSession session, Message msg)
    {
        // -1:全体;0:同一房间;剩下:特定的用户
        var users = msg.ToUserId == -1 ? olUserList : msg.ToUserId == 0 ? olUserList.Where(m => m.RoomId == msg.RoomId).ToList() : olUserList.Where(m => m.Id == msg.ToUserId).ToList();
        users.ForEach(u =>
        {
            var ss = session.AppServer.GetAppSessionByID(u.SessionId);
            if (ss != null)
            {
                ss.Send(JsonConvert.SerializeObject(msg));
            }
         });
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    另外,WebSocketServer的启动和停止也非常的简单:

    public void Start()
    {
        if (!wsServer.Setup(IP, PORT))
        {
            throw new Exception("设置WebSocket服务侦听地址失败!");
        }
    
        if (!wsServer.Start())
        {
            throw new Exception("启动WebSocket服务侦听失败!");
        }
    }
    
    public void Stop()
    {
        if (wsServer != null)
        {
            wsServer.Stop();
        }
    }
  • 相关阅读:
    Jenkins分享
    Java静态绑定和动态绑定
    SpringBoot中RedisTemplate订阅发布对象
    Idea项目:Failed to create a Maven project ‘…pom.xml’ already exists in VFS 解决
    Java Web不能不懂的知识
    Required String parameter 'id' is not present
    Hive使用druid做连接池代码实现
    Docker Toolbox常见错误解决方案
    初学者手册-MyBatis踩坑记(org.apache.ibatis.binding.BindingException)
    SpringMVC日志管理(自定义异常及自定义注解)
  • 原文地址:https://www.cnblogs.com/zxtceq/p/7278805.html
Copyright © 2011-2022 走看看