MINA2 之IoBuffer

最近做的一个项目用到了开源的C / S应用的服务器框架MINA,当初做的时候资料非常少,只能自己不停的测试,总结出了一些规律经验。

从网上看的资料上看,这个服务器框架还是比较稳定和支持的并发数还是很不错的,不过没有准确的数据,而且我做完的时候也没有拿到真正的实际环境中测试过,用的时候也发现了很多优点和缺点,使用者可以自己去根据自己的使用需求去衡量是否使用该框架。

服务器是商业系统很重要的一部分,主要负责数据采集,文件分发,与端机的通信,和自动作业等任务,服务器大多是24小时运行的,因此服务器的实现必须强壮、稳定、安全,而速度虽然也是很重要,不过最重要的还是前三者。服务器框架MINA就是要为这样的服务器提供了一个网络应用框架,当然这样的服务器框架也可以自己去实现。MINA为我们封装了socket的底层通信实现,提供了日志,线程池等功能,使用起来非常简单、方便。

MINA是一个异步框架,是通过网络事件激发的,它包含两层:IO层和协议层。首先介绍IO层,要说明的是我用的版本是0.
8.2 ,可能不同版本会稍有不同。

Client产生一个底层IO事件,比如说连接和发送数据包,IoAcceptor执行所有底层IO,将他们翻译成抽象的IO事件,接着这里可以添加(也可以部添加)一个IoFilters对IO事件进行过滤,并把翻译过的事件或过滤过的事件和关联的IoSession 发送给IoHandler。IoSession是一个有效的网络连接会话,此会话将一直保持连接,除非网络断开或用户主动断开连接(session.close()),用户可以通过IoSession获得有关该会话连接的信息和配置会话的对象和属性;IoHandler是网络事件的监听器,也就是说当有网络事件发生时会通知IoHandler,用户不用去主动接受数据。用户只要实现此接口爱干吗干吗去吧。IoFilter:Io过滤器,对Io事件进行过滤,比如添加日志过滤器和线程池过滤器。

使用说明:

import java.util.logging.Level;

import org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.SessionConfig;
import org.apache.mina.io.IoHandlerAdapter;
import org.apache.mina.io.IoSession;
import org.apache.mina.io.socket.SocketSessionConfig;
import org.apache.mina.util.SessionLog;

public class ServerHandler extends IoHandlerAdapter {

public ServerHandler() {

}


public void dataRead(IoSession session, ByteBuffer buffer) throws Exception {

// 当有数据读入时此方法被调用,数据封装在ByteBuffer中,可以用以下方法对出buffer的数据,ByteBuffer的数据读出后内存中就没有了。
// String message = "";
// byte[] bytes = new byte[rb.remaining()];
// int j = 0; // while (rb.hasRemaining()) {
// bytes[j++] = rb.get();
// }
// message = new String(bytes);

// 接着可以进行逻辑处理

}


public void dataWritten(IoSession session, Object mark) throws Exception {

// 当数据被写入通道时此方法被调用,实际就是调用了session.write(IoSession,Object)方法
SessionLog.log(Level.INFO,session,mark.toString()); // 必要时打印所写入的内容,mark的内容就是session.write(session,mark)中的第二个参数
}


public void exceptionCaught(IoSession session, Throwable arg1)
throws Exception {

// 当出现异常时此方法被调用,从而进行各种异常处理,该方法可以捕捉网络异常(如连接非正常关闭)和所有其他方法产生的异常,这里要注意如果客户端要保持与服务器端的连接时不要在这里马上重新连接不然会抛出CancelKeyException运行期异常直接导致程序死掉(特别是与服务器端有超过两个连接时一定会发生并且此异常无法捕获),建议的方法是启动一个单独的线程来完成与服务器端的重新连接,还有要注意的是如果网络是正常关闭的,比如说是客户端正常关闭连接,而此时服务器端是不愿意关闭的话,这个异常本方法是捕捉不了的,因此只能在session.close()方法中处理这种情况。
session.close();

}


public void sessionClosed(IoSession session) throws Exception {

// 当网络连接被关闭是此方法被调用
SessionLog.log(Level.INFO,session, " Close a Session " ); // 必要时打印出信息
}


public void sessionCreated(IoSession session) throws Exception {

// 当网络连接被创建时此方法被调用(这个肯定在sessionOpened(IoSession session)方法之前被调用),这里可以对Socket设置一些网络参数
SessionConfig cfg = session.getConfig();
if (cfg instanceof SocketSessionConfig) {
((SocketSessionConfig) cfg).setSessionReceiveBufferSize(
2048 );
((SocketSessionConfig) cfg).setKeepAlive(
true );
((SocketSessionConfig) cfg).setSoLinger(
true , 0 );
((SocketSessionConfig) cfg).setTcpNoDelay(
true );
((SocketSessionConfig) cfg).setWriteTimeout(
1000 * 5 );
}

}


public void sessionIdle(IoSession arg0, IdleStatus arg1) throws Exception {
// 当网络通道空闲时此方法被调用,在这里可以判断是读空闲、写空闲还是两个都空闲,以便做出正确的处理

一半的网络通讯程序都要与服务器端保持长连接,所以这里可以发一下网络测试数据以保持与服务器端的连接
}


public void sessionOpened(IoSession session) throws Exception {

// 当网络连接被打开时此方法被调用,这里可以对session设置一些参数或者添加一些IoFilter的实现,也可以对客户端做一些认证之类的工作
session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 60 );
}


}


// 启动监听连接的服务器

import org.apache.mina.common. * ;
import org.apache.mina.io. * ;
import org.apache.mina.io.filter. * ;
import org.apache.mina.registry. * ;

public class Server {
/** */ /** Choose your favorite port number. */
private static final int PORT = 8080 ;

public static void main( String[] args ) throws Exception
{
ServiceRegistry registry
= new SimpleServiceRegistry();

// 可以添加各种过滤器,比如线程池过滤器,增加一个线程池处理来自不同的连接

IoAcceptor ioAcceptor
= registry.getIoAcceptor();
IoThreadPoolFilter ioThreadPoolFilter
= new IoThreadPoolFilter();
ioThreadPoolFilter.setMaximumPoolSize(
10 );
ioThreadPoolFilter.start();
ioAcceptor.getFilterChain().addLast(
" IoThreadPool " ,
ioThreadPoolFilter);

// Bind
Service service = new Service( " serviceName " ,
TransportType.SOCKET, PORT );
registry.bind( service,
new ServerHandler() );

System.out.println(
" Listening on port " + PORT );
}

}


// 如果是连接服务器的可以如下启动连接请求

import org.apache.mina.io.filter.IoThreadPoolFilter;
import org.apache.mina.io.socket.SocketConnector;
import java.net.InetSocketAddress;

public class Client {
public static void main( String[] args ) throws Exception
{
private static final int CONNECT_TIMEOUT = 3 ; // 设置超时连接时间

// 可以添加各种过滤器,比如线程池过滤器,增加一个线程池处理来自不同的连接

IoThreadPoolFilter ioThreadPoolFilter
= new IoThreadPoolFilter();
ioThreadPoolFilter.setMaximumPoolSize(
10 );
ioThreadPoolFilter.start();
SocketConnector connector
= new SocketConnector();

connector.getFilterChain().addFirst(
" threadPool " ,
ioThreadPoolFilter);

// 初始化客户端的监听处理器
ClientHandler clientHandler = new ClientHandler();

InetSocketAddress address
= new InetSocketAddress( " serverIp " ,serverPort);

try {

connector.connect(address, CONNECT_TIMEOUT,
clientHandler);

System.out.println(
" connect sucessfully! " );

}
catch (Exception e) {

System.err.println(
" Failed to connect. " );

}

}


如果一个协议非常复杂,如果只用一个Io层是非常复杂的,因为IO层没有帮助你分离‘message解析’和‘实际的业务逻辑,MINA提供了一个协议层来解决这个问题。



使用协议层必须实现5个接口:ProtocolHandler, ProtocolProvider, ProtocolCodecFactory, ProtocolEncoder, 和 ProtocolDecoder:

第一步:实现ProtocolDecoder和ProtocolEncoder,当有IO事件时便先调用这两个类的方法

import org.apache.mina.common.ByteBuffer;
import org.apache.mina.protocol.ProtocolDecoderOutput;
import org.apache.mina.protocol.ProtocolSession;
import org.apache.mina.protocol.ProtocolViolationException;
import org.apache.mina.protocol.codec.MessageDecoder;
import org.apache.mina.protocol.codec.MessageDecoderResult;
import java.util. * ;

public class ServerDecoder implements MessageDecoder {

public ServerTranInfoDecoder() {

}



public MessageDecoderResult decodable(ProtocolSession session, ByteBuffer in) {

// 对接受的数据判断是否与协议相同,如果相同返回MessageDecoderResult.OK,否则返回MessageDecoderResult.NOT_OK,这里如果要从ByteBuffer读出数据,需要重新用ByteBuffer.put(ByteBuffer)放回内存中,以便decode方法使用;
return MessageDecoderResult.OK;
return MessageDecoderResult.NOT_OK;
}


public MessageDecoderResult decode(ProtocolSession session, ByteBuffer in,
ProtocolDecoderOutput out)
throws ProtocolViolationException {

// 根据协议将介绍到的数据(放在ByteBuffer中)组装成相对应的实体,调用out.write(Object)方法发送给协议层进行业务逻辑的处理,如果成功返回MessageDecoderResult.OK,否则返回MessageDecoderResult.NOT_OK;

out.write(object);

}


import org.apache.mina.common.ByteBuffer;
import org.apache.mina.protocol.ProtocolEncoderOutput;
import org.apache.mina.protocol.ProtocolSession;
import org.apache.mina.protocol.ProtocolViolationException;
import org.apache.mina.protocol.codec.MessageEncoder;
import java.util. * ;

public class ServerEncoder implements MessageEncoder {
public static Set TYPES;

public ServerEncoder() {

}


public Set getMessageTypes() {
HashSet set
= new HashSet();
set.add(Send.
class ); // 增加要进行编码的实体类
TYPES = Collections.unmodifiableSet( set );
return TYPES;
}



public void encode( ProtocolSession session, Object message, ProtocolEncoderOutput out )
throws ProtocolViolationException {
// 将回应报文实体message编码层returnStr后发送到客户端

byte [] bytes = returnStr.getBytes();
ByteBuffer rb
= ByteBuffer.allocate(bytes.length);
rb.put(bytes);
rb.flip();
out.write(rb);
}

}


第二步:实现ProtocolCodecFactory

import org.apache.mina.protocol.codec.DemuxingProtocolCodecFactory;

public class ServerProtocolCodecFactory extends DemuxingProtocolCodecFactory {
public ServerProtocolCodecFactory( boolean server) {
if (server) {
super .register(ServerDecoder. class );
super .register(ServerEncoder. class );
}

}

}


第三步:实现ProtocolHandler,在有IO事件发生后,经过decode和encode的处理后就把协议实体交个这个处理器进行业务逻辑的处理,因此实现了协议解释和业务逻辑的分离,它与IoHandler非常相似,不过这里处理的是经过编码与解码后的对象实体。

import org.apache.mina.common.IdleStatus;
import org.apache.mina.protocol.ProtocolSession;
import org.apache.mina.util.SessionLog;
import org.apache.mina.protocol.handler.DemuxingProtocolHandler;

public class ServerSessionHandler extends DemuxingProtocolHandler {
public ServerSessionHandler() {
}


public void sessionCreated(ProtocolSession session) throws Exception {
}


public void sessionOpened(ProtocolSession session) throws Exception {
session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE,
60 );
}


public void sessionClosed(ProtocolSession session) {
}


public void messageReceived(ProtocolSession session, Object message)
throws Exception {

// 根据解码后的message,进行业务逻辑的处理
session.close();
}


public void messageSent(ProtocolSession session, Object message) {
}


public void sessionIdle(ProtocolSession session, IdleStatus status)
throws Exception {

// 网络出现空闲时进行处理,并关掉连接
session.close();
}


public void exceptionCaught(ProtocolSession session, Throwable cause) {
cause.printStackTrace();

// 处理所有Handler方法抛出的异常,和Mina架构抛出的异常,并关掉连接
session.close();
}

}


第四步:实现ProtocolProvider

import org.apache.mina.protocol. * ;

public class ServerProtocolProvider implements ProtocolProvider {
private static final ProtocolCodecFactory CODEC_FACTORY = new SemsProtocolCodecFactory(
true );

private static final ProtocolHandler HANDLER = new ServerSessionHandler();

public ServerProtocolProvider() {

}


public ProtocolCodecFactory getCodecFactory() {
return CODEC_FACTORY;
}


public ProtocolHandler getHandler() {
return HANDLER;
}

}


这样协议层便完成了,启动时跟IO层的差不多,不过我们还可以在IO层和协议层用两个线程池,如下:

public class Server {
// 服务器的监听端口号
public static final int SERVER_PORT = 8000 ;

public static void main(String[] args) {

// 进行服务器的相关配置
ServiceRegistry registry = new SimpleServiceRegistry();
IoProtocolAcceptor protocolAcceptor
= (IoProtocolAcceptor) registry
.getProtocolAcceptor(TransportType.SOCKET);
ProtocolThreadPoolFilter protocolThreadPoolFilter
= new ProtocolThreadPoolFilter();
protocolThreadPoolFilter.setMaximumPoolSize(
10 );
protocolThreadPoolFilter.start();
protocolAcceptor.getFilterChain().addLast(
" IoProtocolThreadPool " ,
protocolThreadPoolFilter);

IoAcceptor ioAcceptor
= protocolAcceptor.getIoAcceptor();
IoThreadPoolFilter ioThreadPoolFilter
= new IoThreadPoolFilter();
ioThreadPoolFilter.setMaximumPoolSize(
10 );
ioThreadPoolFilter.start();
ioAcceptor.getFilterChain().addLast(
" IoThreadPool " ,
ioThreadPoolFilter);

Service service
= new Service( " TranServer " , TransportType.SOCKET,
SERVER_PORT);

// 绑定了刚刚实现的ServerProtocolProvider
registry.bind(service, new ServerProtocolProvider());

}


整个MINA框架经常用到的就是这些了,这样的事件触发框架和两层框架使用起来非常方便,不过这种异步框架还是有些非常明显的缺陷:

第一,MINA只会为每个Session分配一个线程,也就是只能一个一个事件按顺序执行,就算你在某个方法执行时产生了新的事件,比如收到新的数据,MINA也会先将该事件缓冲起来,所以你在执行某个方法时是不可能执行dataRead方法的,所以MINA框架是不会阻塞的,要想在一个逻辑方法中实现交互是实现不了的,因此要想出另外的实现方法。

第二,如果客户端发完一个数据给服务器就想马上得到回复,而不等整个业务逻辑执行完,也是实现不到的,因为MINA框架要将整个接收事件处理完了,再把回复信息发给客户端。

第三,如果MINA是作为服务器端等待连接的,当客户端正常关闭后业务逻辑也可继续正常执行,但是如果MINA是连接服务器的客户端,则当服务器关闭后,MINA的session也会关闭。

最后要说明的是MINA使用的线程池是用Leader
/ Followers Tread Pool实现的,默认最大支持2G的线程。当然MINA框架是开源的,用户可以根据自己的需要改写代码,而其MINA的功能也是不断可以扩展的。

以上是我使用MINA的经验总结,其实MINA的相关文档和例子也介绍了很多了,我这里算是一个总结吧,不过有很多地方只是我的个人见解,不一定正确,如果有不对的,希望高手可以提出。

你可能感兴趣的:(buffer)