摘 要: 分析了鄉(xiāng)村可視化遠(yuǎn)程醫(yī)療系統(tǒng)采用多線程技術(shù)實(shí)現(xiàn)網(wǎng)絡(luò)通信存在的不足,闡述了Java非阻塞I/O的基本原理。系統(tǒng)采用非阻塞I/O通信技術(shù)只使用一個(gè)線程并行實(shí)現(xiàn)大量客戶無阻塞的通信,有效地減少了系統(tǒng)開銷,較好地提升了系統(tǒng)的性能。
關(guān)鍵詞: 非阻塞I/O;遠(yuǎn)程醫(yī)療系統(tǒng);線程阻塞;通道
1 鄉(xiāng)村可視化遠(yuǎn)程醫(yī)療系統(tǒng)的應(yīng)用前景
目前我國(guó)廣大的農(nóng)村地區(qū)醫(yī)療條件比較差,這些地區(qū)的大量患者因?yàn)榈貌坏郊皶r(shí)有效的治療,給患者以及家屬帶來了不必要的痛苦。使用網(wǎng)絡(luò)多媒體通信技術(shù)實(shí)現(xiàn)的鄉(xiāng)村可視化遠(yuǎn)程醫(yī)療系統(tǒng)可以讓高水平的醫(yī)療專家給鄉(xiāng)村的患者進(jìn)行遠(yuǎn)程診病,以及指導(dǎo)治療與護(hù)理。鄉(xiāng)村衛(wèi)生所、鄉(xiāng)鎮(zhèn)醫(yī)院等醫(yī)療單位可以和具有優(yōu)質(zhì)醫(yī)療資源的醫(yī)院或直接與專家建立醫(yī)療合作關(guān)系,雙方合作使用可視化醫(yī)療系統(tǒng),讓專家為病人進(jìn)行遠(yuǎn)程診療服務(wù)。在鄉(xiāng)村這一端,一般由專業(yè)醫(yī)務(wù)人員指導(dǎo)病人接受專家遠(yuǎn)程診療,醫(yī)療專家通過可視化醫(yī)療系統(tǒng)實(shí)時(shí)對(duì)病人進(jìn)行診斷、處方、治療甚至指導(dǎo)手術(shù)等?;颊咭部梢栽诩抑惺褂眠h(yuǎn)程醫(yī)療系統(tǒng)直接接受遠(yuǎn)程專家的指導(dǎo),尤其在處理突發(fā)性疾病時(shí),可以在第一時(shí)間內(nèi)得到醫(yī)療專家的幫助。目前隨著生活水平的大幅度提高,不少需要“私人醫(yī)生”的人員也可以方便地使用該系統(tǒng)得到醫(yī)療專家的服務(wù)??梢?,可視化醫(yī)療系統(tǒng)有著很廣闊的應(yīng)用市場(chǎng)和較好的發(fā)展前景。
2 鄉(xiāng)村可視化遠(yuǎn)程醫(yī)療系統(tǒng)功能簡(jiǎn)介
鄉(xiāng)村遠(yuǎn)程醫(yī)療系統(tǒng)可以同時(shí)提供若干對(duì)醫(yī)療服務(wù),每一對(duì)服務(wù)有2個(gè)或2個(gè)以上的客戶端進(jìn)行通信??蛻舳朔謩e由醫(yī)療專家和患者使用,醫(yī)療專家使用的客戶端稱為專家端,患者接受診療的客戶端稱為醫(yī)務(wù)端。每個(gè)客戶(醫(yī)務(wù)端和專家端)首先登錄服務(wù)器,服務(wù)器將每個(gè)登錄客戶的用戶名、IP地址等相關(guān)信息發(fā)送給相關(guān)的其他客戶,然后客戶端之間建立直接連接,并可進(jìn)行文件傳送、文字和多媒體等通信。通過這種綜合的通信方式,醫(yī)療專家可以遠(yuǎn)程為患者提供醫(yī)療服務(wù)。為了建立醫(yī)療檔案,服務(wù)器需要保存診療時(shí)間、參與人員、患者檢驗(yàn)報(bào)告、病情診斷、處方等信息?;颊哌€可以通過服務(wù)器進(jìn)行專家預(yù)約,專家和患者均可以在服務(wù)器上進(jìn)行診療信息查詢。由于視頻信息量比較大,且保存意義不是十分大,因此并不保存在服務(wù)器上。系統(tǒng)結(jié)構(gòu)如圖1所示。
3 使用多線程和阻塞通信模式存在的局限
可視化遠(yuǎn)程醫(yī)療服務(wù)器需要同時(shí)為多個(gè)專家端和醫(yī)務(wù)端提供服務(wù),在傳統(tǒng)的阻塞通信模式中使用多線程技術(shù)來處理多客戶連接,一般需要服務(wù)器為每個(gè)客戶端建立一個(gè)線程。但使用多線程技術(shù)存在以下局限:
(1)Java虛擬機(jī)會(huì)為每個(gè)線程分配獨(dú)立的堆??臻g,工作線程數(shù)目越多,系統(tǒng)開銷就越大,而且增加了Java虛擬機(jī)調(diào)度線程的負(fù)擔(dān),增加了線程之間同步的復(fù)雜性,提高了線程死鎖的可能性;
(2)當(dāng)主線程接收客戶連接,在醫(yī)療服務(wù)過程中,服務(wù)器從網(wǎng)絡(luò)發(fā)送或接收數(shù)據(jù)時(shí),線程常常進(jìn)入阻塞狀態(tài),這就使工作線程的許多時(shí)間都浪費(fèi)在阻塞操作上,CPU的利用率降低,此外Java虛擬機(jī)需要頻繁地轉(zhuǎn)讓CPU的使用權(quán)。
由此可見,工作線程并不是越多越好。實(shí)驗(yàn)表明,適量的工作線程會(huì)提高服務(wù)器的并發(fā)性能,但是當(dāng)工作線程的數(shù)目達(dá)到某個(gè)極限而超出系統(tǒng)的負(fù)荷時(shí),反而會(huì)降低并發(fā)性能,使得許多客戶端得不到服務(wù)器的及時(shí)響應(yīng)。為了改善服務(wù)器性能,在遠(yuǎn)程醫(yī)療系統(tǒng)中采用了Java非阻塞I/O通信技術(shù)。
4 Java非阻塞I/O工作原理簡(jiǎn)介
在傳統(tǒng)的阻塞通信模式中,服務(wù)器在接受客戶連接后創(chuàng)建Socket對(duì)象與客戶進(jìn)行通信,Socket對(duì)象由線程進(jìn)行管理。當(dāng)有多個(gè)客戶連接時(shí),就需要多個(gè)線程分別處理服務(wù)器與各個(gè)客戶的通信。Java非阻塞I/O通信服務(wù)器程序只需要一個(gè)線程就能夠?qū)崿F(xiàn)多個(gè)客戶端的連接、同時(shí)接收多個(gè)客戶端發(fā)送的數(shù)據(jù),以及同時(shí)向多個(gè)客戶端發(fā)送響應(yīng)數(shù)據(jù)。這種非阻塞通信采用了基于Channel(通道)、Selector(選擇器) 、Buffer(緩沖器)的新模式。
非阻塞I/O通信中Channel是一個(gè)接口,功能類似于傳統(tǒng)I/O中的Stream,但通道具有雙向性,既可讀入,也可寫出。SocketChannel和ServerSocketChannel均可實(shí)現(xiàn)Channel接口,它們是Socket和ServerSocket的替代類,但比Socket和ServerSocket具有更多的功能,它支持非阻塞通信、可選擇通信、異步通信和套接字對(duì)等通信等。
由于網(wǎng)絡(luò)接收和傳送數(shù)據(jù)均比較緩慢,常常導(dǎo)致線程阻塞,影響系統(tǒng)性能。在Java非阻塞通信模式中使用Buffer來緩沖數(shù)據(jù)。SocketChannel和ServerSocketChannel對(duì)象一端連接著緩沖區(qū),另一端連接著網(wǎng)絡(luò)。
ServerSocketChannel和SocketChannel在Selector中注冊(cè)連接就緒事件、讀就緒事件和寫就緒事件。注冊(cè)時(shí)創(chuàng)建一個(gè)SelectionKey對(duì)象,這個(gè)SelectionKey對(duì)象用來跟蹤注冊(cè)事件的句柄,其中包含有注冊(cè)該對(duì)象的通道和緩沖區(qū)等信息。Selector會(huì)一直監(jiān)控已經(jīng)注冊(cè)的事件。當(dāng)通道中有數(shù)據(jù)需要讀取時(shí),Selector中就有已注冊(cè)的讀就緒事件發(fā)生,這時(shí)調(diào)用相關(guān)的程序可將通道中的數(shù)據(jù)讀到緩沖區(qū)中,然后再提供給程序進(jìn)行處理。同樣地,當(dāng)有數(shù)據(jù)需要通過網(wǎng)絡(luò)發(fā)送時(shí),數(shù)據(jù)先進(jìn)入緩沖區(qū),這時(shí)Selector中就發(fā)生寫就緒事件,程序再利用通道向網(wǎng)絡(luò)發(fā)送數(shù)據(jù)[1]。
非阻塞通信模式處理流程如下:
while(檢測(cè)Selector對(duì)象,等待連接就緒事件、讀就緒事
件或?qū)懢途w事件發(fā)生) { //阻塞
if(有客戶連接)//非阻塞
接收客戶連接,創(chuàng)建SocketChannel對(duì)象與客
戶通信;
if(某個(gè)SocketChannel輸入流中有可讀數(shù)據(jù))
//非阻塞
從輸入流讀數(shù)據(jù);
if(某個(gè)SocketChannel輸出流中有可寫數(shù)據(jù))
//非阻塞
向輸出流寫數(shù)據(jù);
}
上述處理流程采用輪詢工作方式,當(dāng)某一種操作就緒時(shí),執(zhí)行該操作,否則就查看是否還有其他就緒操作可以執(zhí)行,線程不會(huì)因?yàn)槟骋粋€(gè)連接、讀、寫等操作還沒有就緒,就進(jìn)入阻塞狀態(tài)。在非阻塞通信模式中,只需要一個(gè)線程管理Selector對(duì)象就可以了,而不需要多線程。并且只有檢測(cè)Selector對(duì)象時(shí)才有可能導(dǎo)致線程阻塞,因此可以大大提高系統(tǒng)的性能。
5 非阻塞通信在鄉(xiāng)村可視化遠(yuǎn)程醫(yī)療系統(tǒng)中的應(yīng)用研究
在鄉(xiāng)村可視化遠(yuǎn)程醫(yī)療系統(tǒng)中,服務(wù)器端需要和各個(gè)客戶端建立連接、接受客戶端的操作請(qǐng)求并將操作結(jié)果返回給客戶端。服務(wù)器只需要使用一個(gè)線程就可以處理客戶端連接請(qǐng)求和向各個(gè)客戶端讀寫數(shù)據(jù),服務(wù)器自身進(jìn)行的數(shù)據(jù)庫(kù)操作可以使用另外的線程。
專家端和醫(yī)務(wù)端在服務(wù)器端的數(shù)據(jù)庫(kù)中進(jìn)行查詢、插入和修改等操作,這些操作信息是各種SQL語句,服務(wù)器返回給專家端和醫(yī)務(wù)端的是查詢、保存或修改結(jié)果,它們可能是一個(gè)字符串、一條記錄,也可能是多條記錄。為了方便操作,這些信息均封裝成XML格式,根標(biāo)志為<teleMedi></teleMedi>。這種格式封裝數(shù)據(jù)便于檢驗(yàn)數(shù)據(jù)的完整性與正確性,也容易解析。在非阻塞通信中,除boolean類型以外,每種基本數(shù)據(jù)類型都有對(duì)應(yīng)的緩沖區(qū)類,在本系統(tǒng)中使用ByteBuffer類對(duì)象數(shù)據(jù)緩沖區(qū)。程序中需要向網(wǎng)絡(luò)發(fā)送數(shù)據(jù)時(shí),數(shù)據(jù)進(jìn)入緩沖區(qū)前先要把字符數(shù)據(jù)編碼成字節(jié)數(shù)據(jù),然后再由通道向網(wǎng)絡(luò)發(fā)送,而從網(wǎng)絡(luò)上接收的字節(jié)流數(shù)據(jù)先存放進(jìn)緩沖區(qū),解碼后再由程序處理。
在非阻塞通信模式中,一次完整的數(shù)據(jù)發(fā)送可能需要多次讀緩沖區(qū),這避免了阻塞通信模式中讀取數(shù)據(jù)時(shí)間拖得過長(zhǎng)而導(dǎo)致的線程阻塞。每次讀緩沖區(qū)后都要進(jìn)行數(shù)據(jù)完整性檢查,由于采用XML格式封裝數(shù)據(jù),所以很容易檢驗(yàn)是不是讀取到了完整的數(shù)據(jù)。
鄉(xiāng)村可視化遠(yuǎn)程醫(yī)療系統(tǒng)中每次通過網(wǎng)絡(luò)讀寫的數(shù)據(jù)長(zhǎng)短不同。醫(yī)療中需要傳送的數(shù)據(jù)最常見的長(zhǎng)度小于1 KB,因此緩沖區(qū)最初設(shè)定為1 KB,當(dāng)數(shù)據(jù)超過這個(gè)長(zhǎng)度時(shí),將緩沖區(qū)的容量擴(kuò)大一倍,如果超過2 KB,再將緩沖區(qū)擴(kuò)大一倍,如此類推。使用這種可伸縮的方式可以更有效地利用內(nèi)存資源。
6 非阻塞通信在鄉(xiāng)村可視化遠(yuǎn)程醫(yī)療系統(tǒng)中的實(shí)現(xiàn)[2]
在非阻塞模式下,服務(wù)器主程序MedicalSever只使用一個(gè)線程,使用Selector監(jiān)控接收連接就緒事件、讀就緒事件和寫就緒事件。限于篇幅,本文僅介紹幾段主要的代碼。
(1)MedicalServer類的構(gòu)造方法負(fù)責(zé)啟動(dòng)服務(wù)器,把它綁定到一個(gè)本地端口,主要代碼如下:
selector=Selector.open();//創(chuàng)建一個(gè)Selector對(duì)象
ssChannel=ServerSocketChannel.open();
//創(chuàng)建ServerSocketChannel對(duì)象
ssChannel.configureBlocking(false);
//設(shè)置ServerSocketChannel為阻塞工作模式
ssChannel.socket().bind(new InetSocketAddress(port));
//綁定到本地端口
(2)MedicalSever類的serve()方法負(fù)責(zé)輪詢Selector,主要代碼如下:
public void serve() throws IOException{
ssChannel.register(selector,SelectorKey.OP_ACCEPT);
//在Selector中注冊(cè)連接就緒事件
while(selector.select()>0){
Set readyKeys=selector.selectedKeys();
//獲取Selector中就緒事件集合
Iterator it=readyKeys.iterator();
while(it.hasNext()){
SelectorKey key=null;
try{
key=(SelectionKey)it.next();
//獲取某個(gè)就緒事件
it.remove();//獲取就緒事件后刪除
if(key.isReadable()){處理連接就緒事件}
if(key.isReadable()){處理讀就緒事件}
if(key.isWritable()){處理寫就緒事件}
}catch(IOException e){ }}}
(3)處理連接就緒事件方法中的主要代碼如下:
ServerSocketChannel ssc=(ServerSocketChannel)key.channel();
SocketChannel sChannel=(SocketChannel)ssc.accept();
//獲取關(guān)系的SocketChannel
socketChannel.configureBlocking(false);
//將SocketChannel設(shè)置為非阻塞模式
ByteBuffer buffer=ByteBuffer.allocate(1024);//分配緩沖區(qū)
socketChannel.register(selector,SelectorKey.OP_READ|
SelectionKey.OP_WRITER,buffer);
//注冊(cè)讀就緒和寫就緒事件
(4)處理讀就緒事件方法中的主要代碼如下:
public void receive(SelectionKey key)throws IOException{
ByteBuffer sBuffer=(ByteBuffer)key.attachment();
//獲取與SelectorKey綁定的緩沖區(qū)
SocketChannel sChannel=
(SocketChannel)key.channel();//獲得通道
ByteBuffer buff=ByteBuffer.allocate(1024)
//創(chuàng)建輔助緩沖區(qū)
sChannel.read(buff);//從通道中讀數(shù)據(jù)暫存到buff中
sBuffer.put(buff); }//把buff中的內(nèi)容拷貝到緩沖區(qū)
在非阻塞模式下,sChannel.read(buff)方法無法保證一次把全部數(shù)據(jù)都讀完,因此只好把每次讀到的數(shù)據(jù)先存放到buff中,并判斷每次讀到的數(shù)據(jù)是否以</teleMedi>結(jié)尾,當(dāng)數(shù)據(jù)讀取完整了,再?gòu)?fù)制到sBuffer中提交給程序。
(5)處理寫就緒事件方法中的主要代碼如下:
public void send(SelectionKey key)throws IOExceptio{
ByteBuffer sBuffer=(ByteBuffer)key.attachment();
//獲取需要發(fā)送數(shù)據(jù)的緩沖區(qū)
SocketChannel sChannel=(SocketChannel)key.channel();
synchronized(sBuffer)
{ sBuffer.flip();//將緩沖當(dāng)前的位置值設(shè)為
極限,將位置設(shè)為0,為下一步復(fù)制數(shù)據(jù)做準(zhǔn)備
sChannel.write(sBuffer);//發(fā)送緩沖區(qū)中的數(shù)據(jù)
sChannel.compact();}//刪除已發(fā)送的數(shù)據(jù)
(6)當(dāng)每個(gè)緩沖區(qū)容量不夠時(shí),需要擴(kuò)大緩沖區(qū),主要代碼如下:
protected void resizeBuffer(ByteBuffer bb)
{ if(bb.remaining()<10){//判斷剩余容量是否
小于10 B
ByteBuffer bBuffer=ByteBuffer.allocate(bb.capacity()*2);
//容量擴(kuò)大一倍
bb.flip();
bBuffer.put(bb);//把原緩沖區(qū)中數(shù)據(jù)復(fù)制到
新緩沖區(qū)中
bb=bBuffer;}}
鄉(xiāng)村可視化遠(yuǎn)程醫(yī)療系統(tǒng)中使用Java非阻塞I/O通信技術(shù),可以只使用一個(gè)主線程就能實(shí)現(xiàn)網(wǎng)絡(luò)連接、讀和寫操作,避免了多線程中讀或?qū)懸鸬木€程阻塞,大幅降低了服務(wù)器應(yīng)用程序的開銷,有效地提高了系統(tǒng)的性能。
參考文獻(xiàn)
[1] 吳易,王凌.Java技術(shù)在P2P環(huán)境下的應(yīng)用[J].微計(jì)算機(jī)信息,2005(3):154-155.
[2] JavaTM Platform Standard Edition 6 API Specification[S]. http://download.oracle.com/javase/6/docs/api/.