网络程序应该注册成为系统服务,以保证其自启动以及稳定可靠运行!
这一场,讲讲怎么建立一个生产级别的网络服务。
老规矩,先上源码:https://github.com/NewLifeX/NewLife.Net
系统服务功能,由网络库的兄弟框架,X组件的Agent来支撑,以前也叫XAgent,网上搜索 NewLife.Agent 可以找到不少文章。
XAgent在X组件里面很年轻,才10年,设计于2008年,上海陆家嘴。
阅读本文之前,建议阅读
最终效果
EchoAgent跑起来先来看看最终效果,客户端用上次的EchoTest,大家也可以telnet net.newlifex.com 1234 来看效果
左边窗口就是这次要讲的网络服务程序,工作在循环调试模式。
右边窗口是上一次的EchoTest客户端,连接左边网络服务。
客户端向服务端发送了5次数据,都得到响应,同时,服务端每秒主动向客户端发送一次本机时间。
建立控制台项目
建立一个控制台项目,通过nuget引用NewLife.Core。项目源码 https://github.com/NewLifeX/NewLife.Net/tree/master/EchoAgent
新建一个服务类 MyService,继承自泛型基类 AgentServiceBase<MyService>
Program.Main里面增加一行引导程序:
class Program { static void Main(String[] args) => new MyService().Main(args); }
下面就开始慢慢完善我们的服务类MyService
public MyService() { ServiceName = "EchoAgent"; DisplayName = "回声服务"; Description = "这是NewLife.Net的一个回声服务示例!"; }
指定一些基本参数,看效果图可以猜到用途
服务名、显示名、描述,就这么多!
系统服务的标准动作就是启动和停止
MyNetServer _Server; /// <summary>开始服务</summary> /// <param name="reason"></param> protected override void StartWork(String reason) { // 实例化服务端,指定端口,同时在Tcp/Udp/IPv4/IPv6上监听 var svr = new MyNetServer { Port = 1234, Log = XTrace.Log, #if DEBUG SocketLog = XTrace.Log, LogSend = true, LogReceive = true, #endif }; svr.Start(); _Server = svr; _timer1 = new TimerX(s => ShowStat(_Server), null, 1000, 1000) { Async = true }; _timer2 = new TimerX(s => SendTime(_Server), null, 1000, 1000) { Async = true }; base.StartWork(reason); } /// <summary>停止服务</summary> /// <param name="reason"></param> protected override void StopWork(String reason) { _Server.TryDispose(); _Server = null; base.StopWork(reason); } private TimerX _timer1; private TimerX _timer2; private String _last; /// <summary>显示服务端状态</summary> /// <param name="ns"></param> private void ShowStat(NetServer ns) { if (ns == null) return; var msg = ns.GetStat(); if (msg == _last) return; _last = msg; WriteLog(msg); } /// <summary>向所有客户端发送时间</summary> /// <param name="ns"></param> private void SendTime(NetServer ns) { if (ns == null) return; var str = DateTime.Now.ToFullString() + Environment.NewLine; var buf = str.GetBytes(); ns.SendAllAsync(buf); }
我们重载启动函数,初始化网络服务,并重启停止函数来销毁网络服务。
这里的MyNetServer从上一个例程拷贝过来。
网络服务做一个成员资源,避免被GC回收。
开发调试
既然是控制台项目,先跑起来看看:
红色字体显示重要信息,黄色字体显示菜单,常用功能是235。
我们选择5循环调试,其实就是在控制台里面模拟服务工作流程,让网络服务跑起来。
底下日志可以看到,它监听了4个套接字。
2是安装服务,也就是把当前应用安装成为Windows服务或者Linux的Systemd服务,这里特别注意,一般需要管理员权限,才能安装成功,除非关闭系统UAC。
3是启动服务,只有在安装了服务之后,才能看到。
所以,XAgent程序,既是开发调试控制台程序,也是安装卸载、启动停止服务的操作台,更是Windows服务程序本身!
细心的同学可以发现,安装好的Windows服务实质上就是 EchoAgent.exe -s,带有-s参数。
安装服务
最后,我们把它安装到一台公网服务器上,tcp://net.newlifex.com:1234,telnet上去看看效果
服务端日志如下:
#Software: EchoAgent #ProcessID: 13592 x64 #AppDomain: EchoAgent #FileName: D:\X\NewLife.Net\Bin\Agent\EchoAgent.exe #BaseDirectory: D:\X\NewLife.Net\Bin\Agent\ #TempPath: C:\Users\Stone\AppData\Local\Temp\ #CommandLine: D:\X\NewLife.Net\Bin\Agent\EchoAgent.dll #ApplicationType: Console #CLR: 3.1.5, .NET Core 3.1.5 #OS: Microsoft Windows NT 6.2.9200.0, X3/Stone #CPU: 8 #GC: IsServerGC=False, LatencyMode=Interactive #Date: 2020-07-05 #字段: 时间 线程ID 线程池Y/网页W/普通N/定时T 线程名/任务ID 消息内容 #Fields: Time ThreadID Kind Name Message 13:18:26.412 1 N - MyNet 准备开始监听4个服务器 13:18:26.416 1 N - MyNetTcp.Start Tcp://0.0.0.0:1234 13:18:26.422 1 N - MyNet 开始监听 Tcp://0.0.0.0:1234 13:18:26.424 1 N - MyNetTcp6.Start Tcp://:::1234 13:18:26.425 1 N - MyNet 开始监听 Tcp://:::1234 13:18:26.427 1 N - MyNetUdp.Open Udp://0.0.0.0:1234 13:18:26.431 1 N - MyNet 开始监听 Udp://0.0.0.0:1234 13:18:26.432 1 N - MyNetUdp6.Open Udp://:::1234 13:18:26.433 1 N - MyNet 开始监听 Udp://:::1234 13:18:26.433 1 N - MyNet 准备就绪! 13:18:26.434 1 N - 服务启动 循环开始
至此,我们的Windows网络服务程序开发完成,并安装到公网服务器上,持续对外提供Echo服务!