NewLife/NewLife.Remoting

新增RPC服务器例程
大石头 编写于 2024-04-28 15:00:26
共计: 修改12个文件,增加364行、删除8行。
修改 +12 -1
修改 +4 -2
修改 +1 -1
增加 +21 -0
增加 +61 -0
增加 +76 -0
增加 +32 -0
增加 +60 -0
增加 +61 -0
增加 +32 -0
修改 +1 -1
修改 +3 -3
修改 +12 -1
diff --git a/NewLife.Remoting.sln b/NewLife.Remoting.sln
index 22c20ba..02e7102 100644
--- a/NewLife.Remoting.sln
+++ b/NewLife.Remoting.sln
@@ -18,7 +18,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution 
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NewLife.Remoting", "NewLife.Remoting\NewLife.Remoting.csproj", "{D07654F5-5A89-4781-9094-070D026858CE}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo", "Demo\Demo.csproj", "{685205DD-0754-4877-9A96-40CD18F8D84B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo", "Demo\Demo.csproj", "{685205DD-0754-4877-9A96-40CD18F8D84B}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{8DB8495C-1EB6-41AE-83DD-55DBBB0E6FA2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Zero.RpcServer", "Samples\Zero.RpcServer\Zero.RpcServer.csproj", "{8351AC88-1955-48AD-B6F4-26959E1A0C4C}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -42,10 +46,17 @@ Global
 		{685205DD-0754-4877-9A96-40CD18F8D84B}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{685205DD-0754-4877-9A96-40CD18F8D84B}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{685205DD-0754-4877-9A96-40CD18F8D84B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{8351AC88-1955-48AD-B6F4-26959E1A0C4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8351AC88-1955-48AD-B6F4-26959E1A0C4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8351AC88-1955-48AD-B6F4-26959E1A0C4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8351AC88-1955-48AD-B6F4-26959E1A0C4C}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{8351AC88-1955-48AD-B6F4-26959E1A0C4C} = {8DB8495C-1EB6-41AE-83DD-55DBBB0E6FA2}
+	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {323831A1-A95B-40AB-B9AD-36A0BC10C2CB}
 	EndGlobalSection
修改 +4 -2
diff --git a/NewLife.Remoting/ApiServer.cs b/NewLife.Remoting/ApiServer.cs
index d9b5b85..324b112 100644
--- a/NewLife.Remoting/ApiServer.cs
+++ b/NewLife.Remoting/ApiServer.cs
@@ -5,7 +5,6 @@ using NewLife.Log;
 using NewLife.Messaging;
 using NewLife.Model;
 using NewLife.Net;
-using NewLife.Reflection;
 using NewLife.Threading;
 
 namespace NewLife.Remoting;
@@ -146,6 +145,9 @@ public class ApiServer : ApiHost, IServer
         {
             Name = Name,
             Host = this,
+
+            Log = Log,
+            SessionLog = Log,
             Tracer = Tracer,
         };
         server.Init(new NetUri(NetType.Unknown, "*", Port), this);
@@ -167,7 +169,7 @@ public class ApiServer : ApiHost, IServer
 
         Encoder.Log = EncoderLog;
 
-        Log.Info("启动{0},服务器 {1}", GetType().Name, Server);
+        Log.Info("启动[{0}]", Name);
         Log.Info("编码:{0}", Encoder);
         Log.Info("处理:{0}", Handler);
 
修改 +1 -1
diff --git a/NewLife.Remoting/NewLife.Remoting.csproj b/NewLife.Remoting/NewLife.Remoting.csproj
index 9a71b33..c13e62f 100644
--- a/NewLife.Remoting/NewLife.Remoting.csproj
+++ b/NewLife.Remoting/NewLife.Remoting.csproj
@@ -49,7 +49,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="NewLife.Core" Version="10.9.2024.402" />
+    <PackageReference Include="NewLife.Core" Version="10.10.2024.427-beta2349" />
   </ItemGroup>
 
   <ItemGroup>
增加 +21 -0
diff --git a/Samples/Zero.RpcServer/appsettings.json b/Samples/Zero.RpcServer/appsettings.json
new file mode 100644
index 0000000..512a19b
--- /dev/null
+++ b/Samples/Zero.RpcServer/appsettings.json
@@ -0,0 +1,21 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft": "Warning",
+      "Microsoft.Hosting.Lifetime": "Information"
+    }
+  },
+  "AllowedHosts": "*",
+  //"StarServer": "http://s.newlifex.com:6600",
+  //"RedisCache": "server=127.0.0.1:6379;password=;db=3",
+  "ConnectionStrings": {
+    "Zero": "Data Source=..\\Data\\Zero.db;Provider=SQLite"
+
+    // 各种数据库连接字符串模版,连接名Zero对应Zero.Data/Projects/Model.xml中的ConnName
+    //"Zero": "Server=.;Port=3306;Database=zero;Uid=root;Pwd=root;Provider=MySql",
+    //"Zero": "Data Source=.;Initial Catalog=zero;user=sa;password=sa;Provider=SqlServer",
+    //"Zero": "Server=.;Database=zero;Uid=root;Pwd=root;Provider=PostgreSql",
+    //"Zero": "Data Source=Tcp://127.0.0.1/ORCL;User Id=scott;Password=tiger;Provider=Oracle"
+  }
+}
增加 +61 -0
diff --git a/Samples/Zero.RpcServer/AreaController.cs b/Samples/Zero.RpcServer/AreaController.cs
new file mode 100644
index 0000000..5e62edd
--- /dev/null
+++ b/Samples/Zero.RpcServer/AreaController.cs
@@ -0,0 +1,61 @@
+using NewLife.Log;
+using NewLife.Remoting;
+using XCode.Membership;
+
+namespace Zero.RpcServer;
+
+/// <summary>地区控制器。会话获取,请求过滤</summary>
+[Api("Area")]
+internal class AreaController : IApi, IActionFilter
+{
+    /// <summary>会话。同一Tcp/Udp会话多次请求共用,执行服务方法前赋值</summary>
+    public IApiSession Session { get; set; }
+
+    [Api(nameof(FindByID))]
+    public Area FindByID(Int32 id)
+    {
+        // Session 用法同Web
+        var times = Session["Times"].ToInt();
+        times++;
+        Session["Times"] = times;
+
+        // 故意制造异常
+        if (times >= 2)
+        {
+            // 取得当前上下文
+            var ctx = ControllerContext.Current;
+
+            throw new ApiException(507, $"[{ctx.ActionName}]调用次数过多!Times={times}");
+        }
+
+        return Area.FindByID(id);
+    }
+
+    /// <summary>本控制器执行前</summary>
+    /// <param name="filterContext"></param>
+    public void OnActionExecuting(ControllerContext filterContext)
+    {
+        // 请求参数
+        var ps = filterContext.Parameters;
+
+        // 服务参数
+        var cs = filterContext.ActionParameters;
+
+        foreach (var item in ps)
+        {
+            if (cs != null && !cs.ContainsKey(item.Key))
+                XTrace.WriteLine("服务[{0}]未能找到匹配参数 {1}={2}", filterContext.ActionName, item.Key, item.Value);
+        }
+    }
+
+    /// <summary>本控制器执行后,包括异常发生</summary>
+    /// <param name="filterContext"></param>
+    public void OnActionExecuted(ControllerContext filterContext)
+    {
+        var ex = filterContext.Exception;
+        if (ex != null && !filterContext.ExceptionHandled)
+        {
+            XTrace.WriteLine("控制器拦截到异常:{0}", ex.Message);
+        }
+    }
+}
\ No newline at end of file
增加 +76 -0
diff --git a/Samples/Zero.RpcServer/ClientTest.cs b/Samples/Zero.RpcServer/ClientTest.cs
new file mode 100644
index 0000000..6c5de2a
--- /dev/null
+++ b/Samples/Zero.RpcServer/ClientTest.cs
@@ -0,0 +1,76 @@
+using System.Net.Sockets;
+using System.Text;
+using NewLife;
+using NewLife.Log;
+using NewLife.Remoting;
+using NewLife.Security;
+using NewLife.Serialization;
+
+namespace Zero.RpcServer;
+
+static class ClientTest
+{
+    /// <summary>Tcp连接ApiServer</summary>
+    public static async void TcpTest(Int32 port)
+    {
+        await Task.Delay(1_000);
+        XTrace.WriteLine("");
+        XTrace.WriteLine("Tcp开始连接!");
+
+        // 连接服务端
+        var client = new ApiClient("tcp://127.0.0.2:12346");
+        client.Open();
+
+        await Process(client);
+
+        // 关闭连接
+        client.Close("测试完成");
+    }
+
+    /// <summary>Udp连接ApiServer</summary>
+    public static async void UdpTest(Int32 port)
+    {
+        await Task.Delay(2_000);
+        XTrace.WriteLine("");
+        XTrace.WriteLine("Udp开始连接!");
+
+        // 连接服务端
+        var client = new ApiClient("udp://127.0.0.2:12346");
+        client.Open();
+
+        await Process(client);
+
+        // 关闭连接
+        client.Close("测试完成");
+    }
+
+    /// <summary>Tcp连接ApiServer</summary>
+    public static async void WebSocketTest(Int32 port)
+    {
+        await Task.Delay(3_000);
+        XTrace.WriteLine("");
+        XTrace.WriteLine("WebSocket开始连接!");
+
+        // 连接服务端
+        var client = new ApiClient("ws://127.0.0.2:12346");
+        client.Open();
+
+        await Process(client);
+
+        // 关闭连接
+        client.Close("测试完成");
+    }
+
+    static async Task Process(ApiClient client)
+    {
+        // 获取所有接口
+        var apis = await client.InvokeAsync<String[]>("api/all");
+        client.WriteLog("共有接口数:{0}", apis.Length);
+
+        // 获取服务端信息
+        var state = Rand.NextString(8);
+        var state2 = Rand.NextString(8);
+        var infs = await client.InvokeAsync<IDictionary<String, Object>>("api/info", new { state, state2 });
+        client.WriteLog("服务端信息:{0}", infs.ToJson(true));
+    }
+}
增加 +32 -0
diff --git a/Samples/Zero.RpcServer/MyController.cs b/Samples/Zero.RpcServer/MyController.cs
new file mode 100644
index 0000000..bdb645b
--- /dev/null
+++ b/Samples/Zero.RpcServer/MyController.cs
@@ -0,0 +1,32 @@
+using NewLife;
+using NewLife.Data;
+
+namespace Zero.RpcServer;
+
+/// <summary>自定义控制器。包含多个服务</summary>
+/// <remarks>
+/// 控制器规范:
+/// 1,控制器类不要求公开可见,也无需继承任何基类,需要明码注册到服务端
+/// 2,控制器类中的方法不要求公开可见,但方法名和参数名必须固定,跟客户端一致
+/// 3,第一段路径名为控制器名(去掉Controller),第二段路径名为方法名,如/Area/FindByID
+/// 4,通过[Api]特性指定控制器名和方法名
+/// </remarks>
+internal class MyController
+{
+    /// <summary>添加,标准业务服务,走Json序列化</summary>
+    /// <param name="x"></param>
+    /// <param name="y"></param>
+    /// <returns></returns>
+    public Int32 Add(Int32 x, Int32 y) => x + y;
+
+    /// <summary>RC4加解密,高速业务服务,二进制收发不经序列化</summary>
+    /// <param name="pk"></param>
+    /// <returns></returns>
+    public Packet RC4(Packet pk)
+    {
+        var data = pk.ToArray();
+        var pass = "NewLife".GetBytes();
+
+        return data.RC4(pass);
+    }
+}
\ No newline at end of file
增加 +60 -0
diff --git a/Samples/Zero.RpcServer/Program.cs b/Samples/Zero.RpcServer/Program.cs
new file mode 100644
index 0000000..9c7db62
--- /dev/null
+++ b/Samples/Zero.RpcServer/Program.cs
@@ -0,0 +1,60 @@
+using NewLife.Caching;
+using NewLife.Caching.Services;
+using NewLife.Log;
+using NewLife.Model;
+using NewLife.Remoting;
+using Stardust;
+using Zero.RpcServer;
+
+// 启用控制台日志,拦截所有异常
+XTrace.UseConsole();
+
+var services = ObjectContainer.Current;
+
+// 配置星尘。自动读取配置文件 config/star.config 中的服务器地址、应用标识、密钥
+var star = services.AddStardust();
+
+// 默认内存缓存,如有配置RedisCache可使用Redis缓存
+services.AddSingleton<ICacheProvider, RedisCacheProvider>();
+
+// 引入Redis,用于消息队列和缓存,单例,带性能跟踪。一般使用上面的ICacheProvider替代
+//services.AddRedis("127.0.0.1:6379", "123456", 3, 5000);
+
+// 实例化RPC服务端,指定端口,同时在Tcp/Udp/IPv4/IPv6上监听
+var server = new ApiServer(12346)
+{
+    Name = "银河服务端",
+
+    // 指定编码器
+    Encoder = new JsonEncoder(),
+
+    //EncoderLog = XTrace.Log,
+    Log = XTrace.Log,
+    Tracer = star.Tracer,
+};
+
+// 注册服务控制器
+server.Register<MyController>();
+server.Register<UserController>();
+server.Register<AreaController>();
+
+#if DEBUG
+// 打开编码日志
+server.EncoderLog = XTrace.Log;
+#endif
+
+// 启动网络服务,监听端口,所有逻辑将在 xxxController 中处理
+server.Start();
+XTrace.WriteLine("服务端启动完成!");
+
+// 注册到星尘,非必须
+star?.Service?.Register("MyRpcServer", () => $"tcp://*:{server.Port},udp://*:{server.Port}");
+
+// 客户端测试,非服务端代码,正式使用时请注释掉
+_ = Task.Run(() => ClientTest.TcpTest(server.Port));
+_ = Task.Run(() => ClientTest.UdpTest(server.Port));
+_ = Task.Run(() => ClientTest.WebSocketTest(server.Port));
+
+// 阻塞,等待友好退出
+var host = services.BuildHost();
+await host.RunAsync();
增加 +61 -0
diff --git a/Samples/Zero.RpcServer/UserController.cs b/Samples/Zero.RpcServer/UserController.cs
new file mode 100644
index 0000000..61cbdf7
--- /dev/null
+++ b/Samples/Zero.RpcServer/UserController.cs
@@ -0,0 +1,61 @@
+using NewLife.Log;
+using NewLife.Remoting;
+using XCode.Membership;
+
+namespace Zero.RpcServer;
+
+/// <summary>产品控制器。会话获取,请求过滤</summary>
+[Api("User")]
+internal class UserController : IApi, IActionFilter
+{
+    /// <summary>会话。同一Tcp/Udp会话多次请求共用,执行服务方法前赋值</summary>
+    public IApiSession Session { get; set; }
+
+    [Api(nameof(FindByID))]
+    public User FindByID(Int32 id)
+    {
+        // Session 用法同Web
+        var times = Session["Times"].ToInt();
+        times++;
+        Session["Times"] = times;
+
+        // 故意制造异常
+        if (times >= 2)
+        {
+            // 取得当前上下文
+            var ctx = ControllerContext.Current;
+
+            throw new ApiException(507, $"[{ctx.ActionName}]调用次数过多!Times={times}");
+        }
+
+        return User.FindByID(id);
+    }
+
+    /// <summary>本控制器执行前</summary>
+    /// <param name="filterContext"></param>
+    public void OnActionExecuting(ControllerContext filterContext)
+    {
+        // 请求参数
+        var ps = filterContext.Parameters;
+
+        // 服务参数
+        var cs = filterContext.ActionParameters;
+
+        foreach (var item in ps)
+        {
+            if (cs != null && !cs.ContainsKey(item.Key))
+                XTrace.WriteLine("服务[{0}]未能找到匹配参数 {1}={2}", filterContext.ActionName, item.Key, item.Value);
+        }
+    }
+
+    /// <summary>本控制器执行后,包括异常发生</summary>
+    /// <param name="filterContext"></param>
+    public void OnActionExecuted(ControllerContext filterContext)
+    {
+        var ex = filterContext.Exception;
+        if (ex != null && !filterContext.ExceptionHandled)
+        {
+            XTrace.WriteLine("控制器拦截到异常:{0}", ex.Message);
+        }
+    }
+}
\ No newline at end of file
增加 +32 -0
diff --git a/Samples/Zero.RpcServer/Zero.RpcServer.csproj b/Samples/Zero.RpcServer/Zero.RpcServer.csproj
new file mode 100644
index 0000000..d1ccd59
--- /dev/null
+++ b/Samples/Zero.RpcServer/Zero.RpcServer.csproj
@@ -0,0 +1,32 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+    <AssemblyTitle>RPC服务端</AssemblyTitle>
+    <Description>高性能,长连接,数据接口</Description>
+    <Company>新生命开发团队</Company>
+    <Copyright>©2002-2024 NewLife</Copyright>
+    <VersionPrefix>1.0</VersionPrefix>
+    <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
+    <Version>$(VersionPrefix).$(VersionSuffix)</Version>
+    <FileVersion>$(Version)</FileVersion>
+    <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
+    <Deterministic>false</Deterministic>
+    <OutputPath>..\..\Bin\RpcServer</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <LangVersion>latest</LangVersion>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="NewLife.Redis" Version="5.6.2024.101" />
+    <PackageReference Include="NewLife.Stardust" Version="2.9.2024.101" />
+    <PackageReference Include="NewLife.XCode" Version="11.11.2024.402" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\NewLife.Remoting\NewLife.Remoting.csproj" />
+  </ItemGroup>
+
+</Project>
修改 +1 -1
diff --git a/Test/Test.csproj b/Test/Test.csproj
index d2842da..4f021c1 100644
--- a/Test/Test.csproj
+++ b/Test/Test.csproj
@@ -10,7 +10,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="NewLife.Core" Version="10.9.2024.402" />
+    <PackageReference Include="NewLife.Core" Version="10.10.2024.427-beta2349" />
   </ItemGroup>
 
   <ItemGroup>
修改 +3 -3
diff --git a/XUnitTest/XUnitTest.csproj b/XUnitTest/XUnitTest.csproj
index ec02659..a09bf63 100644
--- a/XUnitTest/XUnitTest.csproj
+++ b/XUnitTest/XUnitTest.csproj
@@ -12,10 +12,10 @@
     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
     <PackageReference Include="Moq" Version="4.20.70" />
-    <PackageReference Include="NewLife.Core" Version="10.9.2024.402" />
+    <PackageReference Include="NewLife.Core" Version="10.10.2024.427-beta2349" />
     <PackageReference Include="NewLife.UnitTest" Version="1.0.2023.1204" />
-    <PackageReference Include="xunit" Version="2.7.0" />
-    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
+    <PackageReference Include="xunit" Version="2.8.0" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>