SignalR注册成Windows后台服务,并实现web前端断线重连
注意下文里面的 SignalR 不是 Core 版本,而是 Framework 下的
本文使用的方式是把 SignalR 写在控制台项目里,再用 Topshelf 注册成 Windows 服务
这样做有两点好处
- 传统 Window 服务项目调试时需要“附加到进程”,开发体验比较差,影响效率
- 使用控制台不仅可以随时打断点调试,还可以随时打印调试信息,非常方便
Topshelf 的使用方法这里不再阐述,在控制台里使用 Topshelf 三个步骤 :
- 定义一个 Owin 自托管作为 SignalR的宿主,里面设置允许跨域,起名为 Startup
using Microsoft.AspNet.SignalR; using Microsoft.Owin.Cors; using Owin; using System; using System.Diagnostics;namespace HenryMes.SignalR.Hosting {/// <summary>/// 配置跨域请求、SignalR Server/// </summary>class Startup{public void Configuration(IAppBuilder app){//app.UseErrorPage();app.UseCors(CorsOptions.AllowAll);// 有关如何配置应用程序的详细信息,请访问 http://go.microsoft.com/fwlink/?LinkID=316888//Hub Modeapp.MapSignalR("/lcc", new HubConfiguration());app.Map("/signalr", map =>{var config = new HubConfiguration{// You can enable JSONP by uncommenting this line// JSONP requests are insecure but some older browsers (and some// versions of IE) require JSONP to work cross domainEnableJSONP = true};//config.EnableCrossDomain = true;// Turns cors support on allowing everything// In real applications, the origins should be locked downmap.UseCors(CorsOptions.AllowAll).RunSignalR(config);});Make long polling connections wait a maximum of 110 seconds for aresponse. When that time expires, trigger a timeout command andmake the client reconnect.//GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110);Wait a maximum of 30 seconds after a transport connection is lostbefore raising the Disconnected event to terminate the SignalR connection.//GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(30);For transports other than long polling, send a keepalive packet every10 seconds. This value must be no more than 1/3 of the DisconnectTimeout value.//GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(10);// Turn tracing on programmaticallyGlobalHost.TraceManager.Switch.Level = SourceLevels.Information;}} }
- 建立可在服务里运行的服务类,使用了上面的Startup配置实例化宿主对象,里面定义了服务的启动,暂停,关闭等触发时的一些动作,本文就建立一个 JobManager 类来完成这些工作
using HenryMes.Utils; using Microsoft.Owin.Hosting; using System; using System.Threading.Tasks;namespace HenryMes.SignalR.Hosting {public class JobManager{private const string displayName = "SignalR 状态监控";IDisposable SignalR { get; set; }public bool Start(){try{//signalr server地址,端口可以更换,确保不被占用,否则服务启动不了 #if DEBUGvar url = $"http://{JsonConfig.Instance.Root()?.Debug?.Ip}:{JsonConfig.Instance.Root()?.Debug?.Port}";var Port = $"{JsonConfig.Instance.Root()?.Debug?.Port}"; #elsevar url = $"http://{JsonConfig.Instance.Root()?.Release?.Ip}:{JsonConfig.Instance.Root()?.Release?.Port}";var Port = $"{JsonConfig.Instance.Root()?.Release?.Port}"; #endifStartOptions options = new StartOptions();options.Urls.Add(url);options.Urls.Add($"http://+:{Port}");//此处需要用一个全局变量来保存WebApp,否则在发布为后台服务的时候生命周期会提前结束,被系统回收掉SignalR = WebApp.Start<Startup>(options);Task.Delay(TimeSpan.FromSeconds(1)).Wait();Console.WriteLine("Server running on {0}", url);Console.WriteLine($"{displayName}服务开始");Console.ReadLine();LogHelper.GetInstance().Information($"{displayName}服务开始,地址 {url}");return true;}catch (Exception ex){LogHelper.GetInstance().Error(ex);}return false;}public bool Stop(){SignalR.Dispose();LogHelper.GetInstance().Information($"{displayName}服务停止");System.Threading.Thread.Sleep(1500);return true;}public bool Shutdown(){SignalR.Dispose();LogHelper.GetInstance().Information($"{displayName}服务停止");System.Threading.Thread.Sleep(1500);return true;}} }
- 在 Program.cs 文件,也就是入口函数 main 调用 Topshelf 对服务进行配置
using Topshelf;namespace HenryMes.SignalR.Hosting
{internal class Program{private const string displayName = "HenryMes.SignalR.Hosting";static void Main(string[] args){HostFactory.Run(x => {x.Service<JobManager>(s =>{s.ConstructUsing(name => new JobManager());s.WhenStarted(tc => tc.Start());s.WhenShutdown(tc => tc.Shutdown());s.WhenStopped(tc => tc.Stop());});x.RunAsLocalSystem();x.StartAutomatically();x.SetDescription(displayName);x.SetDisplayName(displayName);x.SetServiceName(displayName);});}}
}
下面定义一个 SignalR 的 Hub 基类,里面管理了SignalR 的连接和断开,一个线程管理一个连接,连接断开,线程自动取消,建立一个抽象类 BaseHub
using HenryMes.Utils;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;namespace HenryMes.SignalR.Hosting
{/// <summary>/// /// </summary>[HubName(nameof(T))]public abstract class BaseHub<T> : Hub where T : IHub{/// <summary>/// 线程安全版本的字典,管理SignalR的连接/// </summary>protected static readonly ConcurrentDictionary<string, CancellationTokenSource> Connections =new ConcurrentDictionary<string, CancellationTokenSource>();/// <summary>/// 异步锁/// </summary>public static readonly object AsyncObj = new object();/// <summary>/// 设置超时时间/// </summary>abstract protected int millisecondsTimeout { get; }/// <summary>/// 设置线程轮询时间/// </summary>abstract protected int intervalTime { get; }/// <summary>/// 跑单个Task/// </summary>abstract protected Func<object> RunMethod { get; }/// <summary>/// 跑多个Task, 返回是否超时/// </summary>abstract protected Func<CancellationTokenSource, (bool, object)> RunMultiTaskMethod { get; }/// <summary>/// 是否跑多任务/// </summary>abstract protected bool runMultiTask { get; }//当客户端与服务器建立连接后执行的方法public override Task OnConnected(){//获取客户端IDConsole.WriteLine("{0}已连接", Context.ConnectionId);LogHelper.GetInstance().Information($"服务端与客户端:【{typeof(T).Name}】{Context.ConnectionId} 成功建立连接!");return base.OnConnected();}public override Task OnReconnected(){Console.WriteLine("{0}已重连", Context.ConnectionId);LogHelper.GetInstance().Information($"服务端与客户端:【{typeof(T).Name}】{Context.ConnectionId} 已重连!");Send(Context.ConnectionId);return base.OnReconnected();}/// <summary>/// 所有任务执行完是否超时/// </summary>/// <param name="tokenSource"></param>/// <param name="allTasks"></param>/// <returns></returns>public bool IsCompletedAllTasks(CancellationTokenSource tokenSource, Task[] allTasks){try{return Task.WaitAll(allTasks, millisecondsTimeout, tokenSource.Token);}catch (AggregateException ex){LogHelper.GetInstance().Error($"系统错误:{this.GetType().Name},{ex.Flatten().InnerException.Message}");tokenSource.Cancel();}return false;}/// <summary>/// 向客户端发送消息/// </summary>/// <param name="connectId"></param>public void Send(string connectId){lock (AsyncObj){var tokenSource = new CancellationTokenSource();Connections.TryAdd(connectId, tokenSource);Task.Run(() =>{while (!tokenSource.Token.IsCancellationRequested){try{// 是否是多任务if (runMultiTask == false){var result = RunMethod();var message = $"【{typeof(T).Name}】 {connectId} 正在回传数据!";LogHelper.GetInstance().Information(message);// 把组装好的数据推送到前端BaseNotifer<T>.Refresh(connectId, JsonConvert.SerializeObject(result));tokenSource.Token.WaitHandle.WaitOne(intervalTime);}else{// 是否超时var (isCompleted, result) = RunMultiTaskMethod(tokenSource);if (isCompleted){var message = $"【{typeof(T).Name}】 {connectId} 正在回传数据!";LogHelper.GetInstance().Information(message);// 把组装好的数据推送到前端BaseNotifer<T>.Refresh(connectId, JsonConvert.SerializeObject(result));// 下一次推送等待N秒后进行tokenSource.Token.WaitHandle.WaitOne(intervalTime);}else{// 等待超时tokenSource.Cancel();// 打印超时错误日志LogHelper.GetInstance().Error($@"{this.GetType().Name} 推送超时! 当前超时时间设置为{millisecondsTimeout}毫秒!");// 重新执行Connections.TryRemove(connectId, out tokenSource);Send(connectId);}}}catch(AggregateException ex){LogHelper.GetInstance().Error($"系统错误:{this.GetType().Name},{ex.Flatten().InnerException.Message}");tokenSource.Token.WaitHandle.WaitOne(intervalTime);}}}, tokenSource.Token);}}/// <summary>/// 连接断开事件/// </summary>/// <param name="stopCalled"></param>/// <returns></returns>public override Task OnDisconnected(bool stopCalled){lock (AsyncObj){try{var tokenSource = Connections[Context.ConnectionId];Connections.TryRemove(Context.ConnectionId, out tokenSource);tokenSource.Cancel();LogHelper.GetInstance().Information($"服务端与客户端:【{typeof(T).Name}】{Context.ConnectionId} 连接已断开!");}catch (Exception ex){if (Connections.ContainsKey(Context.ConnectionId)){var tokenSource = Connections[Context.ConnectionId];Connections.TryRemove(Context.ConnectionId, out tokenSource);}// 打印错误日志LogHelper.GetInstance().Error($@"{this.GetType().Name} 已断开! {ex.Message}!");}}return base.OnDisconnected(stopCalled);}}
}
以一个具体的 Hub 为例,继承上面的 BaseHub, 建立一个 具体实现的 Hub 名为 OperationKanBanHub ,使用 RunMultiTaskMethod 并行执行一些任务,这是项目里的一个真实案例,不必关心细节
using HenryMes.Entitys;
using HenryMes.WebApi.Controllers;
using HenryMes.WebApi.Controllers.Other;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;namespace HenryMes.SignalR.Hosting.Hubs
{/// <summary>/// 运营看板/// </summary>public class OperationKanBanHub : BaseHub<OperationKanBanHub>{/// <summary>/// 设置超时时间/// </summary>protected override int millisecondsTimeout => 10000;/// <summary>/// 设置线程轮询时间/// </summary>protected override int intervalTime => 5000;/// <summary>/// 是否跑多任务/// </summary>protected override bool runMultiTask => true;/// <summary>/// 跑单个Task/// </summary>protected override Func<object> RunMethod => throw new NotImplementedException();/// <summary>/// 跑多个Task/// </summary>protected override Func<CancellationTokenSource, (bool, object)> RunMultiTaskMethod => (TokenSource) =>{#region Task取数// 菜籽收购var taskSum4Rapeseed = Task.Run(() =>{KanbanController controller = new KanbanController();dynamic data = controller.ControlCenter_Center_Sum4Rapeseed();return data.Content;});// 油品生产,销售var taskSum4Oil = Task.Run(() =>{KanbanController controller = new KanbanController();dynamic data = controller.ControlCenter_Center_Sum4Oil();return data.Content;});// 库存量前10的存货var taskSum4Top = Task.Run(() =>{KanbanController controller = new KanbanController();dynamic data = controller.ControlCenter_Center_Sum4Top();return data.Content;});// 罐区存油,读取 mongodbvar taskTankOilQuantity = Task.Run(() =>{return new List<dynamic>{new { tank = "Tank1001",temperature = "21.4℃",pressure = "21PA", quantity = "100" },new { tank = "Tank1002",temperature = "21.4℃",pressure = "21PA", quantity = "100" },new { tank = "Tank1003",temperature = "21.4℃",pressure = "21PA", quantity = "100" },new { tank = "Tank1004",temperature = "21.4℃",pressure = "21PA", quantity = "100" },new { tank = "Tank1005",temperature = "21.4℃",pressure = "21PA", quantity = "100" },new { tank = "Tank1006",temperature = "21.4℃",pressure = "21PA", quantity = "100" },new { tank = "Tank1007",temperature = "21.4℃",pressure = "21PA", quantity = "100" },new { tank = "Tank1008",temperature = "21.4℃",pressure = "21PA", quantity = "100" },new { tank = "Tank1009",temperature = "21.4℃",pressure = "21PA", quantity = "100" },new { tank = "Tank1010",temperature = "21.4℃",pressure = "21PA", quantity = "100" },new { tank = "Tank1011",temperature = "21.4℃",pressure = "21PA", quantity = "100" },new { tank = "Tank1012",temperature = "21.4℃",pressure = "21PA", quantity = "100" },new { tank = "Tank1013",temperature = "21.4℃",pressure = "21PA", quantity = "100" },new { tank = "Tank1014",temperature = "21.4℃",pressure = "21PA", quantity = "100" },};});// 近一年产出销售 1-12月var taskSaleDispatch4Month = Task.Run(() =>{KanbanController controller = new KanbanController();dynamic data = controller.ControlCenter_Center_SaleDispatch4Month();return data.Content;});// 最近10条采购信息var taskPreInStore4Lately = Task.Run(() =>{KanbanController controller = new KanbanController();dynamic data = controller.ControlCenter_Center_PreInStore4Lately();return data.Content;});// 最近10条生产计划var taskProductionTask = Task.Run(() =>{ProductionTaskController controller = new ProductionTaskController();dynamic data = controller.QueryTake(new SqlSugarPageRequest{PageIndex = 1,PageSize = 10,Filter = new List<SqlSugar.ConditionalModel>()});return data.Content;});#endregion#region 同步阻塞等待所有Task执行完// 所有线程任务是否完成 默认falsevar isCompleted = IsCompletedAllTasks(TokenSource, new Task[] {taskSum4Rapeseed,taskSum4Oil,taskSum4Top,taskTankOilQuantity,taskSaleDispatch4Month,taskPreInStore4Lately,taskProductionTask});#endregionif (isCompleted){#region 所有Task已完成// 菜籽var RapeseedResult = taskSum4Rapeseed.Result;var Rapeseed = new{GYS = new{PurchaseReceiveQuantity = RapeseedResult?.Data?.Rapeseed_GYS?.PurchaseReceiveQuantity,BalanceQuantity = RapeseedResult?.Data?.Rapeseed_GYS?.BalanceQuantity,},SD = new{PurchaseReceiveQuantity = RapeseedResult?.Data?.Rapeseed_SD?.PurchaseReceiveQuantity,BalanceQuantity = RapeseedResult?.Data?.Rapeseed_SD?.BalanceQuantity,}};// 油品生产,销售dynamic Sum4OilResult = taskSum4Oil.Result;var Sum4Oil = new{// 产出成品油TankOilQuantity = Sum4OilResult?.Data?.TankOil?.ProductReceiveQuantity,// 产出包装油PackageOilQuantity = Sum4OilResult?.Data?.PackageOil?.ProductReceiveQuantity,// 销售包装油SaleOilQuantity = Sum4OilResult?.Data?.PackageOil?.SaleDispatchQuantity};// 存货中库存量前10的存货var Sum4TopResult = taskSum4Top.Result;var Sum4Top = new{Sum4TopResult?.Data?.DataSource};// 罐区存油var TankOilQuantity = taskTankOilQuantity.Result;// 近一年产出销售 1-12月var TaskSaleDispatch4MonthResult = taskSaleDispatch4Month.Result;var SaleDispatch4Month = new{Sale = TaskSaleDispatch4MonthResult?.Data?.SaleDispatch.Details,Product = TaskSaleDispatch4MonthResult?.Data?.ProductReceive.Details};// 最近10条采购信息var TaskPreInStore4LatelyResult = taskPreInStore4Lately.Result;var PreInStore4Lately = TaskPreInStore4LatelyResult?.Data?.DataSource;// 最近10条生产计划var taskProductionTaskResult = taskProductionTask.Result;var ProductionTask = taskProductionTaskResult?.Data;return (isCompleted, new{Rapeseed,Sum4Oil,Sum4Top,TankOilQuantity,SaleDispatch4Month,PreInStore4Lately,ProductionTask});#endregion}return (isCompleted, new { });};}
}
此时 SignalR 的后台推送基本就完成了,再来就是web前端的接收推送和断线下的自动重新连接(比如说后台服务程序做了更新,此时需要关闭服务再启动服务,这个时候要求web端不断尝试重新连接,直到后台服务启动并重新连接上为止)
前端使用 Vue 2.0 + jQuery.signalR 2.4.2 , 只列一下关键代码
import $ from "jquery";
import "signalr";
import echarts from "../../pages/kanban/OperationKanBanEcharts.vue";
export default {components: { echarts },data() {return {connection: null,proxy: null,// 是否需要断线重连的标记,当页面关闭时是不需要继续推送的tryReconnect : true}},methods: {// 从SignalR推送过来的数据,刷新看板refreshKanban(message) {// 刷新时间this.getDateTime()let obj = JSON.parse(message)// 省略无关代码......},},mounted() {this.$nextTick(() => {this.connection = $.hubConnection(process.env.SignalR);// 定义服务器端SignalR推送过来的消息接收代理this.proxy = this.connection.createHubProxy("OperationKanBanHub");this.proxy.on("Refresh", (message) => {console.log(`接收到来自服务端 ${this.connection.id} 的数据!`)this.refreshKanban(message)});// 创建连接到服务器端SignalR的连接this.connection.start().done(() => {// 客户端发送信息到服务器this.proxy.invoke("Send", this.connection.id);}).fail((err) => {console.log(err);});this.connection.disconnected(() => {if(this.tryReconnect) {setTimeout(() => {console.log('连接已断开,正尝试重新连接!')this.connection.start().done(() => {this.proxy.invoke("Send", this.connection.id); // 客户端发送信息到服务器}).fail((err) => {console.log(err);});}, 5000); // Restart connection after 5 seconds.}});});},deactivated() {if (this.connection) {// 关闭SignalR连接this.tryReconnect = falsethis.connection.stop();// 清除缓存this.$vnode.parent.componentInstance.cache = {};this.$vnode.parent.componentInstance.keys = [];}},
};
最后的一个步骤,怎么把后台的控制台SignalR宿主程序安装成 Windows 服务?在项目里建立两个批处理文件,Install.bat 安装服务,UnInstall.bat 卸载服务,点击右键点文件属性,把他们的编码改为 ansi(不要问我为什么......因为不改的话,打开批处理命令窗口的时候中文会显示成乱码)
Install.bat
@echo onrem 设置DOS窗口的背景颜色及字体颜色
color 2frem 设置DOS窗口大小
mode con: cols=80 lines=25@echo off
echo 请按任意键开始安装 HenryMes.SignalR.Hosting 服务rem 以管理员身份运行
%1 mshta vbscript:CreateObject("Shell.Application").ShellExecute("cmd.exe","/c %~s0 ::","","runas",1)(window.close)&&exit
:Adminrem 输出空行
echo.
pausecd /d %~dp0
HenryMes.SignalR.Hosting install --autostart start
net start HenryMes.SignalR.Hostingpause
UnInstall.bat
@echo onrem 设置DOS窗口的背景颜色及字体颜色
color 2frem 设置DOS窗口大小
mode con: cols=80 lines=25@echo off
echo 请按任意键开始卸载 HenryMes.SignalR.Hosting 服务rem 以管理员身份运行
%1 mshta vbscript:CreateObject("Shell.Application").ShellExecute("cmd.exe","/c %~s0 ::","","runas",1)(window.close)&&exit
:Adminrem 输出空行
echo.
pausecd /d %~dp0
net stop HenryMes.SignalR.Hosting
HenryMes.SignalR.Hosting uninstallpause
相关文章:

SignalR注册成Windows后台服务,并实现web前端断线重连
注意下文里面的 SignalR 不是 Core 版本,而是 Framework 下的 本文使用的方式是把 SignalR 写在控制台项目里,再用 Topshelf 注册成 Windows 服务 这样做有两点好处 传统 Window 服务项目调试时需要“附加到进程”,开发体验比较差…...

【前端笔试题二】从一个指定数组中,每次随机取一个数,且不能与上次取数相同,即避免相邻取数重复
前言 本篇文章记录下我在笔试过程中遇到的真实题目,供大家参考。 1、题目 系统给定一个数组,需要我们编写一个函数,该函数每次调用,随机从该数组中获取一个数,且不能与上一次的取数相同。 2、思路解析 数组已经有了…...
专栏关注学习
Node学习专栏(全网最细的教程) 【spring系列】 SpringCloud 前端框架Vue java学习过程 RocketMQ Spring Tomcat websocket 从头开始学Redisson 从头开始学Oracle 跟着大宇学Shiro 吃透Shiro源代码 Git基础与进阶 Java并发编程 Spring系列 手写…...

【手写 Vuex 源码】第八篇 - Vuex 的 State 状态安装
一,前言 上一篇,主要介绍了 Vuex 模块安装的实现,针对 action、mutation、getter 的收集与处理,主要涉及以下几个点: Vuex 模块安装的逻辑;Vuex 代码优化;Vuex 模块安装的实现;Vue…...

Mac下拉式终端的安装与配置 (iTerm2)
Mac下拉式终端的安装与配置 使用效果如图所示 安装前置软件 iTerm2 很可惜,如此炫酷的功能在原终端中并不能实现,我们需要借助iTerm2这个软件来实现。 官网链接:iTerm2 - macOS Terminal Replacement 我们点击download下载即可 配置 当我…...
使用 Spring 框架结合阿里云 OSS 实现文件上传的代码示例
使用 Spring 框架结合阿里云 OSS 实现文件上传的代码示例POM文件配置文件上传工具类控制层使用yaml配置文件(第二种用法,看公司要求)注入 OSSClient 对象及工具类(第二种用法,看公司要求)使用 Vue 前端代码…...

神经网络基础知识
神经网络基础知识 文章目录神经网络基础知识一、人工神经网络1.激活函数sigmod函数Tanh函数Leaky Relu函数分析2.过拟合和欠拟合二、学习与感知机1.损失函数与代价函数2. 线性回归和逻辑回归3. 监督学习与无监督学习三、优化1.梯度下降法2.随机梯度下降法(SGD)3. 批量梯度下降法…...

SpringBoot开发规范部分通用模板+idea配置【项目通用-1】
SpringBoot开发规范通用模板 1 分页插件使用 通过MybatisPlus配置分页插件拦截器 Configuration MapperScan("com.xuecheng.content.mapper") //拦截的mapper层 public class MybatisPlusConfig {//定义分页的拦截器Beanpublic MybatisPlusInterceptor getMybatisPl…...

程序的机器级表示part3——算术和逻辑操作
目录 1.加载有效地址 2. 整数运算指令 2.1 INC 和 DEC 2.2 NEG 2.3 ADD、SUB 和 IMUL 3. 布尔指令 3.1 AND 3.2 OR 3.3 XOR 3.4 NOT 4. 移位操作 4.1 算术左移和逻辑左移 4.2 算术右移和逻辑右移 5. 特殊的算术操作 1.加载有效地址 指令效果描述leaq S, DD…...

基于YOLOV5的钢材缺陷检测
数据和源码见文末 1.任务概述 数据集使用的是东北大学收集的一个钢材缺陷检测数据集,需要检测出钢材表面的6种划痕。同时,数据集格式是VOC格式,需要进行转化,上传的源码中的数据集是经过转换格式的版本。 2.数据与标签配置方法 在数据集目录下,train文件夹下有训练集数据…...

Session与Cookie的区别(三)
中场休息 让我们先从比喻回到网络世界里,HTTP 是无状态的,所以每一个 Request 都是不相关的,就像是对小明来说每一位客人都是新的客人一样,他根本不知道谁是谁。 既然你没办法把他们关联,就代表状态这件事情也不存在。…...

七大设计原则之接口隔离原则应用
目录1 接口隔离原则介绍2 接口隔离原则应用1 接口隔离原则介绍 接口隔离原则(Interface Segregation Principle, ISP)是指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。这个原则指导我们在设计接口时…...

【Shell1】shell语法,ssh/build/scp/upgrade,环境变量,自动升级bmc
文章目录1.shell语法:shell是用C语言编写的程序,是用户使用Linux的桥梁,硬件>内核(os)>shell>文件系统1.1 变量:readonly定义只读变量,unset删除变量1.2 函数:shell脚本传递的参数中包含空格&…...
JavaScript HTML DOM - 改变CSS
JavaScript 是一种动态语言,它可以动态地修改网页的外观,并且使用HTML DOM(文档对象模型)可以更方便地控制HTML元素的样式。 JavaScript 通过在HTML DOM中更改CSS属性来更改样式,这些CSS属性包括颜色、位置、字体大小…...

mycat连接mysql 简单配置
mycat三个配置文件位于conf下 可通过Notepad操作 首先配置service.xml中的user标签,设置用户名,密码,查询权限,是否只读等 只是设置了root用户,有所有权限 配置schema.xml <?xml version"1.0"?&g…...
Spring常用注解
文章目录一、Bean交给Spring管理1、Component2、Bean3、Controller4、Service5、Repository6、Configuration7、ComponentScan二、作用域1、Lazy(false)Scope三、依赖注入1、Autowired2、Resource3、Qualifier四、读取配置文件值1、Value一、Bean交给Spring管理 1、Component …...

I.MX6ULL内核开发9:kobject-驱动的基石
目录 一、摘要 二、重点 三、驱动结构模型 四、关键函数分析 kobject_create_and_add()函数 kobject_create()函数 kobject_init()函数 kobject_init_internal()函数 kobject_add()函数 kobject_add_varg&am…...

Docker-harbor私有仓库
一、Harbor概述 1、Harbor的概念 • Harbor是VMware公司开源的企业级Docker Registry项目,其目标是帮助用户迅速搭建一个企业级的Docker Registry服务 • Harbor以 Docker 公司开源的Registry 为基础,提供了图形管理UI、基于角色的访问控制(Role Base…...

Java之动态规划之子序列问题
目录 0.动态规划问题 一.最长递增子序列 1.题目描述 2.问题分析 3.代码实现 二.最长递增子序列 1.题目描述 2.问题分析 3.代码实现 三.最长重复子数组 1.题目描述 2.问题分析 3.代码实现 4.代码的优化(滚动数组) 四.最长公共子序列 1.题目描述 2.问题分析 3.代…...

java ArrayList
目录 一.简单介绍 二.ArrayList的底层结构 2.1ArrayList的底层结构和操作分析 2.ArrayList 底层源码分析 三.ArrayList 方法 四.代码使用方法 一.简单介绍 ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...

nnUNet V2修改网络——暴力替换网络为UNet++
更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...

Xela矩阵三轴触觉传感器的工作原理解析与应用场景
Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知,帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量,能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度,还为机器人、医疗设备和制造业的智…...