目录
介绍
在我担任计算机数字控制 (CNC) 高级软件工程师期间,我有机会开发一个涵盖广泛应用的框架。本文专门介绍了具有服务器回调通知的通用客户端——服务器通信框架。
后续文章将重点介绍使用log4net和DbgView进行诊断、常见的面向WinForm的框架以及用于查看日志文件和实现框架的LogViewer应用程序作为示例。
简单就是美,我已经从框架中删除了本文不需要的所有内容。
本文仅描述在客户端——服务器通信中实现通用远程处理框架必须了解的内容。类的详细情况汇报给每个人欣赏。
DotNet 远程处理框架
在Client-Server通信中,对于客户端来说重要的是它能够随时连接到服务器,无论是在客户端之前或之后启动服务器还是由于某种原因中断了通信并稍后重新建立。对于服务器来说,重要的是它能够识别客户端是否连接,是否断开连接,或者通信由于某种原因被切断并稍后重新建立。
客户端——服务器通信中最重要的事情是服务器应该能够向客户端返回通知。
约束、限制
通用远程处理框架假定多个客户端可以连接到服务器,但服务器只有一个Sink用于所有客户端。它还假定客户端和服务器之间的连接是永久的,没有租用。
客户端——服务器定义
在实现客户端——服务器通信之前,有必要定义两件事。
- 服务器呈现给客户端的接口。
- 服务器用于通知客户端的通知参数的类型。
由于此信息由服务器和客户端使用,因此需要在客户端和服务器的公共项目中定义它们。对于本文中的测试示例,常用项目是TestLibrary。客户端项目是TestClient,服务器是TestServer.
接口
对于此测试示例,接口如下所示:
namespace TestLibrary
{
/// Interface provided by the server for clients.
public interface ITestInterface : IConnect<EventArgs>
{
/// The server will send this message back to the clients asynchronously
void Execute(string message);
/// The server will send this message back to the clients asynchronously
void Execute(Guid clientID, string message);
/// The server will send this message back to the clients synchronously.
void ExecuteSync(string message);
/// The server will generate an exception.
void ExecuteException();
/// The server will notify an exception.
void ExecuteNotifyException();
}
}
接口必须始终派生自IConnect<EventArgs>! IConnect<EventArgs>接口在RemoteServer类服务器端和RemoteConnector类客户端中实现。只有您的接口是使用这两个基类来实现的。
可以在此接口中定义任何方法,但必须注意所使用的方法参数必须是可序列化的。本示例定义了 fife 方法,但数量不限。这些方法只是向服务器发送消息或要求服务器生成异常。服务器实现将通过异步或同步通知将消息发送回客户端或生成异常作为示例。
服务器通知参数
服务器通知参数如下所示:
namespace TestLibrary
{
[Serializable]
public class TestMessageArgs : EventArgs
{
/// The message.
public string Message { get; }
/// Constructs a TestMessageArgs object.
public TestMessageArgs(string message)
{
Message = message;
}
}
}
它应该来自EventArgs但更重要的是它应该是可序列化的。
该服务器上的Executes(..)信息将发回消息通知到使用此参数的客户端。
服务器异常
有两种异常类型。通知异常或服务器抛出的异常。您可以在服务器上捕获服务器异常并通过NotifyExceptionArgs通知通知客户端发生了异常,或者您可以在客户端上捕获通用服务器异常。可以在公共库中定义一个异常,但该异常应该是可序列化的。
服务器实现
服务器实现如下所示:
namespace TestServer
{
/// Class to manage a TestServer.
public class TestServer : RemoteServer<TestSink>
{
/// Constructs a TestServer object, the base class has no default constructor.
public TestServer(IChannel channel)
: base(channel) { }
/// Notify all clients
public void SendNotificationToAllClients(string message)
{
Sink?.PerformNotifyClients(new TestMessageArgs(message));
}
}
}
该TestServer类从泛型的RemoteServer<T>派生,并只用于定义Sink为T参数。
应用程序无法访问由DotNet远程处理创建RemoteServer<T>的T接收器,但该类提供了一个接收器属性,该属性在且仅当第一个客户端连接时创建。一经创建,Sink即为永久。
RemoteServer<T>类提供事件:
- ClientConnect: 客户端连接
- ClientDisconnect: 客户端断开连接
- Starting: 服务器正在启动
- Waiting: 服务器正在等待,可以从事件处理程序中取消服务器
- Stopping: 服务器正在停止
- Stopped: 服务器已停止
最重要的服务器端代码是Sink,根据定义服务器中只有一个 Sink。它实现了服务器接口。
namespace TestServer
{
/// Class to manage the Server Sink defined by its Interface.
public class TestSink : NotifyClientSink, ITestInterface
{
/// The server will send the message back to the clients asynchronously.
public void Execute(string message)
{
PerformNotifyClients(new TestMessageArgs(message));
}
/// The server will send the message back to the client asynchronously.
public void Execute(Guid clientID, string message)
{
PerformNotifyClient(clientID, new TestMessageArgs(message));
}
/// The server will send the message back to the clients synchronously.
public void ExecuteSync(string message)
{
PerformNotifyClientsSync(new TestMessageArgs(message));
}
/// The server will generate an exception.
public void ExecuteException()
{
throw new NotImplementedException("ExecuteException");
}
/// The server will notify an exception.
public void ExecuteNotifyException()
{
try
{
// server code that generates an exception
throw new ApplicationException("ExecuteNotifyException");
}
catch (Exception ex)
{
PerformNotifyClients(new NotifyExceptionArgs(ex));
}
}
}
}
Sink简单地异步或同步被接收的消息通知客户端或客户端。它还会生成通知异常和服务器异常。
注意:如果通知是同步的,服务器会被阻塞,直到客户端将控制权交还。
客户端实现
客户端实现如下所示:
namespace TestClient
{
/// <summary>
/// Class to manage a TestClient
/// </summary>
public class TestClient : IDisposable
{
private Delay _delay;
private RemoteConnector<ITestInterface> _remoteConnector;
private Guid ClientID => _remoteConnector.ClientID;
private ITestInterface TestProvider => _remoteConnector.RemoteObject;
public RemoteState RemoteState => _remoteConnector.RemoteState;
/// Dispose this object
public void Dispose()
{
_remoteConnector?.Dispose();
}
/// <summary>
/// Initialize a remote connector to the server giving the channel.
/// </summary>
/// <param name="channel">The channel.</param>
public void Initialize(IChannel channel)
{
// instantiate the client
_remoteConnector = new RemoteConnector<ITestInterface>(new CHANNEL());
_remoteConnector.Callback += OnRemoteCallback;
_remoteConnector.RemoteStateChanged += OnRemoteStateChanged;
}
/// <summary>
/// Notification from the server to the client.
/// </summary>
/// <param name="sender">The sender is the client ID.</param>
/// <param name="e">The event argument.</param>
private void OnRemoteCallback(object sender, EventArgs e)
{
// is notification for me ?
if ((Guid)sender != ClientID)
return;
// the server sends me a message
if (e is TestMessageArgs args1)
Console.WriteLine("Message from Server: " + args1.Message);
// the server sends me an exception
else if (e is NotifyExceptionArgs args2)
Console.WriteLine("Exception from Server: " + args2.Exception.Message);
}
/// <summary>
/// Notification when the remote connection is changed.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="remoteState">The new remote state.</param>
private void OnRemoteStateChanged(object sender, RemoteState remoteState)
{
switch (remoteState)
{
case RemoteState.Connected:
Console.WriteLine("RemoteState: [" + RemoteState +
"] Client [" + ClientID +
"] Delay [" + _delay.TotalMilliseconds + "] ms");
// execute commands on the server
ExecuteCommandsOnServer();
break;
default:
Console.WriteLine("RemoteState: [" + RemoteState + "]");
break;
}
}
/// Starts the connection with the server.
public void Start()
{
if (_remoteConnector == null)
throw new ApplicationException("Please call Initialize before Start");
_delay = new Delay();
_remoteConnector.Start(10); // wait 10s to connect to the server else exception
}
/// Stops the connection with the server.
public void Stop()
{
_remoteConnector?.Stop();
}
/// Execute commands on the server.
private void ExecuteCommandsOnServer()
{
// the server will send back notification asynchronously to all clients
for (int i = 1; i <= 3; i++)
{
TestProvider.Execute("Hello TestServer all #" + i);
Thread.Sleep(500);
}
// the server will send back notification asynchronously to me
for (int i = 1; i <= 3; i++)
{
TestProvider.Execute(ClientID, "Hello TestServer me #" + i);
Thread.Sleep(500);
}
// the server will send back notification synchronously to all client
for (int i = 1; i <= 3; i++)
{
TestProvider.ExecuteSync("Hello TestServer Sync all #" + i);
Thread.Sleep(500);
}
// the server will generate an exception
try
{
TestProvider.ExecuteException();
}
catch (Exception ex)
{
Console.WriteLine("** Server exception: " + ex.Message);
}
// execute method on the server, the server will notify an exception
TestProvider.ExecuteNotifyException();
}
}
}
这个TestClient类就像一个控制器。它实现了RemoteConnector<ITestInterface>以连接到服务器。远程连接器提供来自服务器的ITestInterface作为TestProvider、服务器Callback事件、来自连接器的RemoteStateChanged事件和客户端ID。它必须实现IDisposable接口以释放远程连接器,因为远程连接器有一个工作线程来连接或重新连接到服务器。
该Start()方法连接到服务器。在这个例子中,它会等待最多10秒直到建立连接,但也可以等待更长时间或不等待(参数= 0)。一旦连接准备好,将生成一个带有RemoteState.Connected参数的RemoteStateChanged事件。该Delay对象通知有关连接的持续时间。准备就绪后,将执行客户端代码ExecuteCommandsOnServer(),但这仅作为示例。客户端可以在任何地方执行服务器调用。
该OnRemoteCallback(..)事件检查通知是否适合我。服务器可以根据需要向指定的客户端发送通知。如果连接了多个客户端,服务器会向所有客户端广播通知。
如何测试示例
要测试远程处理框架,请启动客户端和服务器,停止并重新启动客户端或服务器,然后查看客户端或服务器如何自动重新连接。
本文中的项目
通用客户端——服务器 DotNet 远程项目
此测试示例的项目是:
- FW.Remoting:通用的客户端——服务器 DotNet 远程处理组件
- TestLibrary:服务器和客户端测试项目的公共库
- TestServer: 服务器项目
- TestClient: 客户项目
- TestClientServer:启动服务器和3个客户端的应用程序
其他框架项目
这些组件将在其他出版物中描述。
- FW.Common: 通用组件
- FW.Diagnostics:使用log4net和/或DbgView的所有类型项目的诊断组件
- FW.WinApi:访问Windows API的通用组件
- FW.WinForm:Winform应用程序的通用组件
LogViewer WinForm应用程序
显示Test应用程序生成的xlog文件的Log4Net简单查看器。此查看器将在另一份发布版本中进行描述。
结论
如果您需要与服务器回调进行强大的客户端——服务器远程通信,那么这个框架就是您所需要的!
https://www.codeproject.com/Articles/5296049/Generic-Client-Server-DotNet-Remoting-with-Server