mina早於netty,出自同一人之手。個人感覺netty更棒但項目老大要求使用mina,所以就學習一下mina啦。學習的成果總結如下。
使用mina開發socket只需要IoAcceptor、IoHandlerAdapter、NioSocketConnector、ProtocolCodecFactory等幾個類基本上就可以進行開發了。
首先一個Server(簡單實例並非完整代碼) 負責Mina服務端的啓停
/**
* @author shenbaise([email protected])
* @date 2012-2-10
* TODO Monitor Server2 將Mina交給spring管理,可以通過web界面來管理Mina的起停,參數設置等等
*/
@Controller("monitorServer2")
public class MonitorServer2 {
private static final int PORT = Constant.remote_port; //定義監聽端口
private IoAcceptor acceptor = null;
private InetSocketAddress socketAddres = null;
@Autowired
MonitorServerHandler monitorServerHandler;
/**
* 啓動Server
* @throws IOException
*/
@RequestMapping("start")
public void init() throws IOException{
if(null == acceptor) //如果 上一個Acceptor沒有被關閉,則新創建的Acceptor無法被綁定,同時上一個Acceptor將無法再被關閉。
acceptor = new NioSocketAcceptor();
socketAddres = new InetSocketAddress(Constant.remote_address,PORT);
// Add two filters : a logger and a codec
acceptor.getFilterChain().addLast( "logger", new LoggingFilter() );
acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" ))));
// Attach the business logic to the server
// acceptor.setHandler( monitorServerHandler );
acceptor.setHandler(new MonitorServerHandler());
// Configurate the buffer size and the iddle time
acceptor.getSessionConfig().setReadBufferSize( 2048 );
acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 );
acceptor.getSessionConfig().setUseReadOperation(true);
// And bind !
acceptor.bind(socketAddres);
}
/**
* 銷燬並退出
*/
@RequestMapping("stop")
public void destroy(){
if (null!=acceptor) {
acceptor.unbind(socketAddres);
acceptor.getFilterChain().clear(); // 清空Filter chain,防止下次重新啓動時出現重名錯誤
acceptor.dispose(); // 可以另寫一個類存儲IoAccept,通過spring來創建,這樣調用dispose後也會重新創建一個新的。或者可以在init方法內部進行創建。
acceptor = null;
// System.exit(0); 將導致容器停止
}
}
public static void main(String[] args) throws IOException {
MonitorServer2 server = new MonitorServer2();
server.init();
try {
Thread.sleep(600000);
server.destroy();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
一個對應的Handler,負責業務邏輯處理
@Service("monitorServerHandler")
public class MonitorServerHandler extends IoHandlerAdapter {
@SuppressWarnings("unused")
@Autowired
private MCacheClient cache;
private int count = 0;
public static final String ALERT_MSG_CACHE_KEY = "1457261687388459164";
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
cause.printStackTrace();
super.exceptionCaught(session, cause);
}
/**
* 將接收到的消息存庫或者緩存
*/
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
/*服務端的邏輯一般都在這裏寫*/
}
@Override
public void messageSent(IoSession session, Object message) throws Exception {
System.out.println("SERVER=>messageSent:" + (String)message);
super.messageSent(session, message);
}
@Override
public void sessionClosed(IoSession session) throws Exception {
System.out.println("SERVER=>sessionClosed: current sessionId:"+session.getId());
super.sessionClosed(session);
}
@Override
public void sessionCreated(IoSession session) throws Exception {
System.out.println("SERVER=>sessionCreated: current sessionId:"+session.getId());
super.sessionCreated(session);
}
@Override
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
System.out.println("SERVER=>sessionIdle:" + session.getIdleCount( status ));
super.sessionIdle(session, status);
}
@Override
public void sessionOpened(IoSession session) throws Exception {
System.out.println("SERVER=>sessionOpened: current sessionId:"+session.getId());
super.sessionOpened(session);
}
}
對應Client端也是類似的,一個類負責連接Server,一個來負責業務邏輯的處理。
Client端:
public class MonitorClient {
private static final int PORT = 10000;
public static String address = "127.0.0.1";
private static InetSocketAddress socketAddres = new InetSocketAddress(address,PORT);
private NioSocketConnector connector = null;
/**
* 啓動(在listener中啓動是需要新建一個線程來連接Server,否則web容器會阻塞而無法啓動。)
*/
public void init(){
connector = new NioSocketConnector();
connector.getFilterChain().addLast( "logger", new LoggingFilter() );
connector.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new MyCodeFactory( Charset.forName( "UTF-8" )))); //設置編碼過濾器
connector.setConnectTimeoutMillis(3000);
connector.setHandler(new MonitorClientHandler());//設置事件處理器
ConnectFuture cf = connector.connect(socketAddres);//建立連接
cf.awaitUninterruptibly();
cf.getSession().getCloseFuture().awaitUninterruptibly();//等待連接斷開
}
/**
* 銷燬
*/
public void destroy(){
socketAddres = null;
connector.dispose();
}
public static void main(String[] args) {
// MonitorClient client = new MonitorClient();
// client.init();
// try {
// Thread.sleep(70000);
//
// client.destroy();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
ClientThread thread = new ClientThread();
Thread cThread = new Thread(thread);
cThread.start();
}
/**
* 啓動(在新建線程中連接Server)
*/
public void init2(){
ClientThread thread = new ClientThread();
Thread cThread = new Thread(thread);
cThread.start();
}
}
Client端的Handler:
public class MonitorClientHandler extends IoHandlerAdapter {
int count = 0;
StringBuilder sb = new StringBuilder();
int pos = 0;
// @Autowired BaseDao baseDao;
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
cause.printStackTrace();
super.exceptionCaught(session, cause);
}
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
/*Client端的邏輯會在這裏*/
super.messageReceived(session, message);
}
@Override
public void messageSent(IoSession session, Object message) throws Exception {
/*或者會在這裏,當然每個地方都會有一些東西需要處理,例如創建、關閉、空閒等等*/
super.messageSent(session, message);
}
@Override
public void sessionClosed(IoSession session) throws Exception {
/*這裏可以實現重連,但是這會涉及到你代碼的一些資源分配或調度邏輯,跑一個線程進行重連*/
super.sessionClosed(session);
}
@Override
public void sessionCreated(IoSession session) throws Exception {
System.out.println("CLIENT=>sessionCreated: current sessionId:"+session.getId());
super.sessionCreated(session);
}
@Override
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
System.out.println("CLIENT=>sessionIdle:" + session.getIdleCount( status ));
super.sessionIdle(session, status);
}
@Override
public void sessionOpened(IoSession session) throws Exception {
System.out.println("CLIENT=>sessionOpened: current sessionId:"+session.getId());
super.sessionOpened(session);
}
}
最後還有ProtocolCodecFactory也是比較重要的
一個很簡單的實現如下:
public class MyCodeFactory implements ProtocolCodecFactory {
private LineDelimiter enLineDelimiter = new LineDelimiter(Constant.CHAR2);
private final TextLineEncoder encoder;
private final TextLineDecoder decoder;
/*final static char endchar = 0x1a;*/
final static String endchar = Constant.CHAR2;
public MyCodeFactory() {
this(Charset.forName("gb2312"));
}
public MyCodeFactory(Charset charset) {
encoder = new TextLineEncoder(charset, enLineDelimiter);
decoder = new TextLineDecoder(charset, enLineDelimiter);
}
public ProtocolDecoder getDecoder(IoSession session) throws Exception {
// TODO Auto-generated method stub
return decoder;
}
public ProtocolEncoder getEncoder(IoSession session) throws Exception {
// TODO Auto-generated method stub
return encoder;
}
public int getEncoderMaxLineLength() {
return encoder.getMaxLineLength();
}
public void setEncoderMaxLineLength(int maxLineLength) {
encoder.setMaxLineLength(maxLineLength);
}
public int getDecoderMaxLineLength() {
return decoder.getMaxLineLength();
}
public void setDecoderMaxLineLength(int maxLineLength) {
decoder.setMaxLineLength(maxLineLength);
}
}
mina的TextLineCodecFactory默認以換行來進行分隔,我們也可以自定義。以TextLineCodecFactory爲例,mina在發送是會在發送內容之後自動加一個換行符,在接收時會按換行符來截取收到的內容。
在web工程中啓動mina客戶端時應該新開一個線程,不要用主線程。