diff --git a/Apewer .NET.sln b/Apewer .NET.sln
new file mode 100644
index 0000000..cb16a2f
--- /dev/null
+++ b/Apewer .NET.sln
@@ -0,0 +1,43 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.35325.158
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Apewer", "Apewer\Apewer.csproj", "{7C47A362-78F9-4EC2-86F1-B442658D2466}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Apewer.Source", "Apewer.Source\Apewer.Source.csproj", "{AFF499E2-10D0-406B-A563-CE3327D57962}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Apewer.Web", "Apewer.Web\Apewer.Web.csproj", "{0C9CD2F9-E0C2-4BAC-84C1-33A57798D6CC}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Apewer.Windows", "Apewer.Windows\Apewer.Windows.csproj", "{5FB935E5-01E8-4CA7-9157-DC58BD731408}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {7C47A362-78F9-4EC2-86F1-B442658D2466}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7C47A362-78F9-4EC2-86F1-B442658D2466}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7C47A362-78F9-4EC2-86F1-B442658D2466}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7C47A362-78F9-4EC2-86F1-B442658D2466}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AFF499E2-10D0-406B-A563-CE3327D57962}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AFF499E2-10D0-406B-A563-CE3327D57962}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AFF499E2-10D0-406B-A563-CE3327D57962}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AFF499E2-10D0-406B-A563-CE3327D57962}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0C9CD2F9-E0C2-4BAC-84C1-33A57798D6CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0C9CD2F9-E0C2-4BAC-84C1-33A57798D6CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0C9CD2F9-E0C2-4BAC-84C1-33A57798D6CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0C9CD2F9-E0C2-4BAC-84C1-33A57798D6CC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5FB935E5-01E8-4CA7-9157-DC58BD731408}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5FB935E5-01E8-4CA7-9157-DC58BD731408}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5FB935E5-01E8-4CA7-9157-DC58BD731408}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5FB935E5-01E8-4CA7-9157-DC58BD731408}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {7A4C1E3C-8A0A-4128-A143-627A9F76EF02}
+ EndGlobalSection
+EndGlobal
diff --git a/Apewer.Source/Apewer.Source.csproj b/Apewer.Source/Apewer.Source.csproj
index c0664e0..f615842 100644
--- a/Apewer.Source/Apewer.Source.csproj
+++ b/Apewer.Source/Apewer.Source.csproj
@@ -1,29 +1,29 @@
-
+
-
- SOURCE;$(DefineConstants);
- MYSQL_6_10;$(DefineConstants);
- MYSQL_6_9;$(DefineConstants);
- netstandard2.0;net40;net20
-
+
+ SOURCE;$(DefineConstants);
+ MYSQL_6_10;$(DefineConstants);
+ MYSQL_6_9;$(DefineConstants);
+ netstandard2.0;net40;net20
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
diff --git a/Apewer.Web/Apewer.Web.csproj b/Apewer.Web/Apewer.Web.csproj
index 3dff1d2..f7f4a18 100644
--- a/Apewer.Web/Apewer.Web.csproj
+++ b/Apewer.Web/Apewer.Web.csproj
@@ -1,25 +1,25 @@
-
+
-
- WEB;$(DefineConstants);
- netcoreapp3.1;net40;net20
-
+
+ WEB;$(DefineConstants);
+ netcoreapp3.1;net40;net20
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/Apewer.Web/WebSocket/ChatServer.cs b/Apewer.Web/WebSocket/ChatServer.cs
index db99b1d..4ba3425 100644
--- a/Apewer.Web/WebSocket/ChatServer.cs
+++ b/Apewer.Web/WebSocket/ChatServer.cs
@@ -4,6 +4,7 @@ using Apewer;
using Apewer.Network;
using System;
using System.Collections.Generic;
+using System.Net;
namespace Apewer.WebSocket
{
@@ -127,18 +128,19 @@ namespace Apewer.WebSocket
_websocket = new GenericServer();
lock (_websocket)
{
- var ws = _websocket.Start(_websocketport, _websocketaddress);
- if (ws)
+ try
{
+ _websocket.Start(_websocketport, IPAddress.Parse(_websocketaddress));
+
_websocket.OnMessage += WebSocketReceived;
_websocket.OnOpen += WebsocketOnOpen;
_websocket.OnClose += WebsocketOnClose;
RaiseConsole("WebSocket 服务已启动。");
}
- else
+ catch (Exception ex)
{
_websocket = null;
- RaiseError("WebSocket 服务启动失败。");
+ RaiseError($"WebSocket 服务启动失败:{ex.Message()}");
return;
}
@@ -426,7 +428,7 @@ namespace Apewer.WebSocket
{
var input = Console.ReadLine();
if (input == "exit") break;
- ws.Send(ws.Address, ":", ws.Port.ToString(), "\n", input);
+ ws.Send(ws.Address.ToString(), ":", ws.Port.ToString(), "\n", input);
}
ws.Close();
diff --git a/Apewer.Web/WebSocket/GenericServer.cs b/Apewer.Web/WebSocket/GenericServer.cs
index 608fba7..cba756c 100644
--- a/Apewer.Web/WebSocket/GenericServer.cs
+++ b/Apewer.Web/WebSocket/GenericServer.cs
@@ -3,6 +3,7 @@
using Apewer;
using System;
using System.Collections.Generic;
+using System.Net;
namespace Apewer.WebSocket
{
@@ -11,15 +12,12 @@ namespace Apewer.WebSocket
public sealed class GenericServer : IDisposable
{
- const string DefaultAddress = "0.0.0.0";
- const ushort DefaultPort = 8000;
-
private Dictionary _connections = new Dictionary();
private WebSocketServer _server = null;
private bool _running = false;
- private string _address = null;
- private int _port = DefaultPort;
+ private IPAddress _address = null;
+ private int _port = 0;
///
public event SocketEvent OnOpen;
@@ -54,7 +52,7 @@ namespace Apewer.WebSocket
}
/// 监听的地址。
- public string Address
+ public IPAddress Address
{
get { return _address; }
}
@@ -101,28 +99,46 @@ namespace Apewer.WebSocket
_running = false;
}
+ /// 启动监听,如果监听正在运行则失败。在所有 IPv4 网络接口上自动选择可用的端口号。
+ /// 已监听的端口号。
+ ///
+ ///
+ public void Start() => Start(0, null);
+
/// 启动监听,如果监听正在运行则失败。
- public bool Start(int port = DefaultPort, string address = DefaultAddress)
+ /// 要监听的终结点。指定为 NULL 时将在所有 IPv4 网络接口上自动选择可用的端口号。
+ /// 已监听的端口号。
+ ///
+ ///
+ ///
+ public void Start(IPEndPoint endPoint)
{
- if (address == null) return false;
- if (_running) return false;
-
- _address = address ?? "";
- _port = NumberUtility.Restrict( port, 0, ushort.MaxValue);
+ if (endPoint == null) Start(0, null);
+ else Start(endPoint.Port, endPoint.Address);
+ }
- try
- {
- var location = "ws://" + address + ":" + port.ToString();
- _server = new WebSocketServer(location);
- _server.Start(Initialize);
- _running = true;
- return true;
- }
- catch (Exception exception)
- {
- RaiseExcepted(exception);
- return false;
- }
+ /// 启动监听,如果监听正在运行则失败。
+ /// 要监听的端口号。指定为 0 时将自动选择可用的端口号。
+ /// 要监听的网络接口。指定为 NULL 时将在所有 IPv4 网络接口监听,等同于 。
+ /// 已监听的端口号。
+ ///
+ ///
+ ///
+ public void Start(int port, IPAddress address = null)
+ {
+ if (address == null) address = IPAddress.Any;
+ if (port < 0) throw new ArgumentOutOfRangeException(nameof(port));
+ if (port > 65535) throw new ArgumentOutOfRangeException(nameof(port));
+ if (_running) throw new InvalidOperationException("示例已经在运行中,无法再次启动。");
+
+ _address = address;
+ _port = port;
+
+ var location = "ws://" + _address.ToString() + ":" + port.ToString();
+ _server = new WebSocketServer(location);
+ _server.Start(Initialize);
+ _port = _server.Port;
+ _running = true;
}
/// 对所有连接发送文本。
diff --git a/Apewer.Windows/Apewer.Windows.csproj b/Apewer.Windows/Apewer.Windows.csproj
index fe58ff6..75f1a8a 100644
--- a/Apewer.Windows/Apewer.Windows.csproj
+++ b/Apewer.Windows/Apewer.Windows.csproj
@@ -3,77 +3,77 @@
- CS0108;CS0612
- net461;net40;net20;netcoreapp3.1
+ CS0108;CS0612
+ net461;net40;net20;netcoreapp3.1
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
- Form
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Form
+
+
\ No newline at end of file
diff --git a/Apewer.Windows/WinForm/ModifierKey.cs b/Apewer.Windows/WinForm/ModifierKey.cs
new file mode 100644
index 0000000..1bdc7cf
--- /dev/null
+++ b/Apewer.Windows/WinForm/ModifierKey.cs
@@ -0,0 +1,123 @@
+using System;
+
+namespace Apewer.WinForm
+{
+
+ /// 修饰键。
+ public struct ModifierKey : IEquatable
+ {
+
+ byte _value;
+ bool _alt;
+ bool _ctrl;
+ bool _shift;
+ bool _win;
+
+ /// 包含 ALT 键。
+ public bool WithALT { get => _alt; }
+
+ /// 包含 CTRL 键。
+ public bool WithCTRL { get => _ctrl; }
+
+ /// 包含 SHIFT 键。
+ public bool WithSHIFT { get => _shift; }
+
+ /// 包含 WIN 键。
+ public bool WithWIN { get => _win; }
+
+ /// 修饰键组合的值。
+ public byte Value { get => _value; }
+
+ ///
+ private ModifierKey(byte value)
+ {
+ _alt = value << 7 >> 7 == 1;
+ _ctrl = value << 6 >> 7 == 1;
+ _shift = value << 5 >> 7 == 1;
+ _win = value << 4 >> 7 == 1;
+ _value = Convert.ToByte(value);
+ if (_value != value) throw new OverflowException($"参数【{value}】含有无效的键。");
+ }
+
+ private ModifierKey(bool alt, bool ctrl, bool shift, bool win)
+ {
+ _alt = alt;
+ _ctrl = ctrl;
+ _shift = shift;
+ _win = win;
+
+ var value = 0;
+ if (alt) value += 0x01;
+ if (ctrl) value += 0x02;
+ if (shift) value += 0x04;
+ if (win) value += 0x05;
+ _value = Convert.ToByte(value);
+ }
+
+ ///
+ public override int GetHashCode() => _value;
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is ModifierKey b) return _value == b._value;
+ return false;
+ }
+
+ ///
+ public bool Equals(ModifierKey another) => _value == another._value;
+
+ ///
+ public static implicit operator byte(ModifierKey instance) => instance._value;
+
+ ///
+ ///
+ public static implicit operator ModifierKey(byte value) => new ModifierKey(value);
+
+ ///
+ ///
+ public static ModifierKey operator +(ModifierKey a, ModifierKey b)
+ {
+ if (a._alt && b._alt) throw new ArgumentException("相加的两个组合键共同包含了 ALT 键。");
+ if (a._ctrl && b._ctrl) throw new ArgumentException("相加的两个组合键共同包含了 CTRL 键。");
+ if (a._shift && b._shift) throw new ArgumentException("相加的两个组合键共同包含了 SHIFT 键。");
+ if (a._win && b._win) throw new ArgumentException("相加的两个组合键共同包含了 WIN 键。");
+
+ var alt = a._alt || b._alt;
+ var ctrl = a._ctrl || b._ctrl;
+ var shift = a._shift || b._shift;
+ var win = a._win || b._win;
+ return new ModifierKey(alt, ctrl, shift, win);
+ }
+
+ ///
+ public static ModifierKey operator -(ModifierKey a, ModifierKey b)
+ {
+ var alt = a._alt;
+ var ctrl = a._ctrl;
+ var shift = a._shift;
+ var win = a._win;
+
+ if (b._alt) alt = false;
+ if (b._ctrl) ctrl = false;
+ if (b._shift) shift = false;
+ if (b._win) win = false;
+
+ return new ModifierKey(alt, ctrl, shift, win);
+ }
+
+ /// 表示 ALT 修饰键。
+ public static ModifierKey ALT { get; } = new ModifierKey(0x01);
+
+ /// 表示 CTRL 修饰键。
+ public static ModifierKey CTRL { get; } = new ModifierKey(0x02);
+
+ /// 表示 SHIFT 修饰键。
+ public static ModifierKey SHIFT { get; } = new ModifierKey(0x04);
+
+ /// 表示 WIN 修饰键。
+ public static ModifierKey WIN { get; } = new ModifierKey(0x08);
+
+ }
+
+}
diff --git a/Apewer/BytesUtility.cs b/Apewer/BytesUtility.cs
index 3ea3dfc..d4b2d04 100644
--- a/Apewer/BytesUtility.cs
+++ b/Apewer/BytesUtility.cs
@@ -767,6 +767,17 @@ namespace Apewer
#endregion
+ #region PKCS #5
+
+ /// 使用密码生成密钥。
+ static byte[] PKCS5(byte[] password, byte[] salt = null, int iterations = 1000, int bits = 32)
+ {
+ var rfc2898 = new Rfc2898DeriveBytes(password, salt, 1);
+ return rfc2898.GetBytes(32);
+ }
+
+ #endregion
+
#region AES
private static void Aes256(byte[] key, byte[] salt, Func create, Stream input, Stream output)
@@ -842,6 +853,130 @@ namespace Apewer
return result;
}
+ private static readonly byte[] AesDefaultIV = new byte[16];
+
+ static T UseAes(byte[] key, byte[] iv, CipherMode cipherMode, PaddingMode paddingMode, Func callback)
+ {
+ if (key == null) throw new ArgumentNullException(nameof(key));
+ var keySize = key.Length * 8;
+ switch (keySize)
+ {
+ case 128:
+ case 192:
+ case 256:
+ break;
+ default:
+ throw new ArgumentException($"密钥大小【{keySize}】bits 无效。");
+ }
+
+ if (iv == null) iv = AesDefaultIV;
+ var ivSize = iv.Length * 8;
+ if (ivSize != 128) throw new ArgumentException($"初始化向量【{ivSize}】bits 无效。");
+
+ using (var rijndael = new RijndaelManaged())
+ {
+ rijndael.Key = key;
+ rijndael.IV = iv;
+ rijndael.Mode = cipherMode;
+ rijndael.Padding = paddingMode;
+
+ return callback.Invoke(rijndael);
+ }
+ }
+
+ static void UseAes(Stream input, Stream output, byte[] key, byte[] iv, CipherMode cipherMode, PaddingMode paddingMode, Func create)
+ {
+ if (input == null) throw new ArgumentNullException(nameof(input));
+ if (output == null) throw new ArgumentNullException(nameof(output));
+
+ UseAes