ApacheMINA是一个网络应用程序框架,用来帮助用户简单地开发高性能和高可扩展性的网络应用程序。它提供了一个通过Java NIO在不同的传输例如TCP/IP和UDP/IP上抽象的事件驱动的异步API。
项目结构:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.roden.mina</groupId>
<artifactId>mina</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mina</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>2.0.12</version>
</dependency>
</dependencies>
</project>
消息实体
package com.roden.mina.entity;
/* * 登陆消息 */
public class LoginMsg {
public short startBit=0x76; //起始固定位
public byte packageLen;//包长度
public byte protocolNo;//协议号
public String content;//信息内容
public short serialNumber;//信息序列号
public short errorCheck;//错误校验
public short endBit;//停止位
public short getStartBit() {
return startBit;
}
public void setStartBit(short startBit) {
this.startBit = startBit;
}
public byte getPackageLen() {
return packageLen;
}
public void setPackageLen(byte packageLen) {
this.packageLen = packageLen;
}
public byte getProtocolNo() {
return protocolNo;
}
public void setProtocolNo(byte protocolNo) {
this.protocolNo = protocolNo;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public short getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(short serialNumber) {
this.serialNumber = serialNumber;
}
public short getErrorCheck() {
return errorCheck;
}
public void setErrorCheck(short errorCheck) {
this.errorCheck = errorCheck;
}
public short getEndBit() {
return endBit;
}
public void setEndBit(short endBit) {
this.endBit = endBit;
}
}
编解码
package com.roden.mina.encode;
import java.nio.charset.Charset;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoder;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import com.roden.mina.entity.LoginMsg;
public class LoginMsgEncoder implements ProtocolEncoder {
private Charset charset;
public LoginMsgEncoder(Charset charset) {
this.charset = charset;
}
@Override
public void encode(IoSession session, Object message,
ProtocolEncoderOutput out) throws Exception {
LoginMsg lm = (LoginMsg) message;
IoBuffer buffer = IoBuffer.allocate(100, false).setAutoExpand(true);
buffer.putShort(lm.getStartBit());
buffer.put(lm.getPackageLen());
buffer.put(lm.getProtocolNo());
buffer.put(lm.getContent().getBytes(charset));
buffer.putShort(lm.getSerialNumber());
buffer.putShort(lm.getErrorCheck());
buffer.putShort(lm.getEndBit());
buffer.flip();
out.write(buffer);
}
@Override
public void dispose(IoSession session) throws Exception {
}
}
package com.roden.mina.decode;
import java.nio.charset.Charset;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import com.roden.mina.entity.LoginMsg;
//消息解码器
public class LoginMsgDecoder extends CumulativeProtocolDecoder {
private Charset charset;
public LoginMsgDecoder(Charset charset) {
this.charset = charset;
}
@Override
protected boolean doDecode(IoSession session, IoBuffer buffer,ProtocolDecoderOutput out) throws Exception {
while (buffer.remaining() > 3) {
//记录解码数据起始位置
buffer.mark();
// 读取包格式及大小
short s = buffer.getShort();
byte length = buffer.get();
// 检查读取的包头是否正常,不正常的话清空buffer
if (length < 0) {
buffer.clear();
break;
} else if (length > 3 && buffer.remaining() >= length+7) {
LoginMsg lm = new LoginMsg();
lm.setStartBit(s);
lm.setPackageLen(length);
lm.setProtocolNo(buffer.get());
byte[] content = new byte[lm.getPackageLen()];
buffer.get(content);
lm.setContent(new String(content,charset));
lm.setSerialNumber(buffer.getShort());
lm.setErrorCheck(buffer.getShort());
lm.setEndBit(buffer.getShort());
out.write(lm);
return true;
} else {
// 如果消息包不完整,将指针重新移动消息头的起始位置
buffer.reset();
return false;
}
}
return false;
}
}
package com.roden.mina.common;
import java.nio.charset.Charset;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolEncoder;
import com.roden.mina.decode.LoginMsgDecoder;
import com.roden.mina.encode.LoginMsgEncoder;
//编解码器生成工产
public class LoginMsgProtocolCodecFactory implements ProtocolCodecFactory {
private ProtocolEncoder encoder;
private ProtocolDecoder decoder;
public LoginMsgProtocolCodecFactory()
{
this(Charset.forName("UTF-8"));
}
public LoginMsgProtocolCodecFactory(Charset charset)
{
encoder = new LoginMsgEncoder(charset);
decoder = new LoginMsgDecoder(charset);
}
@Override
public ProtocolDecoder getDecoder(IoSession arg0) throws Exception {
return decoder;
}
@Override
public ProtocolEncoder getEncoder(IoSession arg0) throws Exception {
return encoder;
}
}
程序的关键就是解码时需要处理断包和粘包,本例通过继承CumulativeProtocolDecoder实现doDecode方法
A. 你的doDecode()方法返回true 时,CumulativeProtocolDecoder 的decode()方法会首先判断你是否在doDecode()方法中从内部的IoBuffer 缓冲区读取了数据,如果没有,则会抛出非法的状态异常,也就是你的doDecode()方法返回true 就表示你已经消费了本次数据(相当于聊天室中一个完整的消息已经读取完毕),进一步说,也就是此时你必须已经消费过内部的IoBuffer 缓冲区的数据(哪怕是消费了一个字节的数据)。如果验证过通过,那么CumulativeProtocolDecoder 会检查缓冲区内是否还有数据未读取,如果有就继续调用doDecode()方法,没有就停止对doDecode()方法的调用,直到有新
的数据被缓冲。
B. 当你的doDecode()方法返回false 时,CumulativeProtocolDecoder 会停止对doDecode()方法的调用,但此时如果本次数据还有未读取完的,就将含有剩余数据的IoBuffer 缓冲区保存到IoSession 中,以便下一次数据到来时可以从IoSession 中提取合并。如果发现本次数据全都读取完毕,则清空IoBuffer 缓冲区。简而言之,当你认为读取到的数据已经够解码了,那么就返回true,否则就返回false。这个CumulativeProtocolDecoder 其实最重要的工作就是帮你完成了数据的累积,因为这个工作是很烦琐的。
也就是说返回true,那么CumulativeProtocolDecoder会再次调用decoder,并把剩余的数据发下来
返回false就不处理剩余的,当有新数据包来的时候把剩余的和新的拼接在一起然后再调用decoder
服务端
package com.roden.mina.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.prefixedstring.PrefixedStringCodecFactory;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import com.roden.mina.common.LoginMsgProtocolCodecFactory;
public class MinaTimeServer {
public static final int PORT= 9800;
public static void main(String[] args) throws IOException {
// 创建服务端监控线程
IoAcceptor acceptor = new NioSocketAcceptor();
// 设置日志记录器
acceptor.getFilterChain().addLast("logger", new LoggingFilter());
// 设置编码过滤器
//acceptor.getFilterChain().addLast("codec",new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));
//acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter( new PrefixedStringCodecFactory(Charset.forName("UTF-8"))));
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter( new LoginMsgProtocolCodecFactory(Charset.forName("UTF-8"))));
// 指定业务逻辑处理器
acceptor.setHandler(new TimeServerHandler());
acceptor.getSessionConfig().setReadBufferSize(2048);
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
// 设置端口号
acceptor.bind(new InetSocketAddress(PORT));
}
}
package com.roden.mina.server;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import com.roden.mina.entity.LoginMsg;
//实现接口IoHandler中的回调方法,优先适配器模式
public class TimeServerHandler extends IoHandlerAdapter {
@Override
public void sessionCreated(IoSession session){
// 显示客户端的ip和端口
System.out.println(session.getRemoteAddress().toString());
}
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
LoginMsg lm=(LoginMsg)message;
System.out.println("----------message-----------");
System.out.println(lm.getPackageLen());
System.out.println(lm.getContent());
System.out.println("---------------------");
}
}
客户端
package com.roden.mina.client;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.Scanner;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.prefixedstring.PrefixedStringCodecFactory;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import com.roden.mina.common.LoginMsgProtocolCodecFactory;
import com.roden.mina.entity.LoginMsg;
import com.roden.mina.server.MinaTimeServer;
public class MimaTimeClient {
public static void main(String[] args) throws UnsupportedEncodingException {
// 创建客户端连接器.
NioSocketConnector connector = new NioSocketConnector();
connector.getFilterChain().addLast("logger", new LoggingFilter());
//connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));
//connector.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new PrefixedStringCodecFactory(Charset.forName("UTF-8"))));
connector.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new LoginMsgProtocolCodecFactory(Charset.forName("UTF-8"))));
// 设置连接超时检查时间
connector.setConnectTimeoutCheckInterval(30);
connector.setHandler(new TimeClientHander());
// 建立连接
ConnectFuture cf = connector.connect(new InetSocketAddress("127.0.0.1", MinaTimeServer.PORT));
// 等待连接创建完成
cf.awaitUninterruptibly();
IoSession session = cf.getSession();
String str="我是消息休";
byte a=2;
LoginMsg lm=new LoginMsg();
short s=78;
lm.setStartBit(s);
lm.setPackageLen((byte)str.getBytes("utf-8").length);
lm.setProtocolNo(a);
lm.setContent(str);
lm.setSerialNumber(s);
lm.setErrorCheck(s);
lm.setEndBit(s);
session.write(lm);
//关闭
if(session!=null){
if(session.isConnected()){
// 等待连接断开
cf.getSession().getCloseFuture().awaitUninterruptibly();
}
// 释放连接
connector.dispose(true);
}
}
}
package com.roden.mina.client;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
public class TimeClientHander extends IoHandlerAdapter {
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
System.out.println("client接受信息:"+message.toString());
}
@Override
public void messageSent(IoSession session, Object message) throws Exception {
System.out.println("client发送信息:"+message.toString());
}
}
Hander部分采用适配器模式,自己选择要实现的方法
参考:
http://mina.apache.org/mina-project/quick-start-guide.html