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 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...

如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...