###### tags: `Akka` `Web` `DotNet Core` # 從零到有Akka整合Dotnet Core Web建置程控系統-啟用Akka系統 此文件會手把手教學,該如何在一個Web Server上啟用Akka系統,算偏向建置SOP,故不會在框架上多作解釋。閱讀時須對Dot Net Core與Akka.Net需有一定的了解程度。 此篇文章會以在Web Server內新增一個與外部PLC溝通的範例去實作。 ## 新增專案 在Visual Studio 2019新增Asp Dot Net Core Web應用程式 => 選擇Web MVC應用程式(進階設定先不作設置,可以先取消勾選) ## Step1:Log-新增LogSend類別庫 Log工具實作,系統建置前先設計Akka Log實體Class,在此我們宣告一個ILog介面可抽換Log實際紀錄的方法。目前此例Log實作只對Akka Log做設計。 ![](https://i.imgur.com/DtYXmhF.png) ### 實作檔案 - ILog Class : Log介面 - NLogSend Clsass : 紀錄Akka System Log使用 ### 需安裝Package - 安裝 Akka.Logger.Nlog (Ver:1.3.3) ### 實作 - 實作ILog Class ```= public interface ILog { void E(string title,string content); void A(string title,string contetnt); void I(string title,string content); void D(string title,string content); } ``` - 實作NLogSend Class ```= public class NLogSend : ILog { private ILoggingAdapter _nlog; public NLogSend(ILoggingAdapter nlog = null) { _nlog = nlog; } public void A(string title, string contetnt) { _nlog.Warning("【" + title + "】" + ":" + contetnt); } public void D(string title, string content) { _nlog.Debug("【" + title + "】" + ":" + content); } public void E(string title, string content) { _nlog.Error("【" + title + "】" + ":" + content); } public void I(string title, string content) { _nlog.Info("【" + title + "】" + ":" + content); } } ``` ### Step2 Log-在 Web Server 根目錄下新增NLog Config 在LogSender撰寫完後,接下來在Web Server下新建NLog Config檔案。此檔為Web Server裡Akka系統運作記錄Log的相關設定規則設置。 ![](https://i.imgur.com/PZybzUM.png) - 新增NLog.config至Web Server專案根目錄下 ```= <?xml version="1.0" encoding="utf-8"?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" throwConfigExceptions="true" internalLogToConsole="true" internalLogLevel="info"> <targets async="true" > </targets> <rules> </rules> </nlog> ``` - 注意:記得檔案複製到輸出目錄需選擇->有更新才複製選項 ![](https://i.imgur.com/iUlCRtB.png) ## Step3 AkkaSystem-新增AkkaSysBase類別庫 建至Akka基礎使用Base Class,這邊我們先跟據Akka Life Cycle紀錄Actor的生命週期去設計BaseActor。再根據Akka Socket功能額外設置BaseClient與BaseServer Actor。最後在設計一些基礎設置Model與Akka管理功能。 ![](https://i.imgur.com/yNcrh0w.png) ### 實作檔案 - Base資料夾 - BaseActor Class : 紀錄Actor LifeCycle - BaseClientActor Class : Socket Client Actor - BaseServerActor Class : Socket Server Actor - AkkaPara Class : Akka Config參數設置 - AkkaSocketIP Class : Acotr Socket IP參數 - ISysAkkaManger Class : Create Actor方法 - SysAkkaManager Class : Create Actor方法實作 ### 需安裝Package - 安裝Akka (Ver 1.3.14) - 安裝Akka.ID.Extensions.DependencyInjection (Ver 1.3.2) - 安裝Akka.Remote(Ver 1.3.14) - ### 實作 - 實作AkkaSocketIP Class ``` = public class AkkaSocketIP { public IPEndPoint LocalIpEndPoint { get; set; } public IPEndPoint RemoteIpEndPoint { get; set; } } ``` - 實作BaseActor Class ``` = public class BaseActor : ReceiveActor { protected ILog _log; private string _actorName; public BaseActor(ILog log) { _log = log; _actorName = Context.Self.Path.Name; } protected override void PreStart() { _log.I("AThread生命週期", _actorName + "PreStart"); base.PreStart(); } protected override void PreRestart(Exception reason, object message) { _log.E("AThread生命週期", _actorName + " PreRestart"); _log.E("AThread生命週期", "Reason:" + reason.Message); base.PreRestart(reason, message); } protected override void PostStop() { _log.I("AThread生命週期", _actorName + " PostStop"); base.PostStop(); } protected override void PostRestart(Exception reason) { _log.E("AThread生命週期", _actorName + " PostRestart"); _log.E("AThread生命週期", "Reason:" + reason.Message); base.PostRestart(reason); } } ``` - 實作BaseClientActor Class ```= public IActorRef SndActor; protected IActorRef _connection; protected string _actorName; private readonly AkkaSocketIP _AkkaSocketIP; public BaseClientActor(AkkaSocketIP socketIP, ILog log) : base(log) { _AkkaSocketIP = socketIP; _actorName = Context.Self.Path.Name; Connect(); Receive<Tcp.Connected>(message => TCPConnected(message)); Receive<Tcp.ConnectionClosed>(message => TcpConnectionClosed(message)); Receive<Tcp.CommandFailed>(message => TcpCommandFailed(message)); Receive<Tcp.Received>(message => TcpReceivedData(message)); } protected virtual void TcpReceivedData(Tcp.Received msg) { _log.I("TCP接收資料", "Handle_Tcp_Received. message=" + msg.ToString()); _log.I("TCP接收資料", "ByteString=" + msg.Data.ToString()); _log.I("TCP接收資料", "Count=" + msg.Data.Count.ToString()); } protected virtual void TCPConnected(Tcp.Connected message) { _log.I("TCP連線成功", " Connected. message=" + message.ToString()); _log.I("TCP連線成功", " LocalAddress=" + message.LocalAddress.ToString()); _log.I("TCP連線成功", " RemoteAddress=" + message.RemoteAddress.ToString()); _connection = Sender; _connection.Tell(new Tcp.Register(Self)); } protected virtual void TcpConnectionClosed(Tcp.ConnectionClosed message) { _log.I("TCP連線關閉", " Tcp.ConnectionClosed. message=" + message.ToString()); _log.I("TCP連線關閉", " Message.Cause=" + message.Cause); _log.I("TCP連線關閉", " Message.IsAborted=" + message.IsAborted.ToString()); _log.I("TCP連線關閉", " Message.IsConfirmed=" + message.IsConfirmed.ToString()); _log.I("TCP連線關閉", " Message.IsErrorClosed=" + message.IsErrorClosed.ToString()); _log.I("TCP連線關閉", " Message.IsPeerClosed=" + message.IsPeerClosed.ToString()); Connect(); } protected virtual void TcpCommandFailed(Tcp.CommandFailed message) { _log.E("TCP操作失敗", " Tcp.CommandFailed. message=" + message.ToString()); _log.E("TCP操作失敗", " Tcp.CommandFailed. message=" + message.ToString()); _log.E("TCP操作失敗", " Cmd=" + message.Cmd.ToString()); _log.E("TCP操作失敗", " Message=" + message.Cmd.FailureMessage); Connect(); } /// <summary> /// 連線 /// </summary> protected void Connect() { Context.System.Tcp().Tell(new Tcp.Connect(_AkkaSocketIP.RemoteIpEndPoint)); } ``` - 實作BaseServerActor Class ```= public class BaseServerActor : BaseActor { public BaseServerActor(AkkaSocketIP akkaSysIp, ILog log) : base(log) { Context.System.Tcp().Tell(new Tcp.Bind(Self, akkaSysIp.LocalIpEndPoint)); Receive<Tcp.Bound>(message => TcpBound(message)); Receive<Tcp.Connected>(message => TCPConnected(message)); Receive<Tcp.CommandFailed>(msg => TCPCommandFail(msg)); Receive<Tcp.ConnectionClosed>(message => TcpConnectionClosed(message)); Receive<Tcp.Received>(message => TcpReceivedData(message)); } /// <summary> /// Tcp監聽事件觸發 /// </summary> private void TcpBound(Tcp.Bound msg) { _log.I("TCP監聽", "Tcp.Bound Success. Listening on " + msg.LocalAddress); } protected virtual void TcpReceivedData(Tcp.Received msg) { _log.I("TCP接收資料", "Handle_Tcp_Received. message=" + msg.ToString()); _log.I("TCP接收資料", "ByteString=" + msg.Data.ToString()); _log.I("TCP接收資料", "Count=" + msg.Data.Count.ToString()); } protected virtual void TCPConnected(Tcp.Connected msg) { _log.I("TCP已被連線", " Tcp.Connected. message=" + msg.ToString()); _log.I("TCP已被連線", " message.LocalAddress=" + msg.LocalAddress.ToString()); _log.I("TCP已被連線", " message.RemoteAddress=" + msg.RemoteAddress.ToString()); Sender.Tell(new Tcp.Register(Self)); } protected virtual void TCPCommandFail(Tcp.CommandFailed msg) { Console.WriteLine($"[Error] Tcp Command Failed. {msg.Cmd}"); } protected virtual void TcpConnectionClosed(Tcp.ConnectionClosed msg) { _log.I("TCP連線關閉", " Tcp.ConnectionClosed. message=" + msg.ToString()); _log.I("TCP連線關閉", " Message.Cause=" + msg.Cause); _log.I("TCP連線關閉", " Message.IsAborted=" + msg.IsAborted.ToString()); _log.I("TCP連線關閉", " Message.IsConfirmed=" + msg.IsConfirmed.ToString()); _log.I("TCP連線關閉", " Message.IsErrorClosed=" + msg.IsErrorClosed.ToString()); _log.I("TCP連線關閉", " Message.IsPeerClosed=" + msg.IsPeerClosed.ToString()); } protected virtual void TcpCommandFailed(Tcp.CommandFailed msg) { _log.E("TCP操作失敗", " Tcp.CommandFailed. message=" + msg.ToString()); _log.E("TCP操作失敗", " Tcp.CommandFailed. message=" + msg.ToString()); _log.E("TCP操作失敗", " Cmd=" + msg.Cmd.ToString()); _log.E("TCP操作失敗", " Message=" + msg.Cmd.FailureMessage); } } ``` - 實作AkkaPara Class ```= public static class AkkaPara { /// <summary> /// 建立 config /// </summary> /// <param name="port"> 本地端接口埠號 </param> public static Config Config(string port, string publicHostName) { var strConfig = @" akka { loglevel = DEBUG loggers = [""Akka.Logger.NLog.NLogLogger, Akka.Logger.NLog""] actor { provider = remote debug { receive = on # log any received message autoreceive = on # log automatically received messages, e.g. PoisonPill lifecycle = on # log actor lifecycle changes event-stream = on # log subscription changes for Akka.NET event stream unhandled = on # log unhandled messages sent to actors } } remote { dot-netty.tcp { port = {port} hostname = 0.0.0.0 public-hostname = {publicHostName} } } io { tcp { direct-buffer-pool { buffer-size = 1024 } max-received-message-size = unlimited } } }"; strConfig = strConfig.Replace("{port}", port); return ConfigurationFactory.ParseString(strConfig); } } ``` - 實作ISysAkkaManager Class ```= public interface ISysAkkaManager { ActorSystem ActorSystem { get; } IActorRef CreateActor<T>() where T : ActorBase; IActorRef CreateChildActor<T>(IUntypedActorContext context) where T : ActorBase; IActorRef GetActor(string actName); ActorSelection GetActorSelection(string actorPath); } ``` - 實作SysAkkaManager Class ```= public class SysAkkaManager : ISysAkkaManager { public ActorSystem ActorSystem { get; } private readonly Dictionary<string, IActorRef> _actorDics = new Dictionary<string, IActorRef>(); public SysAkkaManager(ActorSystem actorSystem) { ActorSystem = actorSystem; } public IActorRef CreateActor<T>() where T : ActorBase { return CreateActor<T>(() => ActorSystem); } public IActorRef CreateChildActor<T>(IUntypedActorContext context) where T : ActorBase { return CreateActor<T>(() => context); } private IActorRef CreateActor<T>(Func<IActorRefFactory> func) where T : ActorBase { var actName = typeof(T).Name; if (_actorDics.ContainsKey(actName)) return _actorDics[actName]; return RegisterActor(actName, func().ActorOf(ActorSystem.DI().Props<T>(), typeof(T).Name)); } private IActorRef RegisterActor(string actName, IActorRef actor) { if (_actorDics.ContainsKey(actName)) throw new ArgumentException($"You have been register Action {actName}"); _actorDics.Add(actName, actor); return actor; } public IActorRef GetActor(string actName) { if (!_actorDics.ContainsKey(actName)) throw new ArgumentException($"It't doesn't has register Action {actName}"); return _actorDics[actName]; } public ActorSelection GetActorSelection(string actorPath) { return ActorSystem.ActorSelection(actorPath); } } ``` ## AkkaSystem-新增ExternalSys類別庫專案實作Akka PLC 上述Akka Base Template敲好後,接著實作與外部連線的PLC Akka System ![](https://i.imgur.com/PQvd8eM.png) ### 實作檔案 - PLC資料夾: - PlcRcv Class : PLC Server Actor,與外部PLC系統連線即接收外部PLC發送資料。 - PlcRcvEdit Class : PlcRcv驗證完接收資料後,傳至Plc將資料解析轉成ORM,並將資料傳送給WebServer。 - PlcSndEdit Class : 組成發送資料,組成完畢傳送給PlcSnd發到外部系統。 - PlcSnd Class : PCL Client Actor 連接外部PLC系統,並發送資料給外部PLC系統。 - PlcMgr Class : Akka PLC管理者角色,管理與建置上述Actor。 ### 實作 - PlcRcv Class ```= public class PlcRcv : BaseServerActor { private readonly ISysAkkaManager _akkaManager; private readonly ILog _log; private readonly IActorRef _plcRcvEditActor; public PlcRcv(ISysAkkaManager akkaManager, AkkaSocketIP akkaSysIp, ILog log) : base(akkaSysIp, log) { _akkaManager = akkaManager; _plcRcvEditActor = akkaManager.GetActor(nameof(PlcRcvEdit)); _log = log; } protected override void TcpReceivedData(Tcp.Received msg) { _log.I("接收到訊息"," [Info] Handle_Tcp_Received. message=" + msg.ToString()); _log.I("接收到訊息"," [Info] Count=" + msg.Data.Count.ToString()); _plcRcvEditActor.Tell(msg.Data.ToArray()); } } ``` - PlcRcvEdit Class ```= public class PlcRcvEdit : BaseActor { private readonly ILog _log; public PlcRcvEdit(ILog log) : base(log){ _log = log; } } ``` - PlcSnd Class ```= public class PlcSnd : BaseClientActor { private ILog _log; public PlcSnd(AkkaSocketIP akkaSysIp, ILog log) : base(akkaSysIp, log) { _log = log; } } ``` - PlcSndEdit Class ```= public class PlcSndEdit : BaseActor { private readonly IActorRef _plcSndActor; private readonly ILog _log; public PlcSndEdit(ISysAkkaManager akkaManager, ILog log) : base(log) { _plcSndActor = akkaManager.GetActor(nameof(PlcSnd)); _log = log; } } ``` ## Step4 : Log-設置Web Server 內的 NLog Config 規範內容 在ExternalSys Akka系統建置好後,目前使用的架構方法是使用Akka NLog去紀錄Akka系統的Log,設置方式則透過NLog Config設定準則去更改紀錄Log機制。 在剛剛建置的Akka Plc得知,除了Akka本身機制的Log設置紀錄,我們還須新增PlcRcv, PlcRcvEdit, PlcSnd, PlcSndEdit。 需設置參數區段 - 1.variable 設定Log存放路近 - 2.targets Log設置機制與名稱 - 3.rules Log存取Level以及寫到哪個Target ![](https://i.imgur.com/PZybzUM.png) ### 實做檔案 - NLog.config ### 實做 ```= <?xml version="1.0" encoding="utf-8"?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" throwConfigExceptions="true" internalLogToConsole="true" internalLogLevel="info"> <variable name="LogPath" value="C:\AkkaWebTemplate"/> <targets async="true" > <target name="LogControl" xsi:type="File" fileName="${LogPath}/AkkaLog.log" archiveFileName="${LogPath}/AkkaLog.{#}.log" archiveNumbering="DateAndSequence" archiveAboveSize="1000000" encoding="utf-8" maxArchiveFiles="5" archiveDateFormat="yyyyMMdd" archiveEvery="Day" /> <target name="PlcMgrLog" xsi:type="File" fileName="${LogPath}/PlcMgrLog.log" archiveFileName="${LogPath}/PlcMgrLog.{#}.log" archiveNumbering="DateAndSequence" archiveAboveSize="1000000" encoding="utf-8" maxArchiveFiles="5" archiveDateFormat="yyyyMMdd" archiveEvery="Day" /> <target name="PlcRcvLog" xsi:type="File" fileName="${LogPath}/PlcRcvLog.log" archiveFileName="${LogPath}/PlcRcvLog.{#}.log" archiveNumbering="DateAndSequence" archiveAboveSize="1000000" encoding="utf-8" maxArchiveFiles="5" archiveDateFormat="yyyyMMdd" archiveEvery="Day" /> <target name="PlcRcvEditLog" xsi:type="File" fileName="${LogPath}/PlcRcvEditLog.log" archiveFileName="${LogPath}/PlcRcvEditLog.{#}.log" archiveNumbering="DateAndSequence" archiveAboveSize="1000000" encoding="utf-8" maxArchiveFiles="5" archiveDateFormat="yyyyMMdd" archiveEvery="Day" /> <target name="PlcSndLog" xsi:type="File" fileName="${LogPath}/PlcSndLog.log" archiveFileName="${LogPath}/PlcSndLog.{#}.log" archiveNumbering="DateAndSequence" archiveAboveSize="1000000" encoding="utf-8" maxArchiveFiles="5" archiveDateFormat="yyyyMMdd" archiveEvery="Day" /> <target name="PlcSndEditLog" xsi:type="File" fileName="${LogPath}/PlcSndEditLog.log" archiveFileName="${LogPath}/PlcSndEditLog.{#}.log" archiveNumbering="DateAndSequence" archiveAboveSize="1000000" encoding="utf-8" maxArchiveFiles="5" archiveDateFormat="yyyyMMdd" archiveEvery="Day" /> </targets> <rules> <logger name="PlcMgrLog" minlevel="Debug" writeTo="PlcMgrLog"/> <logger name="PlcRcvLog" minlevel="Debug" writeTo="PlcRcvLog" /> <logger name="PlcRcvEditLog" minlevel="Debug" writeTo="PlcRcvEditLog"/> <logger name="PlcSndLog" minlevel="Debug" writeTo="PlcSndLog" /> <logger name="PlcSndEditLog" minlevel="Debug" writeTo="PlcSndEditLog" /> <logger name="*" minlevel="Debug" writeTo="LogControl" /> </rules> </nlog> ``` ## Step5 : AkkaSystem-設置Akka參數至Web Server appsetting.json 因為是在Web Server Service下啟用Akka,故需在appsetting.json內設置相關參數 - 1. Akka System Name - 2. Akka System Port - 3. Akka System Public Host IP - 4. Akka Plc Soket IP Port設置 ![](https://i.imgur.com/FDePgOy.png) ### 實做檔案: - appsetting.json ### 實做 ```= "AkkaConfigure": { "Name": "AkkaSystem", "Port": "8200", "PublicHostIP": "127.0.0.1", "PLC": { "RemoteIp": "127.0.0.1", "RemotePort": "7791", "LocalIp": "127.0.0.1", "LocalPort": "9101" } } ``` ## Step6 : AkkaSystem-至Web Server新增Akka DI Service Akka與外部連結系統建置完成後,接著要在Web Server啟用Akka系統。因為 Dot Net Core框架已全面使用Dependency Injection(DI)機制,故此部分在Akka建至需Align現有框架。所以需要將Akka註冊到DI Container裡 需註冊至Container項目 - 註冊 Akka System - 註冊 Akka PLC - 註冊 Akka Server Engine ![](https://i.imgur.com/CcvvVmT.png) ### 實做檔案 - AkkaSys資料夾 - AkkaSysDIService Class : 註冊Akka DI相關設置 - AkkaServerEngine Class : 啟動Akka系統 ### 實做 - AkkaSysDIService Class - Inject : 註冊Akka Class - GetLog : Create Nlog Instance - NewIPPoint : 讀取appsetting.json Akka Socket Ip Port設定檔 ```= public class AkkaDIService { private readonly IServiceCollection _service; private readonly IConfiguration _configuration; public AkkaDIService(IServiceCollection service, IConfiguration configuration) { _service = service; _configuration = configuration; } public void Inject() { // Register Akka System _service.AddSingleton<ISysAkkaManager>(provider => { var sysName = _configuration["AkkaConfigure:Name"]; var sysPort = _configuration["AkkaConfigure:Port"]; var sysPublicIP = _configuration["AkkaConfigure:PublicHostIP"]; var actSystem = ActorSystem.Create(sysName, AkkaPara.Config(sysPort, sysPublicIP)); actSystem.UseServiceProvider(provider); return new SysAkkaManager(actSystem); }); #region Register Akka PLC _service.AddScoped(p => { var akkaManager = p.GetService<ISysAkkaManager>(); return new PlcMgr(akkaManager, GetLog<PlcMgr>(p, "PlcMgrLog")); }); _service.AddScoped(p => { var ipPoint = NewIPPoint("PLC"); var akkaManager = p.GetService<ISysAkkaManager>(); return new PlcRcv(akkaManager, ipPoint, GetLog<PlcRcv>(p, "PlcRcvLog")); }); _service.AddScoped(p => { return new PlcRcvEdit(GetLog<PlcRcvEdit>(p, "PlcRcvEditLog")); }); _service.AddScoped(p => { var ipPoint = NewIPPoint("PLC"); return new PlcSnd(ipPoint, GetLog<PlcSnd>(p, "PlcSndLog")); }); _service.AddScoped(p => { var akkaManager = p.GetService<ISysAkkaManager>(); return new PlcSndEdit(akkaManager,GetLog<PlcSndEdit>(p, "PlcSndEditLog")); }); #endregion // Register Akka Server Engin _service.AddScoped(provider => { var akkaManager = provider.GetService<ISysAkkaManager>(); return new AkkaServerEngine(akkaManager); }); } private ILog GetLog<T>(IServiceProvider context, string nlogName) { return new NLogSend(Logging.GetLogger(context.GetService<ISysAkkaManager>().ActorSystem, nlogName)); } private AkkaSocketIP NewIPPoint(string sysName) { var localIp = _configuration[$"AkkaConfigure:{sysName}:LocalIp"]; var localPort = _configuration[$"AkkaConfigure:{sysName}:LocalPort"]; var remoteIp = _configuration[$"AkkaConfigure:{sysName}:RemoteIp"]; var remotePort = _configuration[$"AkkaConfigure:{sysName}:RemotePort"]; var ipPoint = new AkkaSocketIP { LocalIpEndPoint = new IPEndPoint(IPAddress.Parse(localIp), Int32.Parse(localPort)), RemoteIpEndPoint = new IPEndPoint(IPAddress.Parse(remoteIp), Int32.Parse(remotePort)) }; return ipPoint; } } ``` - AkkaServerEngine Class ```= public class AkkaServerEngine { public AkkaServerEngine(ISysAkkaManager akkaManager) { akkaManager.CreateActor<PlcMgr>(); } } ``` ## Step7:Web Server-設置StarUp Akka DI相關設置Class與啟動Engine建至完後,將Akka DI Class與Engine加入Starup。 在ConfigureServices新增AkkaSysDIService,並使用Inject() Method。接著在Configure啟用AkkaEngine。在Web Server啟動時,走Middleware Pipline時就會啟動Akka System。 ![](https://i.imgur.com/F557atr.png) ### 修改StarUp - 1.在StarUp建構子新增IWebHostEnvironment,並宣告IWebHostEnvironment內部變數 ```= private readonly IConfiguration _configuration; private readonly IWebHostEnvironment _environment; public Startup(IConfiguration configuration,IWebHostEnvironment environment) { _configuration = configuration; _environment = environment; } ``` - 2.Configure Method新增IServiceProvider ```= public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider) ``` - 3.在ConfigureServices Mehod new AkkaDIService ```= public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); new AkkaDIService(services, _configuration).Inject(); } ``` - 4.在Configure 啟用Akka ```= // 啟用Akka var serverEngine = serviceProvider.GetService<AkkaServerEngine>(); ``` ## Demo Web啟動後,可用第三方[SocketTest3](https://sourceforge.net/projects/sockettest/)測試是否可以跟Web Service裡的Akka連線以及檢察Log路徑檔是否有成功產生Nlog檔案 ![](https://i.imgur.com/eKvWUS6.png) Akka Log檔案產生 ![](https://i.imgur.com/OfoeipW.png) ## [Sample Code](https://drive.google.com/file/d/1WNsWKHMG9CSc6c3ggQLTILgE1_QRNLHe/view?usp=sharing) ![](https://i.imgur.com/SyYbiMg.png)