本篇文章為大家展示了使用Java怎么建立一個穩(wěn)定的多線程服務(wù)器,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
資陽ssl適用于網(wǎng)站、小程序/APP、API接口等需要進行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:18982081108(備注:SSL證書合作)期待與您的合作!
消息系統(tǒng)的建立
這套服務(wù)器的消息系統(tǒng)采用的是對象傳輸?shù)臋C制,而不是以前常常使用的字符串傳輸。采用對象傳輸?shù)暮锰幨菙U展方便,如需要建立一個新的消息只需要從一個統(tǒng)一的基類繼承下來,然后再寫自己實現(xiàn)的方法就行了。這樣也符合面向?qū)ο箢I(lǐng)域里一條重要的原則: OCP(open_closed Principle),即一個好的設(shè)計應(yīng)該能夠容納新的功能的增加,但是增加的方式不是修改原有的類,而是添加新的類。
首先建立一個基類:Msg,該抽象類中有兩個域sender和receiver分別紀錄消息的發(fā)送者和接收者。這兩個域是在構(gòu)造消息類時就填寫的,receiver域可以為空,空表示發(fā)給誰都可以,由轉(zhuǎn)發(fā)服務(wù)器來決定。該類的方法包括取得這兩個域的值和消息的處理函數(shù)。消息的處理函數(shù)process()是空函數(shù),供繼承者重載。
建立了這個抽象基類后,你就可以繼承它完成你自己的類。舉個例子,假如我要建立一個分組協(xié)同工作的繪圖系統(tǒng),而且支持組員之間的對話,那么我可以建立如下的類集合:
SendTextMsg(String sender,String receiver,String info)//向指定的人發(fā)送對話。 AddLineMsg(String sender,Point a,Point b)//在指定的點之間繪制一條直線 AddRectangle(String sender,point start,Point end)//建立指定的矩形 AddRotundaMsg(String sender,Point center,int radius)//建立指定的圓 RemoveObjectMsg(String sender,int ID)//刪除指定編號的圖形對象 …… |
以此類推,可以建立很多的消息類。在每個類的內(nèi)部都由一個處理該類的方法process(),填寫該方法就可以實現(xiàn)對消息類的處理,而服務(wù)器只負責完成消息的轉(zhuǎn)發(fā)功能。這樣,一套消息系統(tǒng)就建立了。
服務(wù)器的結(jié)構(gòu)
如果要服務(wù)器實現(xiàn)同時為每個客戶端服務(wù),就要使用多線程,建立一個線程池,當有客戶端連接時就在池中開辟一個線程為它服務(wù)。同樣,要避免大量消息到達時處理不過來而導(dǎo)致丟失的情況,就要使用消息隊列。這個服務(wù)器是分層的處理的。
類關(guān)系圖如下所示:
服務(wù)器的工作過程是這樣的,建立了一個Server類作為主類,它含有程序的入口函數(shù)main()。在構(gòu)造函數(shù)中初始化一個數(shù)組存放ClientSingle類,它其實就是單獨處理一個連接用戶的類。然后啟動一個線程PORTListenThread,該線程的作用就是監(jiān)聽端口上有沒有人登陸,當有人連接時交給Server的addClient()處理。Server的addClient()方法會在剛才那個數(shù)組中建立一個ClientSingle對象,然后把剩下的事都交給它做。
端口監(jiān)聽線程類PORTListenThread
該線程類在run()函數(shù)的開始部分首先要檢查serverScoket是否為空,保證循環(huán)開始時不要出錯。然后進入一個死循環(huán)的監(jiān)聽:
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)聽端口時出錯"+e);}//顯示錯誤 } |
單個客戶端在連接池中的映像類ClientSingle
每一個客戶端連接到服務(wù)器后,服務(wù)器會自動在連接池中建立該客戶端的一個映像,所有的操作都交給這個映像去具體執(zhí)行,所以ClientSingle中一定要包含客戶端的一些基本的信息。比如客戶端的名稱、登陸時間等等。在該類中有兩個消息隊列sendQueue(發(fā)送隊列)和receiveQueue(接收隊列)緩存消息。
ClientSingle類是繼承自Thread的,它還是一個調(diào)用者。在初始化的時候啟動兩個子線程類SingleSender和SingleListener運行。SingleSender負責監(jiān)聽指令發(fā)送隊列中有沒有指令,有則發(fā)送;SingleListener負責監(jiān)聽有沒有消息到達,有則把這些消息加入到接收隊列中去,由ClientSingle處理。所以ClientSingle的主要任務(wù)就是對這兩個隊列的處理。這兩個隊列可以用Vector實現(xiàn),非常地簡單。
//-------將消息加入發(fā)送隊列中------------ synchronized void send(Object o) { sendQueue.add(o); } |
為了穩(wěn)定控制子線程的運行,并不鼓勵在run()方法的死循環(huán)標志都用true,而是使用了一個布爾型的變量finish。外部可以通過把這個標志置為假而停止線程的運行。
發(fā)送子線程類啟動后執(zhí)行run()中的循環(huán)(以finish為結(jié)束標志),在該循環(huán)內(nèi)首先判斷ClientSingle中的發(fā)送隊列是否為空,為空時睡眠一定的時間再重新判斷,這也是一個while循環(huán)。不為空則開始處理隊列中的消息,把它取出后放入輸出流中發(fā)送。
public void run(){ while (!father.finish){ //循環(huán)監(jiān)聽 while(father.v.isEmpty()){ //當發(fā)送隊列為空的時候線程睡眠500毫秒 try{Thread.sleep(500);} catch(InterruptedException e){System.out.println(e);} } if (!father.v.isEmpty()){ //發(fā)送隊列不為空時 try{ Object a=father.v.firstElement();//取出隊列中的***個消息 father.v.removeElementAt(0);//從隊列中刪除 oos.writeObject(a);//發(fā)送該消息 oos.flush(); }catch(IOException e){ displayMessage(" 傳輸失敗 !"); father.finish=false; } } } } |
接收子線程SingleListener類和發(fā)送子線程是類似的,它們的run()方法都差不多。不同的是接收子線程把收到的消息加入到ClientSingle的接收隊列中去,由它處理。
ClientSingle類的run()方法就在循環(huán)地讀取接收隊列receiveQueue中的內(nèi)容,為空時等待;不為空時依次取出處理和轉(zhuǎn)發(fā)。處理消息的函數(shù)是processMsg(),它只是執(zhí)行消息類自己的process()方法罷了。在處理完后,會調(diào)用Server類的方法進行各種類型的轉(zhuǎn)發(fā)。
分組轉(zhuǎn)發(fā)的實現(xiàn)類Group
為了實現(xiàn)對客戶端分組,我建立了Group類。在這個類中有一個列表存放已經(jīng)存在于連接池中的那些ClientSingle類的引址。只要遍歷整個列表就能訪問所有組中的成員。這個列表可以用Vector實現(xiàn),也可以用哈希表,我推薦后者,主要是為了能夠按名字存取。
組對象本身也是可以存在Server類的組列表中的。
分組功能對多人的協(xié)同系統(tǒng)來說是非常重要的,特別是分組對某一個共享空間操作的時候。就以上面的協(xié)同繪圖系統(tǒng)為例,如果10個人里有三個人要另起爐灶,那么他們?nèi)齻€的畫板就不能讓其他人看到,這就必須有"組"個劃分。
主服務(wù)器類Server
Server類是最核心的類,它在這個框架中起到調(diào)度全局的作用,上面介紹的那些類都由它來統(tǒng)一的構(gòu)造和調(diào)用。
Server類的域包括一個定長的數(shù)組存放ClientSingle實例,它就是連接池的實現(xiàn)。還要有一個哈希表存放Group實例。Server類的方法都是對這兩個類的操作。
建立ClientSingle數(shù)組的目的是保證服務(wù)器的穩(wěn)定性。其實,你也可以選擇不建立它,只是動態(tài)地構(gòu)造對象,但是那樣不好管理連接的用戶,而且由于各種操作系統(tǒng)對進程的處理不同,動態(tài)建立服務(wù)線程會很不穩(wěn)定。所以我先建立一個數(shù)組作為這些對象的容器,在開始時就估計好連接者的***數(shù)量。Server類的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對象 } |
erver類中轉(zhuǎn)發(fā)的方法有:sendToAll()、sendToOne()、sendToGroup()等等。這些方法都是對線程池中的方法的操作,比較簡單,不外乎都是找到線程池中的某個ClientSingle對象,然后調(diào)用它的send()方法罷了。
上述內(nèi)容就是使用Java怎么建立一個穩(wěn)定的多線程服務(wù)器,你們學(xué)到知識或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識儲備,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。