準備MINA
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>2.0.7</version>
</dependency>
由於服務端是C寫的。這面先寫個客戶端。使用的是短鏈接
客戶端代碼
//抽象客戶端父類
public abstract class AbstractMinaProvider implements MinaProvider {
/**
* 主機
*/
private String host = "127.0.0.1";
/**
* 端口號
*/
private int port = 1234;
/**
* 超時時間
*/
private long timeOut = 10;
/**
* 請求類型
*/
private boolean snyc = true;
/**
* socket連接器接口
*/
private SocketConnector connector;
/**
* socket會話配置接口
*/
private SocketSessionConfig sessionConfig;
/**
* 用於connect()後,執行一些操作,獲取getSession()等。
*/
private ConnectFuture connectFuture;
/**
* socket會話
*/
private IoSession session;
/**
*構造器,判斷是否同步通訊
*/
protected AbstractMinaProvider(boolean sync) {
//讀取配置文件
loadConfigs();
// 實例化connector
connector = new NioSocketConnector();
// 添加過濾器,在最後添加編碼解碼過濾器
connector.getFilterChain().addLast("codec",new ProtocolCodecFilter(new CodecFactory(true))); //沒有他就別玩了~
if(sync){//如果是同步傳輸,使用默認配置。
sessionConfig = getConnector().getSessionConfig();
//開啓讀方法,設置IoSession的read()方法爲可用,默認爲false
sessionConfig.setUseReadOperation(true);
}
}
private void loadConfigs() {
//略
}
@Override
public void connect() {
//獲得連接
connectFuture = getConnector().connect(new InetSocketAddress(getHost(),getPort()));
//等待連接創建完成
connectFuture.awaitUninterruptibly();
//獲取會話
session = connectFuture.getSession();
}
@Override
public void disconnect() {
if(session!=null){
//等待連接斷開
session.close(true).awaitUninterruptibly();
session = null;
}
}
//setter/getter略
}
客戶端同步通訊子類 代碼如下:
public class MinaRunner extends AbstractMinaProvider {
/**
* 構造函數,創建connect、Session,true同步傳輸
*/
public MinaRunner() {
super(true);
}
/**
* 發送消息,
*/
@SuppressWarnings("unchecked")
@Override
public <K,T> T sendMessage(K k) {
Object o = null;
if(getSession()!=null){
//向服務器發送K對象,等待發送完畢
getSession().write(k).awaitUninterruptibly();
ReadFuture readFuture = getSession().read();
//判斷傳輸超時否
if(readFuture.awaitUninterruptibly(getTimeOut(),TimeUnit.SECONDS)){
o = readFuture.getMessage();
}else{
System.out.println("time out!!!!");
}
}
return (T)o;
}
}
使用泛型進行傳輸數據。數據父類Object。啥對象都可以傳了。
返回值爲T類型的對象o。
泛型用着很爽啊~~~~~
需要注意的一點。同步傳輸數據需要在write和read方法後加上awaitUninterruptibly方法。表示需要等待傳輸結束。
awaitUninterruptibly方法有三個
/**
* Wait for the asynchronous operation to complete uninterruptibly.
* The attached listeners will be notified when the operation is
* completed.
*
* @return the current IoFuture
*/
IoFuture awaitUninterruptibly();//等待結束
/**
* Wait for the asynchronous operation to complete with the specified timeout
* uninterruptibly.
*
* @return <tt>true</tt> if the operation is completed.
*/
boolean awaitUninterruptibly(long timeout, TimeUnit unit);//等待多長時間後,超時結束
/**
* Wait for the asynchronous operation to complete with the specified timeout
* uninterruptibly.
*
* @return <tt>true</tt> if the operation is finished.
*/
boolean awaitUninterruptibly(long timeoutMillis);//等待多長時間後結束
寫操作肯定是需要等待寫完。所以用awaitUninterruptibly()。
讀操作我這使用的是readFuture.awaitUninterruptibly(getTimeOut(),TimeUnit.SECONDS)返回boolean,是否超時。
同步傳輸,我也不能總等着啊。是吧。
編碼解碼過濾器
先建立個領域模型,請求對象DOMAIN
public class UserReq
{
private int age;
private int sex;
private String username;
//由於服務端是C語言寫的服務器端,所以這個地方需要加一個\0
private int separate = 0x00;
/**
* 將userReq轉碼,變爲byte[]傳輸
*/
@Override
public byte[] encode(){
byte[] age = ByteUtils.int2Bytes(getAge());
byte[] sex = ByteUtils.int2Bytes(getSex());
//此處需添加username的長度,因爲服務器端需要根據長度解析String類型的username
byte[] username_len = ByteUtils.int2Bytes(getUsername().lenght());
byte[] username = ByteUtils.string2Byte(getUsername());
byte[] separate = ByteUtils.int2Bytes(getSeparate());
//組裝Byte[],此處省略了重複的操作,將上面的byte[]全組在一起。ByteUtils類,百度一大把。
byte[] tmp = ByteUtils.join(username_len,username);
return ByteUtils.join(tmp, separate);
}
//setter/getter略
}
這個encode()方法什麼時候去調用呢。
我們上面有個connector.getFilterChain().addLast("codecfilter",new ProtocolCodecFilter(new CodecFactory(true)));
這個ProtocolCodecFilter的傳入參數是一個工廠,需要實現ProtocolCodecFactory,只有兩個方法。
public interface ProtocolCodecFactory {
/**
* Returns a new (or reusable) instance of {@link ProtocolEncoder} which
* encodes message objects into binary or protocol-specific data.
*/
ProtocolEncoder getEncoder(IoSession session) throws Exception;//獲取編碼器協議
/**
* Returns a new (or reusable) instance of {@link ProtocolDecoder} which
* decodes binary or protocol-specific data into message objects.
*/
ProtocolDecoder getDecoder(IoSession session) throws Exception;//獲取解碼器協議
}
這裏我們自定義工廠繼承Mina的code工廠,這裏就需要一個編碼器協議和一個解碼器協議
public class CodecFactory implements ProtocolCodecFactory{
public CodecFactory(boolean client){
//重名了。。。
encoder = new com.test.mina.provider.codec.ProtocolEncoder();
decoder = new com.test.mina.provider.codec.ProtocolDecoder(client);
}
@Override
public ProtocolDecoder getDecoder(IoSession session) throws Exception {
return decoder;
}
@Override
public ProtocolEncoder getEncoder(IoSession session) throws Exception {
return encoder;
}
}
我們再看看ProtocolEncoder。類描述:編碼成二進制或更高級別的消息對象特定於協議的數據。
public interface ProtocolEncoder {
/**
* Encodes higher-level message objects into binary or protocol-specific data.
* MINA invokes {@link #encode(IoSession, Object, ProtocolEncoderOutput)}
* method with message which is popped from the session write queue, and then
* the encoder implementation puts encoded messages (typically {@link IoBuffer}s)
* into {@link ProtocolEncoderOutput}.
*
* @throws Exception if the message violated protocol specification
*/
void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception;
/**
* Releases all resources related with this encoder.
*
* @throws Exception if failed to dispose all resources
*/
void dispose(IoSession session) throws Exception;
}
我們只需要自己寫個實現類,實現這個編碼接口,就可以自定義我們想要的二進制消息了。 另外一個類似。
好了,現在我們程序在初始化程序時,會創建一個過濾器,過濾器的內容就是實例化編碼和解碼協議,然後當我們write()的時候,會調用過濾器,看一下write方法
write方法,接口IoSession,實現類AbstractIoSession。
//write方法中的一段。獲取過濾器,激活寫過濾器。
IoFilterChain filterChain = getFilterChain();
filterChain.fireFilterWrite(writeRequest);
再看下fireFilterWrite方法,Entry是一個目錄,可以理解爲單向鏈表,本次的過濾器,下次的過濾器,..........,最後一個過濾器。
然後CALL 過濾器了!
public void fireFilterWrite(WriteRequest writeRequest) {
Entry tail = this.tail;
callPreviousFilterWrite(tail, session, writeRequest);
}
private void callPreviousFilterWrite(Entry entry, IoSession session, WriteRequest writeRequest) {
try {
IoFilter filter = entry.getFilter();//獲取過濾器
NextFilter nextFilter = entry.getNextFilter();//獲取下個過濾器
filter.filterWrite(nextFilter, session, writeRequest);//執行了!
} catch (Throwable e) {
writeRequest.getFuture().setException(e);
fireExceptionCaught(e);
}
}
然後找一下,好多個實現類,有一個ProtocolCodecFilter類,查看filterWrite方法
// Get the encoder in the session
ProtocolEncoder encoder = factory.getEncoder(session);//獲取編碼器
encoder.encode(session, message, encoderOut);//編碼執行了!
關鍵就在這了!獲取factory的編碼器!我們的factory是自定義的CodecFactory。我們的encoder就是我們的com.test.mina.provider.codec.ProtocolEncoder();
然後就執行了我們的ProtocolEncoder的encode方法了!
代碼片段
@Override
public void encode(IoSession session, Object message,
ProtocolEncoderOutput out) throws Exception{
System.out.println("call me!");
byte[] result = invokeEncoder(message);//利用反射,調用serReq的encoder方法!
IoBuffer buf = IoBuffer.allocate(result.length, false);//似曾相識呢。C的內存分配。true本地內存 false堆內存
buf.put(result);
buf.flip();//將buf指針指回開始位置,準備從頭讀數據
out.write(buf);
}
invokeEncoder方法:
public byte[] invokeEncoder(Object message){
if (message== null){
throw new NullPointerException("invoke encoder object is null");
}
byte[] result = (byte[]) callParameter(message, "encode"); return result;
}
callParameter方法:
public Object callParameter(Object target, String invokeMethodName){
try{
MethodDescriptor[] methodDescriptor =
methodDescriptor(target.getClass());
Method method = findMethod(methodDescriptor, invokeMethodName);
return method.invoke(target, new Object[] {});
} catch (Exception e) {
throw new IllegalArgumentException("method invoke is failed!");
}
}
測試程序
UserReq userReq = new UserReq();
userReq.setSex(0);
userReq.setAge(25);
userReq.setUsername("Mr.S");
MinaProvider provider = new MinaRunner();
provider.connect();
UserRes a = (UserRes)provider.sendMessage(userReq);
System.out.println("snyc func username is "+a.getUsername());
provider.disconnect();
代碼片段僅供參考。
同步傳輸大致就這麼個流程,細節以後再上傳完整代碼。
可以不使用反射去封裝byte[],原先寫過一個類,封裝byte[]用的。那個代碼比較好閱讀。但是重用性基本沒有。(不過可以複製粘貼~哈哈)
補充:
UserReq需要一個父類,封裝一下buffer的長度等基本信息。