基于SignalR的小型IM系统

这个IM系统真是太轻量级了,提供的功能如下:

1.聊天内容美化

2.用户上下线提示

3.心跳包检测机制

4.加入用户可群聊

下面来一步一步的讲解具体的制作方法。

开篇准备工作

首先,巧妇难为无米之炊,这是总所周知的。这里我们需要两个东西,一个是Asp.net MVC4项目;另一个是Signalr组件。

新建一个Asp.net MVC4项目,然后通过以下命令安装Signalr组件:

Install-Package Microsoft.AspNet.SignalR -Version 1.1.3

这样我们就将组件安装完毕了。

后台交互部分

接着在项目中,新建一个文件夹名称为Hubs,在这个文件夹下面新建一个名称为IChatHub的接口,定义如下:

 interface IChatHub
    {
        //服务器下发消息到各个客户端
        void SendChat(string id, string name, string message);

        //用户上线通知
        void SendLogin(string id, string name);

        //用户下线通知
        void SendLogoff(string id, string name);

        //接收客户端发送的心跳包并处理
        void TriggerHeartbeat(string id, string name);
    }

其中,SendChat方法主要用户Signalr后端向前台发送数据;SendLogin方法主要用于通知用户上线;SendLogoff方法主要用于通知用户下线;而TriggerHeartbeat方法主要用于接收前端发送的心跳包并做处理,以便于判断用户是否断开连接(有时候用户直接关闭浏览器或者在任务管理器中关闭浏览器,是无法检测用户离线与否的,所以这里引入了心跳包机制,一旦用户在20秒之后未发送任何心跳包到后端,则视为掉线)。

接下来添加一个ChatHub的类,具体实现如下:

public class ChatHub:Hub, IChatHub
    {
        private IList<UserChat> userList = ChatUserCache.userList;

        public void SendChat(string id, string name, string message)
        {
            Clients.All.addNewMessageToPage(id, name + " " + DateTime.Now.ToString("yyyy/MM/dd hh:mm:ss"), message);
        }

        public void TriggerHeartbeat(string id, string name)
        {
            var userInfo = userList.Where(x => x.ID.Equals(id) && x.Name.Equals(name)).FirstOrDefault();
            userInfo.count = 0;  //收到心跳,重置计数器
        }

        public void SendLogin(string id,string name)
        {
            var userInfo = new UserChat() { ID = id, Name = name };
            userInfo.action += () =>
            {
                //用户20s无心跳包应答,则视为掉线,会抛出事件,这里会接住,然后处理用户掉线动作。
                SendLogoff(id, name);
            };

            var comparison = new ChatUserCompare();
            if (!userList.Contains<UserChat>(userInfo, comparison))
                userList.Add(userInfo);
            Clients.All.loginUser(userList);
            SendChat(id, name, "<====用户 " + name + " 加入了讨论组====>");
        }

        public void SendLogoff(string id,string name)
        {
            var userInfo = userList.Where(x => x.ID.Equals(id) && x.Name.Equals(name)).FirstOrDefault();
            if (userInfo != null)
            {
                if (userList.Remove(userInfo))
                {
                    Clients.All.logoffUser(userList);
                    SendChat(id, name, "<====用户 " + name + " 退出了讨论组====>");
                }
            }
        }
    }

这个类的设计思想有如下几个部分:

首先,所有用户的登陆信息,我持久化到了缓存集合中:IList<UserChat>,这个缓存集合的定义如下:

public static class ChatUserCache
    {
        public static IList<UserChat> userList = new List<UserChat>();
    }

这样,用户登陆信息就会保存到内存中,一旦有新用户进来或者是旧用户退出,我就可以通过新增条目或者删除条目来维护这个列表,维护完毕,将这个列表推到前端。这样前台用户就能实时看到,哪些用户上线,哪些用户下线了。

其次,心跳包检测机制部分,前端用户每隔5秒钟会发送一次心跳包到处理中心,处理中心收到心跳包,会将实体类的计数器置为0;也就是说,如果用户登陆正常,那么用户实体中的计数器每隔5秒钟自动置为0;但是如果用户不按正常渠道退出(直接关闭浏览器或者在任务管理器中关闭浏览器),那么用户实体中的计数器就会一直递增,直到加到第20秒,然后会抛出事件,提示当前用户已经断开连接。

用户实体设计如下:

public class UserChat
    {
        public UserChat()
        {
            count = 0;
            if (Timer == null) Timer = new Timer();
            Timer.Interval = 1000;  //1s触发一次
            Timer.Start();
            Timer.Elapsed += (sender, args) =>
            {
                count++;
                if (count >= 20)
                    action();  //该用户掉线了,抛出事件通知
            };
        }

        private readonly Timer Timer;
        public event Action action;

        public string ID { get; set; }
        public string Name { get; set; }

        //内部计数器(每次递增1),如果服务端每5s能收到客户端的心跳包,那么count被重置为0;
        //如果服务端20s后仍未收到客户端心跳包,那么视为掉线
        public int count{get;set;}

    }

当用户意外退出,会有一个action事件抛出,我们在SendLogin方法中进行了接收,当这个事件抛出,就会立马触发用户的Logoff事件,通知掉线:

 public void SendLogin(string id,string name)
        {
            var userInfo = new UserChat() { ID = id, Name = name };
            userInfo.action += () =>
            {
                //用户20s无心跳包应答,则视为掉线,会抛出事件,这里会接住,然后处理用户掉线动作。
                SendLogoff(id, name);
            };

            var comparison = new ChatUserCompare();
            if (!userList.Contains<UserChat>(userInfo, comparison))
                userList.Add(userInfo);
            Clients.All.loginUser(userList);
            SendChat(id, name, "<====用户 " + name + " 加入了讨论组====>");
        }

这就是处理中心的所有内容了。

需要注意的是,在ChatHub类中,SendChat方法,TriggerHeartbeat方法,SendLogin方法,SendLogoff方法都是Singnalr处理对象所拥有的方法,而addNewMessageToPage方法,loginUser方法,logoffUser方法则是其回调方法。也就是说,当你在前台通过SendChat方法向处理中心发送数据的时候,你可以注册addNewMessageToPage方法来接收处理中心返回给你的数据。

前端逻辑及布局

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<div id="tb" class="easyui-panel panel" title="专家在线咨询系统" >
    <div id="messageboard">
        <ul id="discussion"></ul>
    </div>
    <div id="userContainer">
        <ul id="userList"></ul>
    </div>
    <div id="messagecontainer" >
        <textarea id="message" class="rte-zone" rows="3"></textarea>
        <div>
            <input type="button" id="send" class="btn" value="发送" />
            <input type="button" id="close" class="btn" value="关闭" /><input type="hidden" id="displayname" />
        </div>
    </div>
</div>
@section scripts{
    <style>
    .panel{padding:5px;height:auto;min-height:650px;}
    .current{color:Green;}
    .rte-zone{width:815px;margin:0;padding:0;height:160px;border:1px #999 solid;clear:both}
    .rte-toolbar{width:800px;margin-top:10px;}
    .rte-toolbar div{float:left;width:100%;}
    .rte-toolbar a,.rte-toolbar a img{border:0}
    .rte-toolbar p{float:left;margin:0;padding-right:5px}
    #messageboard{border:1px solid #B6DF7D;float:left;width:800px;padding:10px;height:450px;overflow:auto;border-radius:10px; -moz-box-shadow:2px 2px 5px #333333; -webkit-box-shadow:2px 2px 5px #333333; box-shadow:2px 2px 5px #333333;}
    #userContainer{border:1px solid #B6DF7D;float:right;width:200px;height:565px;padding:5px;border-radius:10px; -moz-box-shadow:2px 2px 5px #333333; -webkit-box-shadow:2px 2px 5px #333333; box-shadow:2px 2px 5px #333333;}
    #messagecontainer{float:left;width:800px;}
    #messagecontainer div{float:right;}
    #message{border:1px solid #B6DF7D;width:815px; height:70px;margin-top:5px;border-radius:10px; -moz-box-shadow:2px 2px 5px #333333; -webkit-box-shadow:2px 2px 5px #333333; box-shadow:2px 2px 5px #333333;}
    #userList li{border-bottom:1px solid #B6DF7D;cursor:pointer;}
    #userList li:hover{background-color:#ccc;}
    .btn{width:75px;height:25px;}
    </style>
    <script src="../../Content/jqueryplugin/jquery.rte.js" type="text/javascript"></script>
    <!--Reference the SignalR library. -->
    <script src="../../Scripts/jquery.signalR-1.1.4.min.js" type="text/javascript"></script>
    <!--Reference the autogenerated SignalR hub script. -->
    <script src="../../signalr/hubs"></script>

    <script>
        $(function () {

            $(‘.rte-zone‘).rte("css url", "http://batiste.dosimple.ch/blog/posts/2007-09-11-1/");
            //添加对自动生成的Hub的引用
            var chat = $.connection.chatHub;

            //调用Hub的callback回调方法

            //后端SendChat调用后,产生的addNewMessageToPage回调
            chat.client.addNewMessageToPage = function (id, name, message) {
                $(‘#discussion‘).append(‘<li style="color:blue;">‘ + htmlEncode(name) + ‘</li><li> ‘ + htmlEncode(message) + ‘</li>‘)
            };

            //后端SendLogin调用后,产生的loginUser回调
            chat.client.loginUser = function (userlist) {
                reloadUser(userlist);
            };

            //后端SendLogoff调用后,产生的logoffUser回调
            chat.client.logoffUser = function (userlist) {
                reloadUser(userlist);
            };

            $(‘#displayname‘).val(prompt(‘请输入昵称:‘, ‘‘));

            //启动链接
            $.connection.hub.start().done(function () {

                var userid = guid();
                var username = $(‘#displayname‘).val();

                //发送上线信息
                chat.server.sendLogin(userid, username);

                //点击按钮,发送聊天内容
                $(‘#send‘).click(function () {
                    var chatContent = $(‘#message‘).contents().find(‘.frameBody‘).html();
                    chat.server.sendChat(userid, username, chatContent);
                });

                //点击按钮,发送用户下线信息
                $(‘#close‘).click(function () {
                    chat.server.sendLogoff(userid, username);
                    $("#send").css("display", "none");
                });

                //每隔5秒,发送心跳包信息
                setInterval(function () {
                    chat.server.triggerHeartbeat(userid, username);
                }, 5000);
            });

        });

        //重新加载用户列表
        var reloadUser = function (userlist) {
            $("#userList").children("li").remove();
            for (i = 0; i < userlist.length; i++) {
                $("#userList").append("<li><img src=‘../../Content/images/charge_100.png‘ />" + userlist[i].Name + "</li>");
            }
        }

        //div内容html化
        var htmlEncode = function (value) {
            var encodedValue = $(‘<div />‘).html(value).html();
            return encodedValue;
        }

        //guid序号生成
        var guid = (function () {
            function s4() {
                return Math.floor((1 + Math.random()) * 0x10000)
                           .toString(16)
                           .substring(1);
            }
            return function () {
                return s4() + s4() + ‘-‘ + s4() + ‘-‘ + s4() + ‘-‘ +
                       s4() + ‘-‘ + s4() + s4() + s4();
            };
        })();
    </script>
}

在如上代码中:

第49行,加载一个富文本编辑器

第51行,添加对自动生成的proxy的引用

第56行~第68行,注册回调方法,以便于更新前台UI

第73行,打开处理中心hub

第79行,发送用户上线信息

第94行,每隔5秒钟发送一次心跳包

如此而已,非常简便。

运行截图

打开界面,首先提示输入用户昵称:

输入完毕之后,用户上线:

后续两个用户加入进来:

用户聊天内容记录:

用户“浅浅的”正常退出:

用户“书韵妍香”非正常退出:

代码我会稍后补上。

参考文章:Tutorial: Getting Started with SignalR 1.x

基于SignalR的小型IM系统

时间: 2024-11-07 08:06:01

基于SignalR的小型IM系统的相关文章

开源Asp.Net Core小型社区系统

参考页面: http://www.yuanjiaocheng.net/ASPNET-CORE/core-identity.html http://www.yuanjiaocheng.net/ASPNET-CORE/core-authorize-attribute.html http://www.yuanjiaocheng.net/ASPNET-CORE/core-identity-configuration.html http://www.yuanjiaocheng.net/ASPNET-COR

基于Jenkins的自动构建系统开发_android总结

持续集成相关理论 1.1 极限编程的概述 1.1.1 极限编程的产生 2001年,为了解决许多公司的软件团队陷入不断增长的过程泥潭,一批业界专家一起概括出了一些可以让软件开发团队具有快速工作.响应变化能力的价值观和原则,他们称自己为敏捷联盟.敏捷开发过程的方法很多,主要有:SCRUM,Crystal,特征驱动软件开发(Feature Driven Development,简称FDD),自适应软件开发(Adaptive Software Development,简称ASD),以及最重要的极限编程(

Unix C语言编写基于进程的小型并发服务器

并发介绍 如果逻辑控制流在时间上是重叠的,那么它们就是并发的,可以出现在计算机系统的不同层面上,硬件异常处理程序.进程和Unix信号处理程序都是并发的.并发可以看作是操作系统内核用来运行多个应用程序的机制,但是并发不局限于内核.它也可以在应用程序中扮演角色.并发的主要作用有:访问慢速IO设备;与人交互的程序;通过推迟工作以降低延迟;服务多个网络客户端的请求.并发通常可以有三种,基于进程.基于IO多路复用.基于线程. 基于进程的并发 进程是一个程序运行的实例.每一个进程都有自己独立的地址空间,一般

基于Cobbler实现多版本系统批量部署

前言 运维自动化在生产环境中占据着举足轻重的地位,尤其是面对几百台,几千台甚至几万台的服务器时,仅仅是安装操作系统,如果不通过自动化来完成,根本是不可想象的.记得前面我们探究了基于PXE实现系统全自动安装,但PXE同时只能提供单一操作系统的批量部署,面对生产环境中不同服务器的需求,该如何实现批量部署多版本的操作系统呢?Cobbler便可以的满足这一实际需求,本文带来的是基于Cobbler实现多版本操作系统批量部署. Cobbler 简介 Cobbler是一款自动化操作系统部署的实现工具,由Pyt

RDIFramework.NET — 基于.NET的快速信息化系统开发框架 — 系列目录

RDIFramework.NET - 基于.NET的快速信息化系统开发框架 - 系列目录 RDIFramework.NET,基于.NET的快速信息化系统开发.整合框架,给用户和开发者最佳的.Net框架部署方案.  框架简单介绍 RDIFramework.NET,基于.NET的快速信息化系统开发.整合框架,为企业或个人在.NET环境下快速开发系统提供了强大的支持,开发人员不需要开发系统的基础功能和公共模块,框架自身提供了强大的函数库和开发包,开发人员只须集中精力专注于业务部分的开发,因此大大提高开

基于cobbler实现自动化安装系统

基于cobbler实现自动化安装系统 环境介绍 centos6.8 为centos6.8提供两块网卡 (非必要) 一块为桥接,方便xshell连接和测试 一块为vmnet3:用来搭建dhcp,tftp,和为客户端提供cobbler服务 前提,(确保安装TFTP,dhcp,rsync) # yum install httpd cobbler cobbler-web pykickstart debmirror 1.启动对应的服务 # service httpd start # service cob

Android弹幕实现:基于B站弹幕开源系统(4)-重构

?? Android弹幕实现:基于B站弹幕开源系统(4)-重构 弹幕在视频播放的APP中比较常见,但是逻辑比较复杂,现在在附录1,2,3的基础上,我再次对弹幕进行抽象和重构,把弹幕从底向上抽象成不同的层,便于复用. 第一步,抽象数据层.通常弹幕的来源是来源于后台的数据接口请求,在实时直播时候,是通过网络的轮询机制获取数据,那么,我把这部分代码抽出来设计成一个MGDanmakuHttpController,该类专注于数据的获取与分发: package zhangphil.danmaku; impo

Android弹幕实现:基于B站弹幕开源系统(1)

?? Android弹幕实现:基于B站弹幕开源系统(1) 如今的视频播放,流行在视频上飘弹幕.这里面做的相对比较成熟.稳定.使用量较多的弹幕系统,当推B站的弹幕系统,B站的弹幕系统已经作为开源项目在github上,其项目地址:https://github.com/Bilibili/DanmakuFlameMaster 以B站开源的弹幕项目为基础,现给出一个简单的例子,实现发送简单的文本弹幕.第一步,首先要在Android的build.gradle文件中引入B站的项目: repositories

基于SignalR的web端即时通讯 - ChatJS

先上图. ChatJS 是基于SignalR实现的Web端IM,界面风格模仿的是“脸书”,可以很方便的集成到已有的产品中. 项目官网:http://chatjs.net/ github地址:https://github.com/andrerpena/ChatJS 在浏览器端,ChatJS是一系列的jQuery插件,这些代码都是使用TypeScript(微软开发的JS的一个面向对象超集,可以编译成JS)编写.在服务端,是一个简单的类库.如果要集成ChatJS ,服务端需要做的仅仅是实现 IChat