自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用Java建立穩(wěn)定的多線程服務(wù)器

開(kāi)發(fā) 前端
要建立穩(wěn)定的服務(wù)器程序,消息隊(duì)列和線程池是很重要的。此外,也要考慮到很多的意外情況的發(fā)生。一般的程序員在寫(xiě)完線程的run()方法的循環(huán)后就不管了,其實(shí)還應(yīng)該考慮跳出循環(huán)后的資源釋放等問(wèn)題。本文將講述如何使用Java建立穩(wěn)定的多線程服務(wù)器。

Java語(yǔ)言是完全面向?qū)ο蟮?,它的線程機(jī)制和對(duì)象序列化特別容易使用,使用Java來(lái)建立一套多線程服務(wù)器要比使用其它語(yǔ)言方便的多,如果你再把它的異常處理機(jī)制利用好,那么你就可以建立一個(gè)商業(yè)級(jí)的多線程服務(wù)器了。由于采用了消息隊(duì)列和Socket傳輸方式,所以不會(huì)出現(xiàn)丟消息的問(wèn)題。這套服務(wù)器可以作為實(shí)時(shí)聊天服務(wù)器、多人協(xié)同的協(xié)作服務(wù)器等。

消息系統(tǒng)的建立

這套服務(wù)器的消息系統(tǒng)采用的是對(duì)象傳輸?shù)臋C(jī)制,而不是以前常常使用的字符串傳輸。采用對(duì)象傳輸?shù)暮锰幨菙U(kuò)展方便,如需要建立一個(gè)新的消息只需要從一個(gè)統(tǒng)一的基類(lèi)繼承下來(lái),然后再寫(xiě)自己實(shí)現(xiàn)的方法就行了。這樣也符合面向?qū)ο箢I(lǐng)域里一條重要的原則: OCP(open_closed Principle),即一個(gè)好的設(shè)計(jì)應(yīng)該能夠容納新的功能的增加,但是增加的方式不是修改原有的類(lèi),而是添加新的類(lèi)。

首先建立一個(gè)基類(lèi):Msg,該抽象類(lèi)中有兩個(gè)域sender和receiver分別紀(jì)錄消息的發(fā)送者和接收者。這兩個(gè)域是在構(gòu)造消息類(lèi)時(shí)就填寫(xiě)的,receiver域可以為空,空表示發(fā)給誰(shuí)都可以,由轉(zhuǎn)發(fā)服務(wù)器來(lái)決定。該類(lèi)的方法包括取得這兩個(gè)域的值和消息的處理函數(shù)。消息的處理函數(shù)process()是空函數(shù),供繼承者重載。

建立了這個(gè)抽象基類(lèi)后,你就可以繼承它完成你自己的類(lèi)。舉個(gè)例子,假如我要建立一個(gè)分組協(xié)同工作的繪圖系統(tǒng),而且支持組員之間的對(duì)話,那么我可以建立如下的類(lèi)集合:

SendTextMsg(String sender,String receiver,String info)//向指定的人發(fā)送對(duì)話。
  AddLineMsg(String sender,Point a,Point b)//在指定的點(diǎn)之間繪制一條直線
  AddRectangle(String sender,point start,Point end)//建立指定的矩形
  AddRotundaMsg(String sender,Point center,int radius)//建立指定的圓
  RemoveObjectMsg(String sender,int ID)//刪除指定編號(hào)的圖形對(duì)象
  ……

以此類(lèi)推,可以建立很多的消息類(lèi)。在每個(gè)類(lèi)的內(nèi)部都由一個(gè)處理該類(lèi)的方法process(),填寫(xiě)該方法就可以實(shí)現(xiàn)對(duì)消息類(lèi)的處理,而服務(wù)器只負(fù)責(zé)完成消息的轉(zhuǎn)發(fā)功能。這樣,一套消息系統(tǒng)就建立了。

服務(wù)器的結(jié)構(gòu)

如果要服務(wù)器實(shí)現(xiàn)同時(shí)為每個(gè)客戶(hù)端服務(wù),就要使用多線程,建立一個(gè)線程池,當(dāng)有客戶(hù)端連接時(shí)就在池中開(kāi)辟一個(gè)線程為它服務(wù)。同樣,要避免大量消息到達(dá)時(shí)處理不過(guò)來(lái)而導(dǎo)致丟失的情況,就要使用消息隊(duì)列。這個(gè)服務(wù)器是分層的處理的。

類(lèi)關(guān)系圖如下所示:

 

服務(wù)器的工作過(guò)程是這樣的,建立了一個(gè)Server類(lèi)作為主類(lèi),它含有程序的入口函數(shù)main()。在構(gòu)造函數(shù)中初始化一個(gè)數(shù)組存放ClientSingle類(lèi),它其實(shí)就是單獨(dú)處理一個(gè)連接用戶(hù)的類(lèi)。然后啟動(dòng)一個(gè)線程PORTListenThread,該線程的作用就是監(jiān)聽(tīng)端口上有沒(méi)有人登陸,當(dāng)有人連接時(shí)交給Server的addClient()處理。Server的addClient()方法會(huì)在剛才那個(gè)數(shù)組中建立一個(gè)ClientSingle對(duì)象,然后把剩下的事都交給它做。

#p#

端口監(jiān)聽(tīng)線程類(lèi)PORTListenThread

該線程類(lèi)在run()函數(shù)的開(kāi)始部分首先要檢查serverScoket是否為空,保證循環(huán)開(kāi)始時(shí)不要出錯(cuò)。然后進(jìn)入一個(gè)死循環(huán)的監(jiān)聽(tīng):

 while(true) { //死循環(huán)監(jiān)
   try{Socket clientSocket=null;
       clientSocket=serverSocket.accept();
       server.addClient(clientSocket);//轉(zhuǎn)交Server處理
   }
   catch (IOException e){System.out.println("監(jiān)聽(tīng)端口時(shí)出錯(cuò)"+e);}//顯示錯(cuò)誤
 }

單個(gè)客戶(hù)端在連接池中的映像類(lèi)ClientSingle

每一個(gè)客戶(hù)端連接到服務(wù)器后,服務(wù)器會(huì)自動(dòng)在連接池中建立該客戶(hù)端的一個(gè)映像,所有的操作都交給這個(gè)映像去具體執(zhí)行,所以ClientSingle中一定要包含客戶(hù)端的一些基本的信息。比如客戶(hù)端的名稱(chēng)、登陸時(shí)間等等。在該類(lèi)中有兩個(gè)消息隊(duì)列sendQueue(發(fā)送隊(duì)列)和receiveQueue(接收隊(duì)列)緩存消息。

ClientSingle類(lèi)是繼承自Thread的,它還是一個(gè)調(diào)用者。在初始化的時(shí)候啟動(dòng)兩個(gè)子線程類(lèi)SingleSender和SingleListener運(yùn)行。SingleSender負(fù)責(zé)監(jiān)聽(tīng)指令發(fā)送隊(duì)列中有沒(méi)有指令,有則發(fā)送;SingleListener負(fù)責(zé)監(jiān)聽(tīng)有沒(méi)有消息到達(dá),有則把這些消息加入到接收隊(duì)列中去,由ClientSingle處理。所以ClientSingle的主要任務(wù)就是對(duì)這兩個(gè)隊(duì)列的處理。這兩個(gè)隊(duì)列可以用Vector實(shí)現(xiàn),非常地簡(jiǎn)單。

 //-------將消息加入發(fā)送隊(duì)列中------------
 synchronized void send(Object o)
 { sendQueue.add(o);
 }

為了穩(wěn)定控制子線程的運(yùn)行,并不鼓勵(lì)在run()方法的死循環(huán)標(biāo)志都用true,而是使用了一個(gè)布爾型的變量finish。外部可以通過(guò)把這個(gè)標(biāo)志置為假而停止線程的運(yùn)行。

發(fā)送子線程類(lèi)啟動(dòng)后執(zhí)行run()中的循環(huán)(以finish為結(jié)束標(biāo)志),在該循環(huán)內(nèi)首先判斷ClientSingle中的發(fā)送隊(duì)列是否為空,為空時(shí)睡眠一定的時(shí)間再重新判斷,這也是一個(gè)while循環(huán)。不為空則開(kāi)始處理隊(duì)列中的消息,把它取出后放入輸出流中發(fā)送。

  public void run(){
     while (!father.finish){ //循環(huán)監(jiān)聽(tīng)
       while(father.v.isEmpty()){ //當(dāng)發(fā)送隊(duì)列為空的時(shí)候線程睡眠500毫秒
         try{Thread.sleep(500);}
 catch(InterruptedException e){System.out.println(e);}
       }
       if (!father.v.isEmpty()){  //發(fā)送隊(duì)列不為空時(shí)
        try{
           Object a=father.v.firstElement();//取出隊(duì)列中的***個(gè)消息
           father.v.removeElementAt(0);//從隊(duì)列中刪除
           oos.writeObject(a);//發(fā)送該消息
           oos.flush();
         }catch(IOException e){
           displayMessage(" 傳輸失敗 !");
           father.finish=false;
         }
       }
     }
}

接收子線程SingleListener類(lèi)和發(fā)送子線程是類(lèi)似的,它們的run()方法都差不多。不同的是接收子線程把收到的消息加入到ClientSingle的接收隊(duì)列中去,由它處理。

ClientSingle類(lèi)的run()方法就在循環(huán)地讀取接收隊(duì)列receiveQueue中的內(nèi)容,為空時(shí)等待;不為空時(shí)依次取出處理和轉(zhuǎn)發(fā)。處理消息的函數(shù)是processMsg(),它只是執(zhí)行消息類(lèi)自己的process()方法罷了。在處理完后,會(huì)調(diào)用Server類(lèi)的方法進(jìn)行各種類(lèi)型的轉(zhuǎn)發(fā)。

#p#

分組轉(zhuǎn)發(fā)的實(shí)現(xiàn)類(lèi)Group

為了實(shí)現(xiàn)對(duì)客戶(hù)端分組,我建立了Group類(lèi)。在這個(gè)類(lèi)中有一個(gè)列表存放已經(jīng)存在于連接池中的那些ClientSingle類(lèi)的引址。只要遍歷整個(gè)列表就能訪問(wèn)所有組中的成員。這個(gè)列表可以用Vector實(shí)現(xiàn),也可以用哈希表,我推薦后者,主要是為了能夠按名字存取。

組對(duì)象本身也是可以存在Server類(lèi)的組列表中的。

分組功能對(duì)多人的協(xié)同系統(tǒng)來(lái)說(shuō)是非常重要的,特別是分組對(duì)某一個(gè)共享空間操作的時(shí)候。就以上面的協(xié)同繪圖系統(tǒng)為例,如果10個(gè)人里有三個(gè)人要另起爐灶,那么他們?nèi)齻€(gè)的畫(huà)板就不能讓其他人看到,這就必須有"組"個(gè)劃分。

主服務(wù)器類(lèi)Server

Server類(lèi)是最核心的類(lèi),它在這個(gè)框架中起到調(diào)度全局的作用,上面介紹的那些類(lèi)都由它來(lái)統(tǒng)一的構(gòu)造和調(diào)用。

Server類(lèi)的域包括一個(gè)定長(zhǎng)的數(shù)組存放ClientSingle實(shí)例,它就是連接池的實(shí)現(xiàn)。還要有一個(gè)哈希表存放Group實(shí)例。Server類(lèi)的方法都是對(duì)這兩個(gè)類(lèi)的操作。

建立ClientSingle數(shù)組的目的是保證服務(wù)器的穩(wěn)定性。其實(shí),你也可以選擇不建立它,只是動(dòng)態(tài)地構(gòu)造對(duì)象,但是那樣不好管理連接的用戶(hù),而且由于各種操作系統(tǒng)對(duì)進(jìn)程的處理不同,動(dòng)態(tài)建立服務(wù)線程會(huì)很不穩(wěn)定。所以我先建立一個(gè)數(shù)組作為這些對(duì)象的容器,在開(kāi)始時(shí)就估計(jì)好連接者的***數(shù)量。Server類(lèi)的addClient()函數(shù):

void addClient(Socket socket){
     int c=0;
     try{while (sch[c]!=null) c++;}//搜索數(shù)組中的空余空間
     catch(ArrayIndexOutOfBoundsException e){
       try{ socket.close();}//出現(xiàn)異常關(guān)閉槽連接
       catch(IOException ee){ System.out.println("數(shù)組溢出");}
       return;
    }
     sch[c]=new ClientSingle(c,socket,father,this);//在搜索到的位置建立ClientSingle對(duì)象
 }

erver類(lèi)中轉(zhuǎn)發(fā)的方法有:sendToAll()、sendToOne()、sendToGroup()等等。這些方法都是對(duì)線程池中的方法的操作,比較簡(jiǎn)單,不外乎都是找到線程池中的某個(gè)ClientSingle對(duì)象,然后調(diào)用它的send()方法罷了。

注意:這些轉(zhuǎn)發(fā)的方法可能被很多子線程同時(shí)調(diào)用,所以為了保持線程的穩(wěn)定,千萬(wàn)記住要在方法前加synchronized關(guān)鍵字。

【編輯推薦】

  1. Java EE開(kāi)發(fā)三劍客現(xiàn)狀及發(fā)展淺析
  2. Java EE的Web服務(wù)原理和體系結(jié)構(gòu)
  3. 三步學(xué)會(huì)Java Socket編程
責(zé)任編輯:楊鵬飛 來(lái)源: IBM
相關(guān)推薦

2009-02-27 11:15:00

多線程服務(wù)器MTS專(zhuān)用服務(wù)器

2010-03-17 17:54:25

java Socket

2010-03-19 14:01:55

Java Socket

2011-12-08 13:04:06

JavaNIO

2010-03-16 10:50:21

Java多線程服務(wù)器

2010-03-16 13:47:48

Java多線程服務(wù)器

2010-08-03 11:49:26

Ubuntu nfs服

2009-11-23 17:23:59

DNS服務(wù)器內(nèi)部建立

2018-12-20 09:36:24

2011-06-30 18:03:58

QT 多線程 服務(wù)器

2010-09-06 17:08:23

2010-08-26 13:04:06

DHCP服務(wù)器

2010-07-01 09:47:18

DNS服務(wù)器BIND

2010-03-24 11:49:37

Turbo Linux

2015-10-27 09:40:31

TCPIP網(wǎng)絡(luò)協(xié)議

2010-09-02 14:56:03

建立DHCP服務(wù)器

2011-12-07 17:05:45

JavaNIO

2021-09-11 15:26:23

Java多線程線程池

2010-09-02 11:20:47

SQL刪除

2010-03-19 17:04:01

Java socket
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)