Java進(jìn)階學(xué)習(xí):網(wǎng)絡(luò)服務(wù)器編程
Java的Socket API提供了一個(gè)很方便的對(duì)象接口進(jìn)行網(wǎng)絡(luò)編程。本文用一個(gè)簡(jiǎn)單的TCP Echo Server做例子,演示了如何使用Java完成一個(gè)網(wǎng)絡(luò)服務(wù)器。
用作例子的TCP Echo Server是按以下方式工作的:
當(dāng)一個(gè)客戶端通過(guò)TCP連接到服務(wù)器后,客戶端可以通過(guò)這個(gè)連接發(fā)送數(shù)據(jù)到服務(wù)端,而服務(wù)端接收到數(shù)據(jù)后會(huì)把這些數(shù)據(jù)用同一個(gè)TCP連接發(fā)送回客戶端。服務(wù)端會(huì)一直保持這個(gè)連接直到客戶端關(guān)閉它為止。
因?yàn)榉?wù)器需要能同時(shí)處理多個(gè)客戶端,我們先選用一個(gè)常見的多線程服務(wù)模型:
讓一個(gè)Thread負(fù)責(zé)監(jiān)聽服務(wù)端口,當(dāng)有新的連接建立的時(shí)候,這個(gè)監(jiān)聽的Thread會(huì)為這個(gè)連接創(chuàng)建一個(gè)新的Thread來(lái)處理它。這樣,服務(wù)器可以接受多個(gè)連接,并讓多個(gè)Thread來(lái)分別處理它們。
以下是相應(yīng)的服務(wù)端程序:
public class EchoServer implements Runnable { public void run() { try { ServerSocket svr = new ServerSocket(7); while (true) { Socket sock = svr.accept(); new Thread(new EchoSession(sock)).start(); } } catch (IOException ex) { throw new ExceptionAdapter(ex); } } } |
這段代碼先創(chuàng)建了一個(gè)ServerSocket的對(duì)象并讓其監(jiān)聽在TCP端口7上,然后在一個(gè)循環(huán)中用accept()方法接收新的連接,并創(chuàng)建處理這一連接的Thread。實(shí)際處理每個(gè)客戶端連接的邏輯包含在EchoSession這個(gè)類里面。
在以上代碼中使用了ExceptionAdapter這個(gè)類,它的作用是把一個(gè)checked Exception包裝成RuntimeException。詳細(xì)的說(shuō)明可以參考避免在Java中使用Checked Exception 一文。
以下是EchoSession的代碼:
public class EchoSession implements Runnable { public EchoSession(Socket s) { _sock = s; } public void run() { |
try { try { InputStream input = _sock.getInputStream(); OutputStream output = _sock.getOutputStream(); byte [] buf = new byte [128]; while (true) { int count = input.read(buf); if (count == -1) break; output.write(buf, 0 , count); } } finally { _sock.close(); } } catch (IOException ex) { throw new ExceptionAdapter(ex); } } protected Socket _sock = null; } |
EchoSession接受一個(gè)Socket對(duì)象作為構(gòu)造參數(shù),在其run()方法中,它不停的從這個(gè)Socket對(duì)象的InputStream里面讀數(shù)據(jù)并寫回到該Socket的OutputStream中去,直到這個(gè)連接被客戶端關(guān)閉為止(InputStream的read方法返回-1)。
EchoSession需要一個(gè)線程來(lái)執(zhí)行,這容易讓人聯(lián)想到用Thread來(lái)作為EchoSession的父類。不過(guò),這樣做不夠靈活,開銷也比較大。而選擇讓EchoSession實(shí)現(xiàn)Runnable接口就靈活得多。在接下來(lái)的使用Thread Pool的Echo Server中可以看到這一點(diǎn)。
以上已經(jīng)是一個(gè)完整的TCP Echo Server,不過(guò)隨著客戶不停的連接和斷開,這個(gè)服務(wù)器會(huì)不停的產(chǎn)生和消除線程,而這兩個(gè)都是比較‘昂貴'的操作。為了避免這種消耗,可以考慮采用Thread Pool的機(jī)制。
使用在一個(gè)簡(jiǎn)單的Thread緩沖池的實(shí)現(xiàn)一文中Thread Pool的實(shí)現(xiàn),可以對(duì)EchoServer作如下修改(EchoSession無(wú)需做修改):
public class EchoServer implements Runnable { public void run() { try { ServerSocket svr = new ServerSocket(7); |
// 初始化Thread Pool SyncQueue queue = new SyncQueue(10); for (int i = 0; i < 10; i ++) { new Thread(new Worker(queue)).start(); } while (true) { Socket sock = svr.accept(); // 把任務(wù)放入Thread Pool queue.put(new EchoSession(sock)); } } catch (IOException ex) { throw new ExceptionAdapter(ex); } } } |
這里可以看出讓EchoSession實(shí)現(xiàn)Runnable接口的靈活性,無(wú)需修改它就可以在Thread Pool里使用。
在這個(gè)例子里使用的Thread Pool比較簡(jiǎn)單,沒(méi)有動(dòng)態(tài)調(diào)整Thread數(shù)量的功能,所以這個(gè)Echo Server最多只能同時(shí)服務(wù)10個(gè)客戶端。然而通過(guò)重載SyncQueue,我們可以很方便地加入這個(gè)功能以突破這個(gè)限制。
在對(duì)網(wǎng)絡(luò)服務(wù)器的性能以及并發(fā)度要求很高的時(shí)候,讓每個(gè)客戶端由一個(gè)專門的Thread來(lái)處理有可能不能滿足我們的要求(想象一下同時(shí)有數(shù)千個(gè)客戶端的情況)。這時(shí)可以考慮使用Java的NIO API來(lái)構(gòu)建服務(wù)器架構(gòu),因?yàn)镹IO中IO操作都是非阻塞的,我們只需要很少的Thread就可以充分地利用CPU來(lái)處理多個(gè)客戶端的請(qǐng)求。關(guān)于NIO的話題,在這篇文章就不再贅述,希望以后能有機(jī)會(huì)討論。 :)
- 零基礎(chǔ)寫Java知乎爬蟲之進(jìn)階篇
- Java進(jìn)階學(xué)習(xí):jar打包詳解
- Java 正則表達(dá)式入門詳解(基礎(chǔ)進(jìn)階)
- Java異常處理之try...catch...語(yǔ)句的使用進(jìn)階
- Java中jqGrid 學(xué)習(xí)筆記整理——進(jìn)階篇(二)
- Java進(jìn)階:JNI使用技巧點(diǎn)滴
- Java進(jìn)階教程之IO基礎(chǔ)
- 深入學(xué)習(xí)Java編程中的字符串的進(jìn)階使用
- 老生常談Java字符串進(jìn)階(必看篇)
- 開發(fā)10年,全記在這本Java進(jìn)階寶典里了
相關(guān)文章
Java中char[] 和 String 類型占用字節(jié)大小問(wèn)題
這篇文章主要介紹了Java中char[] 和 String 類型占用字節(jié)大小問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08詳解SpringBoot開發(fā)使用@ImportResource注解影響攔截器
這篇文章主要介紹了詳解SpringBoot開發(fā)使用@ImportResource注解影響攔截器,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11springboot?jpa?實(shí)現(xiàn)返回結(jié)果自定義查詢
這篇文章主要介紹了springboot?jpa?實(shí)現(xiàn)返回結(jié)果自定義查詢方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02springboot實(shí)現(xiàn)攔截器之驗(yàn)證登錄示例
本篇文章主要介紹了springboot實(shí)現(xiàn)攔截器之驗(yàn)證登錄示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02Java?EventBus手把手帶你實(shí)現(xiàn)
EventBus是Guava的事件處理機(jī)制,是設(shè)計(jì)模式中觀察者模式(生產(chǎn)/消費(fèi)者編程模型)的優(yōu)雅實(shí)現(xiàn)。本文就來(lái)和大家聊聊EventBus的使用,需要的可以參考一下2023-01-01使用Easyexcel實(shí)現(xiàn)不同場(chǎng)景的數(shù)據(jù)導(dǎo)出功能
這篇文章主要為大家詳細(xì)介紹了如何在不同場(chǎng)景下使用Easyexcel實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03Spring Security使用中Preflight請(qǐng)求和跨域問(wèn)題詳解
這篇文章主要給大家介紹了關(guān)于Spring Security使用中Preflight請(qǐng)求和跨域問(wèn)題的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11