一、理解SignalR

ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信(即:客户端(Web页面)和服务器端可以互相实时的通知消息及调用方法),SignalR有三种传输模式:LongLooping(长轮询)、WebSocket(HTML5的WEB套接字)、Forever Frame(隐藏框架的长请求连接),可以在WEB客户端显式指定一种或几种,也可以采取默认(推荐),若采取默认,SignalR会根据浏览器的环境自动选择合适的传输方式。

二、SignalR的三种实现方式

第一种:采用集线器类(Hub)+非自动生成代理模式:服务端与客户端分别定义的相对应的方法,客户端通过代理对象调用服务端的方法,服务端通过IHubConnectionContext回调客户端的方法,客户端通过回调方法接收结果。

之前我写过一篇文章《分享一个基于长连接+长轮询+原生的JS及AJAX实现的多人在线即时交流聊天室》,是通过长轮询+长连接的方式来实现的在线多人聊天室功能,从代码量来看就知道实现起来并不简单,而如今有了SignalR,会简单很多,我这里使用SignalR再来写一个简单的在线多人聊天室示例,以便大家快速掌握SignalR。

DEMO - 1 示例代码如下:

服务端:

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
72
73
74
//Startup类文件
 
using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;
using Microsoft.AspNet.SignalR;
 
[assembly: OwinStartup(typeof(TestWebApp.Models.Startup))]
 
namespace TestWebApp.Models
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
    }
}
 
 
//ChatHub类文件
 
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Web;
 
namespace TestWebApp.Models
{
    [HubName("chat")]
    public class ChatHub : Hub
    {
        public static ConcurrentDictionary<stringstring> OnLineUsers = new ConcurrentDictionary<stringstring>();
 
        [HubMethodName("send")]
        public void Send(string message)
        {
            string clientName = OnLineUsers[Context.ConnectionId];
            message = HttpUtility.HtmlEncode(message).Replace("\r\n""<br/>").Replace("\n""<br/>");
            Clients.All.receiveMessage(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), clientName, message);
        }
 
        [HubMethodName("sendOne")]
        public void Send(string toUserId, string message)
        {
            string clientName = OnLineUsers[Context.ConnectionId];
            message = HttpUtility.HtmlEncode(message).Replace("\r\n""<br/>").Replace("\n""<br/>");
            Clients.Caller.receiveMessage(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), string.Format("您对 {1}", clientName, OnLineUsers[toUserId]), message);
            Clients.Client(toUserId).receiveMessage(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), string.Format("{0} 对您", clientName), message);
        }
          
        public override System.Threading.Tasks.Task OnConnected()
        {
            string clientName = Context.QueryString["clientName"].ToString();
            OnLineUsers.AddOrUpdate(Context.ConnectionId, clientName, (key, value) => clientName);
            Clients.All.userChange(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), string.Format("{0} 加入了。", clientName), OnLineUsers.ToArray());
            return base.OnConnected();
        }
 
        public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
        {
            string clientName = Context.QueryString["clientName"].ToString();
            Clients.All.userChange(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), string.Format("{0} 离开了。", clientName), OnLineUsers.ToArray());
            OnLineUsers.TryRemove(Context.ConnectionId, out clientName);
            return base.OnDisconnected(stopCalled);
        }
 
    }
}
1
2
3
4
5
6
7
8
public ActionResult Index()
{
    ViewBag.ClientName = "聊客-" + Guid.NewGuid().ToString("N");
    var onLineUserList = ChatHub.OnLineUsers.Select(u => new SelectListItem() { Text = u.Value, Value = u.Key }).ToList();
    onLineUserList.Insert(0, new SelectListItem() { Text = "-所有人-", Value = "" });
    ViewBag.OnLineUsers = onLineUserList;
    return View();
}

WEB客户端:

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <meta charset="utf-8" />
    <title>聊天室</title>
    <script src="~/Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
    <script src="~/Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></script>
    <style type="text/css">
        #chatbox {
            width: 100%;
            height: 500px;
            border: 2px solid blue;
            padding: 5px;
            margin: 5px 0px;
            overflow-x: hidden;
            overflow-y: auto;
        }
 
        .linfo {
        }
 
        .rinfo {
            text-align: right;
        }
    </style>
    <script type="text/javascript">
        $(function () {
 
            var clientName = $("#clientname").val();
            var eChatBox = $("#chatbox");
            var eUsers = $("#users");
 
            var conn = $.hubConnection();
            conn.qs = { "clientName": clientName };
 
 
            conn.start().done(function () {
 
                $("#btnSend").click(function () {
                    var toUserId = eUsers.val();
                    if (toUserId != "") {
                        chat.invoke("sendOne", toUserId, $("#message").val())
                        .done(function () {
                            //alert("发送成功!");
                            $("#message").val("").focus();
                        })
                        .fail(function (e) {
                            alert(e);
                            $("#message").focus();
                        });
                    }
                    else {
                        chat.invoke("send", $("#message").val())
                        .done(function () {
                            //alert("发送成功!");
                            $("#message").val("").focus();
                        })
                        .fail(function (e) {
                            alert(e);
                            $("#message").focus();
                        });
                    }
                });
 
            });
 
            var chat = conn.createHubProxy("chat");
 
            chat.on("receiveMessage", function (dt, cn, msg) {
                var clsName = "linfo";
                if (cn == clientName || cn.indexOf("您对") >= 0) clsName = "rinfo";
                eChatBox.append("<p class='" + clsName + "'>" + dt + " <strong>" + cn + "</strong> 说:<br/>" + msg + "</p>");
                eChatBox.scrollTop(eChatBox[0].scrollHeight);
            });
 
            chat.on("userChange", function (dt, msg, users) {
                eChatBox.append("<p>" + dt + " " + msg + "</p>");
                eUsers.find("option[value!='']").remove();
                for (var i = 0; i < users.length; i++) {
                    if (users[i].Value == clientName) continue;
                    eUsers.append("<option value='" + users[i].Key + "'>" + users[i].Value + "</option>")
                }
            });
 
 
 
        });
    </script>
</head>
<body>
    <h3>大众聊天室</h3>
    <div id="chatbox">
    </div>
    <div>
        <span>聊天名称:</span>
        @Html.TextBox("clientname", ViewBag.ClientName as stringnew { @readonly "readonly", style = "width:300px;" })
        <span>聊天对象:</span>
        @Html.DropDownList("users", ViewBag.OnLineUsers as IEnumerable<SelectListItem>)
    </div>
    <div>
        @Html.TextArea("message"new { rows = 5, style = "width:500px;" })
        <input type="button" value="发送消息" id="btnSend" />
    </div>
</body>
</html>

服务端与客户端代码都比较简单,网上相关的说明也有,这里就不再解说了,只说一下这种方式JS端调用服务端方法采用:chat.invoke,而被服务端回调的方法则采用:chat.on (这里的chat是createHubProxy创建得来的)

第二种:采用集线器类(Hub)+自动生成代理模式

DEMO - 2 示例代码如下:

服务端与DEMO 1相同,无需改变

客户端:

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <meta charset="utf-8" />
    <title>聊天室</title>
    <script src="~/Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
    <script src="~/Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></script>
    <script src="~/signalr/hubs" type="text/javascript"></script>
    <style type="text/css">
        #chatbox {
            width: 100%;
            height: 500px;
            border: 2px solid blue;
            padding: 5px;
            margin: 5px 0px;
            overflow-x: hidden;
            overflow-y: auto;
        }
 
        .linfo {
        }
 
        .rinfo {
            text-align: right;
        }
    </style>
    <script type="text/javascript">
        $(function () {
 
            var clientName = $("#clientname").val();
            var eChatBox = $("#chatbox");
            var eUsers = $("#users");
 
            var chat = $.connection.chat;
            $.connection.hub.qs = { "clientName": clientName };
            chat.state.test = "test";
 
            chat.client.receiveMessage = function (dt, cn, msg) {
                var clsName = "linfo";
                if (cn == clientName || cn.indexOf("您对")>=0) clsName = "rinfo";
                eChatBox.append("<p class='" + clsName + "'>" + dt + " <strong>" + cn + "</strong> 说:<br/>" + msg + "</p>");
                eChatBox.scrollTop(eChatBox[0].scrollHeight);
            }
 
            chat.client.userChange = function (dt, msg, users) {
                eChatBox.append("<p>" + dt + " " + msg + "</p>");
                eUsers.find("option[value!='']").remove();
                for (var i = 0; i < users.length; i++) {
                    if (users[i].Value == clientName) continue;
                    eUsers.append("<option value='" + users[i].Key + "'>" + users[i].Value + "</option>")
                }
            }
 
            $.connection.hub.start().done(function () {
 
                $("#btnSend").click(function () {
                    var toUserId = eUsers.val();
                    if (toUserId != "") {
                        chat.server.sendOne(toUserId, $("#message").val())
                            .done(function () {
                                //alert("发送成功!");
                                $("#message").val("").focus();
                            })
                            .fail(function (e) {
                                alert(e);
                                $("#message").focus();
                            });
                    }
                    else {
                        chat.server.send($("#message").val())
                        .done(function () {
                            //alert("发送成功!");
                            $("#message").val("").focus();
                        })
                        .fail(function (e) {
                            alert(e);
                            $("#message").focus();
                        });
                    }
                });
 
            });
 
        });
    </script>
</head>
<body>
    <h3>大众聊天室</h3>
    <div id="chatbox">
    </div>
    <div>
        <span>聊天名称:</span>
        @Html.TextBox("clientname", ViewBag.ClientName as string, new { @readonly = "readonly", style = "width:300px;" })
        <span>聊天对象:</span>
        @Html.DropDownList("users", ViewBag.OnLineUsers as IEnumerable<SelectListItem>)
    </div>
    <div>
        @Html.TextArea("message", new { rows = 5, style = "width:500px;" })
        <input type="button" value="发送消息" id="btnSend" />
    </div>
</body>
</html>

上述代码中特别需要注意的是,需要引用一个“不存在的JS目录”:<script src="~/signalr/hubs" type="text/javascript"></script>,为什么要打引号,是因为我们在写代码的时候是不存在的,而当运行后就会自动生成signalr的代理脚本,这就是与非自动生成代理脚本最根本的区别,也正是因为这个自动生成的脚本,我们可以在JS中更加方便的调用服务端方法及定义回调方法,调用服务端方法采用:chat.server.XXX,而被服务端回调的客户端方法则采用:chat.client.XXX

看一下上述两种的运行效果截图吧:

第三种:采用持久化连接类(PersistentConnection)

DEMO - 3 示例代码如下:

服务端:

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
72
73
74
75
76
77
78
//Startup类:
 
using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;
using Microsoft.AspNet.SignalR;
 
[assembly: OwinStartup(typeof(TestWebApp.Models.Startup))]
 
namespace TestWebApp.Models
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR<MyConnection>("/MyConnection");
        }
    }
}
 
 
//MyConnection类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNet.SignalR;
 
namespace TestWebApp.Models
{
    public class MyConnection : PersistentConnection
    {
        private static List<string> monitoringIdList = new List<string>();
        protected override Task OnConnected(IRequest request, string connectionId)
        {
            bool IsMonitoring = (request.QueryString["Monitoring"] ?? "").ToString() == "Y";
            if (IsMonitoring)
            {
                if (!monitoringIdList.Contains(connectionId))
                {
                    monitoringIdList.Add(connectionId);
                }
                return Connection.Send(connectionId, "ready");
            }
            else
            {
                if (monitoringIdList.Count > 0)
                {
                    return Connection.Send(monitoringIdList, "in_" + connectionId);
                }
                else
                {
                    return Connection.Send(connectionId, "nobody");
                }
            }
        }
 
        protected override Task OnReceived(IRequest request, string connectionId, string data)
        {
            if (monitoringIdList.Contains(connectionId))
            {
                return Connection.Send(data, "pass");
            }
            return null;
        }
 
        protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled)
        {
            if (!monitoringIdList.Contains(connectionId))
            {
                return Connection.Send(monitoringIdList, "out_" + connectionId);
            }
            return null;
        }
    }
}

WEB客户端:

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<!-- MonitoringPage.cshtml 监控管理页面-->
 
 
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>MonitoringPage</title>
    <script src="~/Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
    <script src="~/Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></script>
    <style type="text/css">
        table {
            border:1px solid #808080;
            width:600px;
        }
        td {
            border:1px solid #808080;
            padding:3px;
        }
        .odd{ }
        .even{ }
        .non-temptr {
            display:none;
        }
    </style>
    <script type="text/javascript">
        $(function () {
            $("#userstable tbody tr:odd").addClass("odd");
            $("#userstable tbody tr:even").addClass("even");
 
            var conn = $.connection("/MyConnection", {"Monitoring":"Y"});
 
            conn.start().done(function () {
                $("#userstable").delegate("button.pass", "click", function () {
                    var rid = $(this).parent("td").prev().attr("data-rid");
                    conn.send(rid);
                    var tr = $(this).parents("tr");
                    tr.remove();
                });
                 
            }).fail(function (msg) {
                alert(msg);
            });
 
            conn.received(function (msg) {
                if (msg == "ready")
                {
                    $("#spstatus").html("监控服务已就绪");
                    return;
                }
                else if (msg.indexOf("in_") == 0) {
                    var tr = $(".non-temptr").clone(true);
                    tr.removeClass("non-temptr");
                    var td = tr.children().first();
                    var rid = msg.toString().substr("in_".length);
                    td.html(rid + "进入被监控页面,是否允许?");
                    td.attr("data-rid", rid);
                    $("#userstable tbody").append(tr);
                }
                else
                {
                    var rid = msg.toString().substr("out_".length);
                    $("td[data-rid=" + rid + "]").parent("tr").remove();
                }
            });
 
        });
    </script>
</head>
<body>
    <div>
        以下是实时监控到进入EnterPage页面的用户情况:(服务状况:<strong><span id="spstatus"></span></strong>)
    </div>
    <table id="userstable">
        <tr>
            <td>用户进入消息</td>
            <td>授 权</td>
        </tr>
        <tr class="non-temptr">
            <td></td>
            <td style="width:100px"><button class="pass">允许</button></td>
        </tr>
    </table>
</body>
</html>
 
 
<!-- EnterPage.cshtml 监控受限页面-->
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>EnterPage</title>
    <script src="~/Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
    <script src="~/Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></script>
</head>
<body>
    <script type="text/javascript">
        $(function () {
            var conn = $.connection("/MyConnection");
 
            conn.start().fail(function (msg) {
                alert(msg);
            });
 
            conn.received(function (data) {
                if (data == "pass") {
                    $("#msg").html("管理员已审核通过,可以进入浏览详情。");
                    setTimeout(function () {
                        self.location = "http://www.zuowenjun.cn";
                    }, 3000);
                }
                else
                {
                    $("#msg").html("无管理员在线,请稍候再重新进入该页面。");
                }
            });
        });
    </script>
    <div id="msg">
        该页面浏览受限,已自动将您的浏览请求发给管理员,请稍候。。。
    </div>
</body>
</html>

上述代码可以看出与采用Hub(集线器类)的不同之处,一是:Startup.Configuration中是需要指定app.MapSignalR<MyConnection>("/MyConnection"),二是需实现继承自PersistentConnection类的自定义的持久化连接类,在这个连接中可以重写:OnConnected、OnDisconnected、OnReceived、OnReconnected、ProcessRequest方法,同时有几个重要的属性成员Connection、Groups,服务端发消息给客户端采用:Connection.Broadcast(广播,所有客户端都可以收到消息),Connection.Send(发送给指定的客户端)

运行效果如下截图示:

SignalR支持额外附加:QueryString、Cookie、State,具体的客户端设置与服务端接收请见上面的代码,同时也可以参见如下其它博主总结的表格(SignalR的Javascript客户端API使用方式整理):

SignalR的三个Demo的更多相关文章

  1. 通过三个DEMO学会SignalR的三种实现方式

    一.理解SignalR ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信(即:客户端(Web页面)和服务器端可以互相实时的通知消息 ...

  2. SignalR代理对象异常:Uncaught TypeError: Cannot read property 'client' of undefined 推出的结论 SignalR 简单示例 通过三个DEMO学会SignalR的三种实现方式 SignalR推送框架两个项目永久连接通讯使用 SignalR 集线器简单实例2 用SignalR创建实时永久长连接异步网络应用程序

    SignalR代理对象异常:Uncaught TypeError: Cannot read property 'client' of undefined 推出的结论   异常汇总:http://www ...

  3. 通过三个DEMO学会SignalR的三种实现方式 转载https://www.cnblogs.com/zuowj/p/5674615.html

    一.理解SignalR ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信(即:客户端(Web页面)和服务器端可以互相实时的通知消息 ...

  4. RobotFrameWork接口报文测试-----(三)demo的加强版(数据驱动测试)

    在上一篇RobotFrameWork接口报文测试-----(二)demo的升级版基础上,将接口的xml的格式保存在xml文件中,然后程序如果增加一个接口,在xml文件里添加即可,无需修改自动化测试里的 ...

  5. vue学习-第三个DEMO(计算属性和监视) v-model基础用法

    <div id="demo"> 姓:<input type="text" placeholder="First Name" ...

  6. SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=》提升)

     SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=>提升,5个Demo贯彻全篇,感兴趣的玩才是真的学) 官方demo:http://www.asp.net/si ...

  7. 【转】IOS AutoLayout详解(三)用代码实现(附Demo下载)

    转载自:blog.csdn.net/hello_hwc IOS SDK详解 前言: 在开发的过程中,有时候创建View没办法通过Storyboard来进行,又需要AutoLayout,这时候用代码创建 ...

  8. 实现服务器端与客户端的高频实时通信 SignalR(2)

    说明:本篇文章与上篇文章 实现服务器端与客户端的实时通信 SignalR(1) 基本代码类似,只是做了些处理 高频 的改动. 一.本文出处:SignalR 实例介绍 (建议看原著里面有DEMO下载) ...

  9. 实现服务器端与客户端的实时通信 SignalR(1)

    一.本文出处:SignalR 实例介绍 (建议看原著里面有DEMO下载) 二.这篇文章介绍如何利用 VS2012 创建一个简单的实时聊天系统,建好后的样子如下(模拟三个在线用户):    三.Demo ...

随机推荐

  1. Andrew Ng机器学习课程17(2)

    Andrew Ng机器学习课程17(2) 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 说明:主要介绍了利用value iteration和policy i ...

  2. TCP 客户端编程

    1.Qt中TCP客户端编程 对Qt编程而言,网络只是数据传输的通道: Qt提供了QTcpSocket类(封装了TCP协议细节): 将QTcpSocket的对象当做黑盒使用,进行数据首发. 1.1QTc ...

  3. ${__setProperty(row,rowNum)};不能在import XXX后面使用;

    如下 ${__javaScript只能用一次调用 excel.CWResultFile.CWOutputFile.wOutputFile("/Users/iot/1.xls", & ...

  4. 【CodeForces】868F. Yet Another Minimization Problem

    原题链接 题目大意是有N个数,分成K段,每一段的花费是这个数里相同的数的数对个数,要求花费最小 如果只是区间里相同数对个数的话,莫队就够了 而这里是!边单调性优化边莫队(只是类似莫队)!而移动的次数和 ...

  5. java中如何测试一段代码的运行时间

    一.以毫秒为单位.long startTime = System.currentTimeMillis(); //获取开始时间 doSomething(); //测试的代码段 long endTime ...

  6. python中检测某个变量是否有定义

    目录 第一种方法使用内置函数locals() 第二种方法使用内置函数dir() 第三种方法使用内置函数vars() 第一种方法使用内置函数locals() 'testvar' in locals(). ...

  7. SAS学习笔记4 基本运算语句(lag、retain、_n_函数)

    lag:返回的是上一次lag函数运行时的实参,即lag(argument)=上一次lag函数执行时的argument retain:对变量进行值的初始化和保留到下一个迭代步 _n_:data步的自动变 ...

  8. docker部署mysql,nginx,php,并上传镜像到私有仓库

    前言 最近公司准备把现有环境全部搞成容器化,所以笔者就先了解了一下docker,并搞了一搞,并把自己搞的过程记录下来.话不多说直接开干 环境说明 Centos7 Docker version 18.0 ...

  9. WinRAR 去广告的姿势

    一直在使用WinRAR解压文件,感觉非常的好用,可是现在WinRAR添加了广告,每次打开压缩包都会弹出广告,有时候甚至在解压的时候弹出来,而每次弹出广告都会卡顿一下,忍了很长时间今天实在是受够了,准备 ...

  10. Luogu4770 NOI2018你的名字(后缀自动机+线段树合并)

    先考虑l=1,r=n,并且不要求本质不同的情况.对原串建SAM,将询问串在上面跑,得到每个前缀的最长匹配后缀即可得到答案. 然后考虑本质不同.对询问串也建SAM,统计每个节点的贡献,得到该点right ...