本文转自: http://blog.sina.com.cn/s/blog_46e73e770100aj34.html
关于mina的开发文档和说明网上应该可以找到很多,但比较适合初学者的那种比较详细的说明,还不不多的。由于在实际工作中用到了socket server方面的功能,所以,简单的研究了一下mina,也用mina做了一个socket server。下面就是我用mina做socket server的步骤和一些示例代码。
开发环境:
工具:eclise3.3+jdk1.6.0
需要的库:
backport-util-concurrent.jar
commons-logging.jar
log4j-1.2.14.jar
mina-core-2.0.0-M1.jar
mina-core-2.0.0-M1-sources.jar
mina-filter-compression-2.0.0-M1.jar
mina-filter-compression-2.0.0-M1-sources.jar
slf4j-api-1.5.2.jar
slf4j-jdk14-1.5.2.jar
至于这些库的下载地址,网上应该很容易找到。
开发步骤:
1、首先在eclipse中新建一个java工程,简单类型的即可
2、然后在工程的目录下新建lib目录,把前面提到的所需要的库
都拷贝到lib下
3、在工程的目录下新建log目录,把log4j的配置文件放到该log目录下。至于log4j的配置文件,网上也很容易找到。我的网络硬盘上也有,可以直接去下载。
这是工程的目录结构看起来如下图所示:
4、接下来就可以创建socket消息对象了,也就是通过socket接收、发送的对象。例如:
public class TestMessage implements Serializable{ private byte version; private short protocolType; private int msgLen; private String msgBody; public TestMessage(){ } ...//get or set method }
在此,假设通讯的socket消息结构如下:一个字节的版本号+2个字节的协议类型
+4个字节的消息体长度+字
符串格式的消息体。所以,对应的消息对象定义就如上定义。在解析socket接收到的内容时,就需要根据该协议格式进行解析。如果有很多消息类型的话,最好通过抽象类、接口等模式进行设计。
5、创建真正用来解析socket server接收到内容的解码器,也就是新建一个类,要实现mina中的MessageDecoder接口以及方法。需要注意的是,
解码器必须是没有参数的构造函数
。如:
public class TestMessageDecoder implements MessageDecoder{ private byte version; private short protocolType; //通过版本号与协议类型区分要用该解码器解析socket数据 public TestMessageDecoder (){ this.version = 1; this.protocolType = 2; } public MessageDecoderResult decodable(IoSession session, IoBuffer in) { //7=1字节的version+2字节的protocolType+4字节的body长度,也就是协议头字节长度 if(in.remaining() < 7){ return MessageDecoderResult.NEED_DATA; } //get version byte ver = in.get(); //get protocol type short type = in.getShort(); //get message length int len = in.getInt(); //如果数据未接收完,继续接收 if(in.remaining() < len) return MessageDecoderResult.NEED_DATA; //如果版本号与协议类型都匹配,就可以进行解析 if(ver == this.version && type == this.protocolType ){ return MessageDecoderResult.OK; } return MessageDecoderResult.NOT_OK; } //真正解析socket数据 public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { TestMessage tMsg = new TestMessage(); tMsg.setVersion(in.get()); tMsg.setProtocolType(in.getShort()); int msgLen = in.getInt(); //字符串解码格式 Charset charset = Charset.forName("GBK"); CharsetDecoder decoder = charset.newDecoder(); String msgBody = in.getString(msgLen,decoder); tMsg.setMsgLen(msgLen); tMsg.setMsgBody(msgBody); out.write(tMsg); return MessageDecoderResult.OK; } public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception { } }
6、创建协议解析工厂,也就是要把上面创建的decoder添加到解析队列中。代码如:
public class TestMessageCodecFactory extends DemuxingProtocolCodecFac tory{ public TestMessageCodecFactory (){ super.addMessageDecoder(TestMessageDecoder .class); } }
7、创建socket会话句柄。扩展IoHandlerAdapter
类并重载其中的方法即可。示例代码如下:
public class TestServerSessionHandler extends IoHandlerAdapter { private final Logger log = Logger.getLogger(TestServerSessionHandler .class); private final int IDLE = 300; public void exceptionCaught(IoSession session, Throwable cause) throws Exception { session.close(); log.error("session occured exception, so close it."); } public void messageReceived(IoSession session, Object message) throws Exception { if(message instanceof TestMessage){ TestMessage msg = (TestMessage )message; //在此得到的msg就是通过socket接收到的且已经安装协议解析好的对象了。在此仅打印出而已 System.out.println("received message content:"+msg.getMsgBody()); } } public void messageSent(IoSession session, Object message) throws Exception { super.messageSent(session, message); } public void sessionCreated(IoSession session) throws Exception { log.info("remote client ["+session.getRemoteAddress().toString()+"] connected."); } public void sessionIdle(IoSession session, IdleStatus status) throws Exception { log.info("session idle, so disconnecting......"); session.close(); log.info("disconnected."); } public void sessionOpened(IoSession session) throws Exception { //设置socket空闲时间 session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, IDLE); //其他配置也在此设置 } } 8、准备工作都已完成,接下来就可以创建socket server了。如:
public class TestSocketServer{ private static final int DEFAULT_SERVER_PORT = 1234; private int port; public TestSocketServer(int port){ this.port = port; } public void startServer()throws Exception{ if(this.port < 0){ this.port = DEFAULT_SERVER_PORT; } NioSocketAcceptor acceptor = new NioSocketAcceptor(); //set self codecFactory acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TestMessageCodecFactory ())); acceptor.getFilterChain().addLast("logger", new LoggingFilter()); //set self handler acceptor.setHandler(new TestServerSessionHandler ()); acceptor.bind(new InetSocketAddress(this.port)); System.out.println("socket server started,waitting for connection......"); } public static void main(String[] args) { PropertyConfigurator.configure("log/log4j.properties"); ItvSocketServer server = new ItvSocketServer(1234); try { server.startServer(); } catch (Exception e) { e.printStackTrace(); } } }
至此,一个基于mina的socket server就全部完成了,可以接收符合协议的socket数据,且接收到的内容已经封装成符合协议的对象。如果要根据接收到的内容做进一步处理,在TestServerSessionHandle中的messageReceived方法中进行即可。 对socket server的数据处理流程,可以这样理解: 1、当有socket数据过来时,首先查找已经注册了的解码器中是否有匹配的解码器,如果没有,直接报错返回;否则就进入对应的解码器。 2、进入对应的解码器后,首先进入过滤的是解码器中的decodable方法,判断是否具备解码的条件。只有具备了解码的条件后,才真正由解码器中的decoder方法按照协议进行解码,并把socket内容解析成对应的对象。 3、当一个对应的对象解析完成后,就会自动被socket的回话句柄TestServerSessionHandler 中的messageReceived方法拦截,对接收到底消息进行响应和处理。 通常,socket server在接收到消息后,会根据解析情况以及处理结果返回给client一些信息,这就需要socket server要具有发送消息的功能。发送消息就需要把要发送的内容编码后通过socket的会话写出去,具体的实现步骤和方法,待续。