Mina.NET 基础

这篇文章将介绍Mina.NET的基础架构,以及如何使用Mina.NET构建服务端与客户端应用程序,并且给出几个TCP、UDP服务端和客户端的简单示例。

应用程序架构

下图描述了Mina.NET的内部结构,以及各部分组件的任务:

(图片来自Apache MINA)

一般情况下,Mina.NET应用程序可以划分为三层:
  • I/O 服务 - 真实 I/O 发生的地方
  • I/O 过滤器链 - 在字节数据和指定数据结构之间进行处理或转换
  • I/O Handler - 业务逻辑层

因此,创建Mina.NET应用程序通常需要:
  • 创建 I/O 服务 - 选择一个合适的已有服务,或者自定义新的服务
  • 创建过滤器链 - 选择已有的过滤器,或者新建自定义过滤器
  • 创建 I/O Handler - 处理消息的业务逻辑

TCP服务端示例

以TimeServer为例。创建服务器需要以下几步:
  1. 创建一个Acceptor
  2. 创建过滤器链
  3. 创建IOHandler,添加至Acceptor
  4. 侦听端口

创建Acceptor

IoAcceptor acceptor = new AsyncSocketAcceptor();

这里我们创建了一个异步套接字服务(AsyncSocketAcceptor)。

创建过滤器链

acceptor.FilterChain.AddLast("logger", new LoggingFilter());
acceptor.FilterChain.AddLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Encoding.UTF8)));

我们添加了一个LoggingFilter和一个ProtocolCodecFilterLoggingFilter将记录所有的事件,包括会话建立、消息接收、消息发送和会话关闭等。第二个ProtocolCodecFilter能够在二进制数据和指定协议之间进行编解码。在这里我们使用一个现有的命令行协议(TextLineCodecFactory),它能够处理文本消息,我们不必重新写一个编解码器。

创建IOHandler,添加至Acceptor

acceptor.MessageReceived += (s, e) =>
{
	String str = e.Message.ToString();

	// "Quit" ? let's get out ...
	if (str.Trim().Equals("quit", StringComparison.OrdinalIgnoreCase))
	{
		e.Session.Close(true);
		return;
	}

	// Send the current date back to the client
	e.Session.Write(DateTime.Now.ToString());
	Console.WriteLine("Message written...");
};

侦听端口

Int32 port = 9123;
acceptor.Bind(new IPEndPoint(IPAddress.Any, port));

TCP客户端示例

以Sumup客户端为例。建立客户端需要以下几步:
  • 创建一个Connector
  • 创建过滤器链
  • 创建IOHandler,添加至Connector
  • 连接服务器

创建Connector

IoConnector connector = new AsyncSocketConnector();

首先我们创建一个异步套接字连接(AsyncSocketConnector)。

创建过滤器链

if (USE_CUSTOM_CODEC)
{
	connector.FilterChain.AddLast("codec",
			new ProtocolCodecFilter(new SumUpProtocolCodecFactory(false)));
}
else
{
	connector.FilterChain.AddLast("codec",
			new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
}

创建IOHandler,添加至Connector

connector.SessionOpened += (s, e) =>
{
	for (int i = 0; i < values.Length; i++)
	{
		AddMessage m = new AddMessage();
		m.Sequence = i;
		m.Value = values[i];
		e.Session.Write(m);
	}
};

connector.MessageReceived += (s, e) =>
{
	ResultMessage rm = (ResultMessage)e.Message;
	if (rm.OK)
	{
		if (rm.Sequence == values.Length - 1)
		{
			Console.WriteLine("The sum: " + rm.Value);
			e.Session.Close(true);
		}
	}
	else
	{
		Console.WriteLine("Server error, disconnecting...");
		e.Session.Close(true);
	}
};

连接服务器

IoSession session;
while (true)
{
	try
	{
		IConnectFuture future = connector.Connect(new IPEndPoint(IPAddress.Loopback, PORT));
		future.Await();
		session = future.Session;
		break;
	}
	catch (Exception ex)
	{
		Console.WriteLine(ex);
		Thread.Sleep(3000);
	}
}

这是客户中最重要的部分。我们向远程服务端发起连接,由于连接是一个异步操作,我们需要使用IConnectFuture接口来了解连接操作什么时候完成。当连接完成后,我们获取关联的会话IoSession,通过这个会话,我们可以向服务端发送数据。

UDP服务端示例

以Udp示例为参考。建立服务端,我们需要完成以下两部分工作:(MemoryMonitor.cs)
  1. 创建UDP套接字接收客户端请求
  2. 创建IoHandler处理Mina.NET事件

首先,我们创建一个AsyncDatagramAcceptor来侦听客户端请求:

AsyncDatagramAcceptor acceptor = new AsyncDatagramAcceptor();

接下来,向AsyncDatagramAcceptor的过滤器链添加一个LoggingFilterLoggingFilter能够在Mina.NET的各个步骤生成日志信息,能够帮助我们很好地了解Mina.NET是如何工作的。

acceptor.FilterChain.AddLast("logger", new LoggingFilter());

接下来我们添加一些控制底层UDP套接字的代码,设置acceptor为重用地址。

acceptor.SessionConfig.ReuseAddress = true;

当然,最后我们需要调用Bind()。

int port = 18567;
acceptor.Bind(new IPEndPoint(IPAddress.Any, port));

实现IoHandler

在这个示例中,我们主要需要处理以下3个事件:
  • 会话创建(Session Created)
  • 消息接收(Message Received)
  • 会话关闭(Session Closed)

会话创建事件

acceptor.SessionCreated += (s, e) =>
{
	Console.WriteLine("Session created...");
};

消息接收事件

acceptor.MessageReceived += (s, e) =>
{
	IoBuffer buf = e.Message as IoBuffer;
	if (buf != null)
	{
		Console.WriteLine("New value for {0}: {1}", e.Session.RemoteEndPoint, buf.GetInt64());
	}
};

在消息接收事件中,我们只是打印出接收到的数据。如果需要发送响应消息,可以在这里编写处理代码。

会话关闭事件

acceptor.SessionClosed += (s, e) =>
{
	Console.WriteLine("Session closed...");
};

UDP客户端示例

这一部分是对应于上一部分UDP服务端的客户端代码。实现客户端,我们需要以下几步:
  • 创建套接字连接到服务端
  • 设置IoHandler
  • 获取内存数
  • 发送数据至服务端

代码文件请查看MemMonClient.cs.

IoConnector connector = new AsyncDatagramConnector();
IConnectFuture connFuture = connector.Connect(new IPEndPoint(IPAddress.Loopback, MemoryMonitor.port));

我们创建了一个AsyncDatagramConnector。接下来,等待客户端与服务端连接,连接成功后就可以开始发送数据了。

connFuture.Complete += (s, e) =>
{
	IConnectFuture f = (IConnectFuture)e.Future;
	if (f.Connected)
	{
		Console.WriteLine("...connected");
		IoSession session = f.Session;

		for (int i = 0; i < 30; i++)
		{
			Int64 memory = GC.GetTotalMemory(false);
			IoBuffer buffer = IoBuffer.Allocate(8);
			buffer.PutInt64(memory);
			buffer.Flip();
			session.Write(buffer);

			try
			{
				Thread.Sleep(1000);
			}
			catch (ThreadInterruptedException)
			{
				break;
			}
		}
	}
	else
	{
		Console.WriteLine("Not connected...exiting");
	}
};

这里,我们侦听了IConnectFuture对象的Complete事件,当连接完成,事件被触发时,程序将开始发送数据。

程序将在30秒内,每隔一秒向服务端发送一次内存数据。我们每次分配一个8字节的IoBuffer,正好放下一个长整型变量。在调用Write方法之前,我们需要调用buffer.Flip()

Last edited Apr 10, 2014 at 6:16 AM by longshine, version 1