mirror of
https://github.com/snltty/linker.git
synced 2025-10-24 17:40:27 +08:00
init
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
node_modules
|
||||
/public/*
|
||||
63
README.md
Normal file
63
README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
|
||||
<!--
|
||||
* @Author: snltty
|
||||
* @Date: 2021-08-22 14:09:03
|
||||
* @LastEditors: snltty
|
||||
* @LastEditTime: 2022-11-21 16:36:26
|
||||
* @version: v1.0.0
|
||||
* @Descripttion: 功能说明
|
||||
* @FilePath: \client.service.ui.webd:\desktop\cminitor\README.md
|
||||
-->
|
||||
<div align="center">
|
||||
<p><img src="./readme/logo.png" height="150"></p>
|
||||
|
||||
# class monitor
|
||||
#### Visual Studio 2022 LTSC 17.4.1
|
||||
<a href="https://jq.qq.com/?_wv=1027&k=ucoIVfz4" target="_blank">QQ 群:1121552990</a>
|
||||
|
||||

|
||||

|
||||
[](https://gitee.com/snltty/cminitor/stargazers)
|
||||
[](https://gitee.com/snltty/cminitor/members)
|
||||
|
||||
使用前请确保你已知其中风险
|
||||
|
||||
本软件仅供学习交流,请勿用于违法犯罪
|
||||
|
||||
</div>
|
||||
|
||||
## 说明
|
||||
1. 这是一个粗略的局域网监控程序(说是局域网,你放外网也不是不行)
|
||||
2. 桌面捕获很粗略,只是做了一个减小图片尺寸,没有做区域更新
|
||||
|
||||
## 看图
|
||||
<p><img src="./readme/cmonitor.jpg" height="150"></p>
|
||||
|
||||
|
||||
## 支持平台
|
||||
- [x] 客户端支持 **【windows】**
|
||||
- [x] 服务端支持 **【windows】**、**【linux】**
|
||||
|
||||
## 运行参数
|
||||
|
||||
### 公共参数
|
||||
- [x] **【--mode】** 运行模式 **client,server**
|
||||
|
||||
### 1、客户端
|
||||
- [x] **【--server】** 服务器ip **192.168.1.18**
|
||||
- **【--name】** 机器名 **Dns.GetHostName()**
|
||||
- **【--username-key】** 用户名内存共享key,谁在用此设备 **cmonitor/username**
|
||||
- **【--username-len】** 用户名内存共享长度 **255**
|
||||
- **【--keyboard-key】** 键盘按键内存共享key,按下哪些按键 **cmonitor/keyboard**
|
||||
- **【--keyboard-len】** 键盘按键内存共享长度 **255**
|
||||
- **【--share-key】** 自定义其它数据共享 **cmonitor/share**
|
||||
- **【--share-len】** 长度 **255**
|
||||
|
||||
### 2、服务端
|
||||
- [x] **【--web】** 管理UI端口 **1800**
|
||||
- [x] **【--api】** 管理接口端口 **1801**
|
||||
- [x] **【--service】** 服务端口 **1802**
|
||||
|
||||
## 支持作者
|
||||
请作者喝一杯咖啡,使其更有精力更新代码
|
||||
<p><img src="./readme/qr.jpg" width="360"></p>
|
||||
68
cmonitor.sln
Normal file
68
cmonitor.sln
Normal file
@@ -0,0 +1,68 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.4.33110.190
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "cmonitor", "cmonitor\cmonitor.csproj", "{267DE8BE-F91C-4CCB-9D58-D33FDA661126}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "common.libs", "common.libs\common.libs.csproj", "{00EECF97-99EB-4B12-AAEF-ED2363914275}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "message.win", "message.win\message.win.csproj", "{386F8B8F-2E83-408E-AC3A-4BD35608EDE3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "wallpaper.win", "wallpaper.win\wallpaper.win.csproj", "{88FF2017-FF1A-4E9F-AB2E-2973C5B35C34}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "llock.win", "llock.win\llock.win.csproj", "{DD7F9ABD-7718-454B-950D-A369C6D85FE4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cmonitor.win", "cmonitor.win\cmonitor.win.csproj", "{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
ReleaseLinux|Any CPU = ReleaseLinux|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{267DE8BE-F91C-4CCB-9D58-D33FDA661126}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{267DE8BE-F91C-4CCB-9D58-D33FDA661126}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{267DE8BE-F91C-4CCB-9D58-D33FDA661126}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{267DE8BE-F91C-4CCB-9D58-D33FDA661126}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{267DE8BE-F91C-4CCB-9D58-D33FDA661126}.ReleaseLinux|Any CPU.ActiveCfg = ReleaseLinux|Any CPU
|
||||
{267DE8BE-F91C-4CCB-9D58-D33FDA661126}.ReleaseLinux|Any CPU.Build.0 = ReleaseLinux|Any CPU
|
||||
{00EECF97-99EB-4B12-AAEF-ED2363914275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{00EECF97-99EB-4B12-AAEF-ED2363914275}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{00EECF97-99EB-4B12-AAEF-ED2363914275}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{00EECF97-99EB-4B12-AAEF-ED2363914275}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{00EECF97-99EB-4B12-AAEF-ED2363914275}.ReleaseLinux|Any CPU.ActiveCfg = ReleaseLinux|Any CPU
|
||||
{00EECF97-99EB-4B12-AAEF-ED2363914275}.ReleaseLinux|Any CPU.Build.0 = ReleaseLinux|Any CPU
|
||||
{386F8B8F-2E83-408E-AC3A-4BD35608EDE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{386F8B8F-2E83-408E-AC3A-4BD35608EDE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{386F8B8F-2E83-408E-AC3A-4BD35608EDE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{386F8B8F-2E83-408E-AC3A-4BD35608EDE3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{386F8B8F-2E83-408E-AC3A-4BD35608EDE3}.ReleaseLinux|Any CPU.ActiveCfg = ReleaseLinux|Any CPU
|
||||
{386F8B8F-2E83-408E-AC3A-4BD35608EDE3}.ReleaseLinux|Any CPU.Build.0 = ReleaseLinux|Any CPU
|
||||
{88FF2017-FF1A-4E9F-AB2E-2973C5B35C34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{88FF2017-FF1A-4E9F-AB2E-2973C5B35C34}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{88FF2017-FF1A-4E9F-AB2E-2973C5B35C34}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{88FF2017-FF1A-4E9F-AB2E-2973C5B35C34}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{88FF2017-FF1A-4E9F-AB2E-2973C5B35C34}.ReleaseLinux|Any CPU.ActiveCfg = ReleaseLinux|Any CPU
|
||||
{88FF2017-FF1A-4E9F-AB2E-2973C5B35C34}.ReleaseLinux|Any CPU.Build.0 = ReleaseLinux|Any CPU
|
||||
{DD7F9ABD-7718-454B-950D-A369C6D85FE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DD7F9ABD-7718-454B-950D-A369C6D85FE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DD7F9ABD-7718-454B-950D-A369C6D85FE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DD7F9ABD-7718-454B-950D-A369C6D85FE4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DD7F9ABD-7718-454B-950D-A369C6D85FE4}.ReleaseLinux|Any CPU.ActiveCfg = ReleaseLinux|Any CPU
|
||||
{DD7F9ABD-7718-454B-950D-A369C6D85FE4}.ReleaseLinux|Any CPU.Build.0 = ReleaseLinux|Any CPU
|
||||
{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}.ReleaseLinux|Any CPU.ActiveCfg = ReleaseLinux|Any CPU
|
||||
{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}.ReleaseLinux|Any CPU.Build.0 = ReleaseLinux|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {1F7E3D69-2821-4CA8-A8B5-86016FA9BEAB}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
1
cmonitor.web
Submodule
1
cmonitor.web
Submodule
Submodule cmonitor.web added at b5a3da842b
6
cmonitor.win/App.config
Normal file
6
cmonitor.win/App.config
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
|
||||
</startup>
|
||||
</configuration>
|
||||
50
cmonitor.win/Form1.Designer.cs
generated
Normal file
50
cmonitor.win/Form1.Designer.cs
generated
Normal file
@@ -0,0 +1,50 @@
|
||||
namespace cmonitor.win
|
||||
{
|
||||
partial class Form1
|
||||
{
|
||||
/// <summary>
|
||||
/// 必需的设计器变量。
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// 清理所有正在使用的资源。
|
||||
/// </summary>
|
||||
/// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows 窗体设计器生成的代码
|
||||
|
||||
/// <summary>
|
||||
/// 设计器支持所需的方法 - 不要修改
|
||||
/// 使用代码编辑器修改此方法的内容。
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// Form1
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(0, 0);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
||||
this.Name = "Form1";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.OnClose);
|
||||
this.Load += new System.EventHandler(this.OnLoad);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
104
cmonitor.win/Form1.cs
Normal file
104
cmonitor.win/Form1.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace cmonitor.win
|
||||
{
|
||||
public partial class Form1 : Form
|
||||
{
|
||||
private Process proc;
|
||||
private string[] args;
|
||||
protected override CreateParams CreateParams
|
||||
{
|
||||
get
|
||||
{
|
||||
const int WS_EX_APPWINDOW = 0x40000;
|
||||
const int WS_EX_TOOLWINDOW = 0x80;
|
||||
CreateParams cp = base.CreateParams;
|
||||
cp.ExStyle &= (~WS_EX_APPWINDOW);
|
||||
cp.ExStyle |= WS_EX_TOOLWINDOW;
|
||||
return cp;
|
||||
}
|
||||
}
|
||||
public Form1(string[] args)
|
||||
{
|
||||
this.args = args;
|
||||
InitializeComponent();
|
||||
|
||||
this.FormBorderStyle = FormBorderStyle.None;
|
||||
ShowInTaskbar = false;
|
||||
this.WindowState = FormWindowState.Minimized;
|
||||
this.Hide();
|
||||
this.Opacity = 0;
|
||||
|
||||
AppDomain.CurrentDomain.ProcessExit += (s, e) => KillExe();
|
||||
Application.ApplicationExit += (s, e) => KillExe();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private bool OpenExe()
|
||||
{
|
||||
try
|
||||
{
|
||||
string filename = Process.GetCurrentProcess().MainModule.FileName;
|
||||
string dir = Path.GetDirectoryName(filename);
|
||||
string file = Path.Combine(dir, "./cmonitor.exe");
|
||||
ProcessStartInfo processStartInfo = new ProcessStartInfo()
|
||||
{
|
||||
WorkingDirectory = dir,
|
||||
FileName = file,
|
||||
CreateNoWindow = false,
|
||||
ErrorDialog = false,
|
||||
UseShellExecute = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
Arguments = string.Join(" ", this.args),
|
||||
Verb = "runas",
|
||||
};
|
||||
proc = Process.Start(processStartInfo);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
proc.Kill();
|
||||
proc.Dispose();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
proc = null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private void KillExe()
|
||||
{
|
||||
try
|
||||
{
|
||||
proc?.Close();
|
||||
proc?.Dispose();
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
proc = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoad(object sender, EventArgs e)
|
||||
{
|
||||
OpenExe();
|
||||
}
|
||||
|
||||
private void OnClose(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
KillExe();
|
||||
}
|
||||
}
|
||||
}
|
||||
120
cmonitor.win/Form1.resx
Normal file
120
cmonitor.win/Form1.resx
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
29
cmonitor.win/Program.cs
Normal file
29
cmonitor.win/Program.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace cmonitor.win
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用程序的主入口点。
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Mutex mutex = new Mutex(true, System.Diagnostics.Process.GetCurrentProcess().ProcessName, out bool isAppRunning);
|
||||
if (isAppRunning == false)
|
||||
{
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(new Form1(args));
|
||||
}
|
||||
}
|
||||
}
|
||||
36
cmonitor.win/Properties/AssemblyInfo.cs
Normal file
36
cmonitor.win/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// 有关程序集的一般信息由以下
|
||||
// 控制。更改这些特性值可修改
|
||||
// 与程序集关联的信息。
|
||||
[assembly: AssemblyTitle("cmonitor.win")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("cmonitor.win")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2023")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// 将 ComVisible 设置为 false 会使此程序集中的类型
|
||||
//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
|
||||
//请将此类型的 ComVisible 特性设置为 true。
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
|
||||
[assembly: Guid("9170e23a-b7ca-485f-ae8a-6bc9d29d4c67")]
|
||||
|
||||
// 程序集的版本信息由下列四个值组成:
|
||||
//
|
||||
// 主版本
|
||||
// 次版本
|
||||
// 生成号
|
||||
// 修订号
|
||||
//
|
||||
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
|
||||
//通过使用 "*",如下所示:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
71
cmonitor.win/Properties/Resources.Designer.cs
generated
Normal file
71
cmonitor.win/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,71 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// 此代码由工具生成。
|
||||
// 运行时版本: 4.0.30319.42000
|
||||
//
|
||||
// 对此文件的更改可能导致不正确的行为,如果
|
||||
// 重新生成代码,则所做更改将丢失。
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace cmonitor.win.Properties
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 强类型资源类,用于查找本地化字符串等。
|
||||
/// </summary>
|
||||
// 此类是由 StronglyTypedResourceBuilder
|
||||
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
||||
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
||||
// (以 /str 作为命令选项),或重新生成 VS 项目。
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources
|
||||
{
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回此类使用的缓存 ResourceManager 实例。
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((resourceMan == null))
|
||||
{
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("cmonitor.win.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写当前线程的 CurrentUICulture 属性,对
|
||||
/// 使用此强类型资源类的所有资源查找执行重写。
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceCulture;
|
||||
}
|
||||
set
|
||||
{
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
117
cmonitor.win/Properties/Resources.resx
Normal file
117
cmonitor.win/Properties/Resources.resx
Normal file
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
30
cmonitor.win/Properties/Settings.Designer.cs
generated
Normal file
30
cmonitor.win/Properties/Settings.Designer.cs
generated
Normal file
@@ -0,0 +1,30 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace cmonitor.win.Properties
|
||||
{
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
|
||||
{
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default
|
||||
{
|
||||
get
|
||||
{
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
cmonitor.win/Properties/Settings.settings
Normal file
7
cmonitor.win/Properties/Settings.settings
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
||||
79
cmonitor.win/app.manifest
Normal file
79
cmonitor.win/app.manifest
Normal file
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<!-- UAC 清单选项
|
||||
如果想要更改 Windows 用户帐户控制级别,请使用
|
||||
以下节点之一替换 requestedExecutionLevel 节点。
|
||||
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
|
||||
|
||||
指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
|
||||
如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
|
||||
元素。
|
||||
-->
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的
|
||||
Windows 版本的列表。取消评论适当的元素,
|
||||
Windows 将自动选择最兼容的环境。 -->
|
||||
|
||||
<!-- Windows Vista -->
|
||||
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
|
||||
|
||||
<!-- Windows 7 -->
|
||||
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
|
||||
|
||||
<!-- Windows 8 -->
|
||||
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
|
||||
|
||||
<!-- Windows 8.1 -->
|
||||
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
|
||||
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
|
||||
自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI,无需
|
||||
选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
|
||||
在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
|
||||
|
||||
将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
|
||||
<!--
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
-->
|
||||
|
||||
<!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
|
||||
<!--
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
</assembly>
|
||||
100
cmonitor.win/cmonitor.win.csproj
Normal file
100
cmonitor.win/cmonitor.win.csproj
Normal file
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>cmonitor.win</RootNamespace>
|
||||
<AssemblyName>cmonitor.win</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>embedded</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\cmonitor\web\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseLinux|AnyCPU'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\ReleaseLinux\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>embedded</DebugType>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Deployment" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Form1.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Form1.Designer.cs">
|
||||
<DependentUpon>Form1.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<EmbeddedResource Include="Form1.resx">
|
||||
<DependentUpon>Form1.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<None Include="app.manifest" />
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="App.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
385
cmonitor/Program.cs
Normal file
385
cmonitor/Program.cs
Normal file
@@ -0,0 +1,385 @@
|
||||
using cmonitor.hijack;
|
||||
using cmonitor.server.api;
|
||||
using cmonitor.server.api.services;
|
||||
using cmonitor.server.client;
|
||||
using cmonitor.server.client.reports.active;
|
||||
using cmonitor.server.client.reports.light;
|
||||
using cmonitor.server.client.reports.hijack;
|
||||
using cmonitor.server.client.reports.llock;
|
||||
using cmonitor.server.client.reports.screen;
|
||||
using cmonitor.server.client.reports.volume;
|
||||
using cmonitor.server.service;
|
||||
using cmonitor.server.service.messengers.active;
|
||||
using cmonitor.server.service.messengers.hijack;
|
||||
using cmonitor.server.service.messengers.llock;
|
||||
using cmonitor.server.service.messengers.report;
|
||||
using cmonitor.server.service.messengers.screen;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using cmonitor.server.service.messengers.usb;
|
||||
using cmonitor.server.service.messengers.volume;
|
||||
using cmonitor.server.service.messengers.wallpaper;
|
||||
using cmonitor.server.web;
|
||||
using common.libs;
|
||||
using common.libs.database;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Net;
|
||||
using cmonitor.server.client.reports;
|
||||
using common.libs.extends;
|
||||
using cmonitor.server.service.messengers.light;
|
||||
using System.Reflection;
|
||||
|
||||
namespace cmonitor
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
Mutex mutex = new Mutex(true, System.Diagnostics.Process.GetCurrentProcess().ProcessName, out bool isAppRunning);
|
||||
if (isAppRunning == false)
|
||||
{
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += (a, b) =>
|
||||
{
|
||||
Logger.Instance.Error(b.ExceptionObject + "");
|
||||
};
|
||||
ThreadPool.SetMinThreads(1024, 1024);
|
||||
ThreadPool.SetMaxThreads(65535, 65535);
|
||||
LoggerConsole();
|
||||
|
||||
|
||||
|
||||
Config config = new Config();
|
||||
Dictionary<string, string> dic = ArgumentParser.Parse(args, out string error);
|
||||
config.BroadcastIP = IPAddress.Parse(dic["server"]);
|
||||
config.Name = dic["name"];
|
||||
config.WebPort = int.Parse(dic["web"]);
|
||||
config.ApiPort = int.Parse(dic["api"]);
|
||||
config.ServicePort = int.Parse(dic["service"]);
|
||||
config.UserNameMemoryKey = dic["username-key"];
|
||||
config.UserNameMemoryLength = int.Parse(dic["username-len"]);
|
||||
config.KeyboardMemoryKey = dic["keyboard-key"];
|
||||
config.KeyboardMemoryLength = int.Parse(dic["keyboard-len"]);
|
||||
config.ShareMemoryKey = dic["share-key"];
|
||||
config.ShareMemoryLength = int.Parse(dic["share-len"]);
|
||||
Logger.Instance.Debug($"config:{config.ToJson()}");
|
||||
Logger.Instance.Debug($"args:{string.Join(" ", args)}");
|
||||
|
||||
config.IsCLient = dic.ContainsKey("mode") && dic["mode"].Contains("client");
|
||||
config.IsServer = dic.ContainsKey("mode") && dic["mode"].Contains("server");
|
||||
//注入对象
|
||||
ServiceProvider serviceProvider = null;
|
||||
ServiceCollection serviceCollection = new ServiceCollection();
|
||||
//注入 依赖注入服务供应 使得可以在别的地方通过注入的方式获得 ServiceProvider 以用来获取其它服务
|
||||
serviceCollection.AddSingleton((e) => serviceProvider);
|
||||
|
||||
serviceCollection.AddSingleton<Config>((a) => config);
|
||||
|
||||
serviceCollection.AddTransient(typeof(IConfigDataProvider<>), typeof(ConfigDataFileProvider<>));
|
||||
|
||||
//劫持
|
||||
serviceCollection.AddSingleton<HijackConfig>();
|
||||
serviceCollection.AddSingleton<HijackController>();
|
||||
serviceCollection.AddSingleton<HijackEventHandler>();
|
||||
|
||||
//客户端
|
||||
serviceCollection.AddSingleton<ClientSignInState>();
|
||||
serviceCollection.AddSingleton<ClientTransfer>();
|
||||
serviceCollection.AddSingleton<ClientConfig>();
|
||||
|
||||
serviceCollection.AddSingleton<ReportTransfer>();
|
||||
serviceCollection.AddSingleton<ActiveWindowReport>();
|
||||
serviceCollection.AddSingleton<HijackReport>();
|
||||
serviceCollection.AddSingleton<LLockReport>();
|
||||
serviceCollection.AddSingleton<ScreenReport>();
|
||||
serviceCollection.AddSingleton<UsbReport>();
|
||||
serviceCollection.AddSingleton<VolumeReport>();
|
||||
serviceCollection.AddSingleton<WallpaperReport>();
|
||||
serviceCollection.AddSingleton<LightReport>();
|
||||
serviceCollection.AddSingleton<ShareReport>();
|
||||
|
||||
|
||||
//服务
|
||||
serviceCollection.AddSingleton<TcpServer>();
|
||||
serviceCollection.AddSingleton<MessengerSender>();
|
||||
serviceCollection.AddSingleton<MessengerResolver>();
|
||||
|
||||
serviceCollection.AddSingleton<SignCaching>();
|
||||
serviceCollection.AddSingleton<SignInMessenger>();
|
||||
serviceCollection.AddSingleton<ReportMessenger>();
|
||||
serviceCollection.AddSingleton<CommandMessenger>();
|
||||
serviceCollection.AddSingleton<HijackMessenger>();
|
||||
serviceCollection.AddSingleton<ActiveMessenger>();
|
||||
serviceCollection.AddSingleton<LLockMessenger>();
|
||||
serviceCollection.AddSingleton<ScreenMessenger>();
|
||||
serviceCollection.AddSingleton<UsbMessenger>();
|
||||
serviceCollection.AddSingleton<VolumeMessenger>();
|
||||
serviceCollection.AddSingleton<WallpaperMessenger>();
|
||||
serviceCollection.AddSingleton<LightMessenger>();
|
||||
|
||||
//api
|
||||
serviceCollection.AddSingleton<RuleConfig>();
|
||||
serviceCollection.AddSingleton<IClientServer, ClientServer>();
|
||||
serviceCollection.AddSingleton<SignInClientService>();
|
||||
serviceCollection.AddSingleton<CommandClientService>();
|
||||
serviceCollection.AddSingleton<ReportClientService>();
|
||||
serviceCollection.AddSingleton<HijackClientService>();
|
||||
serviceCollection.AddSingleton<ActiveClientService>();
|
||||
serviceCollection.AddSingleton<LLockClientService>();
|
||||
serviceCollection.AddSingleton<ScreenClientService>();
|
||||
serviceCollection.AddSingleton<UsbClientService>();
|
||||
serviceCollection.AddSingleton<VolumeClientService>();
|
||||
serviceCollection.AddSingleton<WallpaperClientService>();
|
||||
serviceCollection.AddSingleton<LightClientService>();
|
||||
|
||||
//web
|
||||
serviceCollection.AddSingleton<IWebServer, WebServer>();
|
||||
|
||||
|
||||
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
if (config.IsCLient || config.IsServer)
|
||||
{
|
||||
MessengerResolver messengerResolver = serviceProvider.GetService<MessengerResolver>();
|
||||
messengerResolver.LoadMessenger(assemblies);
|
||||
|
||||
}
|
||||
if (config.IsServer)
|
||||
{
|
||||
Logger.Instance.Info($"start server");
|
||||
//api
|
||||
IClientServer clientServer = serviceProvider.GetService<IClientServer>();
|
||||
clientServer.LoadPlugins(assemblies);
|
||||
clientServer.Websocket();
|
||||
Logger.Instance.Info($"api listen:{config.ApiPort}");
|
||||
|
||||
//web
|
||||
IWebServer webServer = serviceProvider.GetService<IWebServer>();
|
||||
webServer.Start();
|
||||
Logger.Instance.Info($"web listen:{config.WebPort}");
|
||||
|
||||
//服务
|
||||
TcpServer tcpServer = serviceProvider.GetService<TcpServer>();
|
||||
tcpServer.Start();
|
||||
Logger.Instance.Info($"service listen:{config.ServicePort}");
|
||||
|
||||
}
|
||||
if (config.IsCLient)
|
||||
{
|
||||
Logger.Instance.Info($"start client");
|
||||
Logger.Instance.Info($"server ip {config.BroadcastIP}");
|
||||
|
||||
ReportTransfer report = serviceProvider.GetService<ReportTransfer>();
|
||||
report.LoadPlugins(assemblies);
|
||||
|
||||
ClientTransfer clientTransfer = serviceProvider.GetService<ClientTransfer>();
|
||||
}
|
||||
|
||||
GCHelper.FlushMemory();
|
||||
|
||||
await Helper.Await();
|
||||
}
|
||||
|
||||
private static void LoggerConsole()
|
||||
{
|
||||
if (Directory.Exists("log") == false)
|
||||
{
|
||||
Directory.CreateDirectory("log");
|
||||
}
|
||||
Logger.Instance.OnLogger += (model) =>
|
||||
{
|
||||
ConsoleColor currentForeColor = Console.ForegroundColor;
|
||||
switch (model.Type)
|
||||
{
|
||||
case LoggerTypes.DEBUG:
|
||||
Console.ForegroundColor = ConsoleColor.Blue;
|
||||
break;
|
||||
case LoggerTypes.INFO:
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
break;
|
||||
case LoggerTypes.WARNING:
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
break;
|
||||
case LoggerTypes.ERROR:
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
string line = $"[{model.Type,-7}][{model.Time:yyyy-MM-dd HH:mm:ss}]:{model.Content}";
|
||||
Console.WriteLine(line);
|
||||
Console.ForegroundColor = currentForeColor;
|
||||
|
||||
try
|
||||
{
|
||||
using StreamWriter sw = File.AppendText(Path.Combine("log", $"{DateTime.Now:yyyy-MM-dd}.log"));
|
||||
sw.WriteLine(line);
|
||||
sw.Flush();
|
||||
sw.Close();
|
||||
sw.Dispose();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Config
|
||||
{
|
||||
public int WebPort { get; set; } = 1800;
|
||||
public int ApiPort { get; set; } = 1801;
|
||||
public int ServicePort { get; set; } = 1802;
|
||||
public IPAddress BroadcastIP { get; set; } = IPAddress.Parse("192.168.1.35");
|
||||
|
||||
public bool IsCLient { get; set; }
|
||||
public bool IsServer { get; set; }
|
||||
|
||||
public string WebRoot { get; set; } = "./web/";
|
||||
public string Name { get; set; } = Dns.GetHostName();
|
||||
|
||||
public string Version { get; set; } = "1.0.0.1";
|
||||
|
||||
|
||||
public string UserNameMemoryKey { get; set; } = "cmonitor/username";
|
||||
public string KeyboardMemoryKey { get; set; } = "cmonitor/keyboard";
|
||||
|
||||
public int UserNameMemoryLength { get; set; } = 255;
|
||||
public int KeyboardMemoryLength { get; set; } = 255;
|
||||
|
||||
|
||||
public string ShareMemoryKey { get; set; } = "cmonitor/sharememory";
|
||||
public int ShareMemoryLength { get; set; } = 1024;
|
||||
|
||||
|
||||
public const int ReportTime = 30;
|
||||
|
||||
public const int ScreenTime = 200;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class ArgumentParser
|
||||
{
|
||||
public static Dictionary<string, string> Parse(string[] args, out string error)
|
||||
{
|
||||
Dictionary<string, string> dic = new Dictionary<string, string>();
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (args[i].IndexOf("--") == 0)
|
||||
{
|
||||
if (i + 1 < args.Length && args[i + 1].IndexOf("--") == -1)
|
||||
{
|
||||
dic.Add(args[i].Substring(2), args[i + 1]);
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
dic.Add(args[i].Substring(2), string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Validate(dic, out error);
|
||||
|
||||
return dic;
|
||||
}
|
||||
static bool Validate(Dictionary<string, string> dic, out string error)
|
||||
{
|
||||
error = string.Empty;
|
||||
|
||||
return ValidateMode(dic) &&
|
||||
ValidateServer(dic, out error) && ValidateName(dic, out error) && ValidatePort(dic, out error) && ValidateMemoryKey(dic, out error);
|
||||
}
|
||||
static bool ValidateMode(Dictionary<string, string> dic)
|
||||
{
|
||||
//模式
|
||||
if (dic.ContainsKey("mode") == false || (dic["mode"].Contains("client") == false && dic["mode"].Contains("server") == false))
|
||||
{
|
||||
dic["mode"] = "server,client";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool ValidateServer(Dictionary<string, string> dic, out string error)
|
||||
{
|
||||
error = string.Empty;
|
||||
//服务器地址
|
||||
if (dic.ContainsKey("server") == false || string.IsNullOrWhiteSpace(dic["server"]))
|
||||
{
|
||||
dic["server"] = "192.168.1.35";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool ValidateName(Dictionary<string, string> dic, out string error)
|
||||
{
|
||||
error = string.Empty;
|
||||
//服务器地址
|
||||
if (dic.ContainsKey("name") == false || string.IsNullOrWhiteSpace(dic["name"]))
|
||||
{
|
||||
dic["name"] = Dns.GetHostName();
|
||||
if (dic["name"].Length > 12)
|
||||
{
|
||||
dic["name"] = dic["name"].Substring(0, 12);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ValidatePort(Dictionary<string, string> dic, out string error)
|
||||
{
|
||||
error = string.Empty;
|
||||
//界面接口
|
||||
if (dic.ContainsKey("web") == false || string.IsNullOrWhiteSpace(dic["web"]))
|
||||
{
|
||||
dic["web"] = "1800";
|
||||
}
|
||||
//管理接口
|
||||
if (dic.ContainsKey("api") == false || string.IsNullOrWhiteSpace(dic["api"]))
|
||||
{
|
||||
dic["api"] = "1801";
|
||||
}
|
||||
//服务接口
|
||||
if (dic.ContainsKey("service") == false || string.IsNullOrWhiteSpace(dic["service"]))
|
||||
{
|
||||
dic["service"] = "1802";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ValidateMemoryKey(Dictionary<string, string> dic, out string error)
|
||||
{
|
||||
error = string.Empty;
|
||||
if (dic.ContainsKey("username-key") == false || string.IsNullOrWhiteSpace(dic["username-key"]))
|
||||
{
|
||||
dic["username-key"] = "cmonitor/username";
|
||||
}
|
||||
if (dic.ContainsKey("username-len") == false || string.IsNullOrWhiteSpace(dic["username-len"]))
|
||||
{
|
||||
dic["username-len"] = "255";
|
||||
}
|
||||
|
||||
if (dic.ContainsKey("keyboard-key") == false || string.IsNullOrWhiteSpace(dic["keyboard-key"]))
|
||||
{
|
||||
dic["keyboard-key"] = "cmonitor/keyboard";
|
||||
}
|
||||
if (dic.ContainsKey("keyboard-len") == false || string.IsNullOrWhiteSpace(dic["keyboard-len"]))
|
||||
{
|
||||
dic["keyboard-len"] = "255";
|
||||
}
|
||||
|
||||
|
||||
if (dic.ContainsKey("share-key") == false || string.IsNullOrWhiteSpace(dic["share-key"]))
|
||||
{
|
||||
dic["share-key"] = "cmonitor/share";
|
||||
}
|
||||
if (dic.ContainsKey("share-len") == false || string.IsNullOrWhiteSpace(dic["share-len"]))
|
||||
{
|
||||
dic["share-len"] = "1024";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
19
cmonitor/Properties/PublishProfiles/FolderProfile.pubxml
Normal file
19
cmonitor/Properties/PublishProfiles/FolderProfile.pubxml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>bin\Release\net7.0\publish\win-x64\</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<PublishReadyToRun>false</PublishReadyToRun>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<History>True|2023-09-04T10:19:54.7492652Z;True|2023-09-04T18:19:32.2969345+08:00;False|2023-09-04T18:18:51.7827366+08:00;True|2023-09-04T18:15:31.6783417+08:00;True|2023-09-04T18:14:40.9964104+08:00;</History>
|
||||
<LastFailureDetails />
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
79
cmonitor/app.manifest
Normal file
79
cmonitor/app.manifest
Normal file
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<!-- UAC 清单选项
|
||||
如果想要更改 Windows 用户帐户控制级别,请使用
|
||||
以下节点之一替换 requestedExecutionLevel 节点。
|
||||
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
|
||||
|
||||
指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
|
||||
如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
|
||||
元素。
|
||||
-->
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的
|
||||
Windows 版本的列表。取消评论适当的元素,
|
||||
Windows 将自动选择最兼容的环境。 -->
|
||||
|
||||
<!-- Windows Vista -->
|
||||
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
|
||||
|
||||
<!-- Windows 7 -->
|
||||
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
|
||||
|
||||
<!-- Windows 8 -->
|
||||
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
|
||||
|
||||
<!-- Windows 8.1 -->
|
||||
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
|
||||
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
|
||||
自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI,无需
|
||||
选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
|
||||
在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
|
||||
|
||||
将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
|
||||
<!--
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
-->
|
||||
|
||||
<!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
|
||||
<!--
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
</assembly>
|
||||
68
cmonitor/cmonitor.csproj
Normal file
68
cmonitor/cmonitor.csproj
Normal file
@@ -0,0 +1,68 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<Configurations>Debug;Release;ReleaseLinux</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLinux|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
<Optimize>True</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="favicon.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="favicon.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\common.libs\common.libs.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="nfapi.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="nfdriver.sys">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="./web/**">
|
||||
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Properties\**" />
|
||||
<Content Remove="Properties\**" />
|
||||
<EmbeddedResource Remove="Properties\**" />
|
||||
<None Remove="Properties\**" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MemoryPack" Version="1.9.16" />
|
||||
|
||||
<PackageReference Include="NAudio" Version="2.2.0" />
|
||||
<PackageReference Include="System.Management" Version="7.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
cmonitor/cmonitor.csproj.user
Normal file
6
cmonitor/cmonitor.csproj.user
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<_LastSelectedProfileId>D:\desktop\cmonitor\cmonitor\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
BIN
cmonitor/favicon.ico
Normal file
BIN
cmonitor/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
36
cmonitor/hijack/HijackConfig.cs
Normal file
36
cmonitor/hijack/HijackConfig.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
namespace cmonitor.hijack
|
||||
{
|
||||
public sealed class HijackConfig
|
||||
{
|
||||
public HijackConfig() { }
|
||||
|
||||
/// <summary>
|
||||
/// 进程白名单
|
||||
/// </summary>
|
||||
public string[] AllowProcesss { get; set; } = Array.Empty<string>();
|
||||
/// <summary>
|
||||
/// 进程黑名单
|
||||
/// </summary>
|
||||
public string[] DeniedProcesss { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 域名白名单
|
||||
/// </summary>
|
||||
public string[] AllowDomains { get; set; } = Array.Empty<string>();
|
||||
/// <summary>
|
||||
/// 域名黑名单
|
||||
/// </summary>
|
||||
public string[] DeniedDomains { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// ip白名单
|
||||
/// </summary>
|
||||
public string[] AllowIPs { get; set; } = Array.Empty<string>();
|
||||
/// <summary>
|
||||
/// ip黑名单
|
||||
/// </summary>
|
||||
public string[] DeniedIPs { get; set; } = Array.Empty<string>();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
321
cmonitor/hijack/HijackController.cs
Normal file
321
cmonitor/hijack/HijackController.cs
Normal file
@@ -0,0 +1,321 @@
|
||||
using common.libs;
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace cmonitor.hijack;
|
||||
|
||||
public sealed class HijackController
|
||||
{
|
||||
private static readonly string SystemDriver = $"{Environment.SystemDirectory}\\drivers\\netfilter2.sys";
|
||||
public const string NFDriver = "nfdriver.sys";
|
||||
public const string Name = "netfilter2";
|
||||
private readonly HijackConfig hijackConfig;
|
||||
private readonly HijackEventHandler hijackEventHandler;
|
||||
|
||||
public HijackController(HijackConfig hijackConfig, HijackEventHandler hijackEventHandler)
|
||||
{
|
||||
this.hijackConfig = hijackConfig;
|
||||
this.hijackEventHandler = hijackEventHandler;
|
||||
|
||||
AppDomain.CurrentDomain.ProcessExit += (sender, e) => Stop();
|
||||
Console.CancelKeyPress += (sender, e) => Stop();
|
||||
}
|
||||
|
||||
public bool Start()
|
||||
{
|
||||
Stop();
|
||||
|
||||
//检查安装驱动
|
||||
CheckDriver();
|
||||
//给驱动获取进程权限
|
||||
NFAPI.nf_adjustProcessPriviledges();
|
||||
|
||||
//初始化驱动
|
||||
NF_STATUS nF_STATUS = NFAPI.nf_init(Name, hijackEventHandler);
|
||||
if (nF_STATUS != NF_STATUS.NF_STATUS_SUCCESS)
|
||||
{
|
||||
throw new Exception($"{Name} start failed.{nF_STATUS}");
|
||||
}
|
||||
SetRules();
|
||||
|
||||
return true;
|
||||
}
|
||||
public void Stop()
|
||||
{
|
||||
try
|
||||
{
|
||||
NFAPI.nf_deleteRules();
|
||||
NFAPI.nf_free();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRules()
|
||||
{
|
||||
List<NF_RULE> rules = new List<NF_RULE>();
|
||||
|
||||
Filter53(rules);
|
||||
FilterIPV6Lan(rules);
|
||||
FilterIPV4Lan(rules);
|
||||
FilterConfigIPs(rules);
|
||||
FilterWan(rules);
|
||||
|
||||
NFAPI.nf_setRules(rules.ToArray());
|
||||
}
|
||||
|
||||
private void Filter53(List<NF_RULE> rules)
|
||||
{
|
||||
rules.AddRange(new NF_RULE[] {
|
||||
//TCP 53
|
||||
new NF_RULE
|
||||
{
|
||||
direction = (byte)NF_DIRECTION.NF_D_OUT,
|
||||
filteringFlag = (uint)NF_FILTERING_FLAG.NF_INDICATE_CONNECT_REQUESTS,
|
||||
protocol = (int)ProtocolType.Tcp,
|
||||
remotePort = BinaryPrimitives.ReverseEndianness((ushort)53),
|
||||
ip_family = (ushort)AddressFamily.InterNetwork
|
||||
},
|
||||
new NF_RULE
|
||||
{
|
||||
direction = (byte)NF_DIRECTION.NF_D_OUT,
|
||||
filteringFlag = (uint)NF_FILTERING_FLAG.NF_INDICATE_CONNECT_REQUESTS,
|
||||
protocol = (int)ProtocolType.Tcp,
|
||||
remotePort = BinaryPrimitives.ReverseEndianness((ushort)53),
|
||||
ip_family = (ushort)AddressFamily.InterNetworkV6
|
||||
},
|
||||
//UDP 53
|
||||
new NF_RULE
|
||||
{
|
||||
direction = (byte)NF_DIRECTION.NF_D_OUT,
|
||||
filteringFlag = (uint)NF_FILTERING_FLAG.NF_FILTER,
|
||||
protocol = (int)ProtocolType.Udp,
|
||||
remotePort = BinaryPrimitives.ReverseEndianness((ushort)53),
|
||||
ip_family = (ushort)AddressFamily.InterNetwork
|
||||
},
|
||||
new NF_RULE
|
||||
{
|
||||
direction = (byte)NF_DIRECTION.NF_D_OUT,
|
||||
filteringFlag = (uint)NF_FILTERING_FLAG.NF_FILTER,
|
||||
protocol = (int)ProtocolType.Udp,
|
||||
remotePort = BinaryPrimitives.ReverseEndianness((ushort)53),
|
||||
ip_family = (ushort)AddressFamily.InterNetworkV6
|
||||
}
|
||||
});
|
||||
}
|
||||
private void FilterIPV6Lan(List<NF_RULE> rules)
|
||||
{
|
||||
rules.AddRange(new NF_RULE[]
|
||||
{
|
||||
//IPV6 环回 ::1/128
|
||||
new NF_RULE
|
||||
{
|
||||
direction = (byte)NF_DIRECTION.NF_D_OUT,
|
||||
filteringFlag = (uint)NF_FILTERING_FLAG.NF_ALLOW,
|
||||
ip_family = (ushort)AddressFamily.InterNetworkV6,
|
||||
remoteIpAddress = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
|
||||
remoteIpAddressMask = new byte[] { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
|
||||
},
|
||||
//IPV6 组播 FF00::/8
|
||||
new NF_RULE
|
||||
{
|
||||
direction = (byte)NF_DIRECTION.NF_D_OUT,
|
||||
filteringFlag = (uint)NF_FILTERING_FLAG.NF_ALLOW,
|
||||
ip_family = (ushort)AddressFamily.InterNetworkV6,
|
||||
remoteIpAddress = new byte[] { 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
remoteIpAddressMask = new byte[] { 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
},
|
||||
//本地链路 FE80::/10
|
||||
new NF_RULE
|
||||
{
|
||||
direction = (byte)NF_DIRECTION.NF_D_OUT,
|
||||
filteringFlag = (uint)NF_FILTERING_FLAG.NF_ALLOW,
|
||||
ip_family = (ushort)AddressFamily.InterNetworkV6,
|
||||
remoteIpAddress = new byte[] { 0xFE, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
remoteIpAddressMask = new byte[] { 255, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
},
|
||||
//本地站点 FEC0::/10
|
||||
new NF_RULE
|
||||
{
|
||||
direction = (byte)NF_DIRECTION.NF_D_OUT,
|
||||
filteringFlag = (uint)NF_FILTERING_FLAG.NF_ALLOW,
|
||||
ip_family = (ushort)AddressFamily.InterNetworkV6,
|
||||
remoteIpAddress = new byte[] { 0xFE, 0xC0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
remoteIpAddressMask = new byte[] { 255, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
}
|
||||
});
|
||||
}
|
||||
private void FilterIPV4Lan(List<NF_RULE> rules)
|
||||
{
|
||||
List<string> intranetIpv4s = new List<string>() {
|
||||
"10.0.0.0/8", "100.64.0.0/10",
|
||||
"127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12",
|
||||
"192.0.0.0/24", "192.0.2.0/24","192.88.99.0/24","192.168.0.0/16",
|
||||
"198.18.0.0/15","198.51.100.0/24",
|
||||
"203.0.113.0/24","224.0.0.0/4", "240.0.0.0/4","255.255.255.255/32"
|
||||
};
|
||||
|
||||
foreach (string item in intranetIpv4s)
|
||||
{
|
||||
string[] arr = item.Split('/');
|
||||
rules.Add(new NF_RULE
|
||||
{
|
||||
filteringFlag = (uint)NF_FILTERING_FLAG.NF_ALLOW,
|
||||
ip_family = (ushort)AddressFamily.InterNetwork,
|
||||
remoteIpAddress = IPAddress.Parse(arr[0]).GetAddressBytes(),
|
||||
remoteIpAddressMask = BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(0xffffffff << 32 - byte.Parse(arr[1]))),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void FilterConfigIPs(List<NF_RULE> rules)
|
||||
{
|
||||
foreach (string item in hijackConfig.DeniedIPs)
|
||||
{
|
||||
string[] arr = item.Split('/');
|
||||
rules.Add(new NF_RULE
|
||||
{
|
||||
filteringFlag = (uint)NF_FILTERING_FLAG.NF_BLOCK,
|
||||
ip_family = (ushort)AddressFamily.InterNetwork,
|
||||
remoteIpAddress = IPAddress.Parse(arr[0]).GetAddressBytes(),
|
||||
remoteIpAddressMask = BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(0xffffffff << 32 - byte.Parse(arr[1]))),
|
||||
});
|
||||
}
|
||||
foreach (string item in hijackConfig.AllowIPs)
|
||||
{
|
||||
string[] arr = item.Split('/');
|
||||
rules.Add(new NF_RULE
|
||||
{
|
||||
filteringFlag = (uint)NF_FILTERING_FLAG.NF_ALLOW,
|
||||
ip_family = (ushort)AddressFamily.InterNetwork,
|
||||
remoteIpAddress = IPAddress.Parse(arr[0]).GetAddressBytes(),
|
||||
remoteIpAddressMask = BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(0xffffffff << 32 - byte.Parse(arr[1]))),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void FilterWan(List<NF_RULE> rules)
|
||||
{
|
||||
rules.AddRange(new List<NF_RULE> {
|
||||
//TCP
|
||||
new NF_RULE
|
||||
{
|
||||
direction = (byte)NF_DIRECTION.NF_D_OUT,
|
||||
filteringFlag = (uint)NF_FILTERING_FLAG.NF_FILTER,
|
||||
protocol = (int)ProtocolType.Tcp,
|
||||
ip_family = (ushort)AddressFamily.InterNetwork
|
||||
},
|
||||
new NF_RULE
|
||||
{
|
||||
direction = (byte)NF_DIRECTION.NF_D_OUT,
|
||||
filteringFlag = (uint)NF_FILTERING_FLAG.NF_FILTER,
|
||||
protocol = (int)ProtocolType.Tcp,
|
||||
ip_family = (ushort)AddressFamily.InterNetworkV6
|
||||
},
|
||||
//UDP
|
||||
new NF_RULE
|
||||
{
|
||||
direction = (byte)NF_DIRECTION.NF_D_OUT,
|
||||
filteringFlag = (uint)NF_FILTERING_FLAG.NF_FILTER,
|
||||
protocol = (int)ProtocolType.Udp,
|
||||
ip_family = (ushort)AddressFamily.InterNetwork
|
||||
},
|
||||
new NF_RULE
|
||||
{
|
||||
direction = (byte)NF_DIRECTION.NF_D_OUT,
|
||||
filteringFlag = (uint)NF_FILTERING_FLAG.NF_FILTER,
|
||||
protocol = (int)ProtocolType.Udp,
|
||||
ip_family = (ushort)AddressFamily.InterNetworkV6
|
||||
},
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private string GetFileVersion(string file)
|
||||
{
|
||||
if (File.Exists(file))
|
||||
return FileVersionInfo.GetVersionInfo(file).FileVersion ?? "";
|
||||
|
||||
return "";
|
||||
}
|
||||
private void CheckDriver()
|
||||
{
|
||||
var binFileVersion = GetFileVersion(NFDriver);
|
||||
var systemFileVersion = GetFileVersion(SystemDriver);
|
||||
|
||||
if (File.Exists(SystemDriver) == false)
|
||||
{
|
||||
// Install
|
||||
InstallDriver();
|
||||
return;
|
||||
}
|
||||
|
||||
var reinstall = false;
|
||||
if (Version.TryParse(binFileVersion, out var binResult) && Version.TryParse(systemFileVersion, out var systemResult))
|
||||
{
|
||||
if (binResult.CompareTo(systemResult) > 0)
|
||||
// Update
|
||||
reinstall = true;
|
||||
else if (systemResult.Major != binResult.Major)
|
||||
// Downgrade when Major version different (may have breaking changes)
|
||||
reinstall = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parse File versionName to Version failed
|
||||
if (!systemFileVersion.Equals(binFileVersion))
|
||||
// versionNames are different, Reinstall
|
||||
reinstall = true;
|
||||
}
|
||||
|
||||
if (!reinstall)
|
||||
return;
|
||||
|
||||
UninstallDriver();
|
||||
InstallDriver();
|
||||
}
|
||||
private void InstallDriver()
|
||||
{
|
||||
if (!File.Exists(NFDriver))
|
||||
throw new Exception("builtin driver files missing, can't install NF driver");
|
||||
|
||||
try
|
||||
{
|
||||
File.Copy(NFDriver, SystemDriver);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception($"Copy {Name}.sys failed\n{e.Message}");
|
||||
}
|
||||
|
||||
// 注册驱动文件
|
||||
if (NFAPI.nf_registerDriver(Name) == NF_STATUS.NF_STATUS_SUCCESS)
|
||||
{
|
||||
Logger.Instance.Debug($"Install {Name} driver finished");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Register {Name} failed");
|
||||
}
|
||||
}
|
||||
private bool UninstallDriver()
|
||||
{
|
||||
Stop();
|
||||
|
||||
if (File.Exists(SystemDriver) == false)
|
||||
return true;
|
||||
|
||||
NFAPI.nf_unRegisterDriver(Name);
|
||||
File.Delete(SystemDriver);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
236
cmonitor/hijack/HijackEventHandler.cs
Normal file
236
cmonitor/hijack/HijackEventHandler.cs
Normal file
@@ -0,0 +1,236 @@
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using common.libs.extends;
|
||||
using System.IO;
|
||||
|
||||
namespace cmonitor.hijack
|
||||
{
|
||||
public sealed class HijackEventHandler : NF_EventHandler
|
||||
{
|
||||
private readonly HijackConfig hijackConfig;
|
||||
private readonly uint currentProcessId = 0;
|
||||
private readonly ConcurrentDictionary<ulong, bool> udpConnections = new ConcurrentDictionary<ulong, bool>();
|
||||
private readonly ConcurrentDictionary<ulong, bool> tcpConnections = new ConcurrentDictionary<ulong, bool>();
|
||||
|
||||
public ulong UdpSend { get; private set; }
|
||||
public ulong UdpReceive { get; private set; }
|
||||
public ulong TcpSend { get; private set; }
|
||||
public ulong TcpReceive { get; private set; }
|
||||
|
||||
public HijackEventHandler(HijackConfig hijackConfig)
|
||||
{
|
||||
this.hijackConfig = hijackConfig;
|
||||
currentProcessId = (uint)Process.GetCurrentProcess().Id;
|
||||
}
|
||||
|
||||
#region tcp无需处理
|
||||
public void tcpCanReceive(ulong id)
|
||||
{
|
||||
}
|
||||
public void tcpCanSend(ulong id)
|
||||
{
|
||||
}
|
||||
public void tcpConnectRequest(ulong id, ref NF_TCP_CONN_INFO pConnInfo)
|
||||
{
|
||||
|
||||
}
|
||||
public void tcpConnected(ulong id, NF_TCP_CONN_INFO pConnInfo)
|
||||
{
|
||||
//是阻止进程
|
||||
if (checkProcess(pConnInfo.processId, out string processName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
tcpConnections.TryAdd(id, true);
|
||||
return;
|
||||
}
|
||||
public void tcpSend(ulong id, IntPtr buf, int len)
|
||||
{
|
||||
if (tcpConnections.ContainsKey(id))
|
||||
{
|
||||
TcpSend += (ulong)len;
|
||||
NFAPI.nf_tcpPostSend(id, buf, len);
|
||||
}
|
||||
}
|
||||
public void tcpReceive(ulong id, IntPtr buf, int len)
|
||||
{
|
||||
TcpReceive += (ulong)len;
|
||||
NFAPI.nf_tcpPostReceive(id, buf, len);
|
||||
}
|
||||
public void tcpClosed(ulong id, NF_TCP_CONN_INFO pConnInfo)
|
||||
{
|
||||
tcpConnections.TryRemove(id, out _);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region udp无需处理
|
||||
public void udpCanReceive(ulong id)
|
||||
{
|
||||
}
|
||||
public void udpCanSend(ulong id)
|
||||
{
|
||||
}
|
||||
public void udpConnectRequest(ulong id, ref NF_UDP_CONN_REQUEST pConnReq)
|
||||
{
|
||||
}
|
||||
public void threadEnd()
|
||||
{
|
||||
}
|
||||
public void threadStart()
|
||||
{
|
||||
}
|
||||
public void udpReceive(ulong id, nint remoteAddress, nint buf, int len, nint options, int optionsLen)
|
||||
{
|
||||
UdpReceive += (ulong)len;
|
||||
NFAPI.nf_udpPostReceive(id, remoteAddress, buf, len, options);
|
||||
}
|
||||
#endregion
|
||||
public void udpClosed(ulong id, NF_UDP_CONN_INFO pConnInfo)
|
||||
{
|
||||
udpConnections.TryRemove(id, out _);
|
||||
//删除udp对象缓存
|
||||
}
|
||||
public void udpCreated(ulong id, NF_UDP_CONN_INFO pConnInfo)
|
||||
{
|
||||
//是阻止进程
|
||||
if (checkProcess(pConnInfo.processId, out string processName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
udpConnections.TryAdd(id, true);
|
||||
}
|
||||
public unsafe void udpSend(ulong id, nint remoteAddress, nint buf, int len, nint options, int optionsLen)
|
||||
{
|
||||
//丢弃进程包
|
||||
if (udpConnections.TryGetValue(id, out _) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//丢弃域名包
|
||||
if (checkDomain(remoteAddress, buf, len))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UdpSend += (ulong)len;
|
||||
NFAPI.nf_udpPostSend(id, remoteAddress, buf, len, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否阻止域名
|
||||
/// </summary>
|
||||
/// <param name="remoteAddress"></param>
|
||||
/// <param name="buf"></param>
|
||||
/// <param name="len"></param>
|
||||
/// <returns></returns>
|
||||
private unsafe bool checkDomain(nint remoteAddress, nint buf, int len)
|
||||
{
|
||||
if (hijackConfig.AllowDomains.Length == 0 && hijackConfig.DeniedDomains.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
byte* p = (byte*)remoteAddress;
|
||||
ushort port = (ushort)(*(p + 2) << 8 & 0xFF00 | *(p + 3));
|
||||
if (port == 53)
|
||||
{
|
||||
try
|
||||
{
|
||||
Span<byte> span = new Span<byte>((void*)buf, len);
|
||||
span = span.Slice(4);
|
||||
ushort length = (ushort)(span[0] << 8 | span[1]);
|
||||
span = span.Slice(8);
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(256);
|
||||
while (span[0] > 0)
|
||||
{
|
||||
sb.Append(span.Slice(1, span[0]).GetString());
|
||||
sb.Append(".");
|
||||
span = span.Slice(1 + span[0]);
|
||||
}
|
||||
string domain = (sb.ToString(0, sb.Length - 1));
|
||||
if (checkDomain(domain))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private bool checkDomain(string domain)
|
||||
{
|
||||
//黑名单
|
||||
if (hijackConfig.DeniedDomains.Length > 0 && checkName(hijackConfig.DeniedDomains, domain))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
//白名单
|
||||
if (hijackConfig.AllowDomains.Length > 0)
|
||||
{
|
||||
return checkName(hijackConfig.AllowDomains, domain) == false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否阻止进程
|
||||
/// </summary>
|
||||
/// <param name="processId"></param>
|
||||
/// <param name="processName"></param>
|
||||
/// <returns></returns>
|
||||
private bool checkProcess(uint processId, out string processName)
|
||||
{
|
||||
processName = string.Empty;
|
||||
if (currentProcessId == processId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
processName = NFAPI.nf_getProcessName(processId);
|
||||
//黑名单
|
||||
if (hijackConfig.DeniedProcesss.Length > 0 && checkName(hijackConfig.DeniedProcesss, processName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
//白名单
|
||||
if (hijackConfig.AllowProcesss.Length > 0)
|
||||
{
|
||||
return checkName(hijackConfig.AllowProcesss, processName) == false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private bool checkName(string[] names, string path)
|
||||
{
|
||||
for (int i = 0; i < names.Length; i++)
|
||||
{
|
||||
if (names[i].Length > path.Length) continue;
|
||||
|
||||
var pathSpan = path.AsSpan();
|
||||
var nameSpan = names[i].AsSpan();
|
||||
try
|
||||
{
|
||||
if (pathSpan.Slice(pathSpan.Length - nameSpan.Length, nameSpan.Length).SequenceEqual(nameSpan))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex + "");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
1042
cmonitor/hijack/NFApi.cs
Normal file
1042
cmonitor/hijack/NFApi.cs
Normal file
File diff suppressed because it is too large
Load Diff
BIN
cmonitor/nfapi.dll
Normal file
BIN
cmonitor/nfapi.dll
Normal file
Binary file not shown.
BIN
cmonitor/nfdriver.sys
Normal file
BIN
cmonitor/nfdriver.sys
Normal file
Binary file not shown.
314
cmonitor/server/api/ClientServer.cs
Normal file
314
cmonitor/server/api/ClientServer.cs
Normal file
@@ -0,0 +1,314 @@
|
||||
using cmonitor.server.api.websocket;
|
||||
using common.libs;
|
||||
using common.libs.extends;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Buffers;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace cmonitor.server.api
|
||||
{
|
||||
/// <summary>
|
||||
/// 前段接口服务
|
||||
/// </summary>
|
||||
public sealed class ClientServer : IClientServer
|
||||
{
|
||||
private readonly Dictionary<string, PluginPathCacheInfo> plugins = new();
|
||||
private readonly ConcurrentDictionary<uint, ConnectionTimeInfo> connectionTimes = new();
|
||||
public uint OnlineNum = 0;
|
||||
|
||||
private readonly ServiceProvider serviceProvider;
|
||||
private WebSocketServer server;
|
||||
private readonly Config config;
|
||||
|
||||
public ClientServer(ServiceProvider serviceProvider, Config config)
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载插件
|
||||
/// </summary>
|
||||
/// <param name="assemblys"></param>
|
||||
public void LoadPlugins(Assembly[] assemblys)
|
||||
{
|
||||
|
||||
Type voidType = typeof(void);
|
||||
|
||||
IEnumerable<Type> types = assemblys.SelectMany(c => c.GetTypes());
|
||||
foreach (Type item in types.Where(c => c.GetInterfaces().Contains(typeof(IClientService))))
|
||||
{
|
||||
string path = item.Name.Replace("ClientService", "");
|
||||
object obj = serviceProvider.GetService(item);
|
||||
foreach (MethodInfo method in item.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
|
||||
{
|
||||
string key = $"{path}/{method.Name}".ToLower();
|
||||
if (!plugins.ContainsKey(key))
|
||||
{
|
||||
bool istask = method.ReturnType.GetProperty("IsCompleted") != null && method.ReturnType.GetMethod("GetAwaiter") != null;
|
||||
bool isTaskResult = method.ReturnType.GetProperty("Result") != null;
|
||||
plugins.TryAdd(key, new PluginPathCacheInfo
|
||||
{
|
||||
IsVoid = method.ReturnType == voidType,
|
||||
Method = method,
|
||||
Target = obj,
|
||||
IsTask = istask,
|
||||
IsTaskResult = isTaskResult
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 开启websockt
|
||||
/// </summary>
|
||||
public void Websocket()
|
||||
{
|
||||
server = new WebSocketServer();
|
||||
try
|
||||
{
|
||||
server.Start(System.Net.IPAddress.Any, config.ApiPort);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
server.OnOpen = (connection) =>
|
||||
{
|
||||
Interlocked.Increment(ref OnlineNum);
|
||||
connectionTimes.TryAdd(connection.Id, new ConnectionTimeInfo());
|
||||
};
|
||||
server.OnDisConnectd = (connection) =>
|
||||
{
|
||||
Interlocked.Decrement(ref OnlineNum);
|
||||
if (OnlineNum < 0) Interlocked.Exchange(ref OnlineNum, 0);
|
||||
connectionTimes.TryRemove(connection.Id, out _);
|
||||
};
|
||||
server.OnMessage = (connection, frame, message) =>
|
||||
{
|
||||
if (connectionTimes.TryGetValue(connection.Id, out ConnectionTimeInfo timeInfo))
|
||||
{
|
||||
timeInfo.DateTime = DateTime.Now;
|
||||
}
|
||||
var req = message.DeJson<ClientServiceRequestInfo>();
|
||||
req.Connection = connection;
|
||||
OnMessage(req).ContinueWith((result) =>
|
||||
{
|
||||
var resp = result.Result.ToJson().ToBytes();
|
||||
connection.SendFrameText(resp);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 收到消息
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<ClientServiceResponseInfo> OnMessage(ClientServiceRequestInfo model)
|
||||
{
|
||||
model.Path = model.Path.ToLower();
|
||||
if (plugins.TryGetValue(model.Path, out PluginPathCacheInfo plugin) == false)
|
||||
{
|
||||
return new ClientServiceResponseInfo
|
||||
{
|
||||
Content = "not exists this path",
|
||||
RequestId = model.RequestId,
|
||||
Path = model.Path,
|
||||
Code = ClientServiceResponseCodes.NotFound
|
||||
};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ClientServiceParamsInfo param = new ClientServiceParamsInfo
|
||||
{
|
||||
RequestId = model.RequestId,
|
||||
Content = model.Content,
|
||||
Connection = model.Connection
|
||||
};
|
||||
dynamic resultAsync = plugin.Method.Invoke(plugin.Target, new object[] { param });
|
||||
object resultObject = null;
|
||||
if (plugin.IsVoid == false)
|
||||
{
|
||||
if (plugin.IsTask)
|
||||
{
|
||||
await resultAsync.ConfigureAwait(false);
|
||||
if (plugin.IsTaskResult)
|
||||
{
|
||||
resultObject = resultAsync.Result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
resultObject = resultAsync;
|
||||
}
|
||||
}
|
||||
return new ClientServiceResponseInfo
|
||||
{
|
||||
Code = param.Code,
|
||||
Content = param.Code != ClientServiceResponseCodes.Error ? resultObject : param.ErrorMessage,
|
||||
RequestId = model.RequestId,
|
||||
Path = model.Path,
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Error(ex);
|
||||
return new ClientServiceResponseInfo
|
||||
{
|
||||
Content = ex.Message,
|
||||
RequestId = model.RequestId,
|
||||
Path = model.Path,
|
||||
Code = ClientServiceResponseCodes.Error
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void Notify(string path, object content)
|
||||
{
|
||||
if (server.Connections.Any())
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] bytes = JsonSerializer.Serialize(new ClientServiceResponseInfo
|
||||
{
|
||||
Code = ClientServiceResponseCodes.Success,
|
||||
Content = content,
|
||||
Path = path,
|
||||
RequestId = 0
|
||||
}).ToBytes();
|
||||
|
||||
foreach (WebsocketConnection connection in server.Connections)
|
||||
{
|
||||
if (connection.Connected && connectionTimes.TryGetValue(connection.Id, out ConnectionTimeInfo timeInfo) && (DateTime.Now - timeInfo.DateTime).TotalMilliseconds < 1000)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
connection.SendFrameText(bytes);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//Logger.Instance.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Notify(string path, string name, Memory<byte> content)
|
||||
{
|
||||
if (server.Connections.Any())
|
||||
{
|
||||
try
|
||||
{
|
||||
Memory<byte> headMemory = JsonSerializer.Serialize(new ClientServiceResponseInfo
|
||||
{
|
||||
Code = ClientServiceResponseCodes.Success,
|
||||
Content = name,
|
||||
Path = path,
|
||||
RequestId = 0
|
||||
}).ToBytes();
|
||||
|
||||
int length = 4 + headMemory.Length + content.Length;
|
||||
byte[] result = ArrayPool<byte>.Shared.Rent(length);
|
||||
|
||||
int index = 0;
|
||||
headMemory.Length.ToBytes(result);
|
||||
index += 4;
|
||||
headMemory.CopyTo(result.AsMemory(index));
|
||||
index += headMemory.Length;
|
||||
content.CopyTo(result.AsMemory(index));
|
||||
index += content.Length;
|
||||
|
||||
foreach (WebsocketConnection connection in server.Connections)
|
||||
{
|
||||
if (connection.Connected && connectionTimes.TryGetValue(connection.Id, out ConnectionTimeInfo timeInfo) && (DateTime.Now - timeInfo.DateTime).TotalMilliseconds < 1000)
|
||||
{
|
||||
try
|
||||
{
|
||||
connection.SendFrameBinary(result.AsMemory(0, length));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
ArrayPool<byte>.Shared.Return(result);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//Logger.Instance.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Notify(string path, object content, WebsocketConnection connection)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (connection.Connected == false) return;
|
||||
|
||||
byte[] bytes = JsonSerializer.Serialize(new ClientServiceResponseInfo
|
||||
{
|
||||
Code = ClientServiceResponseCodes.Success,
|
||||
Content = content,
|
||||
Path = path,
|
||||
RequestId = 0
|
||||
}).ToBytes();
|
||||
|
||||
try
|
||||
{
|
||||
connection.SendFrameText(bytes);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//Logger.Instance.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ConnectionTimeInfo
|
||||
{
|
||||
public DateTime DateTime { get; set; } = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前段接口缓存
|
||||
/// </summary>
|
||||
public struct PluginPathCacheInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 对象
|
||||
/// </summary>
|
||||
public object Target { get; set; }
|
||||
/// <summary>
|
||||
/// 方法
|
||||
/// </summary>
|
||||
public MethodInfo Method { get; set; }
|
||||
/// <summary>
|
||||
/// 是否void
|
||||
/// </summary>
|
||||
public bool IsVoid { get; set; }
|
||||
/// <summary>
|
||||
/// 是否task
|
||||
/// </summary>
|
||||
public bool IsTask { get; set; }
|
||||
/// <summary>
|
||||
/// 是否task result
|
||||
/// </summary>
|
||||
public bool IsTaskResult { get; set; }
|
||||
}
|
||||
}
|
||||
25
cmonitor/server/api/IClientServer.cs
Normal file
25
cmonitor/server/api/IClientServer.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using cmonitor.server.api.websocket;
|
||||
using System.Reflection;
|
||||
|
||||
namespace cmonitor.server.api
|
||||
{
|
||||
/// <summary>
|
||||
/// 前端接口服务
|
||||
/// </summary>
|
||||
public interface IClientServer
|
||||
{
|
||||
/// <summary>
|
||||
/// websocket
|
||||
/// </summary>
|
||||
public void Websocket();
|
||||
/// <summary>
|
||||
/// 加载插件
|
||||
/// </summary>
|
||||
/// <param name="assemblys"></param>
|
||||
public void LoadPlugins(Assembly[] assemblys);
|
||||
public void Notify(string path, object content);
|
||||
public void Notify(string path,string name, Memory<byte> content);
|
||||
public void Notify(string path, object content,WebsocketConnection connection);
|
||||
}
|
||||
|
||||
}
|
||||
133
cmonitor/server/api/IClientService.cs
Normal file
133
cmonitor/server/api/IClientService.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using cmonitor.server.api.websocket;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace cmonitor.server.api
|
||||
{
|
||||
/// <summary>
|
||||
/// 前段接口
|
||||
/// </summary>
|
||||
public interface IClientService { }
|
||||
|
||||
/// <summary>
|
||||
/// 前段接口response
|
||||
/// </summary>
|
||||
public sealed class ClientServiceResponseInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 路径
|
||||
/// </summary>
|
||||
public string Path { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// 请求id
|
||||
/// </summary>
|
||||
public long RequestId { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// 状态码
|
||||
/// </summary>
|
||||
public ClientServiceResponseCodes Code { get; set; } = ClientServiceResponseCodes.Success;
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
public object Content { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前端接口request
|
||||
/// </summary>
|
||||
public sealed class ClientServiceRequestInfo
|
||||
{
|
||||
[JsonIgnore]
|
||||
public WebsocketConnection Connection { get; set; }
|
||||
/// <summary>
|
||||
/// 路径
|
||||
/// </summary>
|
||||
public string Path { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// 请求id
|
||||
/// </summary>
|
||||
public uint RequestId { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前端接口执行参数
|
||||
/// </summary>
|
||||
public sealed class ClientServiceParamsInfo
|
||||
{
|
||||
public WebsocketConnection Connection { get; set; }
|
||||
/// <summary>
|
||||
/// 请求id
|
||||
/// </summary>
|
||||
public uint RequestId { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
public string Content { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// 状态码
|
||||
/// </summary>
|
||||
public ClientServiceResponseCodes Code { get; private set; } = ClientServiceResponseCodes.Success;
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 设置状态码
|
||||
/// </summary>
|
||||
/// <param name="code"></param>
|
||||
/// <param name="errormsg"></param>
|
||||
public void SetCode(ClientServiceResponseCodes code, string errormsg = "")
|
||||
{
|
||||
Code = code;
|
||||
ErrorMessage = errormsg;
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置错误信息
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
public void SetErrorMessage(string msg)
|
||||
{
|
||||
Code = ClientServiceResponseCodes.Error;
|
||||
ErrorMessage = msg;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 前端接口状态码
|
||||
/// </summary>
|
||||
public enum ClientServiceResponseCodes : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
Success = 0,
|
||||
/// <summary>
|
||||
/// 没找到
|
||||
/// </summary>
|
||||
NotFound = 1,
|
||||
/// <summary>
|
||||
/// 失败
|
||||
/// </summary>
|
||||
Error = 0xff,
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前端接口标识特性
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class ClientServiceAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 参数类型
|
||||
/// </summary>
|
||||
public Type Param { get; set; }
|
||||
public ClientServiceAttribute(Type param)
|
||||
{
|
||||
Param = param;
|
||||
}
|
||||
}
|
||||
}
|
||||
386
cmonitor/server/api/RuleConfig.cs
Normal file
386
cmonitor/server/api/RuleConfig.cs
Normal file
@@ -0,0 +1,386 @@
|
||||
using common.libs.database;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace cmonitor.server.api
|
||||
{
|
||||
|
||||
[Table("rule")]
|
||||
public sealed class RuleConfig
|
||||
{
|
||||
private readonly IConfigDataProvider<RuleConfig> configDataProvider;
|
||||
public RuleConfig() { }
|
||||
public RuleConfig(IConfigDataProvider<RuleConfig> configDataProvider)
|
||||
{
|
||||
this.configDataProvider = configDataProvider;
|
||||
RuleConfig config = configDataProvider.Load().Result ?? new RuleConfig
|
||||
{
|
||||
UserNames = new Dictionary<string, UserNameInfo> { { "snltty", new UserNameInfo {
|
||||
Rules = new List<RulesInfo>{ new RulesInfo { ID = 1, Name = "默认" } },
|
||||
Processs = new List<GroupInfo>{ new GroupInfo { ID = 1, Name = "默认" } },
|
||||
} } }
|
||||
};
|
||||
UserNames = config.UserNames;
|
||||
MaxID = config.MaxID;
|
||||
Save();
|
||||
}
|
||||
|
||||
public Dictionary<string, UserNameInfo> UserNames { get; set; } = new Dictionary<string, UserNameInfo>();
|
||||
private uint maxid = 0;
|
||||
public uint MaxID
|
||||
{
|
||||
get => maxid; set
|
||||
{
|
||||
maxid = value;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly object lockObj = new object();
|
||||
|
||||
public string AddName(string name)
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
if (UserNames.ContainsKey(name) == false)
|
||||
{
|
||||
UserNames.Add(name, new UserNameInfo
|
||||
{
|
||||
Rules = new List<RulesInfo> { new RulesInfo { ID = 1, Name = "默认" } },
|
||||
Processs = new List<GroupInfo> { new GroupInfo { ID = 1, Name = "默认" } },
|
||||
});
|
||||
}
|
||||
Save();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public string AddProcessGroup(UpdateGroupInfo updateGroupInfo)
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
if (UserNames.TryGetValue(updateGroupInfo.UserName, out UserNameInfo userNameInfo) == false)
|
||||
{
|
||||
return "不存在此管理用户";
|
||||
}
|
||||
if (userNameInfo.Processs.FirstOrDefault(c => c.Name == updateGroupInfo.Group.Name && c.ID != updateGroupInfo.Group.ID) != null)
|
||||
{
|
||||
return "已存在同名记录";
|
||||
}
|
||||
|
||||
//添加
|
||||
if (updateGroupInfo.Group.ID == 0)
|
||||
{
|
||||
updateGroupInfo.Group.ID = Interlocked.Increment(ref maxid);
|
||||
userNameInfo.Processs.Add(updateGroupInfo.Group);
|
||||
Save();
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
//修改
|
||||
GroupInfo old = userNameInfo.Processs.FirstOrDefault(c => c.ID == updateGroupInfo.Group.ID);
|
||||
if (old == null)
|
||||
{
|
||||
return "不存在记录,无法修改";
|
||||
}
|
||||
old.Name = updateGroupInfo.Group.Name;
|
||||
Save();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
public string AddProcess(UpdateItemInfo updateItem)
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
if (UserNames.TryGetValue(updateItem.UserName, out UserNameInfo userNameInfo) == false)
|
||||
{
|
||||
return "不存在此管理用户";
|
||||
}
|
||||
GroupInfo group = userNameInfo.Processs.FirstOrDefault(c => c.ID == updateItem.GroupID);
|
||||
if (group == null)
|
||||
{
|
||||
return "不存在此分组";
|
||||
}
|
||||
if (group.List.FirstOrDefault(c => c.Name == updateItem.Item.Name && c.ID != updateItem.Item.ID) != null)
|
||||
{
|
||||
return "已存在同名记录";
|
||||
}
|
||||
|
||||
//添加
|
||||
if (updateItem.Item.ID == 0)
|
||||
{
|
||||
updateItem.Item.ID = Interlocked.Increment(ref maxid);
|
||||
group.List.Add(updateItem.Item);
|
||||
Save();
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
//修改
|
||||
ItemInfo old = group.List.FirstOrDefault(c => c.ID == updateItem.Item.ID);
|
||||
if (old == null)
|
||||
{
|
||||
return "不存在记录,无法修改";
|
||||
}
|
||||
old.Name = updateItem.Item.Name;
|
||||
old.AllowType = updateItem.Item.AllowType;
|
||||
old.DataType = updateItem.Item.DataType;
|
||||
Save();
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
public string DeleteProcessGroup(DeleteGroupInfo deleteGroupInfo)
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
if (UserNames.TryGetValue(deleteGroupInfo.UserName, out UserNameInfo userNameInfo) == false)
|
||||
{
|
||||
return "不存在此管理用户";
|
||||
}
|
||||
|
||||
userNameInfo.Processs.Remove(userNameInfo.Processs.FirstOrDefault(c => c.ID == deleteGroupInfo.ID));
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
public string DeleteProcess(DeleteItemInfo deleteItemInfo)
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
if (UserNames.TryGetValue(deleteItemInfo.UserName, out UserNameInfo userNameInfo) == false)
|
||||
{
|
||||
return "不存在此管理用户";
|
||||
}
|
||||
GroupInfo group = userNameInfo.Processs.FirstOrDefault(c => c.ID == deleteItemInfo.GroupID);
|
||||
if (group == null)
|
||||
{
|
||||
return "不存在此分组";
|
||||
}
|
||||
|
||||
group.List.Remove(group.List.FirstOrDefault(c => c.ID == deleteItemInfo.ID));
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public string AddRule(UpdateRuleInfo updateRuleInfo)
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
if (UserNames.TryGetValue(updateRuleInfo.UserName, out UserNameInfo userNameInfo) == false)
|
||||
{
|
||||
return "不存在此管理用户";
|
||||
}
|
||||
if (userNameInfo.Rules.FirstOrDefault(c => c.Name == updateRuleInfo.Rule.Name && c.ID != updateRuleInfo.Rule.ID) != null)
|
||||
{
|
||||
return "已存在同名记录";
|
||||
}
|
||||
|
||||
//添加
|
||||
if (updateRuleInfo.Rule.ID == 0)
|
||||
{
|
||||
updateRuleInfo.Rule.ID = Interlocked.Increment(ref maxid);
|
||||
userNameInfo.Rules.Add(updateRuleInfo.Rule);
|
||||
Save();
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
//修改
|
||||
RulesInfo old = userNameInfo.Rules.FirstOrDefault(c => c.ID == updateRuleInfo.Rule.ID);
|
||||
if (old == null)
|
||||
{
|
||||
return "不存在记录,无法修改";
|
||||
}
|
||||
old.Name = updateRuleInfo.Rule.Name;
|
||||
old.PrivateProcesss = updateRuleInfo.Rule.PrivateProcesss;
|
||||
old.PublicProcesss = updateRuleInfo.Rule.PublicProcesss;
|
||||
Save();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
public string DeleteRule(DeleteRuleInfo deleteRuleInfo)
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
if (UserNames.TryGetValue(deleteRuleInfo.UserName, out UserNameInfo userNameInfo) == false)
|
||||
{
|
||||
return "不存在此管理用户";
|
||||
}
|
||||
|
||||
userNameInfo.Rules.Remove(userNameInfo.Rules.FirstOrDefault(c => c.ID == deleteRuleInfo.ID));
|
||||
Save();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public string UpdateDevices(UpdateDevicesInfo updatDevicesInfo)
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
if (UserNames.TryGetValue(updatDevicesInfo.UserName, out UserNameInfo userNameInfo) == false)
|
||||
{
|
||||
return "不存在此管理用户";
|
||||
}
|
||||
|
||||
userNameInfo.Devices = updatDevicesInfo.Devices;
|
||||
Save();
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public string AddFileName(AddFileNameInfo addFileNameInfo)
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
if (UserNames.TryGetValue(addFileNameInfo.UserName, out UserNameInfo userNameInfo) == false)
|
||||
{
|
||||
return "不存在此管理用户";
|
||||
}
|
||||
if (userNameInfo.FileNames.FirstOrDefault(c => c.FileName == addFileNameInfo.FileName.FileName && c.ID != addFileNameInfo.FileName.ID) != null)
|
||||
{
|
||||
return "已存在同名记录";
|
||||
}
|
||||
|
||||
//添加
|
||||
if (addFileNameInfo.FileName.ID == 0)
|
||||
{
|
||||
addFileNameInfo.FileName.ID = Interlocked.Increment(ref maxid);
|
||||
userNameInfo.FileNames.Add(addFileNameInfo.FileName);
|
||||
Save();
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
//修改
|
||||
FileNameInfo old = userNameInfo.FileNames.FirstOrDefault(c => c.ID == addFileNameInfo.FileName.ID);
|
||||
if (old == null)
|
||||
{
|
||||
return "不存在记录,无法修改";
|
||||
}
|
||||
old.FileName = addFileNameInfo.FileName.FileName;
|
||||
old.Desc = addFileNameInfo.FileName.Desc;
|
||||
Save();
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
public string DelFileName(DeletedFileNameInfo deletedFileNameInfo)
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
if (UserNames.TryGetValue(deletedFileNameInfo.UserName, out UserNameInfo userNameInfo) == false)
|
||||
{
|
||||
return "不存在此管理用户";
|
||||
}
|
||||
userNameInfo.FileNames.Remove(userNameInfo.FileNames.FirstOrDefault(c=>c.ID == deletedFileNameInfo.ID));
|
||||
Save();
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
configDataProvider.Save(this).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class UserNameInfo
|
||||
{
|
||||
public List<RulesInfo> Rules { get; set; } = new List<RulesInfo>();
|
||||
public List<GroupInfo> Processs { get; set; } = new List<GroupInfo>();
|
||||
public List<string> Devices { get; set; } = new List<string>();
|
||||
public List<FileNameInfo> FileNames { get; set; } = new List<FileNameInfo>();
|
||||
}
|
||||
public sealed class UpdateDevicesInfo
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public List<string> Devices { get; set; } = new List<string>();
|
||||
}
|
||||
|
||||
|
||||
public sealed class RulesInfo
|
||||
{
|
||||
public uint ID { get; set; }
|
||||
public string Name { get; set; }
|
||||
public List<uint> PrivateProcesss { get; set; } = new List<uint>();
|
||||
public List<uint> PublicProcesss { get; set; } = new List<uint>();
|
||||
}
|
||||
public sealed class UpdateRuleInfo
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public RulesInfo Rule { get; set; }
|
||||
}
|
||||
public sealed class DeleteRuleInfo
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public uint ID { get; set; }
|
||||
}
|
||||
|
||||
public sealed class GroupInfo
|
||||
{
|
||||
public uint ID { get; set; }
|
||||
public string Name { get; set; }
|
||||
public List<ItemInfo> List { get; set; } = new List<ItemInfo>();
|
||||
}
|
||||
public sealed class UpdateGroupInfo
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public GroupInfo Group { get; set; }
|
||||
}
|
||||
public sealed class DeleteGroupInfo
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public uint ID { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ItemInfo
|
||||
{
|
||||
public uint ID { get; set; }
|
||||
public string Name { get; set; }
|
||||
public DataType DataType { get; set; }
|
||||
public AllowType AllowType { get; set; }
|
||||
}
|
||||
public enum DataType
|
||||
{
|
||||
Process = 0,
|
||||
Domain = 1,
|
||||
IP = 2,
|
||||
}
|
||||
public enum AllowType
|
||||
{
|
||||
Allow = 0,
|
||||
Denied = 1
|
||||
}
|
||||
|
||||
public sealed class UpdateItemInfo
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public uint GroupID { get; set; }
|
||||
public ItemInfo Item { get; set; }
|
||||
}
|
||||
public sealed class DeleteItemInfo
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public uint GroupID { get; set; }
|
||||
public uint ID { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public sealed class FileNameInfo
|
||||
{
|
||||
public uint ID { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public string Desc { get; set; }
|
||||
}
|
||||
public sealed class AddFileNameInfo
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public FileNameInfo FileName { get; set; }
|
||||
}
|
||||
public sealed class DeletedFileNameInfo
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public uint ID { get; set; }
|
||||
}
|
||||
}
|
||||
87
cmonitor/server/api/services/ActiveClientService.cs
Normal file
87
cmonitor/server/api/services/ActiveClientService.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using cmonitor.server.client.reports.active;
|
||||
using cmonitor.server.service;
|
||||
using cmonitor.server.service.messengers.active;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using common.libs;
|
||||
using common.libs.extends;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.api.services
|
||||
{
|
||||
public sealed class ActiveClientService : IClientService
|
||||
{
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly SignCaching signCaching;
|
||||
private readonly RuleConfig ruleConfig;
|
||||
public ActiveClientService(MessengerSender messengerSender, SignCaching signCaching, RuleConfig ruleConfig)
|
||||
{
|
||||
this.messengerSender = messengerSender;
|
||||
this.signCaching = signCaching;
|
||||
this.ruleConfig = ruleConfig;
|
||||
}
|
||||
public async Task<ActiveWindowTimeReportInfo> Get(ClientServiceParamsInfo param)
|
||||
{
|
||||
if (signCaching.Get(param.Content, out SignCacheInfo cache) && cache.Connected)
|
||||
{
|
||||
MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap
|
||||
{
|
||||
Connection = cache.Connection,
|
||||
MessengerId = (ushort)ActiveMessengerIds.Get
|
||||
});
|
||||
if (resp.Code == MessageResponeCodes.OK)
|
||||
{
|
||||
return MemoryPackSerializer.Deserialize<ActiveWindowTimeReportInfo>(resp.Data.Span);
|
||||
}
|
||||
}
|
||||
return new ActiveWindowTimeReportInfo();
|
||||
}
|
||||
|
||||
public async Task<bool> Clear(ClientServiceParamsInfo param)
|
||||
{
|
||||
if (signCaching.Get(param.Content, out SignCacheInfo cache) && cache.Connected)
|
||||
{
|
||||
MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap
|
||||
{
|
||||
Connection = cache.Connection,
|
||||
MessengerId = (ushort)ActiveMessengerIds.Clear
|
||||
});
|
||||
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public async Task<bool> Disallow(ClientServiceParamsInfo param)
|
||||
{
|
||||
DisallowInfo disallowInfo = param.Content.DeJson<DisallowInfo>();
|
||||
byte[] bytes = MemoryPackSerializer.Serialize(disallowInfo.FileNames);
|
||||
foreach (string name in disallowInfo.Names)
|
||||
{
|
||||
if (signCaching.Get(name, out SignCacheInfo cache) && cache.Connected)
|
||||
{
|
||||
await messengerSender.SendOnly(new MessageRequestWrap
|
||||
{
|
||||
Connection = cache.Connection,
|
||||
MessengerId = (ushort)ActiveMessengerIds.Disallow,
|
||||
Payload = bytes
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public string Add(ClientServiceParamsInfo param)
|
||||
{
|
||||
return ruleConfig.AddFileName(param.Content.DeJson<AddFileNameInfo>());
|
||||
}
|
||||
public string Del(ClientServiceParamsInfo param)
|
||||
{
|
||||
return ruleConfig.DelFileName(param.Content.DeJson<DeletedFileNameInfo>());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DisallowInfo
|
||||
{
|
||||
public string[] Names { get; set; }
|
||||
public string[] FileNames { get; set; }
|
||||
}
|
||||
}
|
||||
47
cmonitor/server/api/services/CommandClientService.cs
Normal file
47
cmonitor/server/api/services/CommandClientService.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using cmonitor.server.service;
|
||||
using cmonitor.server.service.messengers.command;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using common.libs;
|
||||
using common.libs.extends;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.api.services
|
||||
{
|
||||
public sealed class CommandClientService : IClientService
|
||||
{
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly SignCaching signCaching;
|
||||
private readonly IClientServer clientServer;
|
||||
public CommandClientService(MessengerSender messengerSender, SignCaching signCaching, IClientServer clientServer)
|
||||
{
|
||||
this.messengerSender = messengerSender;
|
||||
this.signCaching = signCaching;
|
||||
this.clientServer = clientServer;
|
||||
}
|
||||
public async Task<bool> Exec(ClientServiceParamsInfo param)
|
||||
{
|
||||
ExecInfo info = param.Content.DeJson<ExecInfo>();
|
||||
for (int i = 0; i < info.Names.Length; i++)
|
||||
{
|
||||
if (signCaching.Get(info.Names[i], out SignCacheInfo cache) && cache.Connected)
|
||||
{
|
||||
await messengerSender.SendOnly(new MessageRequestWrap
|
||||
{
|
||||
Connection = cache.Connection,
|
||||
MessengerId = (ushort)CommandMessengerIds.Exec,
|
||||
Payload = MemoryPackSerializer.Serialize(info.Commands)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public sealed class ExecInfo
|
||||
{
|
||||
public string[] Names { get; set; }
|
||||
public string[] Commands { get; set; }
|
||||
}
|
||||
}
|
||||
95
cmonitor/server/api/services/HijackClientService.cs
Normal file
95
cmonitor/server/api/services/HijackClientService.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using cmonitor.server.service;
|
||||
using cmonitor.server.service.messengers.hijack;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using common.libs.extends;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.api.services
|
||||
{
|
||||
public sealed class HijackClientService : IClientService
|
||||
{
|
||||
private readonly RuleConfig ruleConfig;
|
||||
private readonly SignCaching signCaching;
|
||||
private readonly MessengerSender messengerSender;
|
||||
public HijackClientService(RuleConfig ruleConfig, SignCaching signCaching, MessengerSender messengerSender)
|
||||
{
|
||||
this.ruleConfig = ruleConfig;
|
||||
this.signCaching = signCaching;
|
||||
this.messengerSender = messengerSender;
|
||||
}
|
||||
public Dictionary<string, UserNameInfo> Info(ClientServiceParamsInfo param)
|
||||
{
|
||||
return ruleConfig.UserNames;
|
||||
}
|
||||
public string AddName(ClientServiceParamsInfo param)
|
||||
{
|
||||
return ruleConfig.AddName(param.Content);
|
||||
}
|
||||
|
||||
public string AddProcessGroup(ClientServiceParamsInfo param)
|
||||
{
|
||||
return ruleConfig.AddProcessGroup(param.Content.DeJson<UpdateGroupInfo>());
|
||||
}
|
||||
public string DeleteProcessGroup(ClientServiceParamsInfo param)
|
||||
{
|
||||
return ruleConfig.DeleteProcessGroup(param.Content.DeJson<DeleteGroupInfo>());
|
||||
}
|
||||
public string AddProcess(ClientServiceParamsInfo param)
|
||||
{
|
||||
return ruleConfig.AddProcess(param.Content.DeJson<UpdateItemInfo>());
|
||||
}
|
||||
public string DeleteProcess(ClientServiceParamsInfo param)
|
||||
{
|
||||
return ruleConfig.DeleteProcess(param.Content.DeJson<DeleteItemInfo>());
|
||||
}
|
||||
|
||||
|
||||
public string AddRule(ClientServiceParamsInfo param)
|
||||
{
|
||||
return ruleConfig.AddRule(param.Content.DeJson<UpdateRuleInfo>());
|
||||
}
|
||||
public string DeleteRule(ClientServiceParamsInfo param)
|
||||
{
|
||||
return ruleConfig.DeleteRule(param.Content.DeJson<DeleteRuleInfo>());
|
||||
}
|
||||
|
||||
public string UpdateDevices(ClientServiceParamsInfo param)
|
||||
{
|
||||
return ruleConfig.UpdateDevices(param.Content.DeJson<UpdateDevicesInfo>());
|
||||
}
|
||||
|
||||
public async Task<List<string>> SetRules(ClientServiceParamsInfo param)
|
||||
{
|
||||
List<string> errorDevices = new List<string>();
|
||||
SetRuleParamInfo setRuleParamInfo = param.Content.DeJson<SetRuleParamInfo>();
|
||||
if (setRuleParamInfo.Devices.Length > 0)
|
||||
{
|
||||
byte[] bytes = MemoryPackSerializer.Serialize(setRuleParamInfo.Rules);
|
||||
for (int i = 0; i < setRuleParamInfo.Devices.Length; i++)
|
||||
{
|
||||
if (signCaching.Get(setRuleParamInfo.Devices[i], out SignCacheInfo cache))
|
||||
{
|
||||
bool res = await messengerSender.SendOnly(new MessageRequestWrap
|
||||
{
|
||||
Connection = cache.Connection,
|
||||
MessengerId = (ushort)HijackMessengerIds.Update,
|
||||
Payload = bytes
|
||||
});
|
||||
if (res == false)
|
||||
{
|
||||
errorDevices.Add(setRuleParamInfo.Devices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return errorDevices;
|
||||
}
|
||||
|
||||
public sealed class SetRuleParamInfo
|
||||
{
|
||||
public string[] Devices { get; set; }
|
||||
public SetRuleInfo Rules { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
41
cmonitor/server/api/services/LLockClientService.cs
Normal file
41
cmonitor/server/api/services/LLockClientService.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using cmonitor.server.service;
|
||||
using cmonitor.server.service.messengers.llock;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using common.libs.extends;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.api.services
|
||||
{
|
||||
public sealed class LLockClientService : IClientService
|
||||
{
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly SignCaching signCaching;
|
||||
public LLockClientService(MessengerSender messengerSender, SignCaching signCaching)
|
||||
{
|
||||
this.messengerSender = messengerSender;
|
||||
this.signCaching = signCaching;
|
||||
}
|
||||
public async Task<bool> Update(ClientServiceParamsInfo param)
|
||||
{
|
||||
LLockUpdateInfo info = param.Content.DeJson<LLockUpdateInfo>();
|
||||
for (int i = 0; i < info.Names.Length; i++)
|
||||
{
|
||||
if (signCaching.Get(info.Names[i], out SignCacheInfo cache) && cache.Connected)
|
||||
{
|
||||
await messengerSender.SendOnly(new MessageRequestWrap
|
||||
{
|
||||
Connection = cache.Connection,
|
||||
MessengerId = (ushort)LLockMessengerIds.Update,
|
||||
Payload = MemoryPackSerializer.Serialize(info.Value)
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public sealed class LLockUpdateInfo
|
||||
{
|
||||
public string[] Names { get; set; }
|
||||
public bool Value { get; set; }
|
||||
}
|
||||
}
|
||||
46
cmonitor/server/api/services/LightClientService.cs
Normal file
46
cmonitor/server/api/services/LightClientService.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using cmonitor.server.service;
|
||||
using cmonitor.server.service.messengers.light;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using common.libs.extends;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.api.services
|
||||
{
|
||||
public sealed class LightClientService : IClientService
|
||||
{
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly SignCaching signCaching;
|
||||
public LightClientService(MessengerSender messengerSender, SignCaching signCaching)
|
||||
{
|
||||
this.messengerSender = messengerSender;
|
||||
this.signCaching = signCaching;
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> Update(ClientServiceParamsInfo param)
|
||||
{
|
||||
LightInfo info = param.Content.DeJson<LightInfo>();
|
||||
for (int i = 0; i < info.Names.Length; i++)
|
||||
{
|
||||
if (signCaching.Get(info.Names[i], out SignCacheInfo cache) && cache.Connected)
|
||||
{
|
||||
await messengerSender.SendOnly(new MessageRequestWrap
|
||||
{
|
||||
Connection = cache.Connection,
|
||||
MessengerId = (ushort)LightMessengerIds.Update,
|
||||
Payload = MemoryPackSerializer.Serialize(info.Value)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public sealed class LightInfo
|
||||
{
|
||||
public string[] Names { get; set; }
|
||||
public int Value { get; set; }
|
||||
}
|
||||
}
|
||||
81
cmonitor/server/api/services/ReportClientService.cs
Normal file
81
cmonitor/server/api/services/ReportClientService.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using cmonitor.server.service;
|
||||
using cmonitor.server.service.messengers.command;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using common.libs;
|
||||
using common.libs.extends;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace cmonitor.server.api.services
|
||||
{
|
||||
public sealed class ReportClientService : IClientService
|
||||
{
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly SignCaching signCaching;
|
||||
private readonly IClientServer clientServer;
|
||||
public ReportClientService(MessengerSender messengerSender, SignCaching signCaching, IClientServer clientServer)
|
||||
{
|
||||
this.messengerSender = messengerSender;
|
||||
this.signCaching = signCaching;
|
||||
this.clientServer = clientServer;
|
||||
}
|
||||
public bool Update(ClientServiceParamsInfo param)
|
||||
{
|
||||
string[] names = param.Content.DeJson<string[]>();
|
||||
for (int i = 0; i < names.Length; i++)
|
||||
{
|
||||
bool res = signCaching.Get(names[i], out SignCacheInfo cache)
|
||||
&& cache.Connected
|
||||
&& cache.GetReport()
|
||||
&& Interlocked.CompareExchange(ref cache.ReportFlag, 0, 1) == 1;
|
||||
if (res)
|
||||
{
|
||||
cache.UpdateReport();
|
||||
_ = messengerSender.SendOnly(new MessageRequestWrap
|
||||
{
|
||||
Connection = cache.Connection,
|
||||
MessengerId = (ushort)ReportMessengerIds.Update,
|
||||
Timeout = 1000,
|
||||
}).ContinueWith((result) =>
|
||||
{
|
||||
Interlocked.Exchange(ref cache.ReportFlag, 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool Ping(ClientServiceParamsInfo param)
|
||||
{
|
||||
string[] names = param.Content.DeJson<string[]>();
|
||||
for (int i = 0; i < names.Length; i++)
|
||||
{
|
||||
if (signCaching.Get(names[i], out SignCacheInfo cache) && cache.Connected && Interlocked.CompareExchange(ref cache.PingFlag, 0, 1) == 1)
|
||||
{
|
||||
string name = names[i];
|
||||
DateTime starTime = DateTime.Now;
|
||||
_ = messengerSender.SendReply(new MessageRequestWrap
|
||||
{
|
||||
Connection = cache.Connection,
|
||||
MessengerId = (ushort)ReportMessengerIds.Ping,
|
||||
Payload = Helper.EmptyArray,
|
||||
Timeout = 1000,
|
||||
}).ContinueWith((result) =>
|
||||
{
|
||||
if (result.Result.Code == MessageResponeCodes.OK)
|
||||
{
|
||||
clientServer.Notify("/notify/report/pong", new { Name = name, Time = (int)((DateTime.Now - starTime).TotalMilliseconds) }, param.Connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
clientServer.Notify("/notify/report/pong", new { Name = name, Time = -1 }, param.Connection);
|
||||
}
|
||||
Interlocked.Exchange(ref cache.PingFlag, 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
cmonitor/server/api/services/ScreenClientService.cs
Normal file
45
cmonitor/server/api/services/ScreenClientService.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using cmonitor.server.service;
|
||||
using cmonitor.server.service.messengers.screen;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using common.libs.extends;
|
||||
|
||||
namespace cmonitor.server.api.services
|
||||
{
|
||||
public sealed class ScreenClientService : IClientService
|
||||
{
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly SignCaching signCaching;
|
||||
public ScreenClientService(MessengerSender messengerSender, SignCaching signCaching)
|
||||
{
|
||||
this.messengerSender = messengerSender;
|
||||
this.signCaching = signCaching;
|
||||
}
|
||||
public bool Update(ClientServiceParamsInfo param)
|
||||
{
|
||||
string[] names = param.Content.DeJson<string[]>();
|
||||
for (int i = 0; i < names.Length; i++)
|
||||
{
|
||||
bool res = signCaching.Get(names[i], out SignCacheInfo cache)
|
||||
&& cache.Connected
|
||||
&& cache.GetScreen()
|
||||
&& Interlocked.CompareExchange(ref cache.ScreenFlag, 0, 1) == 1;
|
||||
if (res)
|
||||
{
|
||||
cache.UpdateScreen();
|
||||
_ = messengerSender.SendOnly(new MessageRequestWrap
|
||||
{
|
||||
Connection = cache.Connection,
|
||||
MessengerId = (ushort)ScreenMessengerIds.Update,
|
||||
Timeout = 1000,
|
||||
}).ContinueWith((result) =>
|
||||
{
|
||||
Interlocked.Exchange(ref cache.ScreenFlag, 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
31
cmonitor/server/api/services/SignInClientService.cs
Normal file
31
cmonitor/server/api/services/SignInClientService.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
|
||||
namespace cmonitor.server.api.services
|
||||
{
|
||||
public sealed class SignInClientService : IClientService
|
||||
{
|
||||
private readonly SignCaching signCaching;
|
||||
private readonly Config config;
|
||||
public SignInClientService(SignCaching signCaching, Config config)
|
||||
{
|
||||
this.signCaching = signCaching;
|
||||
this.config = config;
|
||||
}
|
||||
public List<SignCacheInfo> List(ClientServiceParamsInfo param)
|
||||
{
|
||||
List<SignCacheInfo> caches = signCaching.Get();
|
||||
return caches;
|
||||
}
|
||||
public bool Del(ClientServiceParamsInfo param)
|
||||
{
|
||||
signCaching.Del(param.Content);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
public Config Config(ClientServiceParamsInfo param)
|
||||
{
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
||||
42
cmonitor/server/api/services/UsbClientService.cs
Normal file
42
cmonitor/server/api/services/UsbClientService.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using cmonitor.server.service;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using cmonitor.server.service.messengers.usb;
|
||||
using common.libs.extends;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.api.services
|
||||
{
|
||||
public sealed class UsbClientService : IClientService
|
||||
{
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly SignCaching signCaching;
|
||||
public UsbClientService(MessengerSender messengerSender, SignCaching signCaching)
|
||||
{
|
||||
this.messengerSender = messengerSender;
|
||||
this.signCaching = signCaching;
|
||||
}
|
||||
public async Task<bool> Update(ClientServiceParamsInfo param)
|
||||
{
|
||||
UsbLockInfo info = param.Content.DeJson<UsbLockInfo>();
|
||||
for (int i = 0; i < info.Names.Length; i++)
|
||||
{
|
||||
if (signCaching.Get(info.Names[i], out SignCacheInfo cache) && cache.Connected)
|
||||
{
|
||||
await messengerSender.SendOnly(new MessageRequestWrap
|
||||
{
|
||||
Connection = cache.Connection,
|
||||
MessengerId = (ushort)UsbMessengerIds.Update,
|
||||
Payload = MemoryPackSerializer.Serialize(info.Value)
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class UsbLockInfo
|
||||
{
|
||||
public string[] Names { get; set; }
|
||||
public bool Value { get; set; }
|
||||
}
|
||||
}
|
||||
69
cmonitor/server/api/services/VolumeClientService.cs
Normal file
69
cmonitor/server/api/services/VolumeClientService.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using cmonitor.server.service;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using cmonitor.server.service.messengers.volume;
|
||||
using common.libs.extends;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.api.services
|
||||
{
|
||||
public sealed class VolumeClientService : IClientService
|
||||
{
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly SignCaching signCaching;
|
||||
public VolumeClientService(MessengerSender messengerSender, SignCaching signCaching)
|
||||
{
|
||||
this.messengerSender = messengerSender;
|
||||
this.signCaching = signCaching;
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> Update(ClientServiceParamsInfo param)
|
||||
{
|
||||
VolumeInfo info = param.Content.DeJson<VolumeInfo>();
|
||||
for (int i = 0; i < info.Names.Length; i++)
|
||||
{
|
||||
if (signCaching.Get(info.Names[i], out SignCacheInfo cache) && cache.Connected)
|
||||
{
|
||||
await messengerSender.SendOnly(new MessageRequestWrap
|
||||
{
|
||||
Connection = cache.Connection,
|
||||
MessengerId = (ushort)VolumeMessengerIds.Update,
|
||||
Payload = MemoryPackSerializer.Serialize(info.Value)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> Mute(ClientServiceParamsInfo param)
|
||||
{
|
||||
VolumeMuteInfo info = param.Content.DeJson<VolumeMuteInfo>();
|
||||
for (int i = 0; i < info.Names.Length; i++)
|
||||
{
|
||||
if (signCaching.Get(info.Names[i], out SignCacheInfo cache) && cache.Connected)
|
||||
{
|
||||
await messengerSender.SendOnly(new MessageRequestWrap
|
||||
{
|
||||
Connection = cache.Connection,
|
||||
MessengerId = (ushort)VolumeMessengerIds.Mute,
|
||||
Payload = MemoryPackSerializer.Serialize(info.Value)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class VolumeInfo
|
||||
{
|
||||
public string[] Names { get; set; }
|
||||
public float Value { get; set; }
|
||||
}
|
||||
public sealed class VolumeMuteInfo
|
||||
{
|
||||
public string[] Names { get; set; }
|
||||
public bool Value { get; set; }
|
||||
}
|
||||
}
|
||||
47
cmonitor/server/api/services/WallpaperClientService.cs
Normal file
47
cmonitor/server/api/services/WallpaperClientService.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using cmonitor.server.service;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using cmonitor.server.service.messengers.wallpaper;
|
||||
using common.libs.extends;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.api.services
|
||||
{
|
||||
public sealed class WallpaperClientService : IClientService
|
||||
{
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly SignCaching signCaching;
|
||||
public WallpaperClientService(MessengerSender messengerSender, SignCaching signCaching)
|
||||
{
|
||||
this.messengerSender = messengerSender;
|
||||
this.signCaching = signCaching;
|
||||
}
|
||||
public async Task<bool> Update(ClientServiceParamsInfo param)
|
||||
{
|
||||
WallpaperLockInfo info = param.Content.DeJson<WallpaperLockInfo>();
|
||||
for (int i = 0; i < info.Names.Length; i++)
|
||||
{
|
||||
if (signCaching.Get(info.Names[i], out SignCacheInfo cache) && cache.Connected)
|
||||
{
|
||||
await messengerSender.SendOnly(new MessageRequestWrap
|
||||
{
|
||||
Connection = cache.Connection,
|
||||
MessengerId = (ushort)WallpaperMessengerIds.Update,
|
||||
Payload = MemoryPackSerializer.Serialize(new WallpaperUpdateInfo
|
||||
{
|
||||
Value = info.Value,
|
||||
Url = info.Url
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WallpaperLockInfo
|
||||
{
|
||||
public string[] Names { get; set; }
|
||||
public bool Value { get; set; }
|
||||
public string Url { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
487
cmonitor/server/api/websocket/WebSocketClient.cs
Normal file
487
cmonitor/server/api/websocket/WebSocketClient.cs
Normal file
@@ -0,0 +1,487 @@
|
||||
using common.libs;
|
||||
using common.libs.extends;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace cmonitor.server.api.websocket
|
||||
{
|
||||
/// <summary>
|
||||
/// wensocket客户端
|
||||
/// </summary>
|
||||
public sealed class WebSocketClient : IDisposable
|
||||
{
|
||||
private int bufferSize = 4 * 1024;
|
||||
private SocketAsyncEventArgs readEventArgs;
|
||||
private bool connected = false;
|
||||
private bool connecSuccess = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 连接中,false表示失败,会关闭连接
|
||||
/// </summary>
|
||||
public Func<WebsocketHeaderInfo, bool> OnConnecting = (header) => { return true; };
|
||||
/// <summary>
|
||||
/// 连接失败
|
||||
/// </summary>
|
||||
public Action<string> OnConnectFail { get; set; } = (msg) => { };
|
||||
/// <summary>
|
||||
/// 已断开连接,未收到关闭帧
|
||||
/// </summary>
|
||||
public Action OnDisConnectd = () => { };
|
||||
|
||||
/// <summary>
|
||||
/// 已连接
|
||||
/// </summary>
|
||||
public Action<WebsocketHeaderInfo> OnOpen = (header) => { };
|
||||
/// <summary>
|
||||
/// 已断开连接,收到关闭帧
|
||||
/// </summary>
|
||||
public Action OnClose = () => { };
|
||||
|
||||
/// <summary>
|
||||
/// 文本数据
|
||||
/// </summary>
|
||||
public Action<WebSocketFrameInfo, string> OnMessage = (frame, messgae) => { };
|
||||
/// <summary>
|
||||
/// 二进制数据
|
||||
/// </summary>
|
||||
public Action<WebSocketFrameInfo, Memory<byte>> OnBinary = (frame, data) => { };
|
||||
|
||||
/// <summary>
|
||||
/// 控制帧
|
||||
/// </summary>
|
||||
public Action<WebSocketFrameInfo> OnControll = (frame) => { };
|
||||
/// <summary>
|
||||
/// 非控制帧
|
||||
/// </summary>
|
||||
public Action<WebSocketFrameInfo> OnUnControll = (frame) => { };
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public WebSocketClient()
|
||||
{
|
||||
handles = new Dictionary<WebSocketFrameInfo.EnumOpcode, Action<AsyncServerUserToken>> {
|
||||
//直接添加数据
|
||||
{ WebSocketFrameInfo.EnumOpcode.Data,HandleAppendData},
|
||||
//记录opcode并添加
|
||||
{ WebSocketFrameInfo.EnumOpcode.Text,HandleData},
|
||||
{ WebSocketFrameInfo.EnumOpcode.Binary,HandleData},
|
||||
|
||||
{ WebSocketFrameInfo.EnumOpcode.Close,HandleClose},
|
||||
|
||||
{ WebSocketFrameInfo.EnumOpcode.Ping,HandlePing},
|
||||
{ WebSocketFrameInfo.EnumOpcode.Pong,HandlePong},
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <param name="port"></param>
|
||||
public void Connect(IPAddress ip, int port)
|
||||
{
|
||||
Connect(new IPEndPoint(ip, port));
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="ep"></param>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public void Connect(IPEndPoint ep)
|
||||
{
|
||||
if (connected)
|
||||
{
|
||||
throw new Exception("connected");
|
||||
}
|
||||
|
||||
var socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
socket.KeepAlive();
|
||||
|
||||
AsyncServerUserToken token = new AsyncServerUserToken
|
||||
{
|
||||
TargetSocket = socket,
|
||||
};
|
||||
SocketAsyncEventArgs connectEventArgs = new SocketAsyncEventArgs
|
||||
{
|
||||
UserToken = token,
|
||||
SocketFlags = SocketFlags.None,
|
||||
};
|
||||
connectEventArgs.RemoteEndPoint = ep;
|
||||
connectEventArgs.Completed += Target_IO_Completed;
|
||||
if (!socket.ConnectAsync(connectEventArgs))
|
||||
{
|
||||
TargetProcessConnect(connectEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
private void Target_IO_Completed(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
switch (e.LastOperation)
|
||||
{
|
||||
case SocketAsyncOperation.Connect:
|
||||
TargetProcessConnect(e);
|
||||
break;
|
||||
case SocketAsyncOperation.Receive:
|
||||
TargetProcessReceive();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void TargetProcessConnect(SocketAsyncEventArgs connectEventArgs)
|
||||
{
|
||||
AsyncServerUserToken token = (AsyncServerUserToken)connectEventArgs.UserToken;
|
||||
try
|
||||
{
|
||||
if (connectEventArgs.SocketError == SocketError.Success)
|
||||
{
|
||||
|
||||
token.SecWebSocketKey = WebSocketParser.BuildSecWebSocketKey();
|
||||
byte[] connectData = WebSocketParser.BuildConnectData(new WebsocketHeaderInfo
|
||||
{
|
||||
SecWebSocketKey = token.SecWebSocketKey,
|
||||
});
|
||||
|
||||
token.TargetSocket.Send(connectData, SocketFlags.None);
|
||||
|
||||
connected = true;
|
||||
BindTargetReceive(token);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnConnectFail(connectEventArgs.SocketError.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
Logger.Instance.Error(ex);
|
||||
OnConnectFail(ex.Message);
|
||||
}
|
||||
}
|
||||
private void BindTargetReceive(AsyncServerUserToken connectToken)
|
||||
{
|
||||
AsyncServerUserToken token = new AsyncServerUserToken
|
||||
{
|
||||
TargetSocket = connectToken.TargetSocket,
|
||||
SecWebSocketKey = connectToken.SecWebSocketKey,
|
||||
};
|
||||
try
|
||||
{
|
||||
readEventArgs = new SocketAsyncEventArgs
|
||||
{
|
||||
UserToken = token,
|
||||
SocketFlags = SocketFlags.None,
|
||||
};
|
||||
token.PoolBuffer = new byte[bufferSize];
|
||||
readEventArgs.SetBuffer(token.PoolBuffer, 0, bufferSize);
|
||||
readEventArgs.Completed += Target_IO_Completed;
|
||||
if (token.TargetSocket.ReceiveAsync(readEventArgs) == false)
|
||||
{
|
||||
TargetProcessReceive();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
}
|
||||
private void TargetProcessReceive()
|
||||
{
|
||||
try
|
||||
{
|
||||
AsyncServerUserToken token = (AsyncServerUserToken)readEventArgs.UserToken;
|
||||
if (readEventArgs.BytesTransferred > 0 && readEventArgs.SocketError == SocketError.Success)
|
||||
{
|
||||
int offset = readEventArgs.Offset;
|
||||
int length = readEventArgs.BytesTransferred;
|
||||
|
||||
ReadFrame(token, readEventArgs.Buffer.AsMemory(offset, length));
|
||||
if (token.TargetSocket.Available > 0)
|
||||
{
|
||||
while (token.TargetSocket.Available > 0)
|
||||
{
|
||||
length = token.TargetSocket.Receive(readEventArgs.Buffer);
|
||||
if (length > 0)
|
||||
{
|
||||
ReadFrame(token, readEventArgs.Buffer.AsMemory(0, length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (token.TargetSocket.Connected == false)
|
||||
{
|
||||
CloseClientSocket();
|
||||
return;
|
||||
}
|
||||
if (token.TargetSocket.ReceiveAsync(readEventArgs) == false)
|
||||
{
|
||||
TargetProcessReceive();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseClientSocket();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CloseClientSocket();
|
||||
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseClientSocket(SocketAsyncEventArgs e)
|
||||
{
|
||||
if (connected)
|
||||
{
|
||||
AsyncServerUserToken token = readEventArgs.UserToken as AsyncServerUserToken;
|
||||
token.Clear();
|
||||
readEventArgs.Dispose();
|
||||
OnDisConnectd();
|
||||
}
|
||||
connected = false;
|
||||
connecSuccess = false;
|
||||
}
|
||||
private void CloseClientSocket()
|
||||
{
|
||||
CloseClientSocket(readEventArgs);
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
CloseClientSocket();
|
||||
}
|
||||
|
||||
|
||||
private readonly Dictionary<WebSocketFrameInfo.EnumOpcode, Action<AsyncServerUserToken>> handles;
|
||||
private void ReadFrame(AsyncServerUserToken token, Memory<byte> data)
|
||||
{
|
||||
if (connecSuccess)
|
||||
{
|
||||
if (token.FrameBuffer.Size == 0 && data.Length > 6)
|
||||
{
|
||||
if (WebSocketFrameInfo.TryParse(data, out token.FrameInfo))
|
||||
{
|
||||
ExecuteHandle(token);
|
||||
if (token.FrameInfo.TotalLength == data.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
token.FrameBuffer.AddRange(data.Slice(token.FrameInfo.TotalLength));
|
||||
}
|
||||
else
|
||||
{
|
||||
token.FrameBuffer.AddRange(data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
token.FrameBuffer.AddRange(data);
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (!WebSocketFrameInfo.TryParse(token.FrameBuffer.Data.Slice(token.FrameIndex), out token.FrameInfo))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (token.FrameInfo.Fin == WebSocketFrameInfo.EnumFin.Fin)
|
||||
{
|
||||
ExecuteHandle(token);
|
||||
token.FrameBuffer.RemoveRange(0, token.FrameInfo.TotalLength);
|
||||
token.FrameIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
token.FrameBuffer.RemoveRange(token.FrameIndex, token.FrameInfo.TotalLength - token.FrameInfo.PayloadData.Length);
|
||||
token.FrameIndex += token.FrameInfo.PayloadData.Length;
|
||||
}
|
||||
} while (token.FrameBuffer.Size > 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleConnect(token, data);
|
||||
}
|
||||
}
|
||||
private void ExecuteHandle(AsyncServerUserToken token)
|
||||
{
|
||||
if (handles.TryGetValue(token.FrameInfo.Opcode, out Action<AsyncServerUserToken> action))
|
||||
{
|
||||
action(token);
|
||||
}
|
||||
else if (token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.UnControll3 && token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.UnControll7)
|
||||
{
|
||||
OnUnControll(token.FrameInfo);
|
||||
}
|
||||
else if (token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.Controll11 && token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.Controll15)
|
||||
{
|
||||
OnControll(token.FrameInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
SendFrameClose(WebSocketFrameInfo.EnumCloseStatus.Unexpected);
|
||||
CloseClientSocket();
|
||||
return;
|
||||
}
|
||||
}
|
||||
private void HandleData(AsyncServerUserToken token)
|
||||
{
|
||||
token.Opcode = token.FrameInfo.Opcode;
|
||||
HandleAppendData(token);
|
||||
}
|
||||
private void HandleAppendData(AsyncServerUserToken token)
|
||||
{
|
||||
if (token.FrameInfo.Fin == WebSocketFrameInfo.EnumFin.Fin)
|
||||
{
|
||||
if (token.Opcode == WebSocketFrameInfo.EnumOpcode.Text)
|
||||
{
|
||||
string str = token.FrameInfo.PayloadData.GetString();
|
||||
OnMessage(token.FrameInfo, str);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnBinary(token.FrameInfo, token.FrameInfo.PayloadData);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void HandleClose(AsyncServerUserToken token)
|
||||
{
|
||||
SendFrameClose(WebSocketFrameInfo.EnumCloseStatus.Normal);
|
||||
CloseClientSocket();
|
||||
OnClose();
|
||||
}
|
||||
private void HandlePing(AsyncServerUserToken token)
|
||||
{
|
||||
SendFramePong();
|
||||
}
|
||||
private void HandlePong(AsyncServerUserToken token) { }
|
||||
private void HandleConnect(AsyncServerUserToken token, Memory<byte> data)
|
||||
{
|
||||
WebsocketHeaderInfo header = WebsocketHeaderInfo.Parse(data);
|
||||
if (!WebSocketParser.VerifySecWebSocketAccept(token.SecWebSocketKey, header.SecWebSocketAccept))
|
||||
{
|
||||
OnConnectFail("Sec-WebSocket-Accept Invalid");
|
||||
CloseClientSocket();
|
||||
return;
|
||||
}
|
||||
if (header.StatusCode != HttpStatusCode.SwitchingProtocols)
|
||||
{
|
||||
OnConnectFail($"{(int)header.StatusCode},{header.StatusCode}");
|
||||
CloseClientSocket();
|
||||
return;
|
||||
}
|
||||
if (!OnConnecting(header))
|
||||
{
|
||||
CloseClientSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
connecSuccess = true;
|
||||
OnOpen(header);
|
||||
}
|
||||
public int SendRaw(byte[] buffer)
|
||||
{
|
||||
var socket = (readEventArgs.UserToken as AsyncServerUserToken).TargetSocket;
|
||||
|
||||
return socket.Send(buffer, SocketFlags.None);
|
||||
}
|
||||
public int SendRaw(Memory<byte> buffer)
|
||||
{
|
||||
var socket = (readEventArgs.UserToken as AsyncServerUserToken).TargetSocket;
|
||||
|
||||
return socket.Send(buffer.Span, SocketFlags.None);
|
||||
}
|
||||
public int SendFrame(WebSocketFrameRemarkInfo remark)
|
||||
{
|
||||
remark.Mask = WebSocketFrameInfo.EnumMask.Mask;
|
||||
remark.MaskData = WebSocketParser.BuildMaskKey();
|
||||
var frame = WebSocketParser.BuildFrameData(remark, out int length);
|
||||
int res = SendRaw(frame.AsMemory(0, length));
|
||||
WebSocketParser.Return(frame);
|
||||
return res;
|
||||
}
|
||||
public int SendFrameText(string txt)
|
||||
{
|
||||
return SendFrameText(txt.ToBytes());
|
||||
}
|
||||
public int SendFrameText(byte[] buffer)
|
||||
{
|
||||
return SendFrame(new WebSocketFrameRemarkInfo
|
||||
{
|
||||
Opcode = WebSocketFrameInfo.EnumOpcode.Text,
|
||||
Data = buffer
|
||||
});
|
||||
}
|
||||
public int SendFrameBinary(byte[] buffer)
|
||||
{
|
||||
return SendFrame(new WebSocketFrameRemarkInfo
|
||||
{
|
||||
Opcode = WebSocketFrameInfo.EnumOpcode.Binary,
|
||||
Data = buffer
|
||||
});
|
||||
}
|
||||
public int SendFramePoing()
|
||||
{
|
||||
return SendRaw(WebSocketParser.BuildPingData());
|
||||
}
|
||||
public int SendFramePong()
|
||||
{
|
||||
return SendRaw(WebSocketParser.BuildPongData());
|
||||
}
|
||||
public int SendFrameClose(WebSocketFrameInfo.EnumCloseStatus status)
|
||||
{
|
||||
return SendFrame(new WebSocketFrameRemarkInfo
|
||||
{
|
||||
Opcode = WebSocketFrameInfo.EnumOpcode.Close,
|
||||
Data = ((ushort)status).ToBytes()
|
||||
});
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
CloseClientSocket();
|
||||
GC.Collect();
|
||||
//GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
public sealed class AsyncServerUserToken
|
||||
{
|
||||
public Socket TargetSocket { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前帧数据
|
||||
/// </summary>
|
||||
public WebSocketFrameInfo FrameInfo = null;
|
||||
/// <summary>
|
||||
/// 当前帧的数据下标
|
||||
/// </summary>
|
||||
public int FrameIndex { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// 数据帧缓存
|
||||
/// </summary>
|
||||
public ReceiveDataBuffer FrameBuffer { get; } = new ReceiveDataBuffer();
|
||||
/// <summary>
|
||||
/// 当前帧的数据类型
|
||||
/// </summary>
|
||||
public WebSocketFrameInfo.EnumOpcode Opcode { get; set; }
|
||||
public Memory<byte> SecWebSocketKey { get; set; }
|
||||
public byte[] PoolBuffer { get; set; }
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
TargetSocket?.SafeClose();
|
||||
TargetSocket = null;
|
||||
|
||||
PoolBuffer = Helper.EmptyArray;
|
||||
|
||||
//GC.Collect();
|
||||
//GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
649
cmonitor/server/api/websocket/WebSocketParser.cs
Normal file
649
cmonitor/server/api/websocket/WebSocketParser.cs
Normal file
@@ -0,0 +1,649 @@
|
||||
using common.libs;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace cmonitor.server.api.websocket
|
||||
{
|
||||
/// <summary>
|
||||
/// websocket解析器
|
||||
/// </summary>
|
||||
public static class WebSocketParser
|
||||
{
|
||||
private readonly static SHA1 sha1 = SHA1.Create();
|
||||
private readonly static Memory<byte> magicCode = Encoding.ASCII.GetBytes("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
||||
/// <summary>
|
||||
/// 构建连接数据
|
||||
/// </summary>
|
||||
/// <param name="header"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] BuildConnectData(WebsocketHeaderInfo header)
|
||||
{
|
||||
string path = header.Path.Length == 0 ? "/" : Encoding.UTF8.GetString(header.Path.Span);
|
||||
|
||||
StringBuilder sb = new StringBuilder(10);
|
||||
sb.Append($"GET {path} HTTP/1.1\r\n");
|
||||
sb.Append($"Upgrade: websocket\r\n");
|
||||
sb.Append($"Connection: Upgrade\r\n");
|
||||
sb.Append($"Sec-WebSocket-Version: 13\r\n");
|
||||
sb.Append($"Sec-WebSocket-Key: {Encoding.UTF8.GetString(header.SecWebSocketKey.Span)}\r\n");
|
||||
if (header.SecWebSocketProtocol.Length > 0)
|
||||
{
|
||||
sb.Append($"Sec-WebSocket-Protocol: {Encoding.UTF8.GetString(header.SecWebSocketProtocol.Span)}\r\n");
|
||||
}
|
||||
if (header.SecWebSocketExtensions.Length > 0)
|
||||
{
|
||||
sb.Append($"Sec-WebSocket-Extensions: {Encoding.UTF8.GetString(header.SecWebSocketExtensions.Span)}\r\n");
|
||||
}
|
||||
sb.Append("\r\n");
|
||||
|
||||
return Encoding.UTF8.GetBytes(sb.ToString());
|
||||
}
|
||||
/// <summary>
|
||||
/// 构建连接回应数据
|
||||
/// </summary>
|
||||
/// <param name="header"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] BuildConnectResponseData(WebsocketHeaderInfo header)
|
||||
{
|
||||
string acceptStr = BuildSecWebSocketAccept(header.SecWebSocketKey);
|
||||
|
||||
StringBuilder sb = new StringBuilder(10);
|
||||
sb.Append($"HTTP/1.1 {(int)header.StatusCode} {AddSpace(header.StatusCode)}\r\n");
|
||||
sb.Append($"Sec-WebSocket-Accept: {acceptStr}\r\n");
|
||||
if (header.Connection.Length > 0)
|
||||
{
|
||||
sb.Append($"Connection: {Encoding.UTF8.GetString(header.Connection.Span)}\r\n");
|
||||
}
|
||||
if (header.Upgrade.Length > 0)
|
||||
{
|
||||
sb.Append($"Upgrade: {Encoding.UTF8.GetString(header.Upgrade.Span)}\r\n");
|
||||
}
|
||||
if (header.SecWebSocketVersion.Length > 0)
|
||||
{
|
||||
sb.Append($"Sec-WebSocket-Version: {Encoding.UTF8.GetString(header.SecWebSocketVersion.Span)}\r\n");
|
||||
}
|
||||
if (header.SecWebSocketProtocol.Length > 0)
|
||||
{
|
||||
sb.Append($"Sec-WebSocket-Protocol: {Encoding.UTF8.GetString(header.SecWebSocketProtocol.Span)}\r\n");
|
||||
}
|
||||
if (header.SecWebSocketExtensions.Length > 0)
|
||||
{
|
||||
sb.Append($"Sec-WebSocket-Extensions: {Encoding.UTF8.GetString(header.SecWebSocketExtensions.Span)}\r\n");
|
||||
}
|
||||
sb.Append("\r\n");
|
||||
|
||||
return Encoding.UTF8.GetBytes(sb.ToString());
|
||||
}
|
||||
/// <summary>
|
||||
/// 生成随机key
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static byte[] BuildSecWebSocketKey()
|
||||
{
|
||||
byte[] bytes = new byte[16];
|
||||
Random random = new Random(DateTime.Now.Ticks.GetHashCode());
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes[i] = (byte)random.Next(0, 255);
|
||||
}
|
||||
byte[] res = Encoding.UTF8.GetBytes(Convert.ToBase64String(bytes));
|
||||
return res;
|
||||
}
|
||||
/// <summary>
|
||||
/// 构建mask数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static byte[] BuildMaskKey()
|
||||
{
|
||||
byte[] bytes = new byte[4];
|
||||
|
||||
Random random = new Random(DateTime.Now.Ticks.GetHashCode());
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes[i] = (byte)random.Next(0, 255);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
/// <summary>
|
||||
/// 生成accept回应
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
private static string BuildSecWebSocketAccept(Memory<byte> key)
|
||||
{
|
||||
int keyLength = key.Length + magicCode.Length;
|
||||
byte[] acceptBytes = new byte[keyLength];
|
||||
|
||||
key.CopyTo(acceptBytes);
|
||||
magicCode.CopyTo(acceptBytes.AsMemory(key.Length));
|
||||
|
||||
string acceptStr = Convert.ToBase64String(sha1.ComputeHash(acceptBytes, 0, keyLength));
|
||||
|
||||
return acceptStr;
|
||||
}
|
||||
/// <summary>
|
||||
/// 验证回应的accept
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="accept"></param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifySecWebSocketAccept(Memory<byte> key, Memory<byte> accept)
|
||||
{
|
||||
string acceptStr = BuildSecWebSocketAccept(key);
|
||||
return acceptStr == Encoding.UTF8.GetString(accept.Span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建ping帧
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static byte[] BuildPingData()
|
||||
{
|
||||
return new byte[]
|
||||
{
|
||||
(byte)WebSocketFrameInfo.EnumFin.Fin | (byte)WebSocketFrameInfo.EnumOpcode.Ping, //fin + ping
|
||||
(byte)WebSocketFrameInfo.EnumMask.None | 0, //没有 mask 和 payload length
|
||||
};
|
||||
}
|
||||
/// <summary>
|
||||
/// 构建pong帧
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static byte[] BuildPongData()
|
||||
{
|
||||
return new byte[]
|
||||
{
|
||||
(byte)WebSocketFrameInfo.EnumFin.Fin | (byte)WebSocketFrameInfo.EnumOpcode.Pong, //fin + pong
|
||||
(byte)WebSocketFrameInfo.EnumMask.None | 0, //没有 mask 和 payload length
|
||||
};
|
||||
}
|
||||
/// <summary>
|
||||
/// 构建数据帧
|
||||
/// </summary>
|
||||
/// <param name="remark"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static byte[] BuildFrameData(WebSocketFrameRemarkInfo remark, out int length)
|
||||
{
|
||||
if (remark.Mask > 0 && remark.MaskData.Length != 4)
|
||||
{
|
||||
throw new Exception("mask data just 4byte");
|
||||
}
|
||||
|
||||
length = 1 + 1 + remark.Data.Length;
|
||||
int index = 2;
|
||||
if (remark.Mask == WebSocketFrameInfo.EnumMask.Mask)
|
||||
{
|
||||
length += 4;
|
||||
}
|
||||
|
||||
byte payloadLength;
|
||||
int dataLength = remark.Data.Length;
|
||||
if (dataLength > ushort.MaxValue)
|
||||
{
|
||||
length += 8;
|
||||
payloadLength = 127;
|
||||
}
|
||||
else if (dataLength > 125)
|
||||
{
|
||||
length += 2;
|
||||
payloadLength = 126;
|
||||
}
|
||||
else
|
||||
{
|
||||
payloadLength = (byte)dataLength;
|
||||
}
|
||||
|
||||
|
||||
byte[] bytes = ArrayPool<byte>.Shared.Rent(length);
|
||||
var memory = bytes.AsMemory();
|
||||
bytes[0] = (byte)((byte)remark.Fin | (byte)remark.Rsv1 | (byte)remark.Rsv2 | (byte)remark.Rsv3 | (byte)remark.Opcode);
|
||||
bytes[1] = (byte)((byte)remark.Mask | payloadLength);
|
||||
|
||||
|
||||
if (dataLength > ushort.MaxValue)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt64BigEndian(memory.Slice(index).Span, (ulong)dataLength);
|
||||
index += 8;
|
||||
}
|
||||
else if (dataLength > 125)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt16BigEndian(memory.Slice(index).Span, (ushort)dataLength);
|
||||
index += 2;
|
||||
}
|
||||
|
||||
if (remark.Mask == WebSocketFrameInfo.EnumMask.Mask)
|
||||
{
|
||||
remark.MaskData.CopyTo(bytes.AsMemory(index, remark.MaskData.Length));
|
||||
index += remark.MaskData.Length;
|
||||
}
|
||||
|
||||
if (remark.Data.Length > 0)
|
||||
{
|
||||
remark.Data.CopyTo(bytes.AsMemory(index));
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
public static void Return(byte[] bytes)
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 给每个大写字母前加一个空格,例如ProxyAuthenticationRequired 变成Proxy Authentication Required
|
||||
/// </summary>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <returns></returns>
|
||||
private static string AddSpace(HttpStatusCode statusCode)
|
||||
{
|
||||
ReadOnlySpan<char> span = statusCode.ToString().AsSpan();
|
||||
|
||||
int totalLength = span.Length * 2;
|
||||
|
||||
char[] result = new char[totalLength];
|
||||
Span<char> resultSpan = result.AsSpan(0, totalLength);
|
||||
span.CopyTo(resultSpan);
|
||||
|
||||
int length = 0;
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
if (i > 0 && span[i] >= 65 && span[i] <= 90)
|
||||
{
|
||||
resultSpan.Slice(i + length, totalLength - (length + i) - 1).CopyTo(resultSpan.Slice(i + length + 1));
|
||||
resultSpan[i + length] = (char)32;
|
||||
length++;
|
||||
}
|
||||
}
|
||||
|
||||
string resultStr = resultSpan.Slice(0, span.Length + length).ToString();
|
||||
|
||||
return resultStr;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WebSocketFrameRemarkInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否是结束帧,如果只有一帧,那必定是结束帧
|
||||
/// </summary>
|
||||
public WebSocketFrameInfo.EnumFin Fin { get; set; } = WebSocketFrameInfo.EnumFin.Fin;
|
||||
/// <summary>
|
||||
/// 保留位1
|
||||
/// </summary>
|
||||
public WebSocketFrameInfo.EnumRsv1 Rsv1 { get; set; } = WebSocketFrameInfo.EnumRsv1.None;
|
||||
/// <summary>
|
||||
/// 保留位2
|
||||
/// </summary>
|
||||
public WebSocketFrameInfo.EnumRsv2 Rsv2 { get; set; } = WebSocketFrameInfo.EnumRsv2.None;
|
||||
/// <summary>
|
||||
/// 保留位3
|
||||
/// </summary>
|
||||
public WebSocketFrameInfo.EnumRsv3 Rsv3 { get; set; } = WebSocketFrameInfo.EnumRsv3.None;
|
||||
/// <summary>
|
||||
/// 数据描述,默认TEXT数据
|
||||
/// </summary>
|
||||
public WebSocketFrameInfo.EnumOpcode Opcode { get; set; } = WebSocketFrameInfo.EnumOpcode.Text;
|
||||
|
||||
/// <summary>
|
||||
/// 是否有掩码
|
||||
/// </summary>
|
||||
public WebSocketFrameInfo.EnumMask Mask { get; set; } = WebSocketFrameInfo.EnumMask.None;
|
||||
/// <summary>
|
||||
/// 掩码key 4字节
|
||||
/// </summary>
|
||||
public Memory<byte> MaskData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// payload data
|
||||
/// </summary>
|
||||
public Memory<byte> Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据帧解析
|
||||
/// </summary>
|
||||
public sealed class WebSocketFrameInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否是结束帧
|
||||
/// </summary>
|
||||
public EnumFin Fin { get; set; }
|
||||
/// <summary>
|
||||
/// 保留位
|
||||
/// </summary>
|
||||
public EnumRsv1 Rsv1 { get; set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public EnumRsv2 Rsv2 { get; set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public EnumRsv3 Rsv3 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作码 0附加数据,1文本,2二进制,3-7为非控制保留,8关闭,9ping,a pong,b-f 为控制保留
|
||||
/// </summary>
|
||||
public EnumOpcode Opcode { get; set; }
|
||||
/// <summary>
|
||||
/// 掩码
|
||||
/// </summary>
|
||||
public EnumMask Mask { get; set; }
|
||||
/// <summary>
|
||||
/// 总长度
|
||||
/// </summary>
|
||||
public int TotalLength { get; set; }
|
||||
/// <summary>
|
||||
/// 数据 如果OPCODE是 EnumOpcode.Close 则数据的前2字节为关闭状态码,余下的为其它描述数据
|
||||
/// </summary>
|
||||
public Memory<byte> PayloadData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 解析帧,如果false解析失败,则应该把data缓存起来,等待下次来数据后,拼接起来再次解析
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="frameInfo"></param>
|
||||
/// <returns></returns>
|
||||
public static bool TryParse(Memory<byte> data, out WebSocketFrameInfo frameInfo)
|
||||
{
|
||||
frameInfo = null;
|
||||
|
||||
//小于2字节不可解析
|
||||
if (data.Length < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Span<byte> span = data.Span;
|
||||
int index = 2;
|
||||
|
||||
//第2字节
|
||||
//1位 是否mask
|
||||
EnumMask mask = (EnumMask)(span[1] & (byte)EnumMask.Mask);
|
||||
int payloadLength = (span[1] & 0b01111111);
|
||||
if (payloadLength == 126)
|
||||
{
|
||||
payloadLength = BinaryPrimitives.ReadUInt16BigEndian(span.Slice(2, 2));
|
||||
index += 2;
|
||||
}
|
||||
else if (payloadLength == 127)
|
||||
{
|
||||
payloadLength = (int)BinaryPrimitives.ReadUInt64BigEndian(span.Slice(2, 8));
|
||||
index += 8;
|
||||
}
|
||||
//数据长+头长 大于 整个数据长,则不是一个完整的包
|
||||
if (data.Length < payloadLength + index + (mask == EnumMask.Mask ? 4 : 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//第1字节
|
||||
//1位 是否是结束帧
|
||||
EnumFin fin = (EnumFin)(byte)(span[0] & (byte)EnumFin.Fin);
|
||||
//2 3 4 保留
|
||||
EnumRsv1 rsv1 = (EnumRsv1)(byte)(span[0] & (byte)EnumRsv1.Rsv);
|
||||
EnumRsv2 rsv2 = (EnumRsv2)(byte)(span[0] & (byte)EnumRsv2.Rsv);
|
||||
EnumRsv3 rsv3 = (EnumRsv3)(byte)(span[0] & (byte)EnumRsv3.Rsv);
|
||||
//5 6 7 8 操作码
|
||||
EnumOpcode opcode = (EnumOpcode)(byte)(span[0] & (byte)EnumOpcode.Last);
|
||||
|
||||
|
||||
Span<byte> maskKey = Helper.EmptyArray;
|
||||
if (mask == EnumMask.Mask)
|
||||
{
|
||||
//mask掩码key 用来解码数据
|
||||
maskKey = span.Slice(index, 4);
|
||||
index += 4;
|
||||
}
|
||||
|
||||
//数据
|
||||
Memory<byte> payloadData = data.Slice(index, payloadLength);
|
||||
if (mask == EnumMask.Mask)
|
||||
{
|
||||
//解码
|
||||
Span<byte> payloadDataSpan = payloadData.Span;
|
||||
for (int i = 0; i < payloadDataSpan.Length; i++)
|
||||
{
|
||||
payloadDataSpan[i] = (byte)(payloadDataSpan[i] ^ maskKey[3 & i]);
|
||||
}
|
||||
}
|
||||
|
||||
frameInfo = new WebSocketFrameInfo
|
||||
{
|
||||
Fin = fin,
|
||||
Rsv1 = rsv1,
|
||||
Rsv2 = rsv2,
|
||||
Rsv3 = rsv3,
|
||||
Opcode = opcode,
|
||||
Mask = mask,
|
||||
PayloadData = payloadData,
|
||||
TotalLength = index + payloadLength
|
||||
};
|
||||
return true;
|
||||
}
|
||||
public enum EnumFin : byte
|
||||
{
|
||||
None = 0x0,
|
||||
Fin = 0b10000000,
|
||||
}
|
||||
public enum EnumMask : byte
|
||||
{
|
||||
None = 0x0,
|
||||
Mask = 0b10000000,
|
||||
}
|
||||
public enum EnumRsv1 : byte
|
||||
{
|
||||
None = 0x0,
|
||||
Rsv = 0b01000000,
|
||||
}
|
||||
public enum EnumRsv2 : byte
|
||||
{
|
||||
None = 0x0,
|
||||
Rsv = 0b00100000,
|
||||
}
|
||||
public enum EnumRsv3 : byte
|
||||
{
|
||||
None = 0x0,
|
||||
Rsv = 0b00010000,
|
||||
}
|
||||
public enum EnumOpcode : byte
|
||||
{
|
||||
Data = 0x0,
|
||||
Text = 0x1,
|
||||
Binary = 0x2,
|
||||
UnControll3 = 0x3,
|
||||
UnControll4 = 0x4,
|
||||
UnControll5 = 0x5,
|
||||
UnControll6 = 0x6,
|
||||
UnControll7 = 0x7,
|
||||
Close = 0x8,
|
||||
Ping = 0x9,
|
||||
Pong = 0xa,
|
||||
Controll11 = 0xb,
|
||||
Controll12 = 0xc,
|
||||
Controll13 = 0xd,
|
||||
Controll14 = 0xe,
|
||||
Controll15 = 0xf,
|
||||
Last = 0xf,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭的状态码
|
||||
/// </summary>
|
||||
public enum EnumCloseStatus : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// 正常关闭
|
||||
/// </summary>
|
||||
Normal = 1000,
|
||||
/// <summary>
|
||||
/// 正在离开
|
||||
/// </summary>
|
||||
Leaving = 1001,
|
||||
/// <summary>
|
||||
/// 协议错误
|
||||
/// </summary>
|
||||
ProtocolError = 1002,
|
||||
/// <summary>
|
||||
/// 只能接收TEXT数据
|
||||
/// </summary>
|
||||
TextOnly = 1003,
|
||||
/// <summary>
|
||||
/// 保留
|
||||
/// </summary>
|
||||
None1004 = 1004,
|
||||
/// <summary>
|
||||
/// 保留
|
||||
/// </summary>
|
||||
None1005 = 1005,
|
||||
/// <summary>
|
||||
/// 保留
|
||||
/// </summary>
|
||||
None1006 = 1006,
|
||||
/// <summary>
|
||||
/// 消息类型不一致
|
||||
/// </summary>
|
||||
DataTypeError = 1007,
|
||||
/// <summary>
|
||||
/// 通用状态码,没有别的合适的状态码时,用这个
|
||||
/// </summary>
|
||||
PublicError = 1008,
|
||||
/// <summary>
|
||||
/// 数据太大,无法处理
|
||||
/// </summary>
|
||||
DataTooBig = 1009,
|
||||
/// <summary>
|
||||
/// 扩展错误
|
||||
/// </summary>
|
||||
ExtendsError = 1010,//正常关闭
|
||||
/// <summary>
|
||||
/// 意外情况
|
||||
/// </summary>
|
||||
Unexpected = 1011,
|
||||
/// <summary>
|
||||
/// TLS握手失败
|
||||
/// </summary>
|
||||
TLSError = 1015
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求头解析
|
||||
/// </summary>
|
||||
public sealed class WebsocketHeaderInfo
|
||||
{
|
||||
static byte[][] bytes = new byte[][] {
|
||||
Encoding.ASCII.GetBytes("Connection: "),
|
||||
Encoding.ASCII.GetBytes("Upgrade: "),
|
||||
Encoding.ASCII.GetBytes("Origin: "),
|
||||
Encoding.ASCII.GetBytes("Sec-WebSocket-Version: "),
|
||||
Encoding.ASCII.GetBytes("Sec-WebSocket-Key: "),
|
||||
Encoding.ASCII.GetBytes("Sec-WebSocket-Extensions: "),
|
||||
Encoding.ASCII.GetBytes("Sec-WebSocket-Protocol: "),
|
||||
Encoding.ASCII.GetBytes("Sec-WebSocket-Accept: "),
|
||||
};
|
||||
static byte[] httpBytes = Encoding.UTF8.GetBytes("HTTP/");
|
||||
|
||||
public HttpStatusCode StatusCode { get; set; } = HttpStatusCode.SwitchingProtocols;
|
||||
public Memory<byte> Method { get; private set; }
|
||||
|
||||
private string _pathSet { get; set; }
|
||||
/// <summary>
|
||||
/// 用这个设置path值
|
||||
/// </summary>
|
||||
public string PathSet
|
||||
{
|
||||
get
|
||||
{
|
||||
return _pathSet;
|
||||
}
|
||||
set
|
||||
{
|
||||
_pathSet = value;
|
||||
Path = Encoding.UTF8.GetBytes(_pathSet);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 如果 仅1个字符,那就是 /
|
||||
/// </summary>
|
||||
public Memory<byte> Path { get; private set; }
|
||||
public Memory<byte> Connection { get; private set; }
|
||||
public Memory<byte> Upgrade { get; private set; }
|
||||
public Memory<byte> Origin { get; private set; }
|
||||
public Memory<byte> SecWebSocketVersion { get; private set; }
|
||||
public Memory<byte> SecWebSocketKey { get; set; }
|
||||
public Memory<byte> SecWebSocketExtensions { get; set; }
|
||||
public Memory<byte> SecWebSocketProtocol { get; set; }
|
||||
public Memory<byte> SecWebSocketAccept { get; set; }
|
||||
public static WebsocketHeaderInfo Parse(Memory<byte> header)
|
||||
{
|
||||
Span<byte> span = header.Span;
|
||||
int flag = 0xff;
|
||||
int bit = 0x01;
|
||||
|
||||
ulong[] res = new ulong[bytes.Length];
|
||||
|
||||
for (int i = 0, len = span.Length; i < len; i++)
|
||||
{
|
||||
if (span[i] == 13 && span[i + 1] == 10 && span[i + 2] == 13 && span[i + 3] == 10)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (span[i] == 13 && span[i + 1] == 10)
|
||||
{
|
||||
int startIndex = i + 2;
|
||||
for (int k = 0; k < bytes.Length; k++)
|
||||
{
|
||||
if ((flag >> k & 1) == 1 && span[startIndex] == bytes[k][0])
|
||||
{
|
||||
if (span.Slice(startIndex, bytes[k].Length).SequenceEqual(bytes[k]))
|
||||
{
|
||||
int index = span.Slice(startIndex).IndexOf((byte)13);
|
||||
flag &= ~(bit << k);
|
||||
|
||||
#pragma warning disable CS0675 // 对进行了带符号扩展的操作数使用了按位或运算符
|
||||
res[k] = ((ulong)(startIndex + bytes[k].Length) << 32) | (ulong)(index - bytes[k].Length);
|
||||
#pragma warning restore CS0675 // 对进行了带符号扩展的操作数使用了按位或运算符
|
||||
|
||||
i += index + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WebsocketHeaderInfo headerInfo = new WebsocketHeaderInfo
|
||||
{
|
||||
Connection = header.Slice((int)(res[0] >> 32), (int)(res[0] & 0xffffffff)),
|
||||
Upgrade = header.Slice((int)(res[1] >> 32), (int)(res[1] & 0xffffffff)),
|
||||
Origin = header.Slice((int)(res[2] >> 32), (int)(res[2] & 0xffffffff)),
|
||||
SecWebSocketVersion = header.Slice((int)(res[3] >> 32), (int)(res[3] & 0xffffffff)),
|
||||
SecWebSocketKey = header.Slice((int)(res[4] >> 32), (int)(res[4] & 0xffffffff)),
|
||||
SecWebSocketExtensions = header.Slice((int)(res[5] >> 32), (int)(res[5] & 0xffffffff)),
|
||||
SecWebSocketProtocol = header.Slice((int)(res[6] >> 32), (int)(res[6] & 0xffffffff)),
|
||||
SecWebSocketAccept = header.Slice((int)(res[7] >> 32), (int)(res[7] & 0xffffffff)),
|
||||
};
|
||||
|
||||
int pathIndex = span.IndexOf((byte)32);
|
||||
int pathIndex1 = span.Slice(pathIndex + 1).IndexOf((byte)32);
|
||||
if (header.Slice(0, httpBytes.Length).Span.SequenceEqual(httpBytes))
|
||||
{
|
||||
int code = int.Parse(Encoding.UTF8.GetString(header.Slice(pathIndex + 1, pathIndex1).Span));
|
||||
headerInfo.StatusCode = (HttpStatusCode)code;
|
||||
}
|
||||
else
|
||||
{
|
||||
headerInfo.Path = header.Slice(pathIndex + 1, pathIndex1);
|
||||
headerInfo.Method = header.Slice(0, pathIndex);
|
||||
}
|
||||
|
||||
return headerInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
476
cmonitor/server/api/websocket/WebSocketServer.cs
Normal file
476
cmonitor/server/api/websocket/WebSocketServer.cs
Normal file
@@ -0,0 +1,476 @@
|
||||
using common.libs;
|
||||
using common.libs.extends;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace cmonitor.server.api.websocket
|
||||
{
|
||||
/// <summary>
|
||||
/// websocket服务端
|
||||
/// </summary>
|
||||
public sealed class WebSocketServer
|
||||
{
|
||||
private Socket socket;
|
||||
private int BufferSize = 4 * 1024;
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, AsyncUserToken> connections = new();
|
||||
private readonly NumberSpaceUInt32 numberSpace = new NumberSpaceUInt32(0);
|
||||
|
||||
/// <summary>
|
||||
/// 收到连接,可以在这处理 subProtocol extensions 及其它信息,false表示阻止连接,应设置header 的 StatusCode
|
||||
/// </summary>
|
||||
public Func<WebsocketConnection, WebsocketHeaderInfo, bool> OnConnecting = (connection, header) =>
|
||||
{
|
||||
header.SecWebSocketExtensions = Helper.EmptyArray; return true;
|
||||
};
|
||||
/// <summary>
|
||||
/// 已断开连接,没有收到关闭帧
|
||||
/// </summary>
|
||||
public Action<WebsocketConnection> OnDisConnectd = (connection) => { };
|
||||
|
||||
/// <summary>
|
||||
/// 已连接
|
||||
/// </summary>
|
||||
public Action<WebsocketConnection> OnOpen = (connection) => { };
|
||||
/// <summary>
|
||||
/// 已关闭,收到关闭帧
|
||||
/// </summary>
|
||||
public Action<WebsocketConnection> OnClose = (connection) => { };
|
||||
|
||||
/// <summary>
|
||||
/// 文本数据
|
||||
/// </summary>
|
||||
public Action<WebsocketConnection, WebSocketFrameInfo, string> OnMessage = (connection, frame, message) => { };
|
||||
/// <summary>
|
||||
/// 二进制数据
|
||||
/// </summary>
|
||||
public Action<WebsocketConnection, WebSocketFrameInfo, Memory<byte>> OnBinary = (connection, frame, data) => { };
|
||||
|
||||
/// <summary>
|
||||
/// 控制帧,保留的控制帧,可以自定义处理
|
||||
/// </summary>
|
||||
public Action<WebsocketConnection, WebSocketFrameInfo> OnControll = (connection, frame) => { };
|
||||
/// <summary>
|
||||
/// 非控制帧,保留的非控制帧,可以自定义处理
|
||||
/// </summary>
|
||||
public Action<WebsocketConnection, WebSocketFrameInfo> OnUnControll = (connection, frame) => { };
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public IEnumerable<WebsocketConnection> Connections
|
||||
{
|
||||
get
|
||||
{
|
||||
return connections.Values.Select(c => c.Connectrion);
|
||||
}
|
||||
}
|
||||
public WebSocketServer()
|
||||
{
|
||||
handles = new Dictionary<WebSocketFrameInfo.EnumOpcode, Action<AsyncUserToken>> {
|
||||
//直接添加数据
|
||||
{ WebSocketFrameInfo.EnumOpcode.Data,HandleAppendData},
|
||||
//记录opcode并添加
|
||||
{ WebSocketFrameInfo.EnumOpcode.Text,HandleData},
|
||||
{ WebSocketFrameInfo.EnumOpcode.Binary,HandleData},
|
||||
|
||||
{ WebSocketFrameInfo.EnumOpcode.Close,HandleClose},
|
||||
|
||||
{ WebSocketFrameInfo.EnumOpcode.Ping,HandlePing},
|
||||
{ WebSocketFrameInfo.EnumOpcode.Pong,HandlePong},
|
||||
};
|
||||
}
|
||||
public void Start(IPAddress bindip, int port)
|
||||
{
|
||||
IPEndPoint localEndPoint = new IPEndPoint(bindip, port);
|
||||
|
||||
socket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
socket.Bind(localEndPoint);
|
||||
socket.Listen(int.MaxValue);
|
||||
|
||||
SocketAsyncEventArgs acceptEventArg = new SocketAsyncEventArgs
|
||||
{
|
||||
UserToken = socket,
|
||||
SocketFlags = SocketFlags.None,
|
||||
};
|
||||
acceptEventArg.Completed += IO_Completed;
|
||||
StartAccept(acceptEventArg);
|
||||
}
|
||||
private void IO_Completed(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
switch (e.LastOperation)
|
||||
{
|
||||
case SocketAsyncOperation.Accept:
|
||||
ProcessAccept(e);
|
||||
break;
|
||||
case SocketAsyncOperation.Receive:
|
||||
ProcessReceive(e);
|
||||
break;
|
||||
case SocketAsyncOperation.Send:
|
||||
ProcessSend(e);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void StartAccept(SocketAsyncEventArgs acceptEventArg)
|
||||
{
|
||||
acceptEventArg.AcceptSocket = null;
|
||||
Socket listenSocket = ((Socket)acceptEventArg.UserToken);
|
||||
try
|
||||
{
|
||||
if (!listenSocket.AcceptAsync(acceptEventArg))
|
||||
{
|
||||
ProcessAccept(acceptEventArg);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
private void ProcessAccept(SocketAsyncEventArgs e)
|
||||
{
|
||||
BindReceive(e.AcceptSocket);
|
||||
StartAccept(e);
|
||||
}
|
||||
private void BindReceive(Socket socket)
|
||||
{
|
||||
socket.KeepAlive(10,5);
|
||||
AsyncUserToken token = new AsyncUserToken
|
||||
{
|
||||
Connectrion = new WebsocketConnection { Socket = socket, Id = numberSpace.Increment() }
|
||||
};
|
||||
connections.TryAdd(token.Connectrion.Id, token);
|
||||
SocketAsyncEventArgs readEventArgs = new SocketAsyncEventArgs
|
||||
{
|
||||
UserToken = token,
|
||||
SocketFlags = SocketFlags.None,
|
||||
};
|
||||
token.PoolBuffer = new byte[BufferSize];
|
||||
readEventArgs.SetBuffer(token.PoolBuffer, 0, BufferSize);
|
||||
readEventArgs.Completed += IO_Completed;
|
||||
if (socket.ReceiveAsync(readEventArgs) == false)
|
||||
{
|
||||
ProcessReceive(readEventArgs);
|
||||
}
|
||||
}
|
||||
private void ProcessReceive(SocketAsyncEventArgs e)
|
||||
{
|
||||
AsyncUserToken token = e.UserToken as AsyncUserToken;
|
||||
try
|
||||
{
|
||||
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
|
||||
{
|
||||
var memory = e.Buffer.AsMemory(e.Offset, e.BytesTransferred);
|
||||
ReadFrame(token, memory);
|
||||
if (token.Connectrion.Socket.Available > 0)
|
||||
{
|
||||
while (token.Connectrion.Socket.Available > 0)
|
||||
{
|
||||
int length = token.Connectrion.Socket.Receive(e.Buffer);
|
||||
if (length > 0)
|
||||
{
|
||||
memory = e.Buffer.AsMemory(0, length);
|
||||
ReadFrame(token, memory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!token.Connectrion.Socket.Connected)
|
||||
{
|
||||
CloseClientSocket(e);
|
||||
return;
|
||||
}
|
||||
if (!token.Connectrion.Socket.ReceiveAsync(e))
|
||||
{
|
||||
ProcessReceive(e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseClientSocket(e);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
CloseClientSocket(e);
|
||||
}
|
||||
}
|
||||
private void ProcessSend(SocketAsyncEventArgs e)
|
||||
{
|
||||
if (e.SocketError == SocketError.Success)
|
||||
{
|
||||
AsyncUserToken token = (AsyncUserToken)e.UserToken;
|
||||
if (!token.Connectrion.Socket.ReceiveAsync(e))
|
||||
{
|
||||
ProcessReceive(e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseClientSocket(e);
|
||||
}
|
||||
}
|
||||
private void CloseClientSocket(SocketAsyncEventArgs e)
|
||||
{
|
||||
AsyncUserToken token = e.UserToken as AsyncUserToken;
|
||||
if (token.Disposabled == false)
|
||||
{
|
||||
e.Dispose();
|
||||
if (connections.TryRemove(token.Connectrion.Id, out _))
|
||||
{
|
||||
token.Clear();
|
||||
}
|
||||
OnDisConnectd(token.Connectrion);
|
||||
}
|
||||
|
||||
}
|
||||
public void Stop()
|
||||
{
|
||||
socket?.SafeClose();
|
||||
foreach (var item in connections.Values)
|
||||
{
|
||||
item.Clear();
|
||||
}
|
||||
connections.Clear();
|
||||
}
|
||||
|
||||
|
||||
private readonly Dictionary<WebSocketFrameInfo.EnumOpcode, Action<AsyncUserToken>> handles;
|
||||
/// <summary>
|
||||
/// 读取数据帧,分帧、粘包、半包处理,得到完整的包再根据opcode交给对应的处理
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <param name="data"></param>
|
||||
private void ReadFrame(AsyncUserToken token, Memory<byte> data)
|
||||
{
|
||||
if (token.Connectrion.Connected)
|
||||
{
|
||||
if (token.FrameBuffer.Size == 0 && data.Length > 6)
|
||||
{
|
||||
if (WebSocketFrameInfo.TryParse(data, out token.FrameInfo))
|
||||
{
|
||||
ExecuteHandle(token);
|
||||
if (token.FrameInfo.TotalLength == data.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
token.FrameBuffer.AddRange(data.Slice(token.FrameInfo.TotalLength));
|
||||
}
|
||||
else
|
||||
{
|
||||
token.FrameBuffer.AddRange(data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
token.FrameBuffer.AddRange(data);
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (WebSocketFrameInfo.TryParse(token.FrameBuffer.Data.Slice(token.FrameIndex), out token.FrameInfo) == false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (token.FrameInfo.Fin == WebSocketFrameInfo.EnumFin.Fin)
|
||||
{
|
||||
ExecuteHandle(token);
|
||||
token.FrameBuffer.RemoveRange(0, token.FrameInfo.TotalLength);
|
||||
token.FrameIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
token.FrameBuffer.RemoveRange(token.FrameIndex, token.FrameInfo.TotalLength - token.FrameInfo.PayloadData.Length);
|
||||
token.FrameIndex += token.FrameInfo.PayloadData.Length;
|
||||
}
|
||||
} while (token.FrameBuffer.Size > 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleConnect(token, data);
|
||||
}
|
||||
}
|
||||
private void ExecuteHandle(AsyncUserToken token)
|
||||
{
|
||||
if (handles.TryGetValue(token.FrameInfo.Opcode, out Action<AsyncUserToken> action))
|
||||
{
|
||||
action(token);
|
||||
}
|
||||
else if (token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.UnControll3 && token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.UnControll7)
|
||||
{
|
||||
OnUnControll(token.Connectrion, token.FrameInfo);
|
||||
}
|
||||
else if (token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.Controll11 && token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.Controll15)
|
||||
{
|
||||
OnControll(token.Connectrion, token.FrameInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
token.Connectrion.SendFrameClose(WebSocketFrameInfo.EnumCloseStatus.ExtendsError);
|
||||
token.Connectrion.Close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
private void HandleData(AsyncUserToken token)
|
||||
{
|
||||
token.Opcode = token.FrameInfo.Opcode;
|
||||
HandleAppendData(token);
|
||||
}
|
||||
private void HandleAppendData(AsyncUserToken token)
|
||||
{
|
||||
if (token.FrameInfo.Fin == WebSocketFrameInfo.EnumFin.Fin)
|
||||
{
|
||||
if (token.Opcode == WebSocketFrameInfo.EnumOpcode.Text)
|
||||
{
|
||||
string str = token.FrameInfo.PayloadData.GetString();
|
||||
OnMessage(token.Connectrion, token.FrameInfo, str);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnBinary(token.Connectrion, token.FrameInfo, token.FrameInfo.PayloadData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleClose(AsyncUserToken token)
|
||||
{
|
||||
token.Connectrion.SendFrameClose(WebSocketFrameInfo.EnumCloseStatus.Normal);
|
||||
token.Connectrion.Close();
|
||||
OnClose(token.Connectrion);
|
||||
}
|
||||
private void HandlePing(AsyncUserToken token)
|
||||
{
|
||||
token.Connectrion.SendFramePong();
|
||||
}
|
||||
private void HandlePong(AsyncUserToken token) { }
|
||||
private void HandleConnect(AsyncUserToken token, Memory<byte> data)
|
||||
{
|
||||
WebsocketHeaderInfo header = WebsocketHeaderInfo.Parse(data);
|
||||
if (header.SecWebSocketKey.Length == 0)
|
||||
{
|
||||
header.StatusCode = HttpStatusCode.MethodNotAllowed;
|
||||
token.Connectrion.ConnectResponse(header);
|
||||
token.Connectrion.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (OnConnecting(token.Connectrion, header))
|
||||
{
|
||||
token.Connectrion.Connected = true;
|
||||
token.Connectrion.ConnectResponse(header);
|
||||
OnOpen(token.Connectrion);
|
||||
}
|
||||
else
|
||||
{
|
||||
token.Connectrion.ConnectResponse(header);
|
||||
token.Connectrion.Close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public sealed class WebsocketConnection
|
||||
{
|
||||
public uint Id { get; set; }
|
||||
public Socket Socket { get; init; }
|
||||
public bool Connected { get; set; } = false;
|
||||
public bool SocketConnected=> Socket != null && Socket.Connected;
|
||||
|
||||
private bool Closed = false;
|
||||
public int ConnectResponse(WebsocketHeaderInfo header)
|
||||
{
|
||||
var data = WebSocketParser.BuildConnectResponseData(header);
|
||||
return SendRaw(data);
|
||||
}
|
||||
public int SendRaw(byte[] buffer)
|
||||
{
|
||||
return Socket.Send(buffer, SocketFlags.None);
|
||||
}
|
||||
public int SendRaw(Memory<byte> buffer)
|
||||
{
|
||||
return Socket.Send(buffer.Span, SocketFlags.None);
|
||||
}
|
||||
public int SendFrame(WebSocketFrameRemarkInfo remark)
|
||||
{
|
||||
var frame = WebSocketParser.BuildFrameData(remark, out int length);
|
||||
int res = SendRaw(frame.AsMemory(0, length));
|
||||
WebSocketParser.Return(frame);
|
||||
return res;
|
||||
}
|
||||
public int SendFrameText(string txt)
|
||||
{
|
||||
return SendFrameText(txt.ToBytes());
|
||||
}
|
||||
public int SendFrameText(byte[] buffer)
|
||||
{
|
||||
return SendFrame(new WebSocketFrameRemarkInfo
|
||||
{
|
||||
Opcode = WebSocketFrameInfo.EnumOpcode.Text,
|
||||
Data = buffer
|
||||
});
|
||||
}
|
||||
public int SendFrameBinary(Memory<byte> buffer)
|
||||
{
|
||||
return SendFrame(new WebSocketFrameRemarkInfo
|
||||
{
|
||||
Opcode = WebSocketFrameInfo.EnumOpcode.Binary,
|
||||
Data = buffer
|
||||
});
|
||||
}
|
||||
public int SendFramePong()
|
||||
{
|
||||
return SendRaw(WebSocketParser.BuildPongData());
|
||||
}
|
||||
public int SendFrameClose(WebSocketFrameInfo.EnumCloseStatus status)
|
||||
{
|
||||
return SendFrame(new WebSocketFrameRemarkInfo
|
||||
{
|
||||
Opcode = WebSocketFrameInfo.EnumOpcode.Close,
|
||||
Data = ((ushort)status).ToBytes()
|
||||
});
|
||||
}
|
||||
public void Close()
|
||||
{
|
||||
if (!Closed)
|
||||
{
|
||||
Socket?.SafeClose();
|
||||
}
|
||||
Closed = true;
|
||||
Connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AsyncUserToken
|
||||
{
|
||||
public WebsocketConnection Connectrion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前帧数据
|
||||
/// </summary>
|
||||
public WebSocketFrameInfo FrameInfo = null;
|
||||
/// <summary>
|
||||
/// 当前帧的数据下标
|
||||
/// </summary>
|
||||
public int FrameIndex { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// 数据帧缓存
|
||||
/// </summary>
|
||||
public ReceiveDataBuffer FrameBuffer { get; } = new ReceiveDataBuffer();
|
||||
/// <summary>
|
||||
/// 当前帧的数据类型
|
||||
/// </summary>
|
||||
public WebSocketFrameInfo.EnumOpcode Opcode { get; set; }
|
||||
public byte[] PoolBuffer { get; set; }
|
||||
public bool Disposabled { get; private set; } = false;
|
||||
public void Clear()
|
||||
{
|
||||
Disposabled = true;
|
||||
Connectrion.Close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
35
cmonitor/server/client/ClientConfig.cs
Normal file
35
cmonitor/server/client/ClientConfig.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using common.libs.database;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace cmonitor.server.client
|
||||
{
|
||||
|
||||
[Table("client")]
|
||||
public sealed class ClientConfig
|
||||
{
|
||||
private readonly IConfigDataProvider<ClientConfig> configDataProvider;
|
||||
public ClientConfig() { }
|
||||
public ClientConfig(IConfigDataProvider<ClientConfig> configDataProvider)
|
||||
{
|
||||
this.configDataProvider = configDataProvider;
|
||||
ClientConfig config = configDataProvider.Load().Result ?? new ClientConfig();
|
||||
Lock = config.Lock;
|
||||
Wallpaper = config.Wallpaper;
|
||||
Usb = config.Usb;
|
||||
Save();
|
||||
}
|
||||
|
||||
public bool Lock { get; set; }
|
||||
public bool Wallpaper { get; set; }
|
||||
public bool Usb { get; set; }
|
||||
|
||||
public void Save()
|
||||
{
|
||||
configDataProvider.Save(this).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
12
cmonitor/server/client/ClientSignInState.cs
Normal file
12
cmonitor/server/client/ClientSignInState.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using cmonitor.server.service;
|
||||
|
||||
namespace cmonitor.server.client
|
||||
{
|
||||
public sealed class ClientSignInState
|
||||
{
|
||||
public IConnection Connection { get; set; }
|
||||
public bool Connected => Connection != null && Connection.Connected;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
110
cmonitor/server/client/ClientTransfer.cs
Normal file
110
cmonitor/server/client/ClientTransfer.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using cmonitor.server.client.reports;
|
||||
using cmonitor.server.service;
|
||||
using cmonitor.server.service.messengers.report;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using common.libs;
|
||||
using common.libs.extends;
|
||||
using MemoryPack;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace cmonitor.server.client
|
||||
{
|
||||
public sealed class ClientTransfer
|
||||
{
|
||||
private readonly ClientSignInState clientSignInState;
|
||||
private readonly Config config;
|
||||
private readonly TcpServer tcpServer;
|
||||
private readonly MessengerSender messengerSender;
|
||||
|
||||
public ClientTransfer(ClientSignInState clientSignInState, Config config, TcpServer tcpServer, MessengerSender messengerSender)
|
||||
{
|
||||
this.clientSignInState = clientSignInState;
|
||||
this.config = config;
|
||||
this.tcpServer = tcpServer;
|
||||
this.messengerSender = messengerSender;
|
||||
|
||||
if (config.IsCLient)
|
||||
{
|
||||
SignInTask();
|
||||
}
|
||||
}
|
||||
|
||||
private void SignInTask()
|
||||
{
|
||||
Task.Factory.StartNew(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (clientSignInState.Connected == false)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SignIn();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
}
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
}
|
||||
private async Task SignIn()
|
||||
{
|
||||
IPAddress[] ips = new IPAddress[] { config.BroadcastIP };
|
||||
|
||||
//if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
Logger.Instance.Info($"get ip:{ips.ToJson()}");
|
||||
|
||||
if (ips.Length == 0) return;
|
||||
foreach (IPAddress ip in ips)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPEndPoint remote = new IPEndPoint(ip, config.ServicePort);
|
||||
|
||||
Socket socket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
socket.KeepAlive();
|
||||
IAsyncResult result = socket.BeginConnect(remote, null, null);
|
||||
await Task.Delay(500);
|
||||
if (result.IsCompleted == false)
|
||||
{
|
||||
socket.SafeClose();
|
||||
continue;
|
||||
}
|
||||
clientSignInState.Connection = tcpServer.BindReceive(socket);
|
||||
MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap
|
||||
{
|
||||
Connection = clientSignInState.Connection,
|
||||
MessengerId = (ushort)SignInMessengerIds.SignIn,
|
||||
Payload = MemoryPackSerializer.Serialize(new SignInfo
|
||||
{
|
||||
MachineName = config.Name,
|
||||
Version = config.Version
|
||||
})
|
||||
});
|
||||
if (resp.Code != MessageResponeCodes.OK || resp.Data.Span.SequenceEqual(Helper.TrueArray) == false)
|
||||
{
|
||||
clientSignInState.Connection?.Disponse();
|
||||
continue;
|
||||
}
|
||||
GCHelper.FlushMemory();
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
100
cmonitor/server/client/reports/IReport.cs
Normal file
100
cmonitor/server/client/reports/IReport.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using cmonitor.server.service;
|
||||
using common.libs;
|
||||
using MemoryPack;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using common.libs.extends;
|
||||
using System.Reflection;
|
||||
|
||||
namespace cmonitor.server.client.reports
|
||||
{
|
||||
public interface IReport
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
public Dictionary<string, object> GetReports();
|
||||
}
|
||||
|
||||
public sealed class ReportTransfer : IReport
|
||||
{
|
||||
public string Name => string.Empty;
|
||||
|
||||
private readonly ClientSignInState clientSignInState;
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly ServiceProvider serviceProvider;
|
||||
private readonly Config config;
|
||||
|
||||
private List<IReport> reports;
|
||||
private Dictionary<string, Dictionary<string, object>> reportObj;
|
||||
public ReportTransfer(ClientSignInState clientSignInState, MessengerSender messengerSender, ServiceProvider serviceProvider, Config config)
|
||||
{
|
||||
this.clientSignInState = clientSignInState;
|
||||
this.messengerSender = messengerSender;
|
||||
this.serviceProvider = serviceProvider;
|
||||
this.config = config;
|
||||
|
||||
if (config.IsCLient)
|
||||
{
|
||||
ReportTask();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Dictionary<string, object> GetReports()
|
||||
{
|
||||
return new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
public void LoadPlugins(Assembly[] assembs)
|
||||
{
|
||||
IEnumerable<Type> types = ReflectionHelper.GetInterfaceSchieves(assembs, typeof(IReport));
|
||||
reports = types.Select(c => (IReport)serviceProvider.GetService(c)).Where(c => string.IsNullOrWhiteSpace(c.Name) == false).ToList();
|
||||
reportObj = new Dictionary<string, Dictionary<string, object>>(reports.Count);
|
||||
}
|
||||
private uint reportFlag = 0;
|
||||
public void Update()
|
||||
{
|
||||
Interlocked.CompareExchange(ref reportFlag, 1, 0);
|
||||
}
|
||||
private void ReportTask()
|
||||
{
|
||||
Task.Factory.StartNew(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (clientSignInState.Connected == true && Interlocked.CompareExchange(ref reportFlag, 0, 1) == 1)
|
||||
{
|
||||
await SendReport();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
await Task.Delay(Config.ReportTime);
|
||||
}
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
}
|
||||
private async Task SendReport()
|
||||
{
|
||||
reportObj.Clear();
|
||||
foreach (IReport item in reports)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(item.Name) == false)
|
||||
{
|
||||
reportObj.Add(item.Name, item.GetReports());
|
||||
}
|
||||
}
|
||||
byte[] res = MemoryPackSerializer.Serialize(reportObj.ToJson());
|
||||
await messengerSender.SendOnly(new MessageRequestWrap
|
||||
{
|
||||
Connection = clientSignInState.Connection,
|
||||
MessengerId = (ushort)ReportMessengerIds.Report,
|
||||
Payload = res
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
360
cmonitor/server/client/reports/active/ActiveWindowReport.cs
Normal file
360
cmonitor/server/client/reports/active/ActiveWindowReport.cs
Normal file
@@ -0,0 +1,360 @@
|
||||
using common.libs;
|
||||
using MemoryPack;
|
||||
#if DEBUG || RELEASE
|
||||
using Microsoft.Win32;
|
||||
#endif
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace cmonitor.server.client.reports.active
|
||||
{
|
||||
public sealed class ActiveWindowReport : IReport
|
||||
{
|
||||
public string Name => "ActiveWindow";
|
||||
|
||||
private readonly ActiveWindowTimeManager activeWindowTimeManager = new ActiveWindowTimeManager();
|
||||
private ActiveWindow active = new ActiveWindow();
|
||||
private Dictionary<string, object> report = new Dictionary<string, object> {
|
||||
{ "Title", string.Empty }, { "FileName", string.Empty }, { "Desc", string.Empty }, { "Pid", 0 }, { "Disallow", Array.Empty<string>() }
|
||||
};
|
||||
private string[] disallowList = Array.Empty<string>();
|
||||
private List<string> disallowTitles = new List<string>();
|
||||
|
||||
private ActiveWindow[] backgroundWindow = Array.Empty<ActiveWindow>();
|
||||
|
||||
private readonly Config config;
|
||||
public ActiveWindowReport(Config config)
|
||||
{
|
||||
this.config = config;
|
||||
if (config.IsCLient)
|
||||
{
|
||||
Timers();
|
||||
}
|
||||
|
||||
DisallowInit();
|
||||
}
|
||||
|
||||
public Dictionary<string, object> GetReports()
|
||||
{
|
||||
//GetActiveWindow();
|
||||
report["Title"] = active.Title;
|
||||
report["FileName"] = active.FileName;
|
||||
report["Desc"] = active.Desc;
|
||||
report["Pid"] = active.Pid;
|
||||
report["Count"] = disallowList.Length;
|
||||
report["Backs"] = backgroundWindow;
|
||||
return report;
|
||||
}
|
||||
public ActiveWindowTimeReportInfo GetActiveWindowTimes()
|
||||
{
|
||||
return activeWindowTimeManager.GetActiveWindowTimes();
|
||||
}
|
||||
public void ClearActiveWindowTimes()
|
||||
{
|
||||
activeWindowTimeManager.Clear();
|
||||
}
|
||||
|
||||
public void DisallowRun(string[] names)
|
||||
{
|
||||
DisallowRun(false);
|
||||
DisallowRunClear();
|
||||
disallowList = names;
|
||||
if (names.Length > 0)
|
||||
{
|
||||
DisallowRun(true);
|
||||
DisallowRunFileNames(names);
|
||||
|
||||
disallowTitles = names.Where(c => c.EndsWith(".exe") == false).ToList();
|
||||
}
|
||||
Task.Run(() =>
|
||||
{
|
||||
CommandHelper.Windows(string.Empty, new string[] { "gpupdate /force" });
|
||||
});
|
||||
}
|
||||
|
||||
private void Timers()
|
||||
{
|
||||
Task.Factory.StartNew(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
GetActiveWindow();
|
||||
if (disallowTitles.Count > 0 && disallowTitles.Contains(active.Title))
|
||||
{
|
||||
uint pid = active.Pid;
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
CommandHelper.Windows(string.Empty, new string[] { $"taskkill /f /pid {pid}" });
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
activeWindowTimeManager.Update(active);
|
||||
}
|
||||
WindowHelper.UpdateCurrentProcesses();
|
||||
if (WindowHelper.processes != null)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
await Task.Delay(500);
|
||||
}
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
}
|
||||
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr GetForegroundWindow();
|
||||
[DllImport("user32.dll")]
|
||||
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
|
||||
[DllImport("user32.dll")]
|
||||
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||
[DllImport("psapi.dll")]
|
||||
static extern int GetProcessImageFileName(IntPtr hProcess, StringBuilder lpImageFileName, int nSize);
|
||||
|
||||
const int nChars = 256;
|
||||
static StringBuilder buff = new StringBuilder(nChars);
|
||||
private void GetActiveWindow()
|
||||
{
|
||||
IntPtr handle = GetForegroundWindow();
|
||||
GetWindowThreadProcessId(handle, out uint id);
|
||||
if (GetWindowText(handle, buff, nChars) > 0)
|
||||
{
|
||||
Process p = Process.GetProcessById((int)id);
|
||||
string desc = string.Empty;
|
||||
string filename = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
ProcessModule main = p.MainModule;
|
||||
if (main != null)
|
||||
{
|
||||
filename = main.FileName;
|
||||
desc = main.FileVersionInfo.FileDescription;
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
active.Title = buff.ToString();
|
||||
active.FileName = filename;
|
||||
active.Desc = desc;
|
||||
active.Pid = id;
|
||||
return;
|
||||
}
|
||||
active.Title = string.Empty;
|
||||
active.FileName = string.Empty;
|
||||
active.Desc = string.Empty;
|
||||
active.Pid = 0;
|
||||
}
|
||||
|
||||
private void DisallowInit()
|
||||
{
|
||||
CreateKey();
|
||||
DisallowRunClear();
|
||||
DisallowRun(false);
|
||||
Task.Run(() =>
|
||||
{
|
||||
CommandHelper.Windows(string.Empty, new string[] { "gpupdate /force" });
|
||||
});
|
||||
}
|
||||
private void DisallowRunClear()
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
try
|
||||
{
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\DisallowRun", true);
|
||||
if (key != null)
|
||||
{
|
||||
string[] names = key.GetValueNames();
|
||||
if (names != null)
|
||||
{
|
||||
foreach (string name in names)
|
||||
{
|
||||
key.DeleteValue(name, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Error($"application disallow clear {ex}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
private void DisallowRunFileNames(string[] filenames)
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
try
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
|
||||
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\DisallowRun", true);
|
||||
if (key != null)
|
||||
{
|
||||
foreach (string filename in filenames)
|
||||
{
|
||||
key.SetValue(filename, filename, RegistryValueKind.String);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Error($"application disallow {string.Join(",", filenames)} {ex}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
private void DisallowRun(bool value)
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
try
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer", true);
|
||||
if (key != null)
|
||||
{
|
||||
key.SetValue("DisallowRun", value ? 1 : 0, RegistryValueKind.DWord);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
private void CreateKey()
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
try
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer", true);
|
||||
RegistryKey disallowRun = key.OpenSubKey("DisallowRun");
|
||||
if (disallowRun == null)
|
||||
{
|
||||
key.CreateSubKey("DisallowRun");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public sealed class ActiveWindow
|
||||
{
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
public string Desc { get; set; } = string.Empty;
|
||||
|
||||
public uint Pid = 0;
|
||||
}
|
||||
|
||||
public sealed class ActiveWindowTimeManager
|
||||
{
|
||||
private ConcurrentDictionary<string, ActiveWindowTimeInfo> dic = new ConcurrentDictionary<string, ActiveWindowTimeInfo>();
|
||||
private string lastFileName = string.Empty;
|
||||
private string lastTitle = string.Empty;
|
||||
private DateTime StartTime = DateTime.Now;
|
||||
public void Clear()
|
||||
{
|
||||
StartTime = DateTime.Now;
|
||||
dic.Clear();
|
||||
GC.Collect();
|
||||
}
|
||||
public ActiveWindowTimeReportInfo GetActiveWindowTimes()
|
||||
{
|
||||
return new ActiveWindowTimeReportInfo
|
||||
{
|
||||
StartTime = StartTime,
|
||||
List = dic.Values.ToList()
|
||||
};
|
||||
}
|
||||
public void Update(ActiveWindow active)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(active.FileName)) return;
|
||||
|
||||
int index = active.FileName.LastIndexOf('\\');
|
||||
string filename = active.FileName;
|
||||
if (index >= 0)
|
||||
{
|
||||
filename = filename.Substring(index + 1, filename.Length - index - 1);
|
||||
}
|
||||
|
||||
if (dic.TryGetValue(filename, out ActiveWindowTimeInfo info) == false)
|
||||
{
|
||||
info = new ActiveWindowTimeInfo
|
||||
{
|
||||
FileName = filename,
|
||||
Desc = active.Desc,
|
||||
StartTime = DateTime.Now,
|
||||
Titles = new Dictionary<string, uint>()
|
||||
};
|
||||
dic.TryAdd(filename, info);
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(lastFileName) == false)
|
||||
{
|
||||
if (dic.TryGetValue(lastFileName, out ActiveWindowTimeInfo lastInfo))
|
||||
{
|
||||
lastInfo.Time += (ulong)(DateTime.Now - lastInfo.StartTime).TotalMilliseconds;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(lastTitle) == false)
|
||||
{
|
||||
if (info.Titles.TryGetValue(lastTitle, out uint times) == false)
|
||||
{
|
||||
info.Titles.TryAdd(lastTitle, 0);
|
||||
}
|
||||
info.Titles[lastTitle] += (uint)(DateTime.Now - lastInfo.StartTime).TotalMilliseconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info.StartTime = DateTime.Now;
|
||||
|
||||
lastFileName = filename;
|
||||
lastTitle = active.Title;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[MemoryPackable]
|
||||
public sealed partial class ActiveWindowTimeReportInfo
|
||||
{
|
||||
public DateTime StartTime { get; set; } = DateTime.Now;
|
||||
public List<ActiveWindowTimeInfo> List { get; set; } = new List<ActiveWindowTimeInfo>();
|
||||
}
|
||||
[MemoryPackable]
|
||||
public sealed partial class ActiveWindowTimeInfo
|
||||
{
|
||||
public string FileName { get; set; }
|
||||
public string Desc { get; set; }
|
||||
public ulong Time { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public Dictionary<string, uint> Titles { get; set; }
|
||||
}
|
||||
}
|
||||
40
cmonitor/server/client/reports/hijack/HijackReport.cs
Normal file
40
cmonitor/server/client/reports/hijack/HijackReport.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using cmonitor.hijack;
|
||||
using common.libs;
|
||||
|
||||
namespace cmonitor.server.client.reports.hijack
|
||||
{
|
||||
internal sealed class HijackReport : IReport
|
||||
{
|
||||
public string Name => "Hijack";
|
||||
|
||||
private readonly HijackEventHandler hijackEventHandler;
|
||||
private readonly HijackConfig hijackConfig;
|
||||
public HijackReport(HijackEventHandler hijackEventHandler, HijackController hijackController, HijackConfig hijackConfig)
|
||||
{
|
||||
this.hijackEventHandler = hijackEventHandler;
|
||||
this.hijackConfig = hijackConfig;
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
try
|
||||
{
|
||||
hijackController.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, object> GetReports()
|
||||
{
|
||||
ulong upload = hijackEventHandler.UdpSend + hijackEventHandler.TcpSend;
|
||||
ulong download = hijackEventHandler.TcpReceive + hijackEventHandler.UdpReceive;
|
||||
return new Dictionary<string, object> {
|
||||
{ "Upload",upload},
|
||||
{ "Download",download},
|
||||
{ "Count",hijackConfig.AllowIPs.Length + hijackConfig.DeniedIPs.Length + hijackConfig.AllowDomains.Length+hijackConfig.DeniedDomains.Length + hijackConfig.AllowProcesss.Length+hijackConfig.DeniedProcesss.Length }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
33
cmonitor/server/client/reports/light/LightReport.cs
Normal file
33
cmonitor/server/client/reports/light/LightReport.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace cmonitor.server.client.reports.light
|
||||
{
|
||||
public sealed class LightReport : IReport
|
||||
{
|
||||
public string Name => "Light";
|
||||
|
||||
private readonly LightWatcher lightWatcher;
|
||||
public LightReport()
|
||||
{
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
lightWatcher = new LightWatcher();
|
||||
lightWatcher.BrightnessChanged += (e, value) =>
|
||||
{
|
||||
dic["Value"] = value.newBrightness;
|
||||
};
|
||||
dic["Value"] = LightWmiHelper.GetBrightnessLevel();
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, object> dic = new Dictionary<string, object> { { "Value", 0 } };
|
||||
public Dictionary<string, object> GetReports()
|
||||
{
|
||||
return dic;
|
||||
}
|
||||
|
||||
public void SetLight(int value)
|
||||
{
|
||||
LightWmiHelper.SetBrightnessLevel(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
71
cmonitor/server/client/reports/light/LightWatcher.cs
Normal file
71
cmonitor/server/client/reports/light/LightWatcher.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
#if DEBUG || RELEASE
|
||||
using System.Management;
|
||||
#endif
|
||||
namespace cmonitor.server.client.reports.light
|
||||
{
|
||||
public class LightWatcher : IDisposable
|
||||
{
|
||||
public event EventHandler<BrightnessChangedEventArgs> BrightnessChanged;
|
||||
|
||||
public class BrightnessChangedEventArgs : EventArgs
|
||||
{
|
||||
public object newBrightness { get; set; } // new screen brightness value
|
||||
|
||||
public BrightnessChangedEventArgs(object b)
|
||||
{
|
||||
this.newBrightness = b;
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG || RELEASE
|
||||
private void WmiEventHandler(object sender, EventArrivedEventArgs e)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (BrightnessChanged != null)
|
||||
{
|
||||
BrightnessChanged(this, new BrightnessChangedEventArgs(e.NewEvent.Properties["Brightness"].Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ManagementEventWatcher _watcher;
|
||||
#endif
|
||||
|
||||
public LightWatcher()
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
try
|
||||
{
|
||||
var scope = @"root\wmi";
|
||||
var query = "SELECT * FROM WmiMonitorBrightnessEvent";
|
||||
_watcher = new ManagementEventWatcher(scope, query);
|
||||
_watcher.EventArrived += new EventArrivedEventHandler(WmiEventHandler);
|
||||
_watcher.Start();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("Exception {0} Trace {1}", e.Message, e.StackTrace);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (_watcher != null)
|
||||
{
|
||||
_watcher.Stop();
|
||||
}
|
||||
|
||||
_watcher.Dispose();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
91
cmonitor/server/client/reports/light/LightWmiHelper.cs
Normal file
91
cmonitor/server/client/reports/light/LightWmiHelper.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
#if DEBUG || RELEASE
|
||||
using System.Management;
|
||||
#endif
|
||||
using common.libs;
|
||||
|
||||
namespace cmonitor.server.client.reports.light
|
||||
{
|
||||
public static class LightWmiHelper
|
||||
{
|
||||
public static int GetBrightnessLevel()
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
try
|
||||
{
|
||||
var s = new ManagementScope("root\\WMI");
|
||||
var q = new SelectQuery("WmiMonitorBrightness");
|
||||
var mos = new ManagementObjectSearcher(s, q);
|
||||
var moc = mos.Get();
|
||||
|
||||
foreach (var managementBaseObject in moc)
|
||||
{
|
||||
foreach (var o in managementBaseObject.Properties)
|
||||
{
|
||||
if (o.Name == "CurrentBrightness")
|
||||
return Convert.ToInt32(o.Value);
|
||||
}
|
||||
}
|
||||
|
||||
moc.Dispose();
|
||||
mos.Dispose();
|
||||
}
|
||||
catch (ManagementException)
|
||||
{
|
||||
// ignore
|
||||
// it is possible that laptop lid is closed, and using external monitor
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static void SetBrightnessLevel(int brightnessLevel)
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (brightnessLevel < 0 ||
|
||||
brightnessLevel > 100)
|
||||
throw new ArgumentOutOfRangeException("brightnessLevel");
|
||||
|
||||
try
|
||||
{
|
||||
var s = new ManagementScope("root\\WMI");
|
||||
var q = new SelectQuery("WmiMonitorBrightnessMethods");
|
||||
var mos = new ManagementObjectSearcher(s, q);
|
||||
var moc = mos.Get();
|
||||
|
||||
foreach (var managementBaseObject in moc)
|
||||
{
|
||||
var o = (ManagementObject)managementBaseObject;
|
||||
o.InvokeMethod("WmiSetBrightness", new object[]
|
||||
{
|
||||
UInt32.MaxValue,
|
||||
brightnessLevel
|
||||
});
|
||||
}
|
||||
|
||||
moc.Dispose();
|
||||
mos.Dispose();
|
||||
}
|
||||
catch (ManagementException ex)
|
||||
{
|
||||
Logger.Instance.Error(ex);
|
||||
// ignore
|
||||
// it is possible that laptop lid is closed, and using external monitor
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
35
cmonitor/server/client/reports/llock/LLockReport.cs
Normal file
35
cmonitor/server/client/reports/llock/LLockReport.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using common.libs;
|
||||
|
||||
namespace cmonitor.server.client.reports.llock
|
||||
{
|
||||
public sealed class LLockReport : IReport
|
||||
{
|
||||
public string Name => "LLock";
|
||||
|
||||
private Dictionary<string, object> report = new Dictionary<string, object>() { { "Value", false } };
|
||||
public Dictionary<string, object> GetReports()
|
||||
{
|
||||
report["Value"] = WindowHelper.GetHasWindowByName("llock.win");
|
||||
return report;
|
||||
}
|
||||
|
||||
public void Update(bool open)
|
||||
{
|
||||
if (open)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
CommandHelper.Windows(string.Empty, new string[] {
|
||||
$"start llock.win.exe"
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
CommandHelper.Windows(string.Empty, new string[] {
|
||||
"taskkill /f /t /im \"llock.win.exe\""
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
311
cmonitor/server/client/reports/screen/ScreenReport.cs
Normal file
311
cmonitor/server/client/reports/screen/ScreenReport.cs
Normal file
@@ -0,0 +1,311 @@
|
||||
using cmonitor.server.service;
|
||||
using common.libs;
|
||||
using cmonitor.server.service.messengers.screen;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Buffers;
|
||||
#if DEBUG || RELEASE
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Drawing;
|
||||
using System.IO.MemoryMappedFiles;
|
||||
#endif
|
||||
using System.Text;
|
||||
|
||||
namespace cmonitor.server.client.reports.screen
|
||||
{
|
||||
public sealed class ScreenReport : IReport
|
||||
{
|
||||
public string Name => "Screen";
|
||||
private readonly ClientSignInState clientSignInState;
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly Config config;
|
||||
|
||||
Dictionary<string, object> dic = new Dictionary<string, object> { { "LastInput", 0 }, { "UserName", string.Empty }, { "KeyBoard", string.Empty } };
|
||||
|
||||
public ScreenReport(ClientSignInState clientSignInState, MessengerSender messengerSender, Config config)
|
||||
{
|
||||
this.clientSignInState = clientSignInState;
|
||||
this.messengerSender = messengerSender;
|
||||
this.config = config;
|
||||
|
||||
if (config.IsCLient)
|
||||
{
|
||||
ScreenCaptureTask();
|
||||
InitUserNameMemory();
|
||||
InitLastInputInfo();
|
||||
InitKeyBoard();
|
||||
}
|
||||
}
|
||||
public Dictionary<string, object> GetReports()
|
||||
{
|
||||
dic["LastInput"] = GetLastInputInfo();
|
||||
dic["UserName"] = GetUserNameMemory();
|
||||
dic["KeyBoard"] = GetKeyBoard();
|
||||
return dic;
|
||||
}
|
||||
|
||||
|
||||
#region 截图
|
||||
private uint screenCaptureFlag = 0;
|
||||
public void Update()
|
||||
{
|
||||
Interlocked.CompareExchange(ref screenCaptureFlag, 1, 0);
|
||||
}
|
||||
private void ScreenCaptureTask()
|
||||
{
|
||||
Task.Factory.StartNew(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (clientSignInState.Connected == true && Interlocked.CompareExchange(ref screenCaptureFlag, 0, 1) == 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SendScreenCapture();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
}
|
||||
await Task.Delay(Config.ScreenTime);
|
||||
}
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
}
|
||||
private async Task SendScreenCapture()
|
||||
{
|
||||
byte[] bytes = ScreenCapture();
|
||||
if (bytes.Length > 0)
|
||||
{
|
||||
|
||||
// byte[] bytes = MemoryPackSerializer.Serialize(img);
|
||||
await messengerSender.SendOnly(new MessageRequestWrap
|
||||
{
|
||||
Connection = clientSignInState.Connection,
|
||||
MessengerId = (ushort)ScreenMessengerIds.Report,
|
||||
Payload = bytes,
|
||||
});
|
||||
}
|
||||
Return(bytes);
|
||||
}
|
||||
private byte[] ScreenCapture()
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
IntPtr hdc = GetDC(IntPtr.Zero);
|
||||
using Bitmap source = new Bitmap(GetDeviceCaps(hdc, DESKTOPHORZRES), GetDeviceCaps(hdc, DESKTOPVERTRES));
|
||||
using (Graphics g = Graphics.FromImage(source))
|
||||
{
|
||||
g.CopyFromScreen(0, 0, 0, 0, source.Size, CopyPixelOperation.SourceCopy);
|
||||
|
||||
CURSORINFO pci;
|
||||
pci.cbSize = Marshal.SizeOf(typeof(CURSORINFO));
|
||||
if (GetCursorInfo(out pci))
|
||||
{
|
||||
if (pci.flags == CURSOR_SHOWING)
|
||||
{
|
||||
var hdc1 = g.GetHdc();
|
||||
DrawIconEx(hdc1, pci.ptScreenPos.x - 0, pci.ptScreenPos.y - 0, pci.hCursor, 0, 0, 0, IntPtr.Zero, DI_NORMAL);
|
||||
g.ReleaseHdc();
|
||||
}
|
||||
}
|
||||
|
||||
g.Dispose();
|
||||
}
|
||||
ReleaseDC(IntPtr.Zero, hdc);
|
||||
|
||||
|
||||
int newWidth = 384;
|
||||
int newHeight = (int)(source.Height * (newWidth * 1.0 / source.Width));
|
||||
Bitmap bmp = new Bitmap(newWidth, newHeight);
|
||||
bmp.SetResolution(source.HorizontalResolution, source.VerticalResolution);
|
||||
using Graphics graphic = Graphics.FromImage(bmp);
|
||||
graphic.SmoothingMode = SmoothingMode.HighQuality;
|
||||
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
graphic.DrawImage(source, new Rectangle(0, 0, newWidth, newHeight));
|
||||
|
||||
using Image image = bmp;
|
||||
|
||||
using MemoryStream ms = new MemoryStream();
|
||||
image.Save(ms, ImageFormat.Jpeg);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
byte[] bytes = ArrayPool<byte>.Shared.Rent((int)ms.Length);
|
||||
ms.Read(bytes);
|
||||
return bytes;
|
||||
//string base64 = Convert.ToBase64String(bytes, 0, (int)ms.Length);
|
||||
//ArrayPool<byte>.Shared.Return(bytes);
|
||||
//return base64;
|
||||
|
||||
}
|
||||
#endif
|
||||
return Array.Empty<byte>();
|
||||
|
||||
}
|
||||
private void Return(byte[] bytes)
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(bytes);
|
||||
}
|
||||
|
||||
|
||||
const int DESKTOPVERTRES = 117;
|
||||
const int DESKTOPHORZRES = 118;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr GetDC(IntPtr ptr);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "ReleaseDC")]
|
||||
static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);
|
||||
|
||||
[DllImport("gdi32.dll", EntryPoint = "GetDeviceCaps", SetLastError = true)]
|
||||
public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "GetSystemMetrics")]
|
||||
private static extern int GetSystemMetrics(int mVal);
|
||||
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct CURSORINFO
|
||||
{
|
||||
public Int32 cbSize;
|
||||
public Int32 flags;
|
||||
public IntPtr hCursor;
|
||||
public POINTAPI ptScreenPos;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct POINTAPI
|
||||
{
|
||||
public int x;
|
||||
public int y;
|
||||
}
|
||||
private const Int32 CURSOR_SHOWING = 0x0001;
|
||||
private const Int32 DI_NORMAL = 0x0003;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetCursorInfo(out CURSORINFO pci);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
static extern bool DrawIconEx(IntPtr hdc, int xLeft, int yTop, IntPtr hIcon, int cxWidth, int cyHeight, int istepIfAniCur, IntPtr hbrFlickerFreeDraw, int diFlags);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 最后活动时间
|
||||
private LASTINPUTINFO lastInputInfo = new LASTINPUTINFO();
|
||||
private void InitLastInputInfo()
|
||||
{
|
||||
lastInputInfo.cbSize = (uint)Marshal.SizeOf(typeof(LASTINPUTINFO));
|
||||
}
|
||||
private uint GetLastInputInfo()
|
||||
{
|
||||
bool res = GetLastInputInfo(ref lastInputInfo);
|
||||
if (res)
|
||||
{
|
||||
return (uint)Environment.TickCount - lastInputInfo.dwTime;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct LASTINPUTINFO
|
||||
{
|
||||
public uint cbSize;
|
||||
public uint dwTime;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 用户名
|
||||
|
||||
#if DEBUG || RELEASE
|
||||
MemoryMappedFile mmf2;
|
||||
MemoryMappedViewAccessor accessor2;
|
||||
byte[] userNameBytes;
|
||||
#endif
|
||||
private void InitUserNameMemory()
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
userNameBytes = new byte[config.UserNameMemoryLength];
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
mmf2 = MemoryMappedFile.CreateOrOpen(config.UserNameMemoryKey, userNameBytes.Length);
|
||||
accessor2 = mmf2.CreateViewAccessor();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private string GetUserNameMemory()
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
try
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (accessor2 != null)
|
||||
{
|
||||
accessor2.Read(0, out byte length);
|
||||
if (length > 0)
|
||||
{
|
||||
accessor2.ReadArray(1, userNameBytes, 0, length);
|
||||
return Encoding.UTF8.GetString(userNameBytes.AsSpan(0, length));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
return string.Empty;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 键盘
|
||||
|
||||
#if DEBUG || RELEASE
|
||||
MemoryMappedFile mmf3;
|
||||
MemoryMappedViewAccessor accessor3;
|
||||
byte[] keyBoardBytes;
|
||||
#endif
|
||||
private void InitKeyBoard()
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
keyBoardBytes = new byte[config.KeyboardMemoryLength];
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
mmf3 = MemoryMappedFile.CreateOrOpen(config.KeyboardMemoryKey, keyBoardBytes.Length);
|
||||
accessor3 = mmf3.CreateViewAccessor();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
private string GetKeyBoard()
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
try
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (accessor3 != null)
|
||||
{
|
||||
accessor3.Read(0, out byte length);
|
||||
if (length > 0)
|
||||
{
|
||||
accessor3.ReadArray(1, keyBoardBytes, 0, length);
|
||||
return Encoding.UTF8.GetString(keyBoardBytes.AsSpan(0, length));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
return string.Empty;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
63
cmonitor/server/client/reports/share/ShareReport.cs
Normal file
63
cmonitor/server/client/reports/share/ShareReport.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.IO.MemoryMappedFiles;
|
||||
using System.Text;
|
||||
|
||||
namespace cmonitor.server.client.reports.screen
|
||||
{
|
||||
public sealed class ShareReport : IReport
|
||||
{
|
||||
public string Name => "Share";
|
||||
private readonly Config config;
|
||||
|
||||
Dictionary<string, object> dic = new Dictionary<string, object> { { "Value", string.Empty}};
|
||||
|
||||
public ShareReport(Config config)
|
||||
{
|
||||
this.config = config;
|
||||
|
||||
InitShare();
|
||||
|
||||
}
|
||||
public Dictionary<string, object> GetReports()
|
||||
{
|
||||
dic["Value"] = GetShare();
|
||||
return dic;
|
||||
}
|
||||
|
||||
|
||||
MemoryMappedFile mmf3;
|
||||
MemoryMappedViewAccessor accessor3;
|
||||
byte[] bytes;
|
||||
private void InitShare()
|
||||
{
|
||||
bytes = new byte[config.ShareMemoryLength];
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
mmf3 = MemoryMappedFile.CreateOrOpen(config.ShareMemoryKey, bytes.Length);
|
||||
accessor3 = mmf3.CreateViewAccessor();
|
||||
}
|
||||
}
|
||||
private string GetShare()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (accessor3 != null)
|
||||
{
|
||||
int length = accessor3.ReadInt32(0);
|
||||
if (length > 0)
|
||||
{
|
||||
accessor3.ReadArray(4, bytes, 0, length);
|
||||
return Encoding.UTF8.GetString(bytes.AsSpan(0, length));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
98
cmonitor/server/client/reports/usb/UsbReport.cs
Normal file
98
cmonitor/server/client/reports/usb/UsbReport.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using common.libs;
|
||||
#if DEBUG || RELEASE
|
||||
using Microsoft.Win32;
|
||||
#endif
|
||||
|
||||
namespace cmonitor.server.client.reports.llock
|
||||
{
|
||||
public sealed class UsbReport : IReport
|
||||
{
|
||||
public string Name => "Usb";
|
||||
|
||||
public UsbReport()
|
||||
{
|
||||
UnLockUsb();
|
||||
}
|
||||
|
||||
private Dictionary<string, object> report = new Dictionary<string, object>() { { "Value", false } };
|
||||
public Dictionary<string, object> GetReports()
|
||||
{
|
||||
return report;
|
||||
}
|
||||
|
||||
public void Update(bool llock)
|
||||
{
|
||||
if (llock)
|
||||
{
|
||||
LockUsb();
|
||||
}
|
||||
else
|
||||
{
|
||||
UnLockUsb();
|
||||
}
|
||||
|
||||
report["Value"] = GetHasUSBDisabled();
|
||||
}
|
||||
|
||||
private bool GetHasUSBDisabled()
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
try
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\\CurrentControlSet\\Services\\USBSTOR", true);
|
||||
return key != null
|
||||
&& (int)key.GetValue("Start", 3, RegistryValueOptions.None) == 4;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Error($"get usb state {ex}");
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
private void LockUsb()
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
try
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\\CurrentControlSet\\Services\\USBSTOR", true);
|
||||
if (key != null)
|
||||
{
|
||||
key.SetValue("Start", 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Error($"lock usb {ex}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
private void UnLockUsb()
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
try
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
|
||||
RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\\CurrentControlSet\\Services\\USBSTOR", true);
|
||||
if (key != null)
|
||||
{
|
||||
key.SetValue("Start", 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Error($"unlock usb {ex}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
131
cmonitor/server/client/reports/volume/VolumeReport.cs
Normal file
131
cmonitor/server/client/reports/volume/VolumeReport.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using common.libs;
|
||||
#if DEBUG || RELEASE
|
||||
using NAudio.CoreAudioApi;
|
||||
#endif
|
||||
|
||||
namespace cmonitor.server.client.reports.volume
|
||||
{
|
||||
public sealed class VolumeReport : IReport
|
||||
{
|
||||
public string Name => "Volume";
|
||||
public VolumeReport()
|
||||
{
|
||||
Volume();
|
||||
}
|
||||
|
||||
public Dictionary<string, object> GetReports()
|
||||
{
|
||||
return new Dictionary<string, object>()
|
||||
{
|
||||
{ "Value",GetVolume()},
|
||||
{ "Mute",GetVolumeMute()},
|
||||
{ "MasterPeak",GetMasterPeakValue()},
|
||||
};
|
||||
}
|
||||
|
||||
#if DEBUG || RELEASE
|
||||
MMDeviceEnumerator enumerator;
|
||||
MMDevice device;
|
||||
AudioEndpointVolume volumeObject;
|
||||
#endif
|
||||
private void Volume()
|
||||
{
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
try
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
enumerator = new MMDeviceEnumerator();
|
||||
device = enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Console);
|
||||
volumeObject = device.AudioEndpointVolume;
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
private float GetVolume()
|
||||
{
|
||||
try
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
if (volumeObject != null)
|
||||
{
|
||||
return volumeObject.MasterVolumeLevelScalar * 100;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
private float GetMasterPeakValue()
|
||||
{
|
||||
try
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
if (device != null)
|
||||
{
|
||||
return device.AudioMeterInformation.MasterPeakValue * 100;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
return -1;
|
||||
|
||||
}
|
||||
private bool GetVolumeMute()
|
||||
{
|
||||
try
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
if (volumeObject != null)
|
||||
{
|
||||
return volumeObject.Mute;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
volume = Math.Max(0, Math.Min(1, volume));
|
||||
if (volumeObject != null)
|
||||
{
|
||||
volumeObject.MasterVolumeLevelScalar = volume;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
public void SetVolumeMute(bool mute)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
if (volumeObject != null)
|
||||
{
|
||||
volumeObject.Mute = mute;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
cmonitor/server/client/reports/wallpaper/WallpaperReport.cs
Normal file
41
cmonitor/server/client/reports/wallpaper/WallpaperReport.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using common.libs;
|
||||
|
||||
namespace cmonitor.server.client.reports.llock
|
||||
{
|
||||
public sealed class WallpaperReport : IReport
|
||||
{
|
||||
public string Name => "Wallpaper";
|
||||
|
||||
private Dictionary<string, object> report = new Dictionary<string, object>() { { "Value", false } };
|
||||
private readonly Config config;
|
||||
public WallpaperReport(Config config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public Dictionary<string, object> GetReports()
|
||||
{
|
||||
report["Value"] = WindowHelper.GetHasWindowByName("wallpaper.win");
|
||||
return report;
|
||||
}
|
||||
|
||||
public void Update(bool open, string url)
|
||||
{
|
||||
if (open)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
CommandHelper.Windows(string.Empty, new string[] {
|
||||
$"start wallpaper.win.exe \"{url}\" {config.KeyboardMemoryKey} {config.KeyboardMemoryLength}"
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
CommandHelper.Windows(string.Empty, new string[] {
|
||||
"taskkill /f /t /im \"wallpaper.win.exe\""
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
342
cmonitor/server/service/IConnection.cs
Normal file
342
cmonitor/server/service/IConnection.cs
Normal file
@@ -0,0 +1,342 @@
|
||||
using common.libs;
|
||||
using common.libs.extends;
|
||||
using System.Buffers;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace cmonitor.server.service
|
||||
{
|
||||
/// <summary>
|
||||
/// 连接对象
|
||||
/// </summary>
|
||||
public interface IConnection
|
||||
{
|
||||
public string Name{ get; set; }
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// 已连接
|
||||
/// </summary>
|
||||
public bool Connected { get; }
|
||||
|
||||
public IPEndPoint Address { get; }
|
||||
|
||||
#region 接收数据
|
||||
/// <summary>
|
||||
/// 请求数据包装对象
|
||||
/// </summary>
|
||||
public MessageRequestWrap ReceiveRequestWrap { get; }
|
||||
/// <summary>
|
||||
/// 回复数据包装对象
|
||||
/// </summary>
|
||||
public MessageResponseWrap ReceiveResponseWrap { get; }
|
||||
/// <summary>
|
||||
/// 接收到的原始数据
|
||||
/// </summary>
|
||||
public Memory<byte> ReceiveData { get; set; }
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 发送
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public Task<bool> Send(ReadOnlyMemory<byte> data, bool unconnectedMessage = false);
|
||||
/// <summary>
|
||||
/// 发送
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="length"></param>
|
||||
/// <returns></returns>
|
||||
public Task<bool> Send(byte[] data, int length, bool unconnectedMessage = false);
|
||||
|
||||
/// <summary>
|
||||
/// 销毁
|
||||
/// </summary>
|
||||
public void Disponse();
|
||||
|
||||
#region 回复消息相关
|
||||
|
||||
public Memory<byte> ResponseData { get; }
|
||||
public void Write(Memory<byte> data);
|
||||
public void Write(ulong num);
|
||||
public void Write(ushort num);
|
||||
public void Write(ushort[] nums);
|
||||
/// <summary>
|
||||
/// 英文多用这个
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
public void WriteUTF8(string str);
|
||||
/// <summary>
|
||||
/// 中文多用这个
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
public void WriteUTF16(string str);
|
||||
/// <summary>
|
||||
/// 归还池
|
||||
/// </summary>
|
||||
public void Return();
|
||||
#endregion
|
||||
|
||||
|
||||
public Task WaitOne();
|
||||
public void Release();
|
||||
|
||||
}
|
||||
|
||||
public abstract class Connection : IConnection
|
||||
{
|
||||
public Connection()
|
||||
{
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// 已连接
|
||||
/// </summary>
|
||||
public virtual bool Connected => false;
|
||||
/// <summary>
|
||||
/// 地址
|
||||
/// </summary>
|
||||
public IPEndPoint Address { get; protected set; }
|
||||
|
||||
|
||||
#region 接收数据
|
||||
/// <summary>
|
||||
/// 接收请求数据
|
||||
/// </summary>
|
||||
public MessageRequestWrap ReceiveRequestWrap { get; set; }
|
||||
/// <summary>
|
||||
/// 接收回执数据
|
||||
/// </summary>
|
||||
public MessageResponseWrap ReceiveResponseWrap { get; set; }
|
||||
/// <summary>
|
||||
/// 接收数据
|
||||
/// </summary>
|
||||
public Memory<byte> ReceiveData { get; set; }
|
||||
#endregion
|
||||
|
||||
#region 回复数据
|
||||
public Memory<byte> ResponseData { get; private set; }
|
||||
private byte[] responseData;
|
||||
private int length = 0;
|
||||
|
||||
public void Write(Memory<byte> data)
|
||||
{
|
||||
ResponseData = data;
|
||||
}
|
||||
public void Write(ulong num)
|
||||
{
|
||||
length = 8;
|
||||
responseData = ArrayPool<byte>.Shared.Rent(length);
|
||||
num.ToBytes(responseData);
|
||||
ResponseData = responseData.AsMemory(0, length);
|
||||
}
|
||||
public void Write(ushort num)
|
||||
{
|
||||
length = 2;
|
||||
responseData = ArrayPool<byte>.Shared.Rent(length);
|
||||
num.ToBytes(responseData);
|
||||
ResponseData = responseData.AsMemory(0, length);
|
||||
}
|
||||
public void Write(ushort[] nums)
|
||||
{
|
||||
length = nums.Length * 2;
|
||||
responseData = ArrayPool<byte>.Shared.Rent(length);
|
||||
nums.ToBytes(responseData);
|
||||
ResponseData = responseData.AsMemory(0, length);
|
||||
}
|
||||
/// <summary>
|
||||
/// 英文多用这个
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
public void WriteUTF8(string str)
|
||||
{
|
||||
var span = str.AsSpan();
|
||||
responseData = ArrayPool<byte>.Shared.Rent((span.Length + 1) * 3 + 8);
|
||||
var memory = responseData.AsMemory();
|
||||
|
||||
int utf8Length = span.ToUTF8Bytes(memory.Slice(8));
|
||||
span.Length.ToBytes(memory);
|
||||
utf8Length.ToBytes(memory.Slice(4));
|
||||
length = utf8Length + 8;
|
||||
|
||||
ResponseData = responseData.AsMemory(0, length);
|
||||
}
|
||||
/// <summary>
|
||||
/// 中文多用这个
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
public void WriteUTF16(string str)
|
||||
{
|
||||
var span = str.GetUTF16Bytes();
|
||||
length = span.Length + 4;
|
||||
responseData = ArrayPool<byte>.Shared.Rent(length);
|
||||
str.Length.ToBytes(responseData);
|
||||
span.CopyTo(responseData.AsSpan(4));
|
||||
|
||||
ResponseData = responseData.AsMemory(0, length);
|
||||
}
|
||||
/// <summary>
|
||||
/// 归还池
|
||||
/// </summary>
|
||||
public void Return()
|
||||
{
|
||||
if (length > 0 && ResponseData.Length > 0)
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(responseData);
|
||||
}
|
||||
ResponseData = Helper.EmptyArray;
|
||||
responseData = null;
|
||||
length = 0;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 发送
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public abstract Task<bool> Send(ReadOnlyMemory<byte> data, bool logger = false);
|
||||
/// <summary>
|
||||
/// 发送
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="length"></param>
|
||||
/// <returns></returns>
|
||||
public abstract Task<bool> Send(byte[] data, int length, bool logger = false);
|
||||
|
||||
/// <summary>
|
||||
/// 销毁
|
||||
/// </summary>
|
||||
public virtual void Disponse()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Semaphore != null)
|
||||
{
|
||||
if (locked)
|
||||
{
|
||||
locked = false;
|
||||
Semaphore.Release();
|
||||
}
|
||||
Semaphore.Dispose();
|
||||
}
|
||||
Semaphore = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
//ReceiveRequestWrap = null;
|
||||
//ReceiveResponseWrap = null;
|
||||
}
|
||||
|
||||
|
||||
SemaphoreSlim Semaphore = new SemaphoreSlim(1);
|
||||
bool locked = false;
|
||||
public virtual async Task WaitOne()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Semaphore != null)
|
||||
{
|
||||
locked = true;
|
||||
await Semaphore.WaitAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
|
||||
}
|
||||
public virtual void Release()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Semaphore != null)
|
||||
{
|
||||
locked = false;
|
||||
Semaphore.Release();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed class TcpConnection : Connection
|
||||
{
|
||||
public TcpConnection(Socket tcpSocket) : base()
|
||||
{
|
||||
TcpSocket = tcpSocket;
|
||||
|
||||
IPEndPoint address = TcpSocket.RemoteEndPoint as IPEndPoint ?? new IPEndPoint(IPAddress.Any, 0);
|
||||
if (address.Address.AddressFamily == AddressFamily.InterNetworkV6 && address.Address.IsIPv4MappedToIPv6)
|
||||
{
|
||||
address = new IPEndPoint(new IPAddress(address.Address.GetAddressBytes()[^4..]), address.Port);
|
||||
}
|
||||
Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 已连接
|
||||
/// </summary>
|
||||
public override bool Connected => TcpSocket != null && TcpSocket.Connected;
|
||||
|
||||
/// <summary>
|
||||
/// socket
|
||||
/// </summary>
|
||||
public Socket TcpSocket { get; private set; }
|
||||
/// <summary>
|
||||
/// 发送
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<bool> Send(ReadOnlyMemory<byte> data, bool unconnectedMessage = false)
|
||||
{
|
||||
if (Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
await TcpSocket.SendAsync(data, SocketFlags.None);
|
||||
//SentBytes += (ulong)data.Length;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Disponse();
|
||||
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/// <summary>
|
||||
/// 发送
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="length"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<bool> Send(byte[] data, int length, bool unconnectedMessage = false)
|
||||
{
|
||||
return await Send(data.AsMemory(0, length), unconnectedMessage);
|
||||
}
|
||||
/// <summary>
|
||||
/// 销毁
|
||||
/// </summary>
|
||||
public override void Disponse()
|
||||
{
|
||||
base.Disponse();
|
||||
if (TcpSocket != null)
|
||||
{
|
||||
TcpSocket.SafeClose();
|
||||
TcpSocket.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
63
cmonitor/server/service/IMessenger.cs
Normal file
63
cmonitor/server/service/IMessenger.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
namespace cmonitor.server.service
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息接口
|
||||
/// </summary>
|
||||
public interface IMessenger
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息id范围
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class MessengerIdRangeAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 最小
|
||||
/// </summary>
|
||||
public ushort Min { get; set; }
|
||||
/// <summary>
|
||||
/// 最大
|
||||
/// </summary>
|
||||
public ushort Max { get; set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="min"></param>
|
||||
/// <param name="max"></param>
|
||||
public MessengerIdRangeAttribute(ushort min, ushort max)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 消息id
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class MessengerIdAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// id
|
||||
/// </summary>
|
||||
public ushort Id { get; set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
public MessengerIdAttribute(ushort id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
|
||||
[AttributeUsage(AttributeTargets.Enum)]
|
||||
public sealed class MessengerIdEnumAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
162
cmonitor/server/service/MessengerResolver.cs
Normal file
162
cmonitor/server/service/MessengerResolver.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using common.libs;
|
||||
using common.libs.extends;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace cmonitor.server.service
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息处理总线
|
||||
/// </summary>
|
||||
public sealed class MessengerResolver
|
||||
{
|
||||
delegate void VoidDelegate(IConnection connection);
|
||||
delegate Task TaskDelegate(IConnection connection);
|
||||
|
||||
private readonly Dictionary<ushort, MessengerCacheInfo> messengers = new();
|
||||
|
||||
private readonly TcpServer tcpserver;
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly ServiceProvider serviceProvider;
|
||||
|
||||
|
||||
public MessengerResolver(TcpServer tcpserver, MessengerSender messengerSender, ServiceProvider serviceProvider)
|
||||
{
|
||||
this.tcpserver = tcpserver;
|
||||
this.messengerSender = messengerSender;
|
||||
|
||||
this.tcpserver.OnPacket = InputData;
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void LoadMessenger(Assembly[] assemblys)
|
||||
{
|
||||
Type voidType = typeof(void);
|
||||
Type midType = typeof(MessengerIdAttribute);
|
||||
|
||||
foreach (Type type in ReflectionHelper.GetInterfaceSchieves(assemblys, typeof(IMessenger)).Distinct())
|
||||
{
|
||||
object obj = serviceProvider.GetService(type);
|
||||
foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
|
||||
{
|
||||
MessengerIdAttribute mid = method.GetCustomAttribute(midType) as MessengerIdAttribute;
|
||||
if (mid != null)
|
||||
{
|
||||
|
||||
if (messengers.ContainsKey(mid.Id) == false)
|
||||
{
|
||||
MessengerCacheInfo cache = new MessengerCacheInfo
|
||||
{
|
||||
Target = obj
|
||||
};
|
||||
if (method.ReturnType == voidType)
|
||||
{
|
||||
cache.VoidMethod = (VoidDelegate)Delegate.CreateDelegate(typeof(VoidDelegate), obj, method);
|
||||
}
|
||||
else if (method.ReturnType.GetProperty("IsCompleted") != null && method.ReturnType.GetMethod("GetAwaiter") != null)
|
||||
{
|
||||
cache.TaskMethod = (TaskDelegate)Delegate.CreateDelegate(typeof(TaskDelegate), obj, method);
|
||||
}
|
||||
|
||||
messengers.TryAdd(mid.Id, cache);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Instance.Error($"{type.Name}->{method.Name}->{mid.Id} 消息id已存在");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 收到消息
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
/// <returns></returns>
|
||||
public async Task InputData(IConnection connection)
|
||||
{
|
||||
Memory<byte> receive = connection.ReceiveData;
|
||||
//去掉表示数据长度的4字节
|
||||
Memory<byte> readReceive = receive.Slice(4);
|
||||
MessageResponseWrap responseWrap = connection.ReceiveResponseWrap;
|
||||
MessageRequestWrap requestWrap = connection.ReceiveRequestWrap;
|
||||
try
|
||||
{
|
||||
//回复的消息
|
||||
if ((MessageTypes)(readReceive.Span[0] & 0b0000_1111) == MessageTypes.RESPONSE)
|
||||
{
|
||||
responseWrap.FromArray(readReceive);
|
||||
messengerSender.Response(responseWrap);
|
||||
return;
|
||||
}
|
||||
|
||||
//新的请求
|
||||
requestWrap.FromArray(readReceive);
|
||||
//404,没这个插件
|
||||
if (messengers.TryGetValue(requestWrap.MessengerId, out MessengerCacheInfo plugin) == false)
|
||||
{
|
||||
if (requestWrap.Reply == true)
|
||||
{
|
||||
bool res = await messengerSender.ReplyOnly(new MessageResponseWrap
|
||||
{
|
||||
Connection = connection,
|
||||
Code = MessageResponeCodes.NOT_FOUND,
|
||||
RequestId = requestWrap.RequestId
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (plugin.VoidMethod != null)
|
||||
{
|
||||
plugin.VoidMethod(connection);
|
||||
}
|
||||
else if (plugin.TaskMethod != null)
|
||||
{
|
||||
await plugin.TaskMethod(connection);
|
||||
}
|
||||
|
||||
if (requestWrap.Reply == true)
|
||||
{
|
||||
bool res = await messengerSender.ReplyOnly(new MessageResponseWrap
|
||||
{
|
||||
Connection = connection,
|
||||
Payload = connection.ResponseData,
|
||||
RequestId = requestWrap.RequestId
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
connection.Return();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 消息插件缓存
|
||||
/// </summary>
|
||||
private struct MessengerCacheInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 对象
|
||||
/// </summary>
|
||||
public object Target { get; set; }
|
||||
/// <summary>
|
||||
/// 空返回方法
|
||||
/// </summary>
|
||||
public VoidDelegate VoidMethod { get; set; }
|
||||
/// <summary>
|
||||
/// Task返回方法
|
||||
/// </summary>
|
||||
public TaskDelegate TaskMethod { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
156
cmonitor/server/service/MessengerSender.cs
Normal file
156
cmonitor/server/service/MessengerSender.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using common.libs;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace cmonitor.server.service
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息发送器
|
||||
/// </summary>
|
||||
public sealed class MessengerSender
|
||||
{
|
||||
public NumberSpaceUInt32 requestIdNumberSpace = new NumberSpaceUInt32(0);
|
||||
private WheelTimer<TimeoutState> wheelTimer = new WheelTimer<TimeoutState>();
|
||||
private ConcurrentDictionary<uint, WheelTimerTimeout<TimeoutState>> sends = new ConcurrentDictionary<uint, WheelTimerTimeout<TimeoutState>>();
|
||||
|
||||
public MessengerSender()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送并等待回复
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<MessageResponeInfo> SendReply(MessageRequestWrap msg)
|
||||
{
|
||||
if (msg.Connection == null || msg.Connection.Connected == false)
|
||||
{
|
||||
return new MessageResponeInfo { Code = MessageResponeCodes.NOT_CONNECT };
|
||||
}
|
||||
|
||||
if (msg.RequestId == 0)
|
||||
{
|
||||
uint id = msg.RequestId;
|
||||
Interlocked.CompareExchange(ref id, requestIdNumberSpace.Increment(), 0);
|
||||
msg.RequestId = id;
|
||||
}
|
||||
WheelTimerTimeout<TimeoutState> timeout = NewReply(msg);
|
||||
|
||||
bool res = await SendOnly(msg).ConfigureAwait(false);
|
||||
if (res == false)
|
||||
{
|
||||
sends.TryRemove(msg.RequestId, out _);
|
||||
timeout.Cancel();
|
||||
timeout.Task.State.Tcs.SetResult(new MessageResponeInfo { Code = MessageResponeCodes.NOT_CONNECT });
|
||||
}
|
||||
|
||||
return await timeout.Task.State.Tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 只发送,不等回复
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> SendOnly(MessageRequestWrap msg)
|
||||
{
|
||||
if (msg.Connection == null || msg.Connection.Connected == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (msg.RequestId == 0)
|
||||
{
|
||||
uint id = msg.RequestId;
|
||||
Interlocked.CompareExchange(ref id, requestIdNumberSpace.Increment(), 0);
|
||||
msg.RequestId = id;
|
||||
}
|
||||
|
||||
byte[] bytes = msg.ToArray(out int length);
|
||||
bool res = await msg.Connection.Send(bytes.AsMemory(0, length)).ConfigureAwait(false);
|
||||
msg.Return(bytes);
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回复远程消息,收到某个连接的消息后,通过这个再返回消息给它
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
/// <returns></returns>
|
||||
public async ValueTask<bool> ReplyOnly(MessageResponseWrap msg)
|
||||
{
|
||||
if (msg.Connection == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
byte[] bytes = msg.ToArray(out int length);
|
||||
bool res = await msg.Connection.Send(bytes.AsMemory(0, length)).ConfigureAwait(false);
|
||||
msg.Return(bytes);
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/// <summary>
|
||||
/// 回复本地消息,发送消息后,socket收到消息,通过这个方法回复给刚刚发送的对象
|
||||
/// </summary>
|
||||
/// <param name="wrap"></param>
|
||||
public void Response(MessageResponseWrap wrap)
|
||||
{
|
||||
if (sends.TryRemove(wrap.RequestId, out WheelTimerTimeout<TimeoutState> timeout))
|
||||
{
|
||||
timeout.Cancel();
|
||||
timeout.Task.State.Tcs.SetResult(new MessageResponeInfo { Code = wrap.Code, Data = wrap.Payload });
|
||||
}
|
||||
}
|
||||
|
||||
private WheelTimerTimeout<TimeoutState> NewReply(MessageRequestWrap msg)
|
||||
{
|
||||
msg.Reply = true;
|
||||
if (msg.Timeout <= 0)
|
||||
{
|
||||
msg.Timeout = 15000;
|
||||
}
|
||||
WheelTimerTimeout<TimeoutState> timeout = wheelTimer.NewTimeout(new WheelTimerTimeoutTask<TimeoutState>
|
||||
{
|
||||
Callback = TimeoutCallback,
|
||||
State = new TimeoutState { RequestId = msg.RequestId, Tcs = new TaskCompletionSource<MessageResponeInfo>() }
|
||||
}, msg.Timeout);
|
||||
sends.TryAdd(msg.RequestId, timeout);
|
||||
return timeout;
|
||||
}
|
||||
private void TimeoutCallback(WheelTimerTimeout<TimeoutState> timeout)
|
||||
{
|
||||
sends.TryRemove(timeout.Task.State.RequestId, out _);
|
||||
timeout.Task.State.Tcs.SetResult(new MessageResponeInfo { Code = MessageResponeCodes.TIMEOUT });
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class MessageResponeInfo
|
||||
{
|
||||
public MessageResponeCodes Code { get; set; }
|
||||
public ReadOnlyMemory<byte> Data { get; set; }
|
||||
}
|
||||
|
||||
public sealed class TimeoutState
|
||||
{
|
||||
public uint RequestId { get; set; }
|
||||
public TaskCompletionSource<MessageResponeInfo> Tcs { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
185
cmonitor/server/service/ServerMessageWrap.cs
Normal file
185
cmonitor/server/service/ServerMessageWrap.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
using common.libs.extends;
|
||||
using System.Buffers;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace cmonitor.server.service
|
||||
{
|
||||
public sealed class MessageRequestWrap
|
||||
{
|
||||
public int Timeout { get; set; }
|
||||
public ushort MessengerId { get; set; }
|
||||
public uint RequestId { get; set; }
|
||||
public bool Reply { get; internal set; }
|
||||
public Memory<byte> Payload { get; set; }
|
||||
|
||||
public IConnection Connection { get; set; }
|
||||
|
||||
public byte[] ToArray(out int length)
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
length = 4
|
||||
+ 1 //Reply + type
|
||||
+ 4
|
||||
+ 2
|
||||
+ Payload.Length;
|
||||
|
||||
byte[] res = ArrayPool<byte>.Shared.Rent(length);
|
||||
|
||||
((uint)length - 4).ToBytes(res);
|
||||
index += 4;
|
||||
|
||||
res[index] = (byte)((Reply ? 1 : 0) << 4 | (byte)MessageTypes.REQUEST);
|
||||
index += 1;
|
||||
|
||||
RequestId.ToBytes(res.AsMemory(index));
|
||||
index += 4;
|
||||
|
||||
MessengerId.ToBytes(res.AsMemory(index));
|
||||
index += 2;
|
||||
|
||||
Payload.CopyTo(res.AsMemory(index, Payload.Length));
|
||||
index += Payload.Length;
|
||||
|
||||
return res;
|
||||
}
|
||||
public unsafe void FromArray(Memory<byte> memory)
|
||||
{
|
||||
var span = memory.Span;
|
||||
|
||||
int index = 0;
|
||||
|
||||
Reply = span[index] >> 4 == 1;
|
||||
index += 1;
|
||||
|
||||
|
||||
RequestId = span.Slice(index).ToUInt32();
|
||||
index += 4;
|
||||
|
||||
MessengerId = span.Slice(index).ToUInt16();
|
||||
index += 2;
|
||||
|
||||
Payload = memory.Slice(index, memory.Length - index);
|
||||
}
|
||||
public void Return(byte[] array)
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(array);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回执消息包
|
||||
/// </summary>
|
||||
public sealed class MessageResponseWrap
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public IConnection Connection { get; set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public MessageResponeCodes Code { get; set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public uint RequestId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Memory<byte> Payload { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 转包
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public byte[] ToArray(out int length)
|
||||
{
|
||||
length = 4
|
||||
+ 1 //type
|
||||
+ 1 //code
|
||||
+ 4 //requestid
|
||||
+ Payload.Length;
|
||||
|
||||
byte[] res = ArrayPool<byte>.Shared.Rent(length);
|
||||
|
||||
int index = 0;
|
||||
((uint)length - 4).ToBytes(res);
|
||||
index += 4;
|
||||
|
||||
res[index] = (byte)MessageTypes.RESPONSE;
|
||||
index += 1;
|
||||
|
||||
res[index] = (byte)Code;
|
||||
index += 1;
|
||||
|
||||
RequestId.ToBytes(res.AsMemory(index));
|
||||
index += 4;
|
||||
|
||||
if (Payload.Length > 0)
|
||||
{
|
||||
Payload.CopyTo(res.AsMemory(index, Payload.Length));
|
||||
index += Payload.Length;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
/// <summary>
|
||||
/// 解包
|
||||
/// </summary>
|
||||
/// <param name="memory"></param>
|
||||
public void FromArray(Memory<byte> memory)
|
||||
{
|
||||
var span = memory.Span;
|
||||
int index = 0;
|
||||
|
||||
index += 1;
|
||||
|
||||
Code = (MessageResponeCodes)span[index];
|
||||
index += 1;
|
||||
|
||||
RequestId = span.Slice(index).ToUInt32();
|
||||
index += 4;
|
||||
|
||||
Payload = memory.Slice(index, memory.Length - index);
|
||||
}
|
||||
|
||||
public void Return(byte[] array)
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(array);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息状态
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum MessageResponeCodes : byte
|
||||
{
|
||||
[Description("成功")]
|
||||
OK = 0,
|
||||
[Description("网络未连接")]
|
||||
NOT_CONNECT = 1,
|
||||
[Description("网络资源未找到")]
|
||||
NOT_FOUND = 2,
|
||||
[Description("网络超时")]
|
||||
TIMEOUT = 3,
|
||||
[Description("程序错误")]
|
||||
ERROR = 4,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息类别
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum MessageTypes : byte
|
||||
{
|
||||
[Description("请求")]
|
||||
REQUEST = 0,
|
||||
[Description("回复")]
|
||||
RESPONSE = 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
316
cmonitor/server/service/TcpServer.cs
Normal file
316
cmonitor/server/service/TcpServer.cs
Normal file
@@ -0,0 +1,316 @@
|
||||
using common.libs;
|
||||
using common.libs.extends;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace cmonitor.server.service
|
||||
{
|
||||
public sealed class TcpServer
|
||||
{
|
||||
private int bufferSize = 8 * 1024;
|
||||
private Socket socket;
|
||||
private UdpClient socketUdp;
|
||||
private CancellationTokenSource cancellationTokenSource;
|
||||
public Func<IConnection, Task> OnPacket { get; set; } = async (connection) => { await Task.CompletedTask; };
|
||||
|
||||
private readonly Config config;
|
||||
public TcpServer(Config config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
public void Start()
|
||||
{
|
||||
if (socket == null)
|
||||
{
|
||||
cancellationTokenSource = new CancellationTokenSource();
|
||||
socket = BindAccept();
|
||||
}
|
||||
}
|
||||
|
||||
private Socket BindAccept()
|
||||
{
|
||||
IPEndPoint localEndPoint = new IPEndPoint(NetworkHelper.IPv6Support ? IPAddress.IPv6Any : IPAddress.Any, config.ServicePort);
|
||||
Socket socket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
socket.IPv6Only(localEndPoint.AddressFamily, false);
|
||||
socket.ReuseBind(localEndPoint);
|
||||
socket.Listen(int.MaxValue);
|
||||
|
||||
SocketAsyncEventArgs acceptEventArg = new SocketAsyncEventArgs
|
||||
{
|
||||
UserToken = new AsyncUserToken
|
||||
{
|
||||
Socket = socket
|
||||
},
|
||||
SocketFlags = SocketFlags.None,
|
||||
};
|
||||
acceptEventArg.Completed += IO_Completed;
|
||||
StartAccept(acceptEventArg);
|
||||
|
||||
socketUdp = new UdpClient(new IPEndPoint(IPAddress.Any, config.ServicePort));
|
||||
//socketUdp.JoinMulticastGroup(config.BroadcastIP);
|
||||
socketUdp.Client.EnableBroadcast = true;
|
||||
socketUdp.Client.WindowsUdpBug();
|
||||
IAsyncResult result = socketUdp.BeginReceive(ReceiveCallbackUdp, null);
|
||||
|
||||
|
||||
return socket;
|
||||
|
||||
}
|
||||
private async void ReceiveCallbackUdp(IAsyncResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, IPEndPoint.MinPort);
|
||||
byte[] bytes = socketUdp.EndReceive(result, ref endPoint);
|
||||
try
|
||||
{
|
||||
IPHostEntry entry = Dns.GetHostEntry(Dns.GetHostName());
|
||||
|
||||
List<IPAddress> ips = entry.AddressList.Where(c => c.AddressFamily == AddressFamily.InterNetwork).Distinct().ToList();
|
||||
Dictionary<IPAddress, BroadcastEndpointInfo> dic = new Dictionary<IPAddress, BroadcastEndpointInfo>();
|
||||
foreach (var item in ips)
|
||||
{
|
||||
dic.Add(item, new BroadcastEndpointInfo
|
||||
{
|
||||
Web = config.WebPort,
|
||||
Api = config.ApiPort,
|
||||
Service = config.ServicePort
|
||||
});
|
||||
}
|
||||
|
||||
await socketUdp.SendAsync(dic.ToJson().ToBytes(), endPoint);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
result = socketUdp.BeginReceive(ReceiveCallbackUdp, null);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void StartAccept(SocketAsyncEventArgs acceptEventArg)
|
||||
{
|
||||
acceptEventArg.AcceptSocket = null;
|
||||
AsyncUserToken token = (AsyncUserToken)acceptEventArg.UserToken;
|
||||
try
|
||||
{
|
||||
if (token.Socket.AcceptAsync(acceptEventArg) == false)
|
||||
{
|
||||
ProcessAccept(acceptEventArg);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
token.Clear();
|
||||
}
|
||||
}
|
||||
private void IO_Completed(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
switch (e.LastOperation)
|
||||
{
|
||||
case SocketAsyncOperation.Accept:
|
||||
ProcessAccept(e);
|
||||
break;
|
||||
case SocketAsyncOperation.Receive:
|
||||
ProcessReceive(e);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void ProcessAccept(SocketAsyncEventArgs e)
|
||||
{
|
||||
if (e.AcceptSocket != null)
|
||||
{
|
||||
e.AcceptSocket.KeepAlive();
|
||||
BindReceive(e.AcceptSocket);
|
||||
StartAccept(e);
|
||||
}
|
||||
}
|
||||
|
||||
public IConnection BindReceive(Socket socket)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (socket == null || socket.RemoteEndPoint == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
AsyncUserToken userToken = new AsyncUserToken
|
||||
{
|
||||
Socket = socket,
|
||||
Connection = CreateConnection(socket)
|
||||
};
|
||||
|
||||
SocketAsyncEventArgs readEventArgs = new SocketAsyncEventArgs
|
||||
{
|
||||
UserToken = userToken,
|
||||
SocketFlags = SocketFlags.None,
|
||||
};
|
||||
userToken.PoolBuffer = new byte[bufferSize];
|
||||
readEventArgs.SetBuffer(userToken.PoolBuffer, 0, bufferSize);
|
||||
readEventArgs.Completed += IO_Completed;
|
||||
if (socket.ReceiveAsync(readEventArgs) == false)
|
||||
{
|
||||
ProcessReceive(readEventArgs);
|
||||
}
|
||||
return userToken.Connection;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private async void ProcessReceive(SocketAsyncEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
AsyncUserToken token = (AsyncUserToken)e.UserToken;
|
||||
|
||||
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
|
||||
{
|
||||
int offset = e.Offset;
|
||||
int length = e.BytesTransferred;
|
||||
await ReadPacket(token, e.Buffer, offset, length);
|
||||
|
||||
if (token.Socket.Available > 0)
|
||||
{
|
||||
while (token.Socket.Available > 0)
|
||||
{
|
||||
length = token.Socket.Receive(e.Buffer);
|
||||
if (length > 0)
|
||||
{
|
||||
await ReadPacket(token, e.Buffer, 0, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseClientSocket(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (token.Socket.Connected == false)
|
||||
{
|
||||
CloseClientSocket(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.Socket.ReceiveAsync(e) == false)
|
||||
{
|
||||
ProcessReceive(e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseClientSocket(e);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
Logger.Instance.Error(ex);
|
||||
|
||||
CloseClientSocket(e);
|
||||
}
|
||||
}
|
||||
private async Task ReadPacket(AsyncUserToken token, byte[] data, int offset, int length)
|
||||
{
|
||||
//是一个完整的包
|
||||
if (token.DataBuffer.Size == 0 && length > 4)
|
||||
{
|
||||
Memory<byte> memory = data.AsMemory(offset, length);
|
||||
int packageLen = memory.Span.ToInt32();
|
||||
if (packageLen == length - 4)
|
||||
{
|
||||
token.Connection.ReceiveData = data.AsMemory(offset, packageLen + 4);
|
||||
await OnPacket(token.Connection);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//不是完整包
|
||||
token.DataBuffer.AddRange(data, offset, length);
|
||||
do
|
||||
{
|
||||
int packageLen = token.DataBuffer.Data.Span.ToInt32();
|
||||
if (packageLen > token.DataBuffer.Size - 4)
|
||||
{
|
||||
break;
|
||||
}
|
||||
token.Connection.ReceiveData = token.DataBuffer.Data.Slice(0, packageLen + 4);
|
||||
await OnPacket(token.Connection);
|
||||
|
||||
token.DataBuffer.RemoveRange(0, packageLen + 4);
|
||||
} while (token.DataBuffer.Size > 4);
|
||||
}
|
||||
|
||||
private void CloseClientSocket(SocketAsyncEventArgs e)
|
||||
{
|
||||
AsyncUserToken token = e.UserToken as AsyncUserToken;
|
||||
if (token.Socket != null)
|
||||
{
|
||||
token.Clear();
|
||||
e.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public IConnection CreateConnection(Socket socket)
|
||||
{
|
||||
return new TcpConnection(socket)
|
||||
{
|
||||
ReceiveRequestWrap = new MessageRequestWrap(),
|
||||
ReceiveResponseWrap = new MessageResponseWrap()
|
||||
};
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
cancellationTokenSource?.Cancel();
|
||||
socket?.SafeClose();
|
||||
socket = null;
|
||||
}
|
||||
public void Disponse()
|
||||
{
|
||||
Stop();
|
||||
OnPacket = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed class AsyncUserToken
|
||||
{
|
||||
public IConnection Connection { get; set; }
|
||||
public Socket Socket { get; set; }
|
||||
public ReceiveDataBuffer DataBuffer { get; set; } = new ReceiveDataBuffer();
|
||||
public byte[] PoolBuffer { get; set; }
|
||||
public void Clear()
|
||||
{
|
||||
Socket?.SafeClose();
|
||||
Socket = null;
|
||||
|
||||
PoolBuffer = Helper.EmptyArray;
|
||||
|
||||
DataBuffer.Clear(true);
|
||||
|
||||
GC.Collect();
|
||||
// GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BroadcastEndpointInfo
|
||||
{
|
||||
public int Web { get; set; }
|
||||
public int Api { get; set; }
|
||||
public int Service { get; set; }
|
||||
}
|
||||
}
|
||||
39
cmonitor/server/service/messengers/active/ActiveMessenger.cs
Normal file
39
cmonitor/server/service/messengers/active/ActiveMessenger.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using cmonitor.server.api;
|
||||
using cmonitor.server.client;
|
||||
using cmonitor.server.client.reports.active;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using common.libs;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.service.messengers.active
|
||||
{
|
||||
public sealed class ActiveMessenger : IMessenger
|
||||
{
|
||||
private readonly ActiveWindowReport activeWindowReport;
|
||||
|
||||
public ActiveMessenger(ActiveWindowReport activeWindowReport)
|
||||
{
|
||||
this.activeWindowReport = activeWindowReport;
|
||||
}
|
||||
|
||||
[MessengerId((ushort)ActiveMessengerIds.Get)]
|
||||
public void Get(IConnection connection)
|
||||
{
|
||||
connection.Write(MemoryPackSerializer.Serialize(activeWindowReport.GetActiveWindowTimes()));
|
||||
}
|
||||
[MessengerId((ushort)ActiveMessengerIds.Clear)]
|
||||
public void Clear(IConnection connection)
|
||||
{
|
||||
activeWindowReport.ClearActiveWindowTimes();
|
||||
connection.Write(Helper.TrueArray);
|
||||
}
|
||||
|
||||
[MessengerId((ushort)ActiveMessengerIds.Disallow)]
|
||||
public void Disallow(IConnection connection)
|
||||
{
|
||||
activeWindowReport.DisallowRun(MemoryPackSerializer.Deserialize<string[]>(connection.ReceiveRequestWrap.Payload.Span));
|
||||
connection.Write(Helper.TrueArray);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace cmonitor.server.service.messengers.active
|
||||
{
|
||||
public enum ActiveMessengerIds : ushort
|
||||
{
|
||||
Get = 400,
|
||||
Clear = 401,
|
||||
Disallow = 402,
|
||||
|
||||
None = 499
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using cmonitor.server.service.messengers.command;
|
||||
using common.libs;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.service.messengers.report
|
||||
{
|
||||
public sealed class CommandMessenger : IMessenger
|
||||
{
|
||||
public CommandMessenger()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
[MessengerId((ushort)CommandMessengerIds.Exec)]
|
||||
public void Exec(IConnection connection)
|
||||
{
|
||||
string[] commands = MemoryPackSerializer.Deserialize<string[]>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
Task.Run(() =>
|
||||
{
|
||||
CommandHelper.Windows(string.Empty, commands);
|
||||
});
|
||||
|
||||
connection.Write(Helper.TrueArray);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace cmonitor.server.service.messengers.command
|
||||
{
|
||||
public enum CommandMessengerIds : ushort
|
||||
{
|
||||
Exec = 200,
|
||||
|
||||
None = 299,
|
||||
}
|
||||
}
|
||||
63
cmonitor/server/service/messengers/hijack/HijackMessenger.cs
Normal file
63
cmonitor/server/service/messengers/hijack/HijackMessenger.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using cmonitor.hijack;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.service.messengers.hijack
|
||||
{
|
||||
public sealed class HijackMessenger : IMessenger
|
||||
{
|
||||
private readonly HijackConfig hijackConfig;
|
||||
private readonly HijackController hijackController;
|
||||
|
||||
public HijackMessenger(HijackConfig hijackConfig, HijackController hijackController)
|
||||
{
|
||||
this.hijackConfig = hijackConfig;
|
||||
this.hijackController = hijackController;
|
||||
}
|
||||
|
||||
[MessengerId((ushort)HijackMessengerIds.Update)]
|
||||
public void Update(IConnection connection)
|
||||
{
|
||||
SetRuleInfo info = MemoryPackSerializer.Deserialize<SetRuleInfo>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
|
||||
hijackConfig.AllowDomains = info.AllowDomains;
|
||||
hijackConfig.DeniedDomains = info.DeniedDomains;
|
||||
hijackConfig.AllowProcesss = info.AllowProcesss;
|
||||
hijackConfig.DeniedProcesss = info.DeniedProcesss;
|
||||
hijackConfig.AllowIPs = info.AllowIPs;
|
||||
hijackConfig.DeniedIPs = info.DeniedIPs;
|
||||
hijackController.SetRules();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public sealed partial class SetRuleInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 进程白名单
|
||||
/// </summary>
|
||||
public string[] AllowProcesss { get; set; } = Array.Empty<string>();
|
||||
/// <summary>
|
||||
/// 进程黑名单
|
||||
/// </summary>
|
||||
public string[] DeniedProcesss { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 域名白名单
|
||||
/// </summary>
|
||||
public string[] AllowDomains { get; set; } = Array.Empty<string>();
|
||||
/// <summary>
|
||||
/// 域名黑名单
|
||||
/// </summary>
|
||||
public string[] DeniedDomains { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// ip白名单
|
||||
/// </summary>
|
||||
public string[] AllowIPs { get; set; } = Array.Empty<string>();
|
||||
/// <summary>
|
||||
/// ip黑名单
|
||||
/// </summary>
|
||||
public string[] DeniedIPs { get; set; } = Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace cmonitor.server.service.messengers.hijack
|
||||
{
|
||||
public enum HijackMessengerIds : ushort
|
||||
{
|
||||
Update = 300,
|
||||
|
||||
None = 399
|
||||
}
|
||||
}
|
||||
23
cmonitor/server/service/messengers/light/LightMessenger.cs
Normal file
23
cmonitor/server/service/messengers/light/LightMessenger.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using cmonitor.server.client.reports.light;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.service.messengers.light
|
||||
{
|
||||
public sealed class LightMessenger : IMessenger
|
||||
{
|
||||
private readonly LightReport lightReport;
|
||||
public LightMessenger(LightReport lightReport)
|
||||
{
|
||||
this.lightReport = lightReport;
|
||||
}
|
||||
|
||||
[MessengerId((ushort)LightMessengerIds.Update)]
|
||||
public void Update(IConnection connection)
|
||||
{
|
||||
int value = MemoryPackSerializer.Deserialize<int>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
lightReport.SetLight(value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace cmonitor.server.service.messengers.light
|
||||
{
|
||||
public enum LightMessengerIds : ushort
|
||||
{
|
||||
Update = 1000,
|
||||
|
||||
None = 1099
|
||||
}
|
||||
}
|
||||
23
cmonitor/server/service/messengers/llock/LLockMessenger.cs
Normal file
23
cmonitor/server/service/messengers/llock/LLockMessenger.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using cmonitor.server.client.reports.llock;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.service.messengers.llock
|
||||
{
|
||||
public sealed class LLockMessenger : IMessenger
|
||||
{
|
||||
private readonly LLockReport lLockReport;
|
||||
|
||||
public LLockMessenger(LLockReport lLockReport)
|
||||
{
|
||||
this.lLockReport = lLockReport;
|
||||
}
|
||||
|
||||
[MessengerId((ushort)LLockMessengerIds.Update)]
|
||||
public void Update(IConnection connection)
|
||||
{
|
||||
lLockReport.Update(MemoryPackSerializer.Deserialize<bool>(connection.ReceiveRequestWrap.Payload.Span));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace cmonitor.server.service.messengers.llock
|
||||
{
|
||||
public enum LLockMessengerIds : ushort
|
||||
{
|
||||
Update = 500,
|
||||
|
||||
None = 599
|
||||
}
|
||||
}
|
||||
49
cmonitor/server/service/messengers/report/ReportMessenger.cs
Normal file
49
cmonitor/server/service/messengers/report/ReportMessenger.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using cmonitor.server.api;
|
||||
using cmonitor.server.client.reports;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using common.libs;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.service.messengers.report
|
||||
{
|
||||
public sealed class ReportMessenger : IMessenger
|
||||
{
|
||||
private readonly IClientServer clientServer;
|
||||
private readonly ReportTransfer reportTransfer;
|
||||
|
||||
public ReportMessenger(IClientServer clientServer , ReportTransfer reportTransfer)
|
||||
{
|
||||
this.clientServer = clientServer;
|
||||
this.reportTransfer = reportTransfer;
|
||||
}
|
||||
|
||||
[MessengerId((ushort)ReportMessengerIds.Update)]
|
||||
public void Update(IConnection connection)
|
||||
{
|
||||
reportTransfer.Update();
|
||||
connection.Write(Helper.TrueArray);
|
||||
}
|
||||
|
||||
[MessengerId((ushort)ReportMessengerIds.Report)]
|
||||
public void Report(IConnection connection)
|
||||
{
|
||||
string report = MemoryPackSerializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
clientServer.Notify("/notify/report/report", new { connection.Name, Report = report });
|
||||
}
|
||||
|
||||
|
||||
[MessengerId((ushort)ReportMessengerIds.Ping)]
|
||||
public void Ping(IConnection connection)
|
||||
{
|
||||
connection.Write(Helper.TrueArray);
|
||||
}
|
||||
|
||||
[MessengerId((ushort)ReportMessengerIds.Pong)]
|
||||
public void Pong(IConnection connection)
|
||||
{
|
||||
connection.Write(Helper.TrueArray);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace cmonitor.server.service.messengers.sign
|
||||
{
|
||||
public enum ReportMessengerIds : ushort
|
||||
{
|
||||
Report = 100,
|
||||
Update = 101,
|
||||
Ping = 102,
|
||||
Pong = 103,
|
||||
None = 199
|
||||
}
|
||||
}
|
||||
48
cmonitor/server/service/messengers/screen/ScreenMessenger.cs
Normal file
48
cmonitor/server/service/messengers/screen/ScreenMessenger.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using cmonitor.server.api;
|
||||
using cmonitor.server.client.reports.screen;
|
||||
using cmonitor.server.service.messengers.sign;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.service.messengers.screen
|
||||
{
|
||||
public sealed class ScreenMessenger : IMessenger
|
||||
{
|
||||
private readonly ScreenReport screenReport;
|
||||
private readonly IClientServer clientServer;
|
||||
private readonly Config config;
|
||||
private readonly SignCaching signCaching;
|
||||
|
||||
public ScreenMessenger(ScreenReport screenReport, IClientServer clientServer, Config config, SignCaching signCaching)
|
||||
{
|
||||
this.screenReport = screenReport;
|
||||
this.clientServer = clientServer;
|
||||
this.config = config;
|
||||
this.signCaching = signCaching;
|
||||
}
|
||||
|
||||
[MessengerId((ushort)ScreenMessengerIds.Update)]
|
||||
public void Update(IConnection connection)
|
||||
{
|
||||
screenReport.Update();
|
||||
}
|
||||
|
||||
[MessengerId((ushort)ScreenMessengerIds.Report)]
|
||||
public void Report(IConnection connection)
|
||||
{
|
||||
if (signCaching.Get(connection.Name, out SignCacheInfo cache))
|
||||
{
|
||||
if (cache.Version == config.Version)
|
||||
{
|
||||
clientServer.Notify("/notify/report/screen", connection.Name, connection.ReceiveRequestWrap.Payload);
|
||||
//clientServer.Notify("/notify/report/screen", new { connection.Name, Img = connection.ReceiveRequestWrap.Payload.ToArray() });
|
||||
}
|
||||
else
|
||||
{
|
||||
string base64 = MemoryPackSerializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
clientServer.Notify("/notify/report/screen", new { connection.Name, Img = base64 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace cmonitor.server.service.messengers.screen
|
||||
{
|
||||
public enum ScreenMessengerIds : ushort
|
||||
{
|
||||
Update = 800,
|
||||
Report = 801,
|
||||
|
||||
None = 899
|
||||
}
|
||||
}
|
||||
131
cmonitor/server/service/messengers/sign/SignCaching.cs
Normal file
131
cmonitor/server/service/messengers/sign/SignCaching.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using common.libs.database;
|
||||
using MemoryPack;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace cmonitor.server.service.messengers.sign
|
||||
{
|
||||
public sealed class SignCaching
|
||||
{
|
||||
private readonly IConfigDataProvider<SignCacheFileInfo> configDataProvider;
|
||||
private SignCacheFileInfo config;
|
||||
private bool changed = false;
|
||||
|
||||
public SignCaching(IConfigDataProvider<SignCacheFileInfo> configDataProvider)
|
||||
{
|
||||
this.configDataProvider = configDataProvider;
|
||||
config = configDataProvider.Load().Result ?? new SignCacheFileInfo();
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
public void Sign(IConnection connection, SignInfo signInfo)
|
||||
{
|
||||
if (config.Clients.TryRemove(signInfo.MachineName, out SignCacheInfo cache))
|
||||
{
|
||||
cache.Connection?.Disponse();
|
||||
}
|
||||
connection.Name = signInfo.MachineName;
|
||||
cache = new SignCacheInfo
|
||||
{
|
||||
Connection = connection,
|
||||
MachineName = signInfo.MachineName,
|
||||
Version = signInfo.Version
|
||||
};
|
||||
config.Clients.TryAdd(signInfo.MachineName, cache);
|
||||
changed = true;
|
||||
}
|
||||
public bool Get(string machineName, out SignCacheInfo cache)
|
||||
{
|
||||
return config.Clients.TryGetValue(machineName, out cache);
|
||||
}
|
||||
public List<SignCacheInfo> Get()
|
||||
{
|
||||
return config.Clients.Values.ToList();
|
||||
}
|
||||
|
||||
public bool Del(string machineName)
|
||||
{
|
||||
bool res = config.Clients.TryRemove(machineName, out _);
|
||||
changed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SaveConfig()
|
||||
{
|
||||
Task.Factory.StartNew(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (changed == true)
|
||||
{
|
||||
changed = false;
|
||||
configDataProvider.Save(config).Wait();
|
||||
}
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
}
|
||||
}
|
||||
|
||||
[Table("sign-cache")]
|
||||
public sealed class SignCacheFileInfo
|
||||
{
|
||||
public ConcurrentDictionary<string, SignCacheInfo> Clients { get; set; } = new ConcurrentDictionary<string, SignCacheInfo>();
|
||||
}
|
||||
|
||||
public sealed class SignCacheInfo
|
||||
{
|
||||
public string MachineName { get; set; }
|
||||
public string Version { get; set; } = "1.0.0.0";
|
||||
|
||||
[JsonIgnore]
|
||||
public int ReportFlag = 1;
|
||||
[JsonIgnore]
|
||||
public int ReportTime = Environment.TickCount;
|
||||
[JsonIgnore]
|
||||
public int PingFlag = 1;
|
||||
[JsonIgnore]
|
||||
public int ScreenFlag = 1;
|
||||
[JsonIgnore]
|
||||
public int ScreenTime = Environment.TickCount;
|
||||
|
||||
public bool GetReport()
|
||||
{
|
||||
return Environment.TickCount - ReportTime > Config.ReportTime;
|
||||
}
|
||||
public void UpdateReport()
|
||||
{
|
||||
ReportTime = Environment.TickCount;
|
||||
}
|
||||
|
||||
public bool GetScreen()
|
||||
{
|
||||
return Environment.TickCount - ScreenTime > Config.ScreenTime;
|
||||
}
|
||||
public void UpdateScreen()
|
||||
{
|
||||
ScreenTime = Environment.TickCount;
|
||||
}
|
||||
|
||||
|
||||
public bool Connected
|
||||
{
|
||||
get
|
||||
{
|
||||
return Connection != null && Connection.Connected == true;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public IConnection Connection { get; set; }
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public sealed partial class SignInfo
|
||||
{
|
||||
public string MachineName { get; set; }
|
||||
public string Version { get; set; }
|
||||
}
|
||||
}
|
||||
23
cmonitor/server/service/messengers/sign/SignInMessenger.cs
Normal file
23
cmonitor/server/service/messengers/sign/SignInMessenger.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using common.libs;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.service.messengers.sign
|
||||
{
|
||||
public sealed class SignInMessenger : IMessenger
|
||||
{
|
||||
private readonly SignCaching signCaching;
|
||||
public SignInMessenger(SignCaching signCaching)
|
||||
{
|
||||
this.signCaching = signCaching;
|
||||
}
|
||||
|
||||
[MessengerId((ushort)SignInMessengerIds.SignIn)]
|
||||
public void SignIn(IConnection connection)
|
||||
{
|
||||
signCaching.Sign(connection, MemoryPackSerializer.Deserialize<SignInfo>(connection.ReceiveRequestWrap.Payload.Span));
|
||||
connection.Write(Helper.TrueArray);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace cmonitor.server.service.messengers.sign
|
||||
{
|
||||
public enum SignInMessengerIds : ushort
|
||||
{
|
||||
SignIn = 0,
|
||||
|
||||
None = 99
|
||||
}
|
||||
}
|
||||
22
cmonitor/server/service/messengers/usb/UsbMessenger.cs
Normal file
22
cmonitor/server/service/messengers/usb/UsbMessenger.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using cmonitor.server.client.reports.llock;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.service.messengers.usb
|
||||
{
|
||||
public sealed class UsbMessenger : IMessenger
|
||||
{
|
||||
private readonly UsbReport usbReport;
|
||||
|
||||
public UsbMessenger(UsbReport usbReport)
|
||||
{
|
||||
this.usbReport = usbReport;
|
||||
}
|
||||
|
||||
[MessengerId((ushort)UsbMessengerIds.Update)]
|
||||
public void Update(IConnection connection)
|
||||
{
|
||||
usbReport.Update(MemoryPackSerializer.Deserialize<bool>(connection.ReceiveRequestWrap.Payload.Span));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace cmonitor.server.service.messengers.usb
|
||||
{
|
||||
public enum UsbMessengerIds : ushort
|
||||
{
|
||||
Update = 600,
|
||||
|
||||
None = 699
|
||||
}
|
||||
}
|
||||
29
cmonitor/server/service/messengers/volume/VolumeMessenger.cs
Normal file
29
cmonitor/server/service/messengers/volume/VolumeMessenger.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using cmonitor.server.client.reports.volume;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.service.messengers.volume
|
||||
{
|
||||
public sealed class VolumeMessenger : IMessenger
|
||||
{
|
||||
private readonly VolumeReport volumeReport;
|
||||
public VolumeMessenger(VolumeReport volumeReport)
|
||||
{
|
||||
this.volumeReport = volumeReport;
|
||||
}
|
||||
|
||||
[MessengerId((ushort)VolumeMessengerIds.Update)]
|
||||
public void Update(IConnection connection)
|
||||
{
|
||||
float value = MemoryPackSerializer.Deserialize<float>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
volumeReport.SetVolume(value);
|
||||
}
|
||||
|
||||
[MessengerId((ushort)VolumeMessengerIds.Mute)]
|
||||
public void Mute(IConnection connection)
|
||||
{
|
||||
bool value = MemoryPackSerializer.Deserialize<bool>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
volumeReport.SetVolumeMute(value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace cmonitor.server.service.messengers.volume
|
||||
{
|
||||
public enum VolumeMessengerIds : ushort
|
||||
{
|
||||
Update = 900,
|
||||
Mute = 901,
|
||||
|
||||
None = 999
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using cmonitor.server.client.reports.llock;
|
||||
using MemoryPack;
|
||||
|
||||
namespace cmonitor.server.service.messengers.wallpaper
|
||||
{
|
||||
public sealed class WallpaperMessenger : IMessenger
|
||||
{
|
||||
private readonly WallpaperReport wallpaperReport;
|
||||
|
||||
public WallpaperMessenger(WallpaperReport wallpaperReport)
|
||||
{
|
||||
this.wallpaperReport = wallpaperReport;
|
||||
}
|
||||
|
||||
[MessengerId((ushort)WallpaperMessengerIds.Update)]
|
||||
public void Update(IConnection connection)
|
||||
{
|
||||
WallpaperUpdateInfo wallpaperUpdateInfo = MemoryPackSerializer.Deserialize<WallpaperUpdateInfo>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
wallpaperReport.Update(wallpaperUpdateInfo.Value, wallpaperUpdateInfo.Url);
|
||||
}
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public sealed partial class WallpaperUpdateInfo
|
||||
{
|
||||
public bool Value { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace cmonitor.server.service.messengers.wallpaper
|
||||
{
|
||||
public enum WallpaperMessengerIds : ushort
|
||||
{
|
||||
Update = 700,
|
||||
|
||||
None = 799
|
||||
}
|
||||
}
|
||||
14
cmonitor/server/web/IWebServer.cs
Normal file
14
cmonitor/server/web/IWebServer.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace cmonitor.server.web
|
||||
{
|
||||
/// <summary>
|
||||
/// web服务
|
||||
/// </summary>
|
||||
public interface IWebServer
|
||||
{
|
||||
/// <summary>
|
||||
/// 开始
|
||||
/// </summary>
|
||||
public void Start();
|
||||
}
|
||||
|
||||
}
|
||||
100
cmonitor/server/web/WebServer.cs
Normal file
100
cmonitor/server/web/WebServer.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using common.libs;
|
||||
using System.Net;
|
||||
|
||||
namespace cmonitor.server.web
|
||||
{
|
||||
/// <summary>
|
||||
/// 本地web管理端服务器
|
||||
/// </summary>
|
||||
public sealed class WebServer : IWebServer
|
||||
{
|
||||
private readonly Config config;
|
||||
public WebServer(Config config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开启web
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
Task.Factory.StartNew(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpListener http = new HttpListener();
|
||||
http.Prefixes.Add($"http://+:{config.WebPort}/");
|
||||
http.Start();
|
||||
|
||||
while (true)
|
||||
{
|
||||
HttpListenerContext context = http.GetContext();
|
||||
HttpListenerRequest request = context.Request;
|
||||
using HttpListenerResponse response = context.Response;
|
||||
using Stream stream = response.OutputStream;
|
||||
|
||||
try
|
||||
{
|
||||
response.Headers.Set("Server", "snltty");
|
||||
|
||||
string path = request.Url.AbsolutePath;
|
||||
//默认页面
|
||||
if (path == "/") path = "index.html";
|
||||
|
||||
|
||||
path = Path.Join(config.WebRoot, path);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
byte[] bytes = File.ReadAllBytes(path);
|
||||
response.ContentLength64 = bytes.Length;
|
||||
response.ContentType = GetContentType(path);
|
||||
response.Headers.Set("Last-Modified", File.GetLastWriteTimeUtc(path).ToString());
|
||||
stream.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||
}
|
||||
stream.Close();
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Error(ex);
|
||||
}
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
}
|
||||
|
||||
|
||||
private Dictionary<string, string> types = new Dictionary<string, string> {
|
||||
{ ".webp","image/webp"},
|
||||
{ ".png","image/png"},
|
||||
{ ".jpg","image/jpg"},
|
||||
{ ".jpeg","image/jpeg"},
|
||||
{ ".gif","image/gif"},
|
||||
{ ".svg","image/svg+xml"},
|
||||
{ ".ico","image/x-icon"},
|
||||
{ ".js","text/javascript; charset=utf-8"},
|
||||
{ ".html","text/html; charset=utf-8"},
|
||||
{ ".css","text/css; charset=utf-8"},
|
||||
{ ".pac","application/x-ns-proxy-autoconfig; charset=utf-8"},
|
||||
};
|
||||
private string GetContentType(string path)
|
||||
{
|
||||
string ext = Path.GetExtension(path);
|
||||
if (types.ContainsKey(ext))
|
||||
{
|
||||
return types[ext];
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
BIN
cmonitor/web/bg.jpg
Normal file
BIN
cmonitor/web/bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 515 KiB |
BIN
cmonitor/web/cmonitor.win.exe
Normal file
BIN
cmonitor/web/cmonitor.win.exe
Normal file
Binary file not shown.
1
cmonitor/web/css/126.2953429d.css
Normal file
1
cmonitor/web/css/126.2953429d.css
Normal file
File diff suppressed because one or more lines are too long
1
cmonitor/web/css/app.97eae421.css
Normal file
1
cmonitor/web/css/app.97eae421.css
Normal file
@@ -0,0 +1 @@
|
||||
*{margin:0;padding:0;list-style:none}a{text-decoration:none;color:#6f9ccd}.flex{display:flex;display:-ms-flex;display:-o-flex;flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.flex-column{flex-direction:column}.flex-row{flex-direction:row}.flex-1{flex:1 1 0%}.absolute{position:absolute;left:0;top:0;right:0;bottom:0}.relative{position:relative}.h-100{height:100%}.w-100{width:100%}.t-c{text-align:center}.t-r{text-align:right}.t-l{text-align:left!important}.m-r-1{margin-right:1rem}table{border-spacing:0;border-collapse:collapse}html{font-size:10px;background-color:#f4f4f4}body{overflow:hidden}span.split{width:.6rem}span.split-pad{padding:0 .3rem}span.split-pad10{padding:0 1rem}.middle{vertical-align:middle}.red{color:red}.scrollbar,.scrollbar-10,.scrollbar-4{overflow:auto}.scrollbar::-webkit-scrollbar{width:4px;height:1px}.scrollbar::-webkit-scrollbar-thumb{background:rgba(0,0,0,.1);border-radius:10px}.scrollbar-4::-webkit-scrollbar{width:4px;height:4px}.scrollbar-4::-webkit-scrollbar-thumb{background:rgba(0,0,0,.1);border-radius:10px}.scrollbar-10::-webkit-scrollbar{width:10px;height:1px}.scrollbar-10::-webkit-scrollbar-thumb{background:rgba(0,0,0,.1);border-radius:10px}.el-table--scrollable-y .el-table__body-wrapper::-webkit-scrollbar{background:#f5f5f5}.el-table--scrollable-y .el-table__body-wrapper::-webkit-scrollbar-thumb{background:#ddd}.el-collapse-item__header{background-color:#fafafa!important;border-left:1px solid #ebeef5;border-right:1px solid #ebeef5;padding:0 2rem}.el-collapse-item__content{padding:1rem;border:1px solid #ebeef5;border-bottom:0}.el-input.w-search,.el-input.w-search .el-input__inner,.el-select.w-search{width:10rem}.el-form-item.w-search .el-form-item__label{font-size:1.2rem}.table-search .el-form--inline .el-form-item{margin-bottom:0}.el-dropdown,.el-dropdown-menu__item{font-size:1.3rem}.el-dropdown-menu__item a{color:#333}.el-input__inner:focus{border-color:var(--main-color)}.el-date-editor.el-input.w-auto,.el-date-editor.el-input__inner.w-auto{width:auto}.el-table .active-row{background:rgba(0,0,0,.15)}.el-table .table-green-row{background:rgba(0,255,0,.15)}.el-table .table-red-row{background:rgba(255,0,0,.15)}.el-table .table-green-row td,.el-table .table-red-row td{background:transparent!important}.el-date-editor.el-input,.el-date-editor.el-input__inner{width:auto}.el-table .active-row td{background:transparent!important}.el-table--border th{background-color:#fafafa}.el-table td,.el-table th.is-leaf,.el-table--border,.el-table--group,.el-table-filter{border-color:var(--main-border-color)}.el-pagination.is-background .el-pager li:not(.disabled).active{background-color:var(--main-color)}.el-pagination.is-background .el-pager li:not(.disabled):hover{color:var(--main-color)}.el-pagination .btn-next .el-icon,.el-pagination .btn-prev .el-icon{width:inherit}.el-dialog{max-width:96%}.el-dialog__body .el-form-item:last-child{margin-bottom:0}.el-input-group__append,.el-input-group__prepend{padding:0 4px!important;background-color:transparent!important}.el-checkbox__label .el-icon{vertical-align:middle;margin-top:-2px}.el-color-picker{vertical-align:middle}.el-color-picker__trigger{border:0!important}.el-color-picker__color{border:0!important;border-radius:2px}.el-color-picker__color-inner{border-radius:2px}.el-message{min-width:10rem!important}.card-header{font-size:1.4rem}.forward-wrap .el-table--small.el-table .el-table__expanded-cell[class*=cell]{padding:20px 50px 20px 50px}h3.title{font-size:1.6rem;padding-bottom:.6rem;color:#555}.el-message-box{max-width:90%}.el-select-dropdown__item{padding-right:2rem!important}.el-form-item--default{--font-size:13px!important}.el-input__inner{font-size:13px}.el-dialog--center .el-dialog__body{padding-top:1rem!important;padding-bottom:1rem!important}
|
||||
1
cmonitor/web/css/chunk-vendors.faad7142.css
Normal file
1
cmonitor/web/css/chunk-vendors.faad7142.css
Normal file
File diff suppressed because one or more lines are too long
BIN
cmonitor/web/favicon.ico
Normal file
BIN
cmonitor/web/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
cmonitor/web/img/bg.a75a2468.webp
Normal file
BIN
cmonitor/web/img/bg.a75a2468.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user