Java Mina解碼中遇到的問題及解決方案

來源:互聯網
上載者:User

最近一個項目中用到了Java解碼,主要採用的是Mina架構,現將遇到的問題總結一下,以備後查:
終端是用C編碼,通過CAN中轉,最後轉成TCP送出,用Java寫了個服務端,接收並解析入庫

一、位元組序的問題
關於位元組序,請見 http://zh.wikipedia.org/wiki/%E5%AD%97%E8%8A%82%E5%BA%8F
C和Java不一樣,所以代碼中要這樣設定一下

@Overrideprotected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {in.order(ByteOrder.LITTLE_ENDIAN);//...}

二、資料類型的問題
C,C++中有unsigned類型,Java中沒有,所以對於一個位元組,C中可以儲存unsigned類型,即0到255,而java對應的是byte,即-128到127
所以要做轉換

byte cur_b = in.get(); //這個是byteint cur_i = cur_b & 0xff; //做運算時要轉成整形

三、豐富的位元組轉換函式
除了上面的強轉之外,Mina的IoBuffer中提供了很多方便的轉換api,能將一個或多個位元組的位元據方便地轉成這種類型

get()get(byte[])get(byte[], int, int)get(int)getChar()getChar(int)getDouble()getDouble(int)getEnum(int, Class<E>)getEnum(Class<E>)getEnumInt(int, Class<E>)getEnumInt(Class<E>)getEnumSet(int, Class<E>)getEnumSet(Class<E>)getEnumSetInt(int, Class<E>)getEnumSetInt(Class<E>)getEnumSetLong(int, Class<E>)getEnumSetLong(Class<E>)getEnumSetShort(int, Class<E>)getEnumSetShort(Class<E>)getEnumShort(int, Class<E>)getEnumShort(Class<E>)getFloat()getFloat(int)getHexDump()getHexDump(int)getInt()getInt(int)getLong()getLong(int)getMediumInt()getMediumInt(int)getObject()getObject(ClassLoader)getPrefixedString(int, CharsetDecoder)getPrefixedString(CharsetDecoder)getShort()getShort(int)getSlice(int)getSlice(int, int)getString(int, CharsetDecoder)getString(CharsetDecoder)getUnsigned()getUnsigned(int)getUnsignedInt()getUnsignedInt(int)getUnsignedMediumInt()getUnsignedMediumInt(int)getUnsignedShort()getUnsignedShort(int)

基本上足夠使用了

四、處理斷包等問題,只要解碼器Decoder extends CumulativeProtocolDecoder即可
/*
A ProtocolDecoder that cumulates the content of received buffers to a cumulative buffer to help users implement decoders. 
If the received IoBuffer is only a part of a message. decoders should cumulate received buffers to make a message complete or to postpone decoding until more buffers arrive. 
Here is an example decoder that decodes CRLF terminated lines into Command objects:
 */

public class CrLfTerminatedCommandLineDecoder         extends CumulativeProtocolDecoder {     private Command parseCommand(IoBuffer in) {         // Convert the bytes in the specified buffer to a         // Command object.         ...     }     protected boolean doDecode(             IoSession session, IoBuffer in, ProtocolDecoderOutput out)             throws Exception {         // Remember the initial position.         int start = in.position();         // Now find the first CRLF in the buffer.         byte previous = 0;         while (in.hasRemaining()) {             byte current = in.get();             if (previous == '\r' && current == '\n') {                 // Remember the current position and limit.                 int position = in.position();                 int limit = in.limit();                 try {                     in.position(start);                     in.limit(position);                     // The bytes between in.position() and in.limit()                     // now contain a full CRLF terminated line.                     out.write(parseCommand(in.slice()));                 } finally {                     // Set the position to point right after the                     // detected line and set the limit to the old                     // one.                     in.position(position);                     in.limit(limit);                 }                 // Decoded one line; CumulativeProtocolDecoder will                 // call me again until I return false. So just                 // return true until there are no more lines in the                 // buffer.                 return true;             }             previous = current;         }         // Could not find CRLF in the buffer. Reset the initial         // position to the one we recorded above.         in.position(start);         return false;     } }

/*
Please note that this decoder simply forward the call to doDecode(IoSession, IoBuffer, ProtocolDecoderOutput) if the underlying transport doesn't have a packet fragmentation. Whether the transport has fragmentation or not is determined by querying TransportMetadata.

*/

除此之外,還要將未解析完的資料和標誌位等放到IoSession中

Context ctx = getContext(session);private Context getContext(IoSession session) {Context context = (Context) session.getAttribute(CONTEXT);if (context == null) {context = new Context();session.setAttribute(CONTEXT, context);}return context;}

五、多個服務(連接埠)共用Session問題

應用情境比如,一個連接埠9090專門負責資料接收,另外一個連接埠8080接收來自web端的指令並傳送給終端並返回資料。

原理是在一個主函數中開兩個mina服務(連接埠),但此時兩個服務中的IoSession是不能互訪的,所以要在主進程中管理他們

public class Main {private static final Set<IoSession> sessions = Collections.synchronizedSet(new HashSet<IoSession>());//這個至關重要private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);private static final int PORT_DTU = 9090;private static final int PORT_WEB = 8080;private static final ProtocolCodecFactory textLineCodecFactory = new TextLineCodecFactory(Charset.forName("UTF-8"));public static void main(String[] args) {try {new MinaxServer(PORT_DTU, textLineCodecFactory, new MinaxDtuIoHandler()).start();LOGGER.info("Server started at port {}.", PORT_DTU);new MinaxServer(PORT_WEB, textLineCodecFactory, new MinaxWebIoHandler()).start();LOGGER.info("Server started at port {}.", PORT_WEB);} catch (IOException ioe) {LOGGER.error("Can't start server!", ioe);}while (true) {System.out.printf("session count:%d\n", Main.getSessions().size());try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}}}public static Set<IoSession> getSessions() {return sessions;}}

之後在傳送端的IoHandler中加以管理,即在SessionCreated時,加入到Set中,sessionClosed的時候從Set中Remove掉

@Overridepublic void sessionCreated(IoSession session) throws Exception {// super.sessionCreated(session);Main.getSessions().add(session);// String message = String.format("IP:%s, Welcome to dtu server\n", session.getRemoteAddress());// session.write(message);logger.debug("{}:session[{}]Created...", session.getRemoteAddress(), session.getId());}@Overridepublic void sessionOpened(IoSession session) throws Exception {// super.sessionOpened(session);logger.debug("sessionOpened...");}@Overridepublic void sessionClosed(IoSession session) throws Exception {Main.getSessions().remove(session);// super.sessionClosed(session);logger.debug("sessionClosed...");}

在Web端訪問IoSession

@Overridepublic void messageReceived(IoSession session, Object message) throws Exception {logger.debug("messageReceived...");logger.debug("...message:{}", message);String jsonMessage = message.toString();JSONObject o = JSON.parseObject(jsonMessage);Integer dtu_id = o.getInteger("dtu_id");Long session_id = o.getLong("session_id");String action = o.getString("action");String params = o.getString("params");action = null == action ? "" : action.toLowerCase();JSONObject p = JSON.parseObject(params);Set<IoSession> sessions = Main.getSessions();//從主線程中取得sessionswitch (action) {case "quit":session.close(true);break;case "get_session_count":session.write(sessions.size());break;case "broadcast":String msg_bc = null == p ? null : p.getString("message");if (null == msg_bc || msg_bc.length() == 0) {msg_bc = "hello dtu!";}synchronized (sessions) {//注意同步for (IoSession sess : sessions) {// if (session.hashCode() == sess.hashCode()) {// continue;// }if (sess.isConnected()) {sess.write(msg_bc);}}}break;default:session.write("UNKOWN COMMAND");break;}

六、Web端的處理

對於Server接收端,一般採用長串連,http是無狀態的,例如我發送了一個指令以取得線上的終端資料,發送到Server後,通過IoHandler處理,返回資料後在頁面顯示,這個串連就沒有必要再保持了,可以直接關掉。相關代碼

@RequestMapping(value = "/send", produces = "text/html;charset=UTF-8", method = RequestMethod.POST)@ResponseBodypublic String send(@RequestParam String cmd) {logger.debug("...cmd:{}", cmd);// 建立用戶端連接器IoConnector connector = new NioSocketConnector();// 設定事件處理器connector.setHandler(new ClientIoHandler());// 設定編碼過濾器和按行讀取資料模式connector.getFilterChain().addLast("codec",new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));// 建立串連ConnectFuture future = connector.connect(new InetSocketAddress("localhost", 8080));// 等待串連建立完成future.awaitUninterruptibly();// 擷取串連會話IoSession session = future.getSession();// 產生當次發送請求標識,標識由用戶端地址+隨機數// 這裡的標識只是一個例子,標識可以任何方法產生,只要保持在系統中的唯一性String flag = UUID.randomUUID().toString();logger.debug("...flag:{}", flag);// 將標識儲存到當前session中session.setAttribute(ClientIoHandler.SESSION_KEY, flag);// 向伺服器發送命令資訊session.write(cmd);// 等待串連斷開session.getCloseFuture().awaitUninterruptibly();connector.dispose();// 通過標識擷取儲存的結果Object result = ClientIoHandler.RESULTS.get(flag);// 清除標識內容ClientIoHandler.RESULTS.remove(flag);// 將結果返回用戶端return result.toString();}

ClientIoHandler:

public class ClientIoHandler extends IoHandlerAdapter {private final Logger logger = LoggerFactory.getLogger(ClientIoHandler.class);public static final String SESSION_KEY = "com.company.project.client.clientiohandle.SESSION_KEY";public static final Map<String, Object> RESULTS = new ConcurrentHashMap<String, Object>();public void messageReceived(IoSession session, Object message) throws Exception {logger.debug("messageReceived...");// 從session中取到標識String flag = (String) session.getAttribute(SESSION_KEY);logger.debug("...flag:{}", flag);// 將從服務端接收到的message和標識綁定儲存,可以儲存到記憶體、檔案、資料庫等等// 在這裡簡單的以標識為key,message為value儲存到Map中RESULTS.put(flag, message);// 關閉sessionsession.close(true);}}

後繼再補充...

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.