Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;namespace rBits
{internal static class Program{/// <summary>/// 应用程序的主入口点。/// </summary>static void Main(){ServiceBase[] ServicesToRun;ServicesToRun = new ServiceBase[]{new Service1()};ServiceBase.Run(ServicesToRun);}}
}
Service1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using Notify;
using Notify.Log;namespace rBits
{public partial class Service1 : ServiceBase{private TcpServer rBits;private NtLog log = new NtLog();public Service1(){InitializeComponent();rBits = new TcpServer("127.0.0.1", 8421);//订阅事件rBits.LogReady += log.OnLogging;}protected override void OnStart(string[] args){string path = AppDomain.CurrentDomain.BaseDirectory;path += "\\VerCtrl.Cfg.json";rBits.ReadCtrlVersionCfg(path);rBits.Startup();}protected override void OnStop(){rBits.Stop();}}
}
ProjectInstall.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Linq;
using System.Threading.Tasks;namespace rBits
{[RunInstaller(true)]public partial class ProjectInstaller : System.Configuration.Install.Installer{public ProjectInstaller(){InitializeComponent();}}
}
Libraries.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Xml.Linq;
using System.IO;
using System.Diagnostics;
using System.Net.Mail;
using System.Net.Configuration;
using System.Reflection;
using System.IO.Compression;
//不常用的库
//using Microsoft.Data.Sqlite;
//以下是第三方库,从NuGet里导入
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SQLitePCL;
using Microsoft.Data.Sqlite;
using MimeKit;
//using MailKit.Net.Smtp;
//using MailKit.Security;
//以下是自定义命名空间
using Notify;
using Notify.Log;
using Notify.App;
using Notify.App.Cpolar;
//using Notify.Cploar;
//using bRedBit;//项目依赖项
/* Newtonsoft.Json.dll* verctrlcfg.json* * */namespace Notify
{class CtrlVersion{public ushort min { get; set; }public ushort max { get; set; }public CtrlVersion(){min = 498;max = 506;}public CtrlVersion(ushort min, ushort max){this.min = min;this.max = max;}}class NotNtFlagException : Exception{public NotNtFlagException(string message) : base(message) { }}class NtVersionCtrlException : Exception{public NtVersionCtrlException(string message) : base(message) { }}static class Nt{static public bool CheckFlag(ref byte[] msg){//"N" 78 0x4E //"t"116 0x74if (msg != null && msg[0] == 0x4E && msg[1] == 0x74){return true;}return false;}/// <summary>/// 检查消息的前4个字节,检查Flag和版本号/// </summary>/// <param name="msg">接收的数据,消息</param>/// <param name="verctrlcfg">版本控制配置文件</param>/// <returns>Flag和Version都没问题就返回true,否则返回false</returns>static public bool CheckFlagVersion(ref byte[] msg, string verctrlcfg){var ctrlver = ReadConfig(verctrlcfg);if (CheckFlag(ref msg) == true && CtrlVersion(ref msg, ctrlver.min, ctrlver.max) == true)return true;return false;}static public uint CheckFlagVerCmdno(ref byte[] msg, string verctrlcfg){if (CheckFlag(ref msg) == false){throw new NotNtFlagException("NotNtFlag CheckFlag false");}var ctrlver = ReadConfig(verctrlcfg);if (CtrlVersion(ref msg, ctrlver.min, ctrlver.max) == false){throw new NtVersionCtrlException("客户端版本不在控制的范围内");}return GetCmdNo(ref msg);}static public ushort GetVersion(ref byte[] msg){return (ushort)((msg[2] << 8) + msg[3]);}static public bool CtrlVersion(ref byte[] msg, ushort min = 498, ushort max = 503){ushort ver = (ushort)((msg[2] << 8) + msg[3]);//Console.WriteLine($"{ver},{msg[2]},{msg[3]}");if (ver >= min && ver <= max){return true;}return false;}static public bool CtrlVersion(ref byte[] msg, string verctrlcfg){var ctrlver = ReadConfig(verctrlcfg);ushort ver = (ushort)((msg[2] << 8) + msg[3]);//Console.WriteLine($"{ver},{msg[2]},{msg[3]}");if (ver >= ctrlver.min && ver <= ctrlver.max){return true;}return false;}static public uint GetCmdNo(ref byte[] msg){uint CmdNo = (uint)((msg[4] << 24) + (msg[5] << 16) + (msg[6] << 8) + msg[7]);return CmdNo;}static public CtrlVersion ReadConfig(string cfg = "verctrlcfg.json"){try{// 读取文件内容 string jsonContent = File.ReadAllText(cfg); //File.ReadAllText这个操作是阻塞的,并且比较费时。// 反序列化JSON字符串到对象 CtrlVersion vers = JsonConvert.DeserializeObject<CtrlVersion>(jsonContent);// 输出结果 //Console.WriteLine($"{vers.min} >= ver >= {vers.max}");return vers;}catch (Exception e){throw e;}}}class TcpServer{private TcpListener tcpListener;private Thread listenThread;private volatile bool runing = false;private List<TcpClient> clients = new List<TcpClient>();private volatile bool IsWritingLog = false;//只能有一个线程写,不能多个线程同时写。//private int aliveclients = 5;//这个要大约等于3,也就是至少要保留3个线程private CtrlVersion VerCtrl = new CtrlVersion();//private ulong totalAccess = 0;private List<LogContent> logBuffer = new List<LogContent>();private readonly SynchronizationContext _synchronizationContext = SynchronizationContext.Current;//跨线程需要同步上下文public int ClientsCount { get { return clients.Count; } }/// <summary>/// 阶段性访问量,一读就清空。/// </summary>//public ulong TotalAccess //{ // get // {// var temp = this.totalAccess;// this.totalAccess = 0;// return temp; // } //}/// <summary>/// 日志缓存器,这个属性读后就清空了。/// </summary>public List<LogContent> LogBuf{get //深拷贝,防止并发环境里的遗漏{List<LogContent> temp = new List<LogContent>();//在 C# 中,使用 foreach 循环遍历集合时,如果集合在遍历过程中被修改(如添加、删除元素),就会抛出此异常System.InvalidOperationException。foreach (LogContent content in this.logBuffer){temp.Add(content);}foreach (LogContent content in temp){this.logBuffer.Remove(content);}return temp;}}public int LogBufferCount { get { return this.logBuffer.Count; } }// 定义一个委托public delegate void LogReadyHandler(List<LogContent> logs);// 定义一个事件public event LogReadyHandler LogReady;// 模拟数据生产private void LogTrigger(){//#if DEBUG// TempLog.logging($"{this.LogBufferCount}");//#endif//SynchronizationContext _synchronizationContext = SynchronizationContext.Current;//跨线程需要同步上下文//_synchronizationContext = SynchronizationContext.Current;// 触发事件if (this.LogBufferCount > 0){//#if DEBUG// TempLog.logging($"{this.LogBufferCount}触发日志缓存和写入{this.LogReady.ToString()}");//#endif//LogReady?.Invoke(this.LogBuf);//读数据并清空了日志缓存if (_synchronizationContext != null){_synchronizationContext.Send(state =>{try{LogReady?.Invoke(this.LogBuf); // 如果有任何订阅者,调用他们的方法}catch (Exception ex){//Console.WriteLine($"Exception during event invocation: {ex.Message}");
#if DEBUGTempLog.logging($"{this.LogBufferCount}触发日志缓存和写入{this.LogReady.ToString()} {ex.ToString()}");
#endif}}, null);}else{try{LogReady?.Invoke(this.LogBuf); // 如果有任何订阅者,调用他们的方法}catch (Exception ex){//Console.WriteLine($"Exception during event invocation: {ex.Message}");
#if DEBUGTempLog.logging($"{this.LogBufferCount} Context==null {this.LogReady.ToString()} {ex.ToString()}");
#endif}}}}public TcpServer(string ipAddress, int port){this.tcpListener = new TcpListener(IPAddress.Parse(ipAddress), port);if (clients == null)clients = new List<TcpClient>();}public void Startup(){if (this.listenThread == null || this.listenThread.IsAlive == false){this.listenThread = new Thread(new ThreadStart(ListenForClients));this.listenThread.IsBackground = false;//前台线程不会自动退出,thread类默认前台线程,前台线程会阻塞主线程}switch (listenThread.ThreadState){case System.Threading.ThreadState.Unstarted:this.listenThread.Start();runing = true;break;case System.Threading.ThreadState.StopRequested:case System.Threading.ThreadState.AbortRequested:case System.Threading.ThreadState.Aborted:case System.Threading.ThreadState.Stopped:case System.Threading.ThreadState.Suspended:case System.Threading.ThreadState.SuspendRequested:this.listenThread.Abort();this.listenThread = new Thread(new ThreadStart(ListenForClients));this.listenThread.IsBackground = false;//前台线程不会自动退出,thread类默认前台线程,前台线程会阻塞主线程this.listenThread.Start();runing = true;break;case System.Threading.ThreadState.Running:runing = true;break;}if (clients == null)clients = new List<TcpClient>();}public void Startup(string ipaddr, int port){this.tcpListener = new TcpListener(IPAddress.Parse(ipaddr), port);if (this.listenThread == null || this.listenThread.IsAlive == false){this.listenThread = new Thread(new ThreadStart(ListenForClients));this.listenThread.IsBackground = false;//前台线程不会自动退出,thread类默认前台线程,前台线程会阻塞主线程}switch (listenThread.ThreadState){case System.Threading.ThreadState.Unstarted:this.listenThread.Start();runing = true;break;case System.Threading.ThreadState.StopRequested:case System.Threading.ThreadState.AbortRequested:case System.Threading.ThreadState.Aborted:case System.Threading.ThreadState.Stopped:case System.Threading.ThreadState.Suspended:case System.Threading.ThreadState.SuspendRequested:this.listenThread.Abort();this.listenThread = new Thread(new ThreadStart(ListenForClients));this.listenThread.IsBackground = false;//前台线程不会自动退出,thread类默认前台线程,前台线程会阻塞主线程this.listenThread.Start();runing = true;break;case System.Threading.ThreadState.Running:runing = true;break;}if (clients == null)clients = new List<TcpClient>();}public static bool CheckPortAvali(int port, string ipaddr){TcpListener tcp = new TcpListener(IPAddress.Parse(ipaddr), port);try{tcp.Start();tcp.Stop();}catch (SocketException se){//if(se.ErrorCode==10048)if (se.ErrorCode == (int)SocketError.AddressAlreadyInUse){return false;}}return true;}public void ReadCtrlVersionCfg(string cfg = "verctrlcfg.json"){try{// 读取文件内容 string jsonContent = File.ReadAllText(cfg); //File.ReadAllText这个操作是阻塞的,并且比较费时。// 反序列化JSON字符串到对象 CtrlVersion vers = JsonConvert.DeserializeObject<CtrlVersion>(jsonContent);// 输出结果 //Console.WriteLine($"{vers.min} >= ver >= {vers.max}");this.VerCtrl = vers;}catch (Exception e){throw e;}}/// <summary>/// 剔除掉Tcp的Accept阻塞,要与runing = false;配合使用/// </summary>private void kickAcceptBlock(){// 创建一个TcpClient实例并连接到服务器 TcpClient client = new TcpClient();client.Connect((IPEndPoint)this.tcpListener.LocalEndpoint);// 关闭连接 client.Close();client.Dispose();}public void Stop(){//#if DEBUG// TempLog.logging($"IsBlocking:{this.tcpListener.Server.Blocking}");//#endifruning = false;if (this.tcpListener.Server.Blocking)kickAcceptBlock();//#if DEBUG// TempLog.logging($"Clients Count:{clients.Count}");//#endifforeach (TcpClient client in clients){client.Close();client.Dispose();}clients.Clear();//#if DEBUG// TempLog.logging($"Pending:{this.tcpListener.Pending()}");//#endifthis.tcpListener.Server.Blocking = false; //设为非阻塞模式//this.listenThread.Join();//Stop其他线程,本线程会运行,但是会阻塞程序和界面。//Console.WriteLine("Stop");this.tcpListener.Stop();//先调用Stop方法,Socket会报错。//System.Net.Sockets.SocketException (0x80004005): 一个封锁操作被对 WSACancelBlockingCall 的调用中断。this.listenThread.Abort();System.GC.Collect();}private void ListenForClients(){TcpClient client = new TcpClient();this.tcpListener.Start();//#if DEBUG// TempLog.logging($"TcpServierIsRuning:{this.runing}");//#endifTask.Run(() =>{try{while (this.runing){// 阻塞直到客户端连接 client = this.tcpListener.AcceptTcpClient(); //关闭的时候可能会发生异常//this.totalAccess++; //统计接访客户端数量// 创建一个新的线程来处理客户端通信 Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));clientThread.IsBackground = true; //后台线程不阻塞主线程,后台线程用完后自动退出clientThread.Start(client);//剔除休眠客户端for (int i = 0; (i < clients.Count); i++) // && (clients.Count >= this.aliveclients){bool IsConnectedR = false, IsConnectedW = false, IsConnectedE = false;if (clients[i].Available != 0 && clients[i].Client.Poll(6000, SelectMode.SelectRead))IsConnectedR = true;//isConnectedif (clients[i].Client.Poll(6000, SelectMode.SelectWrite))IsConnectedW = true;if (clients[i].Client.Poll(6000, SelectMode.SelectError) == false)IsConnectedE = true;if (!(IsConnectedR || IsConnectedW) || (IsConnectedE == true)){clients[i].Close();clients[i].Dispose();clients.RemoveAt(i);}if (i >= 5)GC.Collect();}//阻塞问题if (this.runing == false){clientThread.Abort();}//#if DEBUG// TempLog.logging("NotifyService Into ListenForClients");//#endif}}catch (ObjectDisposedException ode){this.logBuffer.Add(new LogContent(LogLevel.Critical, ode.Message, ode));//throw ode;//return;//Stop时候报错}catch (SocketException se){// 确保释放资源 if (client != null){client.Close();}if (this.tcpListener != null){this.tcpListener.Stop(); // 如果还没有停止的话 }this.logBuffer.Add(new LogContent(LogLevel.Fault, se.Message, se));//throw se;}catch (Exception ex){this.logBuffer.Add(new LogContent(LogLevel.Exception, ex.Message, ex));//throw ex;}});}/// <summary>/// 注意接受缓存只有4096字节。/// </summary>/// <param name="client">连接进来的客户端</param>private async void HandleClientComm(object client){this.logBuffer.Add(new LogContent(LogLevel.Statistic, "HandleClientComm"));TcpClient tcpClient = (TcpClient)client; //注意tcpClient.Close()关闭之后后面就无法接受连接。NetworkStream clientStream = tcpClient.GetStream();byte[] message = new byte[4096];int messagebytes;while (this.runing){messagebytes = 0;//接收数据try{// 阻塞直到客户端发送数据 messagebytes = clientStream.Read(message, 0, message.Length);//这里有可能缓冲小了}catch (ArgumentOutOfRangeException outrange)//缓存区太小异常{this.logBuffer.Add(new LogContent(LogLevel.Error, outrange.Message, outrange));//return;clientStream.Close();goto End;}catch (ObjectDisposedException ode) //关闭Client,下一个客户端访问就报这个异常。//这个异常会陷入一个死循环,烧CPU{this.logBuffer.Add(new LogContent(LogLevel.Critical, ode.Message, ode));//return; //NetworkStream Dispose, can not sendclientStream.Close();goto End;}catch (IOException ioe) //多个线程写文件操作就会报这个异常{if (ioe.InnerException is SocketException socketEx){switch (socketEx.SocketErrorCode){case SocketError.ConnectionReset:// 连接被对方重置(可能是客户端关闭)this.logBuffer.Add(new LogContent(LogLevel.Warning, "连接被对方重置(可能是客户端关闭)", ioe));break;case SocketError.ConnectionAborted:// 连接被对方异常关闭this.logBuffer.Add(new LogContent(LogLevel.Warning, "连接被对方异常关闭", ioe));break;// 处理其他 SocketError 代码}}//return; //NetworkStream Dispose, can not sendgoto End;}catch (InvalidOperationException ioe) //Socket未启动导致后面的操作无效{this.logBuffer.Add(new LogContent(LogLevel.Error, ioe.Message, ioe));goto End;}catch (ArgumentException ae) //参数引发的异常,可能message有问题{this.logBuffer.Add(new LogContent(LogLevel.Error, ae.Message, ae));goto End;}catch (Exception e) //其他异常,比如 客户端断开了连接 {this.logBuffer.Add(new LogContent(LogLevel.Exception, e.Message, e));goto End;}if (messagebytes < 6){this.logBuffer.Add(new LogContent(LogLevel.Mistake, "messagebytes < 6"));goto End;}//检查版本号 改读类的字段了 //每次这里读配置文件比较费时间。可能是因为其他线程占用文件。if (Nt.CtrlVersion(ref message, this.VerCtrl.min, this.VerCtrl.max) == false){
#if DEBUGTempLog.logging("CheckVersionFalse");
#endifthis.logBuffer.Add(new LogContent(LogLevel.Warning, "来访客户端版本不在受控范围内"));goto End;}//按Falg选择应用框架switch ((message[0] << 8) | message[1]){case (int)SwitchApp.Nt:goto NtApp;//break;case (int)SwitchApp.bRB:goto End;//break;case (int)SwitchApp.ECHO:goto Turn; //ECHO 回显//break;default:this.logBuffer.Add(new LogContent(LogLevel.Warning, "应用标识Flag不存在"));goto End;}NtApp:try{switch (Nt.GetCmdNo(ref message)){case 1://获取Cploar并写缓存{//异步var cpinfos = await DynamicCpolar.GetCpolarInfo();// 写缓存到文件CpolarCache cpolarCache = new CpolarCache();cpolarCache.FurthestUpdate = DynamicCpolar.GetLastUpdateFromCpolarInfp(cpinfos);cpolarCache.cpolarCache = cpinfos;var cpjson = JsonConvert.SerializeObject(cpolarCache);try{if (cpjson != null)File.WriteAllText("./CpolarCache.json.cache", cpjson);//在服务里要用绝对路径}catch (Exception ex){
#if DEBUGTempLog.logging(ex.ToString());
#endifthis.logBuffer.Add(new LogContent(LogLevel.Mistake, "写缓存文件错误\"./CpolarCache.json.cache\"", ex));}// 将CploarInfo对象序列化成JSON字符串 string jsonString = JsonConvert.SerializeObject(cpinfos);ASCIIEncoding encod = new ASCIIEncoding();message = encod.GetBytes(jsonString);messagebytes = message.Length;}break;case 2://读DynamicCpolar缓存文件{string jsonStr = string.Empty;if (File.Exists("./CpolarCache.json.cache")){var cpojson = File.ReadAllText("./CpolarCache.json.cache");CpolarCache CpolarCache = new CpolarCache();CpolarCache = JsonConvert.DeserializeObject<CpolarCache>(cpojson);if (CpolarCache.FurthestUpdate > DateTime.Now.AddHours(-12)){// 将CploarInfo对象序列化成JSON字符串 jsonStr = JsonConvert.SerializeObject(CpolarCache.cpolarCache);ASCIIEncoding encod = new ASCIIEncoding();message = encod.GetBytes(jsonStr);messagebytes = message.Length;}else{goto case 1;}}else{goto case 1;}}break;case 3: //无缓存{//异步var cpinfos = await DynamicCpolar.GetCpolarInfo();// 将CploarInfo对象序列化成JSON字符串 string jsonString = JsonConvert.SerializeObject(cpinfos);ASCIIEncoding encod = new ASCIIEncoding();message = encod.GetBytes(jsonString);messagebytes = message.Length;}break;}goto Turn;}catch (Exception e){this.logBuffer.Add(new LogContent(LogLevel.Exception, e.Message, e));goto End;}// bRBApp:// if (bRB.CheckFlagRB(ref message, messagebytes) == true && messagebytes > 4)// {// byte key = bRB.GetKey(ref message, messagebytes);//#if DEBUG// Console.WriteLine("bRB应用来访");// Console.WriteLine("key:{0}", key);//#endif// byte[] data = new byte[messagebytes - 4];// for (int i = 0; i < data.Length; i++)// {// data[i] = (byte)(message[i + 4] ^ key);// }// goto End;// }// ;Turn:try{//#if DEBUG// TempLog.logging(ASCIIEncoding.ASCII.GetString(message,4,messagebytes));//#endif// 发送回显数据给客户端 clientStream.Write(message, 0, messagebytes);clientStream.Flush();}catch (Exception e){this.logBuffer.Add(new LogContent(LogLevel.Exception, e.Message, e));}End:try{//this.logBuffer.Add(new LogContent(LogLevel.Debug, "进入日志程序"));clientStream.Close();//无法访问已释放的对象。对象名:“System.Net.Sockets.NetworkStream”。tcpClient.Close();//调用此方法最终将导致关联的 Socket 关闭,TcpListener也可能会关闭。//导出日志LogTrigger();}catch (Exception e) //不处理{this.logBuffer.Add(new LogContent(LogLevel.Exception, e.Message, e));return;}return;}}}}namespace Notify.Log
{public enum LogLevel{Trace = 0,Debug = 1,Information = 2,Statistic = 3,Warning = 4,Mistake = 6, //轻微错误Exception = 5,Error = 7, //逻辑错误Critical = 8, //严重错误Fault = 9 //致命错误}//临时日志是写文本文件,这种操作在高并发程序里会造成程序崩溃public class TempLog{//注意只有一个线程写,不能多个线程同时写文件。static public void logging(List<NtLog> logbuf, string filepath = "./NotifyLog.log"){// 确保日志文件的目录存在 Directory.CreateDirectory(Path.GetDirectoryName(filepath));// 使用StreamWriter写入日志 using (StreamWriter writer = new StreamWriter(filepath, true)) // true表示追加模式 {//writer.WriteLine($"Log Entry: {DateTime.Now} - This is a test log entry.");// 这里可以添加更多的日志写入操作//foreach (NtLog log in logbuf)//{// writer.WriteLine(log.ToString());// 移除已经写了的缓存// logbuf.Remove(log);//迭代器安全问题//}for (int i = 0; i < logbuf.Count; i++){//#if DEBUG// Console.WriteLine(logbuf[i].ToString());//#endifwriter.WriteLine(logbuf[i].ToString());logbuf.RemoveAt(i);}}}static public void logging(List<NtLog> logbuf){string fileName = $"{DateTime.Now:yyyy-MM-dd}.log"; // 构建文件名,例如 "2023-04-01.log" string fullPath = Path.Combine("./logs", fileName);// 确保日志文件的目录存在 Directory.CreateDirectory(Path.GetDirectoryName(fullPath));// 使用StreamWriter写入日志 using (StreamWriter writer = new StreamWriter(fullPath, true)) // true表示追加模式 {//writer.WriteLine($"Log Entry: {DateTime.Now} - This is a test log entry.");// 这里可以添加更多的日志写入操作//foreach (NtLog log in logbuf)//{// writer.WriteLine(log.ToString());// 移除已经写了的缓存// logbuf.Remove(log);//迭代器安全问题//}for (int i = 0; i < logbuf.Count; i++){
#if DEBUGConsole.WriteLine(logbuf[i].ToString());
#endifwriter.WriteLine(logbuf[i].ToString());logbuf.RemoveAt(i);}}}static public void logging(string message, string logpath){using (StreamWriter writer = new StreamWriter(logpath, true)) // true表示追加模式 {//string fileName = $"{DateTime.Now:yyyy-MM-dd}.log"; // 构建文件名,例如 "2023-04-01.log" string content = $"{DateTime.Now:yyyy-MM-ddTHH:mm:ss} {message}";writer.WriteLine(content);}}static public void logging(string message){string path = AppDomain.CurrentDomain.BaseDirectory;using (StreamWriter writer = new StreamWriter(path + "//TempLog.log", true)) // true表示追加模式 {//string fileName = $"{DateTime.Now:yyyy-MM-dd}.log"; // 构建文件名,例如 "2023-04-01.log" string content = $"{DateTime.Now:yyyy-MM-ddTHH:mm:ss} {message}";writer.WriteLine(content);}}}public class LogContent{public DateTime Occurrence { get; set; }public LogLevel Level { get; set; }public StackTrace Trace { get; set; }public string Message { get; set; }public LogContent() { }public LogContent(LogLevel level, string message){this.Occurrence = DateTime.Now;this.Level = level;this.Trace = new StackTrace(true);//捕捉当前堆栈this.Message = message;}public LogContent(LogLevel level, string message, Exception e){this.Occurrence = DateTime.Now;this.Level = level;this.Trace = new StackTrace(e, true);//捕捉当前异常堆栈堆栈this.Message = message;}public override string ToString(){string tmp = Occurrence.ToString("yyyy-MM-ddTHH:mm:ss.FFFF") + " " + Level.ToString() + " " + Message + "\n";return tmp;}}public class LogContentX{public DateTime Occurrence { get; set; }public LogLevel Level { get; set; }public StackTrace Trace { get; set; }public string Message { get; set; }public LogContentX() { }public LogContentX(LogLevel level, string message) { this.SetLog(level, message); }public LogContentX(LogLevel level, string message, Exception e) { this.SetLog(level, message, e); }public void SetLog(LogLevel level, string message){this.Occurrence = DateTime.Now;this.Level = level;this.Trace = new StackTrace(true);//捕捉当前堆栈this.Message = message;}public void SetLog(LogLevel level, string message, Exception e){this.Occurrence = DateTime.Now;this.Level = level;this.Trace = new StackTrace(e, true);//捕捉当前异常堆栈堆栈this.Message = message;}public override string ToString(){string tmp = Occurrence.ToString("yyyy-MM-ddTHH:mm:ss.FFFF") + " " + Level.ToString() + " " + Message + "\n";return tmp;}public string TraceDetail(){string tmp = Occurrence.ToString("yyyy-MM-ddTHH:mm:ss.FFFF") + " " + Level.ToString() + " " + Message + "\n";tmp += this.Trace.ToString() + "\r\n";//StackFrame sf = Trace.GetFrame(0); //tmp += $" Method: {sf.GetMethod()}\r\n";//$" File: {sf.GetFileName()}\r\n" +//$" Line Number: {sf.GetFileLineNumber()}\r\n";return tmp;}public static string TraceDetail(LogContent log){string tmp = log.Occurrence.ToString("yyyy-MM-ddTHH:mm:ss.FFFF") + " " + log.Level.ToString() + " " + log.Message + "\n";tmp += log.Trace.ToString() + "\r\n";//StackFrame sf = Trace.GetFrame(0); //tmp += $" Method: {sf.GetMethod()}\r\n";//$" File: {sf.GetFileName()}\r\n" +//$" Line Number: {sf.GetFileLineNumber()}\r\n";return tmp;}public string TraceTopFrame(){return this.Trace.GetFrame(0).ToString();}public StackFrame TraceTopStackFrame(){return Trace.GetFrame(0);}public StackFrame TraceBottomStackFrame(){return Trace.GetFrame(this.Trace.FrameCount - 1);}public int GetSize(){//在safe环境中无法使用sizeof(DateTime),sizeof(DateTime)用8字节代替// Each character in a string is 2 bytes (UTF-16 encoding).int total = sizeof(LogLevel) + this.Message.Length * 2 + 8;for (int i = 0; i < this.Trace.FrameCount; i++){StackFrame sf = this.Trace.GetFrame(i);//sf.GetFileLineNumber();sf.GetFileName(); sf.GetMethod().Name; total += sf.ToString().Length * 2;}return total;}public static int GetSize(LogContent log){//在safe环境中无法使用sizeof(DateTime),sizeof(DateTime)用8字节代替// Each character in a string is 2 bytes (UTF-16 encoding).int total = sizeof(LogLevel) + log.Message.Length * 2 + 8;for (int i = 0; i < log.Trace.FrameCount; i++){StackFrame sf = log.Trace.GetFrame(i);//sf.GetFileLineNumber();sf.GetFileName(); sf.GetMethod().Name; total += sf.ToString().Length * 2;}return total;}}public class LogBuffer{protected List<LogContent> buf = new List<LogContent>();public List<LogContent> Buffer { get { return buf; } }public LogBuffer(){buf = new List<LogContent>();}public LogBuffer(List<LogContent> buf){this.buf = buf;}public void Insert(LogContent log){this.buf.Add(log);}public void Insert(LogBuffer log){foreach (var item in log.Buffer){this.buf.Add(item);}}public int LogSize(){int size = 0;foreach (LogContent cnt in this.buf){size += LogContentX.GetSize(cnt);}return size;}}public class LogBufQuery{public List<LogContent> Buffer { get; set; }public void TimeOrderAsc(){Buffer.Sort((x, y) => x.Occurrence.CompareTo(y.Occurrence));}public void TimeOrderDsc(){Buffer.Sort((x, y) => y.Occurrence.CompareTo(x.Occurrence));}public List<LogContent> FilterByLoglevel(LogLevel loglevel){return Buffer.FindAll(log => log.Level == loglevel);}}public class LogStatistic : LogBufQuery{private SqliteConnection connection;private string dbfile = string.Empty;//将db信息生成文本或文本文件//寻找路径并创建文件private void MakeFile(){string path = AppDomain.CurrentDomain.BaseDirectory;// 获取当前应用程序域的名称(通常是程序集名称)string assemblyName = AppDomain.CurrentDomain.FriendlyName;// 去掉路径和扩展名,只保留文件名assemblyName = Path.GetFileNameWithoutExtension(assemblyName);// 组合成完整的路径this.dbfile = System.IO.Path.Combine(path, assemblyName + ".db");//确保数据库文件存在string connectionString = $"Data Source={this.dbfile}";try{this.connection = new SqliteConnection(connectionString);connection.Open(); // 这将尝试打开数据库,如果不存在则会创建它connection.Close();}catch (Exception ex){throw ex;}}//检查并创建数据表,确保必要的数据表存在。这里就固定了格式和标准。private void EnsureDbTableExist(){//key-word形式}//连接db//释放连接//从Buffer中分析并向表中写入统计数据//读表中数据//插入或更新数据//Clear}//将日志写成文本文件,注意在多线程环境下要排除线程互斥。public class NtLog{private Queue<LogBuffer> Buff = new Queue<LogBuffer>();public string LogPath { get; }private string curfilepath = string.Empty;private string errorLgFile = string.Empty; //定义从Exception到Fault这5个层级为Errorprivate Task task;private Stopwatch watcher = new Stopwatch();private long maxms, minms, lastms;private volatile bool IsWriting = false;public bool Writing { get { return IsWriting; } }/// <summary>/// 平均耗时/// </summary>public long AverageConsum{get{return (maxms + minms + lastms) / 3;}}public NtLog(){this.LogPath = AppDomain.CurrentDomain.BaseDirectory;创建日志文件夹this.LogPath = CreateLogDirectory();MakeLogFileName();maxms = minms = lastms = 0;}public void OnLogging(List<LogContent> logs){//#if DEBUG// TempLog.logging($"查看原始数据。{logs.Count} ");//{logs.First().Message}//#endifthis.Buff.Enqueue(new LogBuffer(logs));//#if DEBUG// TempLog.logging($"执行事件。{this.Buff.Count} {this.IsWriting}");//#endif//#if DEBUG// TempLog.logging($"查看写缓存。{Temp.Buffer.Count} {Temp.Buffer.First().Message}");//#endifthis.UpdatePathFileName();if (this.IsWriting == false && this.Buff.Count > 0){LogBuffer Temp = this.Buff.Dequeue();while (this.Buff.Count > 0){Temp.Insert(this.Buff.Dequeue());}WriteLogByThread(Temp);}}public static string CreateLogDirectory(){string path = AppDomain.CurrentDomain.BaseDirectory;// 获取当前应用程序域的名称(通常是程序集名称)string assemblyName = AppDomain.CurrentDomain.FriendlyName;// 去掉路径和扩展名,只保留文件名assemblyName = Path.GetFileNameWithoutExtension(assemblyName);// 组合成完整的路径path = System.IO.Path.Combine(path, assemblyName + "Log");//TempLog.logging(path);//创建日志文件夹Directory.CreateDirectory(path);return path;}public void UpdatePathFileName(){string path = AppDomain.CurrentDomain.BaseDirectory;// 获取当前应用程序域的名称(通常是程序集名称)string assemblyName = AppDomain.CurrentDomain.FriendlyName;// 去掉路径和扩展名,只保留文件名assemblyName = Path.GetFileNameWithoutExtension(assemblyName);// 组合成完整的路径path = System.IO.Path.Combine(path, assemblyName + "Log");//TempLog.logging(path);//创建日志文件夹Directory.CreateDirectory(path);string dn = DateTime.Now.ToString("yyyy-MM-dd");this.curfilepath = Path.Combine(this.LogPath, assemblyName + dn + ".log");this.errorLgFile = Path.Combine(this.LogPath, assemblyName + dn + "err.log");}public void MakeLogFileName(){// 获取当前应用程序域的名称(通常是程序集名称)string assemblyName = AppDomain.CurrentDomain.FriendlyName;// 去掉路径和扩展名,只保留文件名assemblyName = Path.GetFileNameWithoutExtension(assemblyName);string dn = DateTime.Now.ToString("yyyy-MM-dd");this.curfilepath = Path.Combine(this.LogPath, assemblyName + dn + ".log");this.errorLgFile = Path.Combine(this.LogPath, assemblyName + dn + "err.log");}public void WriteLogFile(){using (StreamWriter writer = new StreamWriter(this.curfilepath, true)) // true表示追加模式 {foreach (var Buffer in this.Buff){foreach (var cnt in Buffer.Buffer){writer.WriteLine(cnt.ToString());}}}}public void WriteErrorLog(){using (StreamWriter writer = new StreamWriter(this.errorLgFile, true)) // true表示追加模式 {foreach (var Buffer in this.Buff){foreach (var cnt in Buffer.Buffer){if (cnt.Level >= LogLevel.Exception)writer.WriteLine(LogContentX.TraceDetail(cnt));}}}}//发现它仍然阻塞主线程//注意只有一个线程写,不能多个线程同时写文件。//请注意,Buffer.Remove(cnt) 在循环中可能会导致问题,因为从集合中移除元素会改变集合的大小,从而可能导致迭代器失效。为了避免这个问题,可以先收集需要删除的元素,然后在循环结束后统一删除它们。public void WriteLogByThread(LogBuffer logs){this.watcher.Start();this.IsWriting = true;//#if DEBUG// TempLog.logging($"BeofreWriteLog。{this.curfilepath} {this.errorLgFile}");//#endif// 使用Task.Run在后台线程中执行文件写入操作this.task = Task.Run(() =>{//FileStream可以设置成独享锁定模式,防止 线程互斥using (FileStream fs1 = new FileStream(this.curfilepath, FileMode.Append, FileAccess.Write, FileShare.None),fs2 = new FileStream(this.errorLgFile, FileMode.Append, FileAccess.Write, FileShare.None)){using (StreamWriter writer = new StreamWriter(fs1), writer2 = new StreamWriter(fs2)){foreach (var cnt in logs.Buffer){writer.WriteLine(cnt.ToString());if (cnt.Level >= LogLevel.Warning)writer2.WriteLine(LogContentX.TraceDetail(cnt));}}}//Buffer没有上锁是希望它尽快完成操作,但有风险});this.watcher.Stop();this.IsWriting = false;if (this.lastms > 0 && this.lastms > this.maxms){this.maxms = this.lastms;}if (this.lastms > 0 && this.minms == 0)this.minms = this.lastms;if (this.lastms > 0 && this.lastms < this.minms){this.minms = this.lastms;}this.lastms = watcher.ElapsedMilliseconds;}}
}namespace Notify.App
{enum SwitchApp{//"N" 78 0x4E //"t"116 0x74Nt = (0x4E << 8) | 0x74,//ASCII 'A' 0x41 65//ASCII 'B' 0x42 66bRB = (0x41 << 8) | 0x42,//ECHO = (0x08 << 8) | 0x0A //Turn; //ECHO 回显}
}namespace Notify.App.Cpolar
{class DynamicCpolar{public static async Task<string> GetTunels(string token){// 定义URL string url = "http://localhost:9200/api/v1/tunnels";// 定义Bearer令牌 //string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjIxNzU0NjcsImlhdCI6MTcyMjAwMjY2NywiVXNlcklEIjowLCJVc2VybmFtZSI6IiIsIkVtYWlsIjoiaHV4eWNAcXEuY29tIiwiQXBpU2VydmljZVRva2VuIjoiZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SmxlSEFpT2pFM01qSXhOelUwTmpjc0ltbGhkQ0k2TVRjeU1qQXdNalkyTnl3aVZYTmxja2xFSWpveU56RXhNVEFzSWxWelpYSnVZVzFsSWpvaVVtbHpZU0lzSWtWdFlXbHNJam9pYUhWNGVXTkFjWEV1WTI5dEluMC5IMWVfYzl0VjZab1pabFlHVUxEMFlIWWN0VFl3RERMbm5MTFczNC1NZVhvIn0.oLtor_R5YnZgH3AQT17qCRXx5dRTDGUEP8amx8OD8Jo";// 创建HttpClient实例 using (HttpClient client = new HttpClient()){// 设置请求头 client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);// 发送GET请求 try{HttpResponseMessage response = await client.GetAsync(url);// 确保HTTP成功状态值 response.EnsureSuccessStatusCode();// 读取响应内容 string responseBody = await response.Content.ReadAsStringAsync();// 打印响应内容 //Console.WriteLine(responseBody);return responseBody;}catch (HttpRequestException hre){//Console.WriteLine("\nException Caught!");//Console.WriteLine($"Message :{hre.Message}");throw hre;//return "";}catch (Exception e){throw e;}}}public static async Task<string> GetToken(){// 创建HttpClient实例 using (var client = new HttpClient()){// 设置请求URL string url = "http://localhost:9200/api/v1/user/login";// 准备要发送的JSON数据 var loginData = new{// 假设你需要发送用户名和密码 email = "youremail@qq.com",password = "yourpassword"};// 将对象序列化为JSON字符串 string jsonContent = JsonConvert.SerializeObject(loginData); // 如果你使用System.Text.Json,则使用JsonSerializer.Serialize // 创建StringContent实例,并设置媒体类型为application/json StringContent content = new StringContent(jsonContent, Encoding.UTF8, "application/json");// 发送POST请求 try{HttpResponseMessage response = await client.PostAsync(url, content);// 检查响应是否成功 response.EnsureSuccessStatusCode();// 读取响应内容 string responseBody = await response.Content.ReadAsStringAsync();// 输出响应内容 //Console.WriteLine(responseBody);// 字符转Json并提取data属性值// 使用 Newtonsoft.Json.Linq 来解析 JSON 字符串 JObject jsonObj = JObject.Parse(responseBody);// 访问 data 属性并提取 token 值 if (jsonObj["data"] != null && jsonObj["data"].Type == JTokenType.Object){JToken tokenToken = jsonObj["data"]["token"];if (tokenToken != null && tokenToken.Type == JTokenType.String){string token = tokenToken.Value<string>();//Console.WriteLine($"Token: {token}");return token;}else{//Console.WriteLine("Token is not a string or is null.");throw new JsonException("Token is not a string or is null.");//return "";}}else{throw new JsonException("Data is null or not an object.");//Console.WriteLine("Data is null or not an object.");//return "";}}catch (HttpRequestException he){// 请求异常处理 //Console.WriteLine("\nException Caught!");//Console.WriteLine("Message :{0} ", e.Message);throw he;}catch (Exception e){throw e;}//return "";}}public static CpolarInfo GetCpolarInfoFromJsonString(string json){//装载CpolarInfo数据CpolarInfo cpolarInfo = new CpolarInfo();try{// 解析JSON字符串为JObject JObject jsonObject = JObject.Parse(json);// 访问data对象下的items数组 JArray itemsArray = (JArray)jsonObject["data"]["items"];//int total = (int)jsonObject["data"]["total"];//获取totalcpolarInfo.total = (int)jsonObject["data"]["total"];cpolarInfo.items = new item[cpolarInfo.total];//Console.WriteLine($"{cpolarInfo.total}");//初始化item,否则会报错for (int j = 0; j < cpolarInfo.total; j++){cpolarInfo.items[j] = new item{name = string.Empty,create_datetime = string.Empty,pubulic_url = string.Empty};}int i = 0;// 遍历items数组,提取publish_tunnelsforeach (JObject item in itemsArray){// 提取name和public_url string name = item["name"].ToString();string public_url = item["public_url"].ToString();string create_datetime = "";// 访问publish_tunnels数组(假设每个条目至少有一个tunnel) JArray tunnelsArray = (JArray)item["publish_tunnels"];// 遍历publish_tunnels数组(这里假设我们只关心第一个tunnel的create_datetime) foreach (JObject tunnel in tunnelsArray){// 提取create_datetime create_datetime = tunnel["create_datetime"].ToString();// 打印或处理数据 //Console.WriteLine($"Name: {name}, Public URL: {public_url}, Create Datetime: {create_datetime}");// 如果不需要处理多个tunnel,可以跳出内层循环 break; // 如果你想处理所有tunnel,请移除这行代码 }// 使用Replace方法替换协议部分 string newUrl = public_url.Replace("tls://", "https://");//Console.WriteLine($"{name},{create_datetime},{newUrl}");// 打印或处理数据 //Console.WriteLine($"{name},{create_datetime},{public_url}");//组装CpolarInfocpolarInfo.items[i].name = name;cpolarInfo.items[i].create_datetime = create_datetime;cpolarInfo.items[i].pubulic_url = newUrl;i++;}}catch (JsonException je){throw je;}catch (Exception e){throw e;}return cpolarInfo;}public static async Task<CpolarInfo> GetCpolarInfo(){string token = await GetToken();// 输出响应内容 //Console.WriteLine(token);string json = await GetTunels(token);//装载CpolarInfo数据CpolarInfo cpolarInfo = new CpolarInfo();// 解析JSON字符串为JObject JObject jsonObject = JObject.Parse(json);// 访问data对象下的items数组 JArray itemsArray = (JArray)jsonObject["data"]["items"];//int total = (int)jsonObject["data"]["total"];//获取totalcpolarInfo.total = (int)jsonObject["data"]["total"];cpolarInfo.items = new item[cpolarInfo.total];//Console.WriteLine($"{cpolarInfo.total}");//初始化item,否则会报错for (int j = 0; j < cpolarInfo.total; j++){cpolarInfo.items[j] = new item{name = string.Empty,create_datetime = string.Empty,pubulic_url = string.Empty};}int i = 0;// 遍历items数组,提取publish_tunnelsforeach (JObject item in itemsArray){// 提取name和public_url string name = item["name"].ToString();string public_url = item["public_url"].ToString();string create_datetime = "";// 访问publish_tunnels数组(假设每个条目至少有一个tunnel) JArray tunnelsArray = (JArray)item["publish_tunnels"];// 遍历publish_tunnels数组(这里假设我们只关心第一个tunnel的create_datetime) foreach (JObject tunnel in tunnelsArray){// 提取create_datetime create_datetime = tunnel["create_datetime"].ToString();// 打印或处理数据 //Console.WriteLine($"Name: {name}, Public URL: {public_url}, Create Datetime: {create_datetime}");// 如果不需要处理多个tunnel,可以跳出内层循环 break; // 如果你想处理所有tunnel,请移除这行代码 }// 打印或处理数据 //Console.WriteLine($"Name: {name},Create Datetime:{create_datetime}, Public URL: {public_url}");//Console.WriteLine($"{name},{create_datetime},{public_url}");// 使用Replace方法替换协议部分 string newUrl = public_url.Replace("tls://", "https://");//Console.WriteLine($"{name},{create_datetime},{newUrl}");//Uri url = new Uri(public_url);//public_url = url.Host;//Console.WriteLine($"{name},{create_datetime},{public_url}");//组装CpolarInfocpolarInfo.items[i].name = name;cpolarInfo.items[i].create_datetime = create_datetime;cpolarInfo.items[i].pubulic_url = newUrl;i++;}return cpolarInfo;}public static DateTime GetLastUpdateFromCpolarInfp(CpolarInfo cptu){DateTime now = DateTime.Now;DateTime furthest = DateTime.Parse(cptu.items[cptu.total - 1].create_datetime);DateTime tmp;for (int i = 0; i < cptu.total - 1; i++){tmp = DateTime.Parse(cptu.items[i].create_datetime);if (furthest.CompareTo(tmp) > 0){furthest = tmp;}}return furthest;}}public class CpolarCache{//public DateTime CacheOn { get; set; } //token请求时间//public string tokenCache { get; set; }public DateTime FurthestUpdate { get; set; } //CpolarInfo所有隧道的距离现在最长的时间public CpolarInfo cpolarCache { get; set; }}public class CpolarInfo{public int total { get; set; }public item[] items { get; set; }}public class item{public string name { get; set; }public string create_datetime { get; set; }public string pubulic_url { get; set; }}
}
rBitsTest
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using Newtonsoft;
using Newtonsoft.Json;namespace rBitsTest
{public class IPport{public string IP { get; set; }public int Port { get; set; }}public class CpolarInfo{public int total { get; set; }public item[] items { get; set; }}public class item{public string name { get; set; }public string create_datetime { get; set; }public string pubulic_url { get; set; }}internal class Program{static void Main(string[] args){try{Console.WriteLine("Version:{0}", Assembly.GetEntryAssembly().GetName().Version.ToString());string jsonFilePath = "ipport.json"; // 替换为你的JSON文件路径 // 读取文件内容 string jsonContent = File.ReadAllText(jsonFilePath);// 反序列化JSON字符串到对象 List<IPport> ipaddrs = JsonConvert.DeserializeObject<List<IPport>>(jsonContent);// 输出结果 foreach (var host in ipaddrs){Console.WriteLine($"Host: IP: {host.IP}, Port: {host.Port}");}创建一个Random对象//Random random = new Random((int)DateTime.Now.Ticks);定义一个数组,其中包含更多你想要的值(例如1),以增加其出现的概率//int[] weightedArray = new int[20];//for (int i = 0; i < 10; i++) // 50% 是 1//{// weightedArray[i] = 1;//}//for (int i = 10; i < 16; i++) // 25% 是 1//{// weightedArray[i] = 0;//}//for (int i = 16; i < 20; i++) // 25% 是 1//{// weightedArray[i] = 2;//}从加权数组中随机选择一个元素//int randomIndex = random.Next(weightedArray.Length);//int randomNumber = weightedArray[randomIndex];//var conhost = ipaddrs[randomNumber]; //randomNumber%2//var conhost = new IPport();var conhost = ipaddrs[2];//conhost.Port = 25984;//conhost.IP = "cn-hb-lt-tmp3.natfrp.cloud";Console.WriteLine($"Selected Connect Host: IP: {conhost.IP}, Port: {conhost.Port}");// 创建一个TcpClient实例并连接到服务器 TcpClient client = new TcpClient($"{conhost.IP}", conhost.Port);// 获取一个NetworkStream对象以进行读写 NetworkStream stream = client.GetStream();// 将消息转换为字节数组 //string message = "Hello from the client!";//byte[] data = Encoding.ASCII.GetBytes(message);//Flag1 Flag2 VerH VerLbyte[] data = { 0x4E, 0x74, 0x01, 0xF9, 0x00, 0x00, 0x00, 0x01 };// 发送消息到服务器 stream.Write(data, 0, data.Length);// 读取服务器的响应 byte[] buffer = new byte[4096];int bytesRead = stream.Read(buffer, 0, buffer.Length);// 将接收到的字节转换为字符串 string responseData = Encoding.ASCII.GetString(buffer, 0, bytesRead);Console.WriteLine("Received from server: " + responseData);Console.WriteLine("ReceivedBytes:{0}", bytesRead);//将Json写入文件File.WriteAllText("cpolarinfo.json", responseData);// 关闭连接 client.Close();}catch (Exception e){Console.WriteLine("Error: " + e.Message);}// 等待用户按键,以便在控制台中查看结果 //Console.WriteLine("Press Enter to continue...");//Console.ReadLine();// 设置定时器,6秒后执行ExitApplication方法var timer = new System.Threading.Timer(ExitApplication, null, 6000, Timeout.Infinite);Console.WriteLine("Press any key to exit...");Console.ReadKey(); // 等待用户按键// 如果用户提前按下了键,取消定时器timer.Dispose();}static void ExitApplication(object state){Console.WriteLine("Time's up! Exiting the application...");Environment.Exit(0); // 退出程序}}
}