劉 雍,孫 冰,馬玉春
(海南熱帶海洋學院 a.海洋信息工程學院; b.海南省嵌入式系統(tǒng)重點實驗室,海南 三亞 572022)
基于消息驅(qū)動的Android TCP服務器類的設計
劉 雍a,孫 冰a,馬玉春b
(海南熱帶海洋學院 a.海洋信息工程學院; b.海南省嵌入式系統(tǒng)重點實驗室,海南 三亞 572022)
網(wǎng)絡應用日趨廣泛,本文所提出的Android TCP服務器模型及實現(xiàn)的TcpServer類通過多線程類來處理遠程客戶機的連接請求和數(shù)據(jù)接收,設置了多種消息用來通知用戶連接是否成功并提交接收到的數(shù)據(jù),以及數(shù)據(jù)收發(fā)過程中是否出現(xiàn)錯誤等.該TcpServer類在工程項目中得到了應用,運行可靠,提高了開發(fā)效率.
安卓(Android); 傳輸控制協(xié)議; 服務器; 多線程
安卓(Android)作為移動設備的開放源碼操作環(huán)境與平臺,在移動互聯(lián)網(wǎng)發(fā)展迅速的今天,不斷擴展網(wǎng)絡應用是其開發(fā)的主要方向.目前,Android網(wǎng)絡編程能夠?qū)崿F(xiàn)信息實時交互、在線存儲,還可以實現(xiàn)電子商務、實時監(jiān)控與移動辦公等.文獻[1]給出了各種網(wǎng)絡編程的初步方案,特別是傳輸控制協(xié)議(TCP:Transmission Control Protocol)的Socket編程.文獻[2]實現(xiàn)了微軟.NET框架下的基于事件驅(qū)動的TcpServer類,偵聽遠程客戶機的連接與數(shù)據(jù)接收都通過多線程在后臺進行,連接成功或收到數(shù)據(jù)均通過事件提交用戶,節(jié)約了系統(tǒng)資源,同時加強了數(shù)據(jù)接收和發(fā)送的處理功能.本文在文獻[1]的基礎之上,結(jié)合文獻[2]的原理,設計并實現(xiàn)一個Android平臺下的TcpServer類,使其支持消息驅(qū)動和便捷的數(shù)據(jù)接收與發(fā)送等功能.
Android中引入Handler作為消息傳遞機制,Handler可以充當子線程與主線程的中介,主要接收子線程發(fā)來的數(shù)據(jù),并以此數(shù)據(jù)配合主線程更新界面(UI)控件顯示的內(nèi)容.當App啟動時,Android首先會開啟一個主線程(即UI線程),主線程為管理界面中的UI控件,對各控件進行事件分發(fā).如果需要執(zhí)行一個耗時的操作,如:讀寫文件和實時數(shù)據(jù)傳輸?shù)?如果將這些操作放在主線程中,就會導致App崩潰,因而,這些操作應該放在子線程中.但是,子線程中的數(shù)據(jù)又需要到主線程的UI中進行更新,Android運行機制不允許在子線程中直接更新主線程中的數(shù)據(jù).本文考慮借助Handler消息傳遞機制來解決這個問題.由于Handler對象運行在主線程(UI),同其子線程之間可以通過Message對象來傳遞數(shù)據(jù),這樣就可以比較流暢地更新主線程中的數(shù)據(jù)[3].
在實時監(jiān)控系統(tǒng)中,可以在初始化Handler對象時定義回調(diào)方法,用來接收和處理子線程發(fā)送的消息,也可使用Handler對象的postDelayed方法執(zhí)行定時查詢功能,以便及時做出處理[4].本文所設計的TcpServer類通過主線程的Handler對象向主線程發(fā)送連接成功和關閉等事件消息和接收到的數(shù)據(jù)消息.
TcpServer的內(nèi)部工作原理模型如圖1所示.首先通過構(gòu)造函數(shù)進行初始化,主線程向構(gòu)造函數(shù)傳送 Handler對象和偵聽接口等參數(shù),然后在多線程中創(chuàng)建ServerSocket對象,調(diào)用該對象的accept方法偵聽端口.連接失敗,則發(fā)送連接失敗消息;連接成功,則啟動新的線程讀取數(shù)據(jù),如果收到數(shù)據(jù),則發(fā)送數(shù)據(jù)接收到的消息,供主線程調(diào)用.TcpServer初始化并與遠程客戶機連接,成功之后還可以直接供主線程調(diào)用,用來發(fā)送數(shù)據(jù)[5].
圖1 TcpServer 內(nèi)部工作原理模型
端口偵聽通過多線程嵌入類Th_Accept實現(xiàn),其中run方法中的關鍵代碼為
try{
_ss = new ServerSocket(_nPort);
_connection = _ss.accept();
_bis = new BufferedInputStream(_connection.getInputStream());
_bos = new BufferedOutputStream(_connection.getOutputStream());
_bConnected = true; //缺省為false
} catch (UnknownHostException e) {
} catch (IOException e) {}
if (_bConnected){
_handler.obtainMessage(MESSAGE_Connected, 0, 0,null).sendToTarget();
th_ReadData = new Th_ReadData();
th_ReadData.start();
}
else
_handler.obtainMessage(MESSAGE_ConnectError, 0, 0,null).sendToTarget();
傳入端口號生成ServerSocket對象_ss,調(diào)用_ss對象的accept方法等候遠程客戶機的連接請求,此時多線程處于阻塞狀態(tài).連接成功后,生成BufferedInputStream對象_bis,用于后續(xù)的數(shù)據(jù)讀取(接收),再生成BufferedOutputStream對象_bos,用于后續(xù)的數(shù)據(jù)發(fā)送.如果以上各項都取得成功,則將連接狀態(tài)參數(shù)_bConnected修改為true(否則,保持false不變).
如果連接成功,則Handler對象_handler通過obtainMessage方法獲得連接成功消息,并通過消息的sendToTarget方法發(fā)送到主線程,隨后啟動讀取數(shù)據(jù)的多線程Th_ReadData對象;如果連接失敗,則向主線程發(fā)送連接失敗的消息.
Th_ReadData也是一個多線程嵌入類,用于在后臺讀取數(shù)據(jù),run方法中為一個讀取數(shù)據(jù)的死循環(huán),其關鍵代碼為:
while(true){
if(_bConnected != true) return;
if(_bStopConnect) return;
try {
if(_bis.available() > 0){
ByteBuffer byteBuf = readByteBuffer();
if(_bConnected != true) return; //be necessary!!
_handler.obtainMessage(MESSAGE_DataArrived, 0, 0, byteBuf).sendToTarget();
}
} catch (IOException e) {}
}
如果連接狀態(tài)_bConnected不為true,或者主線程主動終止連接導致_bStopConnect為true,則退出死循環(huán).否則,嘗試接收數(shù)據(jù),假如_bis對象的可用數(shù)據(jù)大于0,則讀取數(shù)據(jù)存入byteBuf對象,并向主線程發(fā)送數(shù)據(jù)到達消息,同時攜帶所接收的數(shù)據(jù)(存于byteBuf).如果在讀取數(shù)據(jù)的時候發(fā)生錯誤,也可在catch代碼模塊中添加發(fā)送讀取數(shù)據(jù)錯誤消息.
上一節(jié)Th_ReadData類中調(diào)用的readByteBuffer方法是TcpServer類中的私有方法,其定義如下所示.首先分配一個1024個字節(jié)的緩沖區(qū),用ByteBuffer對象buf來保存數(shù)據(jù),然后記錄當前時間lStart,通過while循環(huán)將延遲在_nDelay(一般定義為50毫秒)內(nèi)的數(shù)據(jù)進行合并,最后作為一個數(shù)據(jù)包返回.如果在讀取數(shù)據(jù)包的過程中發(fā)生錯誤,還可以在catch代碼模塊中添加讀取數(shù)據(jù)錯誤消息.
private ByteBuffer readByteBuffer(){
ByteBuffer buf = ByteBuffer.allocate(1024);
long lStart = System.currentTimeMillis();
while(System.currentTimeMillis() - lStart < _nDelay){
try{
while(_bis.available()>0){
byte[]bIn = new byte[_bis.available()];
_bis.read(bIn, 0, _bis.available());
buf.put(bIn);
}
}catch (IOException e) {}
}
return buf;
}
發(fā)送數(shù)據(jù)通過sendBytes方法直接調(diào)用,因而為公有方法,參數(shù)即為需要發(fā)送的字節(jié)數(shù)組,通過BufferedOutputStream對象_bos的write方法進行發(fā)送,第一個參數(shù)為所需要發(fā)送的字節(jié)數(shù)組,第二個參數(shù)為偏移量,這里指從第0個字節(jié)開始發(fā)送,第三個參數(shù)為需要發(fā)送的字節(jié)數(shù).同理,如果在發(fā)送數(shù)據(jù)包的過程中發(fā)生錯誤,還可以在catch代碼模塊中添加發(fā)送數(shù)據(jù)錯誤消息.
public void sendBytes(byte[]bOut){
try {
_bos.write(bOut, 0, bOut.length);
_bos.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
TcpServer類中還包括直接發(fā)送與接收文本字符串的方法,可以很方便地處理POP3協(xié)議之類的純文本協(xié)議[4].處理文本字符串的方法與處理字節(jié)流的方法類似,這里不再贅述.
文獻[2]中的I-7013D溫度采集模塊采用RS-485接口,可以監(jiān)視1路溫度量的變化.為了讓學生進行擴展學習,實現(xiàn)了一個Visual Basic 2010 版的I-7013D模塊仿真軟件.利用本文的TcpServer類,進一步實現(xiàn)了Android版的支持TCP協(xié)議的I-7013D模塊仿真軟件,方法是在UI主線程中定義一個嵌入類IncomingHandlerCallback,實現(xiàn)處理消息的接口方法handleMessage,其代碼如下所示.
class IncomingHandlerCallback implements Handler.Callback{
public boolean handleMessage(Message msg) {
processMessage(msg);
return true;
}
}
processMessage方法用來處理各種消息,一般使用switch語句對消息進行分類處理,為了方便,這里僅用條件語句來處理數(shù)據(jù)到達消息,判斷數(shù)據(jù)到達,則將消息中的數(shù)據(jù)對象進行強制轉(zhuǎn)換,然后即可進行隨后的處理.
private void processMessage(Message msg){
if(msg.what == TcpServer.MESSAGE_DataArrived){
ByteBuffer byteBuf = (ByteBuffer)msg.obj;
// 進一步處理數(shù)據(jù)
}
}
在UI主線程的onCreate方法中,對Handler和TcpServer進行初始化,即可等待遠程客戶機的連接,連接成功后響應其查詢命令.
mHandler = new Handler(new IncomingHandlerCallback());
server = new TcpServer(mHandler, nPort);
圖2所示為Android版I-7013D主控軟件運行效果,能完整地實時取得運行TcpServer對象的I-7013D仿真模塊中的數(shù)據(jù),分別通過文本框(當前溫度31.79度)和繪圖方式進行展示.
圖2 I-7013D主控機軟件運行
Android網(wǎng)絡應用廣泛.本文所提出的Android TCP服務器模型及實現(xiàn)的TcpServer原型類可以在后臺處理遠程客戶機的連接請求和接收數(shù)據(jù),設置了多種消息用來通知用戶連接是否成功,提交接收到的數(shù)據(jù),以及數(shù)據(jù)收發(fā)過程中是否出現(xiàn)錯誤等.由于通過多線程處理耗時工作,TcpServer類不會引起App崩潰,且占用資源少.利用TcpServer類研發(fā)的支持Tcp協(xié)議的Android版I-7013D模塊仿真軟件,可以與對應的主控機軟件無縫連接,運行可靠[6].
[1]陳文,郭依正.深入理解Android網(wǎng)絡編程[M].北京:機械工業(yè)出版社, 2013.
[2]馬玉春.計算機監(jiān)控系統(tǒng)的仿真開發(fā)[M].北京:國防工業(yè)出版社,2015.
[3]陳偉.Android Handler消息傳遞機制在人機界面軟件設計中的應用[J].科技信息,2014, 13 (6):90-92.
[4]劉雍.基于ARM9 的嵌入式視頻監(jiān)控系統(tǒng)的設計與實現(xiàn)[J].瓊州學院學報,2015, 22 (2):35-38.
[5]楊宗德,呂光宏,劉雍.Linux高級程序設計 [M].北京:人民郵電出版社,2012.
[6]文松,王敏,胡春陽, 等.一種用于移動設備的Web 服務遠程證明方法[EB/OL].(2016-11-29)[2016-11-29].http://www.cnki.net/kcms/detail/46.1071.G4.20161129.1713.005.html.
(編校:曾福庚)
Design of Message Driven TCP Server on Android Platform
LIU Yonga, SUN Binga,MA Yu-chunb
(a.School of Ocean Information Engineering; b.Hainan Key Laboratory of Embedded Systems,Hainan Tropic Ocean University, Sanya Hainan 572022, China)
Android network application is becoming more and more extensive.In this paper, on Android platform a message driven TCP server model is established and implemented, in which the connection request from remote client and data receiving is handled by embedded Thread class, with connection establishment and data receiving functioning as messages to main Thread running UI.Prototype class TCP Server has been applied in real project and running reliably.
Android; transmission control protocol; server; thread
格式:劉雍,孫冰,馬玉春.基于消息驅(qū)動的Android TCP服務器類的設計[J].海南熱帶海洋學院學報,2017,24(2):59-63.
2017-02-16
劉雍(1979 - ),女,四川閬中人, 海南熱帶海洋學院海洋信息工程學院副教授,碩士,研究方向為移動應用與嵌入式系統(tǒng).
馬玉春(1969-),男,江蘇南京人, 海南熱帶海洋學院海洋信息工程學院教授,博士,研究方向為移動應用與計算機監(jiān)控技術.
TP311.52
A
2096-3122(2017) 02-0059-05
10.13307/j.issn.2096-3122.2017.02.12