學習mina同步與異步網絡通訊(一)——同步通訊

準備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的長度等基本信息。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章