Stardust/Stardust/LocalStarClient.cs

496 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using NewLife;
using NewLife.Http;
using NewLife.Log;
using NewLife.Messaging;
using NewLife.Remoting;
using Stardust.Models;
#if NET45_OR_GREATER || NETCOREAPP || NETSTANDARD
using TaskEx = System.Threading.Tasks.Task;
#endif
namespace Stardust;
/// <summary>本地星尘客户端。连接本机星尘代理StarAgent</summary>
public class LocalStarClient
{
#region
/// <summary>本机代理信息</summary>
public AgentInfo? Info { get; private set; }
/// <summary>本地服务端地址</summary>
public String? Server { get; set; }
/// <summary>本地星尘代理服务地址</summary>
public static Int32 Port { get; set; } = 5500;
private AgentInfo? _local;
private ApiClient? _client;
#endregion
#region
/// <summary>实例化</summary>
public LocalStarClient()
{
//_local = AgentInfo.GetLocal();
//_local.Server = StarSetting.Current.Server;
}
#endregion
#region
#if !NET40
[MemberNotNull(nameof(_client))]
#endif
private void Init()
{
if (_client != null) return;
_client = new ApiClient($"udp://127.0.0.1:{Port}")
{
Timeout = 3_000,
Log = Log,
};
var set = StarSetting.Current;
if (set.Debug) _client.EncoderLog = Log;
_local = AgentInfo.GetLocal(false);
_local.Server = !Server.IsNullOrEmpty() ? Server : StarSetting.Current.Server;
}
/// <summary>获取信息</summary>
/// <returns></returns>
public AgentInfo? GetInfo()
{
//!!! 通过进程名判断是否存在可能会误判因为获取其它dotnet进程命令行需要管理员权限
//// 检测进程是否存在,如果进程都不存在,没必要获取信息
//if (!Process.GetProcessesByName("StarAgent").Any())
//{
// var pis = Process.GetProcessesByName("dotnet");
// if (pis.Length == 0) return null;
// if (!pis.Any(p => AppInfo.GetProcessName(p).EqualIgnoreCase("StarAgent"))) return null;
//}
// 判断目标端口是否已使用,作为是否探测星尘代理的依据,加速应用启动
//if (!IPAddress.Any.CheckPort(NetType.Udp, 5500) &&
// !IPAddress.Loopback.CheckPort(NetType.Udp, 5500) &&
// !IPAddress.IPv6Loopback.CheckPort(NetType.Udp, 5500)) return null;
try
{
// 某些情况下检查端口占用会抛出异常,原因未知
var gp = IPGlobalProperties.GetIPGlobalProperties();
var eps = gp.GetActiveUdpListeners();
if (eps.Length > 0 && !eps.Any(ep => ep.Port == Port)) return null;
}
catch { }
var task = TaskEx.Run(GetInfoAsync);
return task.Wait(1000) ? task.Result : null;
}
/// <summary>获取信息</summary>
/// <returns></returns>
public async Task<AgentInfo?> GetInfoAsync()
{
Init();
try
{
return Info = await _client.InvokeAsync<AgentInfo>("Info", _local);
}
catch (TimeoutException)
{
return null;
}
catch
{
throw;
}
}
/// <summary>向StarAgent发送心跳</summary>
/// <returns></returns>
public async Task<PingResponse?> PingAsync(AppInfo? appInfo)
{
Init();
return await _client.InvokeAsync<PingResponse>("Ping", appInfo);
}
#endregion
#region
/// <summary>自杀并重启</summary>
/// <returns></returns>
public Boolean KillAndRestartMySelf()
{
Init();
var p = Process.GetCurrentProcess();
var fileName = p.MainModule?.FileName;
var args = "";
if (!fileName.IsNullOrEmpty())
{
var ext = Path.ChangeExtension(fileName, ".dll");
args = Environment.CommandLine.TrimStart(ext).Trim();
}
// 发起命令
var rs = _client.Invoke<String>("KillAndStart", new
{
processId = p.Id,
delay = 3,
fileName,
arguments = args,
workingDirectory = Environment.CurrentDirectory,
});
// 本进程退出
//p.Kill();
return !rs.IsNullOrEmpty();
}
#endregion
#region
/// <summary>探测并安装星尘代理</summary>
/// <param name="url">zip包下载源</param>
/// <param name="version">版本号</param>
/// <param name="target">目标目录</param>
public Boolean ProbeAndInstall(String? url = null, String? version = null, String? target = null)
{
//if (url.IsNullOrEmpty()) throw new ArgumentNullException(nameof(url));
if (url.IsNullOrEmpty())
{
var set = NewLife.Setting.Current;
url = set.PluginServer.EnsureEnd("/");
url += "star/";
if (Environment.Version.Major >= 8)
url += "staragent80.zip";
else if (Environment.Version.Major >= 7)
url += "staragent70.zip";
else if (Environment.Version.Major >= 6)
url += "staragent60.zip";
else if (Environment.Version.Major >= 5)
url += "staragent50.zip";
else if (Environment.Version.Major >= 4)
url += "staragent45.zip";
else
url += "staragent31.zip";
}
// 尝试连接,获取版本
try
{
var info = GetInfo();
if (info != null)
{
// 比目标版本高,不需要安装
if (String.Compare(info.Version, version) >= 0) return true;
if (!info.FileName.IsNullOrEmpty()) info.FileName = info.FileName.TrimEnd(" (deleted)");
if (target.IsNullOrEmpty()) target = Path.GetDirectoryName(info.FileName);
WriteLog("StarAgent在用版本 v{0},低于目标版本 v{1}", info.Version, version);
}
}
catch (Exception ex)
{
WriteLog("没有探测到StarAgent{0}", ex.GetTrue()?.Message);
}
if (target.IsNullOrEmpty())
{
// 在进程中查找
var p = Process.GetProcesses().FirstOrDefault(e => e.ProcessName == "StarAgent");
if (p != null)
{
try
{
if (p.MainModule != null)
target = Path.GetDirectoryName(p.MainModule.FileName);
}
catch { }
if (target.IsNullOrEmpty()) target = Path.GetDirectoryName(p.MainWindowTitle);
WriteLog("发现进程StarAgentProcessId={0}target={1}", p.Id, target);
}
}
// 准备安装,甭管是否能够成功重启,先覆盖了文件再说
{
if (target.IsNullOrEmpty()) target = "..\\staragent";
target = target.GetFullPath();
target.EnsureDirectory(false);
WriteLog("目标:{0}", target);
var ug = new Stardust.Web.Upgrade
{
SourceFile = Path.GetFileName(url).GetFullPath(),
DestinationPath = target,
Log = XTrace.Log,
};
WriteLog("下载:{0}", url);
var client = new HttpClient();
client.DownloadFileAsync(url, ug.SourceFile).Wait();
ug.Extract();
ug.Update();
File.Delete(ug.SourceFile);
}
{
// 在进程中查找
var info = Info;
var inService = info?.Arguments == "-s";
var p = info != null && info.ProcessId > 0 ?
Process.GetProcessById(info.ProcessId) :
Process.GetProcesses().FirstOrDefault(e => e.ProcessName == "StarAgent");
// 重启目标
if (p != null && !inService)
{
try
{
p.Kill();
}
catch (Win32Exception) { }
catch (Exception ex)
{
XTrace.WriteException(ex);
}
}
var fileName = info?.FileName;
if (!fileName.IsNullOrEmpty() && Path.GetFullPath(fileName).EqualIgnoreCase("dotnet.exe")) fileName = info?.Arguments;
var rs = false;
if (Runtime.Windows)
rs = RunAgentOnWindows(fileName, target, inService);
else if (Runtime.Linux)
rs = RunAgentOnLinux(fileName, target, inService);
if (!rs)
rs = RunAgentOnDotnet(fileName, target, inService);
}
return true;
}
private Boolean RunAgentOnWindows(String? fileName, String target, Boolean inService)
{
if (!fileName.IsNullOrEmpty() && Path.GetExtension(fileName) == ".dll") return false;
if (fileName.IsNullOrEmpty()) fileName = target.CombinePath("StarAgent.exe").GetFullPath();
if (!File.Exists(fileName)) return false;
WriteLog("RunAgentOnWindows fileName={0}, inService={1}", fileName, inService);
if (inService)
{
Process.Start(fileName, "-stop");
Process.Start(fileName, "-start");
WriteLog("启动服务成功");
}
else
{
var si = new ProcessStartInfo(fileName, "-run")
{
WorkingDirectory = Path.GetDirectoryName(fileName),
//UseShellExecute = true
};
var p = Process.Start(si);
WriteLog("启动进程成功 pid={0}", p?.Id);
}
return true;
}
private Boolean RunAgentOnLinux(String? fileName, String target, Boolean inService)
{
if (!fileName.IsNullOrEmpty() && Path.GetExtension(fileName) == ".dll") return false;
if (fileName.IsNullOrEmpty()) fileName = target.CombinePath("StarAgent").GetFullPath();
if (!File.Exists(fileName)) return false;
WriteLog("RunAgentOnLinux fileName={0}, inService={1}", fileName, inService);
// 在Linux中设置执行权限
Process.Start("chmod", $"+x {fileName}");
if (inService)
{
Process.Start(fileName, "-stop");
Process.Start(fileName, "-start");
WriteLog("启动服务成功");
}
else
{
var si = new ProcessStartInfo(fileName, "-run")
{
WorkingDirectory = Path.GetDirectoryName(fileName),
//UseShellExecute = true
};
var p = Process.Start(si);
WriteLog("启动进程成功 pid={0}", p?.Id);
}
return true;
}
private Boolean RunAgentOnDotnet(String? fileName, String target, Boolean inService)
{
if (fileName.IsNullOrEmpty()) fileName = target.CombinePath("StarAgent.dll").GetFullPath();
if (!File.Exists(fileName)) return false;
WriteLog("RunAgentOnDotnet fileName={0}, inService={1}", fileName, inService);
if (inService)
{
Process.Start("dotnet", $"{fileName} -stop");
Process.Start("dotnet", $"{fileName} -start");
WriteLog("启动服务成功");
}
else
{
var si = new ProcessStartInfo("dotnet", $"{fileName} -run")
{
WorkingDirectory = Path.GetDirectoryName(fileName),
//UseShellExecute = true
};
var p = Process.Start(si);
WriteLog("启动进程成功 pid={0}", p?.Id);
}
return true;
}
/// <summary>探测并安装星尘代理</summary>
/// <param name="url">zip包下载源</param>
/// <param name="version">版本号</param>
/// <param name="target">目标目录</param>
public static Task ProbeAsync(String? url = null, String? version = null, String? target = null)
{
return TaskEx.Run(() =>
{
var client = new LocalStarClient();
client.ProbeAndInstall(url, version, target);
});
}
#endregion
#region
///// <summary>安装应用服务(星尘代理守护)</summary>
///// <param name="service"></param>
///// <returns></returns>
//public async Task<ProcessInfo> Install(ServiceInfo service)
//{
// Init();
// return await _client.InvokeAsync<ProcessInfo>("Install", service);
//}
///// <summary>安装应用服务(星尘代理守护)</summary>
///// <param name="name">服务名,唯一标识</param>
///// <param name="fileName">文件</param>
///// <param name="arguments">参数</param>
///// <param name="workingDirectory">工作目录</param>
///// <returns></returns>
//public async Task<ProcessInfo> Install(String name, String fileName, String arguments = null, String workingDirectory = null)
//{
// Init();
// return await _client.InvokeAsync<ProcessInfo>("Install", new ServiceInfo
// {
// Name = name,
// FileName = fileName,
// Arguments = arguments,
// WorkingDirectory = workingDirectory,
// Enable = true,
// //ReloadOnChange = true,
// });
//}
///// <summary>卸载应用服务</summary>
///// <param name="serviceName"></param>
///// <returns></returns>
//public async Task<Boolean> Uninstall(String serviceName)
//{
// Init();
// return await _client.InvokeAsync<Boolean>("Uninstall", serviceName);
//}
#endregion
#region
/// <summary>在局域网中广播扫描所有StarAgent</summary>
/// <param name="local">本地信息,用于告知对方我是谁</param>
/// <param name="timeout"></param>
/// <returns></returns>
public static IEnumerable<AgentInfo> Scan(AgentInfo? local = null, Int32 timeout = 15_000)
{
var encoder = new JsonEncoder { Log = XTrace.Log };
// 构造请求消息
//var ms = new MemoryStream();
//var writer = new BinaryWriter(ms);
//writer.Write("Info");
//writer.Write(0);
//var msg = new DefaultMessage
//{
// Payload = ms.ToArray()
//};
//var buf = msg.ToPacket().ToArray();
var buf = encoder.CreateRequest("Info", null).ToPacket()?.ToArray();
if (buf == null) yield break;
// 在局域网中广播消息
var udp = new UdpClient();
udp.Send(buf, buf.Length, new IPEndPoint(IPAddress.Broadcast, Port));
var end = DateTime.Now.AddSeconds(timeout);
while (DateTime.Now < end)
{
var rs = new DefaultMessage();
IPEndPoint? ep = null;
buf = udp.Receive(ref ep);
if (buf != null && rs.Read(buf))
{
var msg = encoder.Decode(rs);
if (msg != null)
{
var js = encoder.DecodeResult(msg.Action, msg.Data, rs);
var info = (AgentInfo)encoder.Convert(js, typeof(AgentInfo));
yield return info;
}
}
}
}
#endregion
#region
/// <summary>日志</summary>
public ILog Log { get; set; } = Logger.Null;
/// <summary>写日志</summary>
/// <param name="format"></param>
/// <param name="args"></param>
public void WriteLog(String format, params Object?[] args) => Log?.Info(format, args);
#endregion
}