Mina實現傳輸物件的編解碼
前言:協議編解碼器是在使用Mina 的時候你最需要關注的物件,因為在網路傳輸的資料都是二進位制資料(byte),而你在程式中面向的是JAVA 物件,這就需要你實現在傳送資料時將JAVA 物件編碼二進位制資料,而接收資料時將二進位制資料解碼為JAVA 物件(注意這裡不是JAVA 物件的序列化和反序列化)。
1)、編碼:編碼是將普通物件的屬性內容依次裝換成二進位制物件屬性內容的過程。
2)、解碼:由轉換成的二進位制物件欄位轉換成普通物件屬性欄位內容的過程。
1、編解碼工廠:提供編碼和解碼實現的呼叫入口
Mina 中的協議編解碼器通過過濾器ProtocolCodecFilter 構造,這個過濾器的構造方法需要一個ProtocolCodecFactory,
ProtocolCodecFactory 中有如下兩個方法:
public interface ProtocolCodecFactory {
ProtocolEncoder getEncoder(IoSession session) throws Exception;//ProtocolEncoder是自定義編碼器要實現的介面
ProtocolDecoder getDecoder(IoSession session) throws Exception;//ProtocolDecoder是自定義解碼器要實現的介面
}
以訊息傳遞工廠為例:
//編解碼器生成工廠 public class MessageProtocolCodecFactory implements ProtocolCodecFactory { private ProtocolEncoder encoder; private ProtocolDecoder decoder; public MessageProtocolCodecFactory() { this(Charset.forName("UTF-8")); } public MessageProtocolCodecFactory(Charset charset) { encoder = new MessageEncoder(charset); decoder = new MessageDecoder(charset); } @Override public ProtocolDecoder getDecoder(IoSession arg0) throws Exception { return decoder; } @Override public ProtocolEncoder getEncoder(IoSession arg0) throws Exception { return encoder; } }
2、訊息傳輸物件:裝載傳輸資料的實體物件
下面是示例程式碼:(模擬手機資訊的編解碼,訊息格式:訊息頭,傳送人,接收人,內容長度,內容資訊)
MsgObject.java: 訊息實體類
public class MsgObject { //傳送者 private String sender; //接收者 private String receiver; //資訊內容 private String content; public String getSender() { return sender; } public void setSender(String sender) { this.sender = sender; } public String getReceiver() { return receiver; } public void setReceiver(String receiver) { this.receiver = receiver; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
3、訊息編碼器 :MessageEncoder.java,該類實現ProtocolEncoder或繼承自ProtocolEncoderAdapter
//訊息編碼器
public class MessageEncoder extends ProtocolEncoderAdapter {
private Charset charset;
public MessageEncoder(Charset charset){
this.charset = charset;
}
@Override
public void encode(IoSession session, Object message, ProtocolEncoderOutput out)
throws Exception {
MsgObject msg = (MsgObject) message;
//生成字元編碼器
CharsetEncoder charsetEncoder = charset.newEncoder();
//得到要傳送物件屬性內容,準備進行編碼
String status = "M sip:wap.fetion.com.cn SIP-C/2.0";
String sender = msg.getSender();
String receiver = msg.getReceiver();
String content = msg.getContent();
//開闢一個快取空間,設定為自動調整大小
IoBuffer ioBuffer = IoBuffer.allocate(100);
ioBuffer.setAutoExpand(true);
//將要傳送的資訊放入快取空間
//訊息頭
ioBuffer.putString(status + "\n", charsetEncoder);
//訊息傳送者
ioBuffer.putString("S: " + sender + "\n", charsetEncoder);
//訊息接收者
ioBuffer.putString("R: " + receiver + "\n", charsetEncoder);
//訊息內容長度
ioBuffer.putString("L: " + content.getBytes(charset).length + "\n", charsetEncoder);
//訊息內容
ioBuffer.putString(content + "\n", charsetEncoder);
//編碼後的資訊已放入ioBuffer中,進行寫回
ioBuffer.flip();
out.write(ioBuffer);
}
}
4、訊息解碼器:MessageDecoder.java,該類實現ProtocolDecoder或繼承自ProtocolDecoderAdapter
//訊息解碼器
public class MessageDecoder extends ProtocolDecoderAdapter{
private Charset charset;
public MessageDecoder(Charset charset) {
this.charset = charset;
}
@Override
protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
CharsetDecoder charDecoder = charset.newDecoder();
IoBuffer buffer = IoBuffer.allocate(100).setAutoExpand(true);
// 接收解碼後的資訊
String status = "";
String sender = "";
String receiver = "";
String contentLen = "";
String content = "";
int textLineNumber = 1;
int columnNumber = 0;
// 如果快取區還有訊息
while (in.hasRemaining()) {
byte bt = in.get();
buffer.put(bt);
//換行
if (bt == 10 && textLineNumber < 5) {
columnNumber++;
if (textLineNumber == 1) {
buffer.flip();
status = buffer.getString(columnNumber, charDecoder);
status = status.substring(0, status.length() - 1);
columnNumber = 0;
buffer.clear();
}
if (textLineNumber == 2) {
buffer.flip();
sender = buffer.getString(columnNumber, charDecoder);
sender = sender.substring(0, sender.length() - 1);
columnNumber = 0;
buffer.clear();
}
if (textLineNumber == 3) {
buffer.flip();
receiver = buffer.getString(columnNumber, charDecoder);
receiver = receiver.substring(0, receiver.length() - 1);
columnNumber = 0;
buffer.clear();
}
if (textLineNumber == 4) {
buffer.flip();
contentLen = buffer.getString(columnNumber, charDecoder);
contentLen = contentLen.substring(0,
contentLen.length() - 1);
columnNumber = 0;
buffer.clear();
}
textLineNumber++;
} else if (textLineNumber == 5) {
columnNumber++;
if (columnNumber == Long.parseLong(contentLen.split(": ")[1])) {
buffer.flip();
content = buffer.getString(columnNumber, charDecoder);
textLineNumber++;
break;
}
} else {
columnNumber++;
}
}
MsgObject smsObject = new MsgObject();
smsObject.setSender(sender.split(": ")[1]);
smsObject.setReceiver(receiver.split(": ")[1]);
smsObject.setContent(content);
out.write(smsObject);
return false;
}
}
5、Mina編解碼需要知道的一些事情
1)、最好了解下Mina IoBuffer的讀取操作。
2)、在過濾器中呼叫這些編解碼器來進行物件的傳輸,伺服器端和客戶端的主程式編寫。
3)、考慮是否分批實現物件解碼
上面的訊息解碼器( MessageDecoder.java)中的解碼考慮的情況是訊息一次性從伺服器傳送過來,但有時訊息可能不是一次性從伺服器傳送過來,而是分成了幾次分批過來,這時就會重複呼叫解碼器的deCode()方法,這時狀態變數textLineNumber和columnNumber就會被重置,所以要把狀態變數儲存起來。可能你會想到將狀態變數儲存在解碼器的成員變數中,但是Mina不保證每次呼叫deCode()方法的都是同一個執行緒,所以狀態變數不是執行緒安全的。所以要將狀態變數儲存到IoSession中,因為IoSession用了一個同步的HashMap儲存物件(客戶端和服務端發起的會話不是同一個程序,所以session不是同一個session,這裡可以體現Mina是事件驅動非同步的API)。
以下是在IoSession中儲存狀態變數:
// 儲存資料狀態物件的key值
private final AttributeKey CONTEXT = new AttributeKey(getClass(), "context");
//通過IoSession.setAttribute和IoSession.getAttribute的儲存和得到儲存資料的物件
private MsgContext getContext(IoSession session) {
MsgContext context = (MsgContext) session.getAttribute(CONTEXT);
if (null == context) {
context = new MsgContext();
session.setAttribute(CONTEXT, context);
}
return context;
}