apache mina 学习(十三)-----状态机和IoHander配合使用

来源:互联网 发布:如何开通淘宝直播权限 编辑:程序博客网 时间:2024/06/15 12:05

现在我们把上一张的收录机的例子改造为一个tcp服务器,用文本的传输,效果如下;

telnet localhost 12345S: + Greetings from your tape deck!C: listS: + (1: "The Knife - Silent Shout", 2: "Kings of convenience - Riot on an empty street")C: load 1S: + "The Knife - Silent Shout" loadedC: playS: + Playing "The Knife - Silent Shout"C: pauseS: + "The Knife - Silent Shout" pausedC: playS: + Playing "The Knife - Silent Shout"C: infoS: + Tape deck is playing. Current tape: "The Knife - Silent Shout"C: ejectS: - Cannot eject while playingC: stopS: + "The Knife - Silent Shout" stoppedC: ejectS: + "The Knife - Silent Shout" ejectedC: quitS: + Bye! Please come back!
完整的代码在org.apache.mina.example.tapedeck 包中
状态如下:
@State public static final String ROOT = "Root";@State(ROOT) public static final String EMPTY = "Empty";@State(ROOT) public static final String LOADED = "Loaded";@State(ROOT) public static final String PLAYING = "Playing";@State(ROOT) public static final String PAUSED = "Paused";
事件方法有些变化:
@IoHandlerTransitions({    @IoHandlerTransition(on = MESSAGE_RECEIVED, in = LOADED, next = PLAYING),    @IoHandlerTransition(on = MESSAGE_RECEIVED, in = PAUSED, next = PLAYING)})public void playTape(TapeDeckContext context, IoSession session, PlayCommand cmd) {    session.write("+ Playing \"" + context.tapeName + "\"");}
我们没有使用上个例子中的Transaction注解,而是用了IoHandlerTransaction注解,当为Mina的IoHandler创建一个状态机时,它会选择让你使用Java enum (枚举)类型来替代我们上面使用的字符串类型。这个在Mina的IoFilter中也是一样的。我们现在使用MESSAGE_RECEIVED来替代"play"来作为事件的名字(on是@IoHandlerTransition的一个属性)。这个常量是在org.apache.mina.statemachine.event.IoHandlerEvents中定义的,它的值是"messageReceived",这个和Mina的IoHandler中的messageReceived()方法是一致的。同时我们自定义了一个StateContext状态上下文的实现TapeDeckContext,主要作用就是用于返回当前收录机的名字。
static class TapeDeckContext extends AbstractStateContext {    public String tapeName;}
playTape()方法使用了PlayCommand命令来作为它的最后的一个参数意味着只有在客户端发送的信息被编码成layCommand命令时,该方法才会被调用
@IoHandlerTransition(on = MESSAGE_RECEIVED, in = EMPTY, next = LOADED)public void loadTape(TapeDeckContext context, IoSession session, LoadCommand cmd) {    if (cmd.getTapeNumber() < 1 || cmd.getTapeNumber() > tapes.length) {        session.write("- Unknown tape number: " + cmd.getTapeNumber());        StateControl.breakAndGotoNext(EMPTY);//如果用户传递了非法的状态,则用empty代替    } else {        context.tapeName = tapes[cmd.getTapeNumber() - 1];        session.write("+ \"" + context.tapeName + "\" loaded");    }}
connect()方法将会在Mina开启一个会话并调用sessionOpened()方法时触发
@IoHandlerTransition(on = SESSION_OPENED, in = EMPTY)public void connect(IoSession session) {    session.write("+ Greetings from your tape deck!");}
它所做的工作就是向客户端发送欢迎的信息。状态机将会保持空的状态。pauseTape(), stopTape() 和 ejectTape() 方法和 playTape()很相似

错误处理如下:

@IoHandlerTransition(on = MESSAGE_RECEIVED, in = ROOT, weight = 10)public void error(Event event, StateContext context, IoSession session, Command cmd) {    session.write("- Cannot " + cmd.getName() + " while "            + context.getCurrentState().getId().toLowerCase());}
主方法:
private static IoHandler createIoHandler() {    StateMachine sm = StateMachineFactory.getInstance(IoHandlerTransition.class).create(EMPTY, new TapeDeckServer());            return new StateMachineProxyBuilder().setStateContextLookup(            new IoSessionStateContextLookup(new StateContextFactory() {                public StateContext create() {                    return new TapeDeckContext();                }            })).create(IoHandler.class, sm);}// This code will work with MINA 1.0/1.1:public static void main(String[] args) throws Exception {    SocketAcceptor acceptor = new SocketAcceptor();    SocketAcceptorConfig config = new SocketAcceptorConfig();    config.setReuseAddress(true);    ProtocolCodecFilter pcf = new ProtocolCodecFilter(            new TextLineEncoder(), new CommandDecoder());    config.getFilterChain().addLast("codec", pcf);    acceptor.bind(new InetSocketAddress(12345), createIoHandler(), config);}// This code will work with MINA trunk:public static void main(String[] args) throws Exception {    SocketAcceptor acceptor = new NioSocketAcceptor();    acceptor.setReuseAddress(true);    ProtocolCodecFilter pcf = new ProtocolCodecFilter(            new TextLineEncoder(), new CommandDecoder());    acceptor.getFilterChain().addLast("codec", pcf);    acceptor.setHandler(createIoHandler());    acceptor.setLocalAddress(new InetSocketAddress(PORT));    acceptor.bind();}

reateIoHandler() 方法创建了一个状态机,这个和我们之前所做的相似。除了我们一个IoHandlerTransition.class类来代替Transition.class 在StateMachineFactory.getInstance(...)方法中。这是我们在使用 @IoHandlerTransition 声明的时候必须要做的。当然这时我们使用了一个IoSessionStateContextLookup和一个自定义的StateContextFactory类,这个在我们创建一个IoHandler 代理时被使用到了。如果我们没有使用IoSessionStateContextLookup ,那么所有的客户端将会使用同一个状态机,这是我们不希望看到的。

main()方法创建了SocketAcceptor实例,并且绑定了一个ProtocolCodecFilter ,它用于编解码命令对象。最后它绑定了12345端口和IoHandler的实例。这个oHandler实例是由createIoHandler()方法创建的。