MINA客戶端的應用應用體系結構
在上一篇文章中我們介紹了MINA服務器端的開發流程,這一章我給大家介紹MINA的客戶端開發流程,客戶端開發我們基于Android來實現相應的開發過程,首先讓我們來看一下MINA客戶端開發的應用體系結構圖:
- 客戶端首先創建一個IOConnector(MINA Construct (結構) 中連接Socket服務端的接口 ),初始化綁定Server
- 創建的一個會話,并且與Connection相關聯
- 應用程序/客戶端寫入會話,遍歷過過濾器鏈之后將數據發送到服務器
- 從服務器接收到的所有響應/消息都將遍歷過濾器鏈,回調給IoHandler進行處理
Android端TCP代碼示例
首先創建android項目,和普通的Android項目一致,引入對應的MINA客戶端jar包:
按照客戶端的體系結構,按順序創建對應的對象:
/**
* 創建tcp客戶端的連接
*/
public void createTcpConnect() throws InterruptedException {
if (ioSession == null) {
//首先創建對應的tcp連接
IoConnector ioConnector = new NioSocketConnector();
//設置超時時間
ioConnector.setConnectTimeoutMillis(CONNECT_TIMEOUT);
//添加過濾器
ioConnector.getFilterChain().addLast("logger", new LoggingFilter());//添加日志過濾器
ioConnector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));//設置字節處理過濾器
//添加IoHandler
ioConnector.setHandler(new ClientSessionHandler());
// IoSession ioSession;
//通過ConnectFuture來連接服務器
for (; ; ) {
try {
ConnectFuture future = ioConnector.connect(new InetSocketAddress(HOSTNAME, PORT));
future.awaitUninterruptibly();
ioSession = future.getSession();
break;//連接成功跳出死循環
} catch (Exception e) {
System.err.println("Failed to connect");
e.printStackTrace();
Thread.sleep(5000);//如果連接失敗,5秒后繼續連接直到連接成功
}
}
}
ioSession.write("tcp-ceshi");
}
和服務端不同的是,客戶端創建了IoConnector用來連接服務器,其他設置屬性,比如過濾器,Handler,等等設置都很相似。
客戶端創建對應的IoSession來實現數據的發送,通過IoHandler來獲取對應的服務端的數據。這里我們主要分析一下ConnectFuture來實現對應的連接,首先看看通過awaitUninterruptibly()方法實現連接成功:
//通過ConnectFuture來連接服務器
for (; ; ) {
try {
ConnectFuture future = ioConnector.connect(new InetSocketAddress(HOSTNAME, PORT));
future.awaitUninterruptibly();
ioSession = future.getSession();
break;//連接成功跳出死循環
} catch (Exception e) {
System.err.println("Failed to connect");
e.printStackTrace();
Thread.sleep(5000);//如果連接失敗,5秒后繼續連接直到連接成功
}
}
ConnectFuture是一個接口繼承自IoFuture,而在使用過程中實際上使用的是默認實現類,我們通過源碼來分析連接過程(AbstractPollingIoConnector.java):
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
protected final ConnectFuture connect0(SocketAddress remoteAddress, SocketAddress localAddress,
IoSessionInitializer<? extends ConnectFuture> sessionInitializer) {
H handle = null;
boolean success = false;
try {
handle = newHandle(localAddress);
if (connect(handle, remoteAddress)) {
ConnectFuture future = new DefaultConnectFuture();//(1)
T session = newSession(processor, handle);
initSession(session, future, sessionInitializer);
// Forward the remaining process to the IoProcessor.
session.getProcessor().add(session);
success = true;
return future;
}
success = true;
} catch (Exception e) {
return DefaultConnectFuture.newFailedFuture(e);
} finally {
if (!success && handle != null) {
try {
close(handle);
} catch (Exception e) {
ExceptionMonitor.getInstance().exceptionCaught(e);
}
}
}
ConnectionRequest request = new ConnectionRequest(handle, sessionInitializer);
connectQueue.add(request);
startupWorker();
wakeup();
return request;
}
我們可以看到,ConnectFuture的實現過程是,首先判斷是否連接成功:
if (connect(handle, remoteAddress)) {
ConnectFuture future = new DefaultConnectFuture();//(1)
T session = newSession(processor, handle);
initSession(session, future, sessionInitializer);
// Forward the remaining process to the IoProcessor.
session.getProcessor().add(session);
success = true;
return future;
}
如果連接成功,創建默認的ConnectFuture對象,初始化創建有效連接的Session會話直接返回即可。但是如果尚沒有連接成功,會創建ConnectionRequest 對象返回,我們該對象對應的源碼 (AbstractPollingIoConnector.java內部類):
public final class ConnectionRequest extends DefaultConnectFuture {
/** The handle associated with this connection request */
private final H handle;
/** The time up to this connection request will be valid */
private final long deadline;
/** The callback to call when the session is initialized */
private final IoSessionInitializer<? extends ConnectFuture> sessionInitializer;
public ConnectionRequest(H handle, IoSessionInitializer<? extends ConnectFuture> callback) {
this.handle = handle;
long timeout = getConnectTimeoutMillis();
if (timeout <= 0L) {
this.deadline = Long.MAX_VALUE;
} else {
this.deadline = System.currentTimeMillis() + timeout;
}
this.sessionInitializer = callback;
}
public H getHandle() {
return handle;
}
public long getDeadline() {
return deadline;
}
public IoSessionInitializer<? extends ConnectFuture> getSessionInitializer() {
return sessionInitializer;
}
@Override
public boolean cancel() {
if (!isDone()) {
boolean justCancelled = super.cancel();
// We haven't cancelled the request before, so add the future
// in the cancel queue.
if (justCancelled) {
cancelQueue.add(this);
startupWorker();
wakeup();
}
}
return true;
}
}
我們可以看到,ConnectionRequest繼承自DefaultConnectFuture ;創建完ConnectionRequest這個對象之后,就會將對應的對象實例添加到隊列中去:
ConnectionRequest request = new ConnectionRequest(handle, sessionInitializer);
connectQueue.add(request);
startupWorker();
wakeup();
return request;
通過方法startupWorker()實現請求隊列的輪詢操作,具體是創建對應的Connector(實現Runnable),然后通過調用線程池去完成對應的操作,我們來看看Connector的實現過程 (AbstractPollingIoConnector.java內部類):
private class Connector implements Runnable {
public void run() {
assert (connectorRef.get() == this);
int nHandles = 0;
while (selectable) {
try {
// the timeout for select shall be smaller of the connect
// timeout or 1 second...
int timeout = (int) Math.min(getConnectTimeoutMillis(), 1000L);
int selected = select(timeout);
nHandles += registerNew();
// get a chance to get out of the connector loop, if we
// don't have any more handles
if (nHandles == 0) {
connectorRef.set(null);
if (connectQueue.isEmpty()) {
assert (connectorRef.get() != this);
break;
}
if (!connectorRef.compareAndSet(null, this)) {
assert (connectorRef.get() != this);
break;
}
assert (connectorRef.get() == this);
}
if (selected > 0) {
nHandles -= processConnections(selectedHandles());
}
processTimedOutSessions(allHandles());
nHandles -= cancelKeys();
} catch (ClosedSelectorException cse) {
// If the selector has been closed, we can exit the loop
ExceptionMonitor.getInstance().exceptionCaught(cse);
break;
} catch (Exception e) {
ExceptionMonitor.getInstance().exceptionCaught(e);
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
ExceptionMonitor.getInstance().exceptionCaught(e1);
}
}
}
if (selectable && isDisposing()) {
selectable = false;
try {
if (createdProcessor) {
processor.dispose();
}
} finally {
try {
synchronized (disposalLock) {
if (isDisposing()) {
destroy();
}
}
} catch (Exception e) {
ExceptionMonitor.getInstance().exceptionCaught(e);
} finally {
disposalFuture.setDone();
}
}
}
}
}
由源碼我們可以看到具體的操作方法processConnections中,實現了主要操作:
/**
* Process the incoming connections, creating a new session for each valid
* connection.
*/
private int processConnections(Iterator<H> handlers) {
int nHandles = 0;
// Loop on each connection request
while (handlers.hasNext()) {
H handle = handlers.next();
handlers.remove();
ConnectionRequest connectionRequest = getConnectionRequest(handle);
if (connectionRequest == null) {
continue;
}
boolean success = false;
try {
if (finishConnect(handle)) {
T session = newSession(processor, handle);
initSession(session, connectionRequest, connectionRequest.getSessionInitializer());
// Forward the remaining process to the IoProcessor.
session.getProcessor().add(session);
nHandles++;
}
success = true;
} catch (Exception e) {
connectionRequest.setException(e);
} finally {
if (!success) {
// The connection failed, we have to cancel it.
cancelQueue.offer(connectionRequest);
}
}
}
return nHandles;
}
該方法判斷連接是否完成,如果完成,創建初始化的Session對象;執行方法 initSession(session, connectionRequest, connectionRequest.getSessionInitializer())之后,就會將ConnectFuture中的狀態碼ready設置為true(這一塊的代碼暫時沒有發現,但是通過打斷點得到執行完該方法,ready=true),修改狀態時,執行方法(ConnectFuture中的方法)setValue(...):
/**
* Sets the result of the asynchronous operation, and mark it as finished.
*
* @param newValue The result to store into the Future
* @return {@code true} if the value has been set, {@code false} if
* the future already has a value (thus is in ready state)
*/
public boolean setValue(Object newValue) {
synchronized (lock) {
// Allowed only once.
if (ready) {
return false;
}
result = newValue;
ready = true;
// Now, if we have waiters, notify them that the operation has completed
if (waiters > 0) {
lock.notifyAll();
}
}
// Last, not least, inform the listeners
notifyListeners();
return true;
}
執行該方法,就會調用notifyListeners方法來實現回調函數,由于awaitUninterruptibly()方法一直在阻塞等待ready狀態變化來結束執行,接下來分別看看兩種方式的執行:
1.awaitUninterruptibly():
/**
* {@inheritDoc}
*/
@Override
public ConnectFuture awaitUninterruptibly() {
return (ConnectFuture) super.awaitUninterruptibly();
}
awaitUninterruptibly方法最終實現方法:
/**
* Wait for the Future to be ready. If the requested delay is 0 or
* negative, this method immediately returns the value of the
* 'ready' flag.
* Every 5 second, the wait will be suspended to be able to check if
* there is a deadlock or not.
*
* @param timeoutMillis The delay we will wait for the Future to be ready
* @param interruptable Tells if the wait can be interrupted or not
* @return <tt>true</tt> if the Future is ready
* @throws InterruptedException If the thread has been interrupted
* when it's not allowed.
*/
private boolean await0(long timeoutMillis, boolean interruptable) throws InterruptedException {
long endTime = System.currentTimeMillis() + timeoutMillis;
if (endTime < 0) {
endTime = Long.MAX_VALUE;
}
synchronized (lock) {
// We can quit if the ready flag is set to true, or if
// the timeout is set to 0 or below : we don't wait in this case.
if (ready||(timeoutMillis <= 0)) {
return ready;
}
// The operation is not completed : we have to wait
waiters++;
try {
for (;;) {
try {
long timeOut = Math.min(timeoutMillis, DEAD_LOCK_CHECK_INTERVAL);
// Wait for the requested period of time,
// but every DEAD_LOCK_CHECK_INTERVAL seconds, we will
// check that we aren't blocked.
lock.wait(timeOut);
} catch (InterruptedException e) {
if (interruptable) {
throw e;
}
}
if (ready || (endTime < System.currentTimeMillis())) {
return ready;
} else {
// Take a chance, detect a potential deadlock
checkDeadLock();
}
}
} finally {
// We get here for 3 possible reasons :
// 1) We have been notified (the operation has completed a way or another)
// 2) We have reached the timeout
// 3) The thread has been interrupted
// In any case, we decrement the number of waiters, and we get out.
waiters--;
if (!ready) {
checkDeadLock();
}
}
}
}
因此我們可以知道,通過阻塞的方式實現最終的連接成功,接著獲取對應的IoSession;
2.通過回調來實現方法:notifyListeners():
/**
* Notify the listeners, if we have some.
*/
private void notifyListeners() {
// There won't be any visibility problem or concurrent modification
// because 'ready' flag will be checked against both addListener and
// removeListener calls.
if (firstListener != null) {
notifyListener(firstListener);
firstListener = null;
if (otherListeners != null) {
for (IoFutureListener<?> listener : otherListeners) {
notifyListener(listener);
}
otherListeners = null;
}
}
}
@SuppressWarnings("unchecked")
private void notifyListener(IoFutureListener listener) {
try {
listener.operationComplete(this);
} catch (Exception e) {
ExceptionMonitor.getInstance().exceptionCaught(e);
}
}
ConnectFuture回調的方法來實現獲取Session的代碼:
connFuture.addListener( new IoFutureListener(){
public void operationComplete(IoFuture future) {
ConnectFuture connFuture = (ConnectFuture)future;
if( connFuture.isConnected() ){
session = future.getSession();
try {
sendData();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
log.error("Not connected...exiting");
}
}
});
我們來看一下addListener的源碼:
/**
* {@inheritDoc}
*/
public IoFuture addListener(IoFutureListener<?> listener) {
if (listener == null) {
throw new IllegalArgumentException("listener");
}
synchronized (lock) {
if (ready) {
// Shortcut : if the operation has completed, no need to
// add a new listener, we just have to notify it. The existing
// listeners have already been notified anyway, when the
// 'ready' flag has been set.
notifyListener(listener);
} else {
if (firstListener == null) {
firstListener = listener;
} else {
if (otherListeners == null) {
otherListeners = new ArrayList<IoFutureListener<?>>(1);
}
otherListeners.add(listener);
}
}
}
return this;
}
至此,MINA客戶端的核心連接IoFuture的連接操作流程已經結束;
參考官網以及對應的源碼包
Android端UDP代碼示例
MINA客戶端的UDP連接和TCP的連接在代碼方面沒有太大的區別,畢竟MINA實現方面使用的是統一的API接口,至于不同點,主要對于IoConnector的實現類不一樣:
/**
* 創建udp客戶端的連接
*/
public void createUdpConnect() throws InterruptedException {
if (udpUoSession == null) {
//首先創建對應的tcp連接
udpIoConnector = new NioDatagramConnector();
//設置超時時間
udpIoConnector.setConnectTimeoutMillis(CONNECT_TIMEOUT);
//添加過濾器
udpIoConnector.getFilterChain().addLast("logger", new LoggingFilter());//添加日志過濾器
udpIoConnector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));//設置字節處理過濾器
//添加IoHandler
udpIoConnector.setHandler(new ClientSessionHandler());
//通過ConnectFuture來連接服務器
for (; ; ) {
try {
ConnectFuture future = udpIoConnector.connect(new InetSocketAddress(HOSTNAME, PORT + 1));
future.awaitUninterruptibly();
udpUoSession = future.getSession();
break;//連接成功跳出死循環
} catch (Exception e) {
System.err.println("Failed to connect");
e.printStackTrace();
Thread.sleep(5000);//如果連接失敗,5秒后繼續連接直到連接成功
}
}
}
udpUoSession.write("udp-ceshi");
}
由代碼我們可以發現,API的調用方式基本一致;
注:Android使用過程中,Socket連接不應該在主線程中實現,否則會報錯
至此我們的MINA的ANDROID客戶端開發大體的流程已經完成,后續會繼續MINA各個單獨模塊進行使用以及源碼分析。