Java網(wǎng)絡(luò)編程精解——孫衛(wèi)琴.doc_第1頁
Java網(wǎng)絡(luò)編程精解——孫衛(wèi)琴.doc_第2頁
Java網(wǎng)絡(luò)編程精解——孫衛(wèi)琴.doc_第3頁
Java網(wǎng)絡(luò)編程精解——孫衛(wèi)琴.doc_第4頁
Java網(wǎng)絡(luò)編程精解——孫衛(wèi)琴.doc_第5頁
已閱讀5頁,還剩25頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

在客戶/服務(wù)器通信模式中,服務(wù)器端需要?jiǎng)?chuàng)建監(jiān)聽特定端口的ServerSocket,ServerSocket負(fù)責(zé)接收客戶連接請(qǐng)求。本章首先介紹ServerSocket類的各個(gè)構(gòu)造方法,以及成員方法的用法,接著介紹服務(wù)器如何用多線程來處理與多個(gè)客戶的通信任務(wù)。本章提供線程池的一種實(shí)現(xiàn)方式。線程池包括一個(gè)工作隊(duì)列和若干工作線程。服務(wù)器程序向工作隊(duì)列中加入與客戶通信的任務(wù),工作線程不斷從工作隊(duì)列中取出任務(wù)并執(zhí)行它。本章還介紹了java.util.concurrent包中的線程池類的用法,在服務(wù)器程序中可以直接使用它們。3.1 構(gòu)造ServerSocketServerSocket的構(gòu)造方法有以下幾種重載形式:l ServerSocket()throws IOExceptionl ServerSocket(int port) throws IOExceptionl ServerSocket(int port, int backlog) throws IOExceptionl ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException在以上構(gòu)造方法中,參數(shù)port指定服務(wù)器要綁定的端口(服務(wù)器要監(jiān)聽的端口),參數(shù)backlog指定客戶連接請(qǐng)求隊(duì)列的長(zhǎng)度,參數(shù)bindAddr指定服務(wù)器要綁定的IP地址。3.1.1 綁定端口除了第一個(gè)不帶參數(shù)的構(gòu)造方法以外,其他構(gòu)造方法都會(huì)使服務(wù)器與特定端口綁定,該端口由參數(shù)port指定。例如,以下代碼創(chuàng)建了一個(gè)與80端口綁定的服務(wù)器:ServerSocket serverSocket=new ServerSocket(80);如果運(yùn)行時(shí)無法綁定到80端口,以上代碼會(huì)拋出IOException,更確切地說,是拋出BindException,它是IOException的子類。BindException一般是由以下原因造成的:l 端口已經(jīng)被其他服務(wù)器進(jìn)程占用;l 在某些操作系統(tǒng)中,如果沒有以超級(jí)用戶的身份來運(yùn)行服務(wù)器程序,那么操作系統(tǒng)不允許服務(wù)器綁定到11023之間的端口。如果把參數(shù)port設(shè)為0,表示由操作系統(tǒng)來為服務(wù)器分配一個(gè)任意可用的端口。由操作系統(tǒng)分配的端口也稱為匿名端口。對(duì)于多數(shù)服務(wù)器,會(huì)使用明確的端口,而不會(huì)使用匿名端口,因?yàn)榭蛻舫绦蛐枰孪戎婪?wù)器的端口,才能方便地訪問服務(wù)器。在某些場(chǎng)合,匿名端口有著特殊的用途,本章3.4節(jié)會(huì)對(duì)此作介紹。3.1.2 設(shè)定客戶連接請(qǐng)求隊(duì)列的長(zhǎng)度當(dāng)服務(wù)器進(jìn)程運(yùn)行時(shí),可能會(huì)同時(shí)監(jiān)聽到多個(gè)客戶的連接請(qǐng)求。例如,每當(dāng)一個(gè)客戶進(jìn)程執(zhí)行以下代碼:Socket socket=new Socket(,80);就意味著在遠(yuǎn)程主機(jī)的80端口上,監(jiān)聽到了一個(gè)客戶的連接請(qǐng)求。管理客戶連接請(qǐng)求的任務(wù)是由操作系統(tǒng)來完成的。操作系統(tǒng)把這些連接請(qǐng)求存儲(chǔ)在一個(gè)先進(jìn)先出的隊(duì)列中。許多操作系統(tǒng)限定了隊(duì)列的最大長(zhǎng)度,一般為50。當(dāng)隊(duì)列中的連接請(qǐng)求達(dá)到了隊(duì)列的最大容量時(shí),服務(wù)器進(jìn)程所在的主機(jī)會(huì)拒絕新的連接請(qǐng)求。只有當(dāng)服務(wù)器進(jìn)程通過ServerSocket的accept()方法從隊(duì)列中取出連接請(qǐng)求,使隊(duì)列騰出空位時(shí),隊(duì)列才能繼續(xù)加入新的連接請(qǐng)求。對(duì)于客戶進(jìn)程,如果它發(fā)出的連接請(qǐng)求被加入到服務(wù)器的隊(duì)列中,就意味著客戶與服務(wù)器的連接建立成功,客戶進(jìn)程從Socket構(gòu)造方法中正常返回。如果客戶進(jìn)程發(fā)出的連接請(qǐng)求被服務(wù)器拒絕,Socket構(gòu)造方法就會(huì)拋出ConnectionException。ServerSocket構(gòu)造方法的backlog參數(shù)用來顯式設(shè)置連接請(qǐng)求隊(duì)列的長(zhǎng)度,它將覆蓋操作系統(tǒng)限定的隊(duì)列的最大長(zhǎng)度。值得注意的是,在以下幾種情況中,仍然會(huì)采用操作系統(tǒng)限定的隊(duì)列的最大長(zhǎng)度:l backlog參數(shù)的值大于操作系統(tǒng)限定的隊(duì)列的最大長(zhǎng)度;l backlog參數(shù)的值小于或等于0;l 在ServerSocket構(gòu)造方法中沒有設(shè)置backlog參數(shù)。以下例程3-1的Client.java和例程3-2的Server.java用來演示服務(wù)器的連接請(qǐng)求隊(duì)列的特性。例程3-1 Client.javaimport .*;public class Client public static void main(String args)throws Exception final int length=100; String host=localhost; int port=8000; Socket sockets=new Socketlength; for(int i=0;ilength;i+) /試圖建立100次連接 socketsi=new Socket(host, port); System.out.println(第+(i+1)+次連接成功); Thread.sleep(3000); for(int i=0;ilength;i+) socketsi.close(); /斷開連接 例程3-2 Server.javaimport java.io.*;import .*;public class Server private int port=8000; private ServerSocket serverSocket; public Server() throws IOException serverSocket = new ServerSocket(port,3); /連接請(qǐng)求隊(duì)列的長(zhǎng)度為3 System.out.println(服務(wù)器啟動(dòng)); public void service() while (true) Socket socket=null; try socket = serverSocket.accept(); /從連接請(qǐng)求隊(duì)列中取出一個(gè)連接 System.out.println(New connection accepted + socket.getInetAddress() + : +socket.getPort(); catch (IOException e) e.printStackTrace(); finally try if(socket!=null)socket.close(); catch (IOException e) e.printStackTrace(); public static void main(String args)throws Exception Server server=new Server(); Thread.sleep(60000*10); /睡眠10分鐘 /server.service(); Client試圖與Server進(jìn)行100次連接。在Server類中,把連接請(qǐng)求隊(duì)列的長(zhǎng)度設(shè)為3。這意味著當(dāng)隊(duì)列中有了3個(gè)連接請(qǐng)求時(shí),如果Client再請(qǐng)求連接,就會(huì)被Server拒絕。下面按照以下步驟運(yùn)行Server和Client程序。(1)把Server類的main()方法中的“server.service();”這行程序代碼注釋掉。這使得服務(wù)器與8 000端口綁定后,永遠(yuǎn)不會(huì)執(zhí)行serverSocket.accept()方法。這意味著隊(duì)列中的連接請(qǐng)求永遠(yuǎn)不會(huì)被取出。先運(yùn)行Server程序,然后再運(yùn)行Client程序,Client程序的打印結(jié)果如下:第1次連接成功第2次連接成功第3次連接成功Exception in thread main .ConnectException: Connection refused: connect at .PlainSocketImpl.socketConnect(Native Method) at .PlainSocketImpl.doConnect(Unknown Source) at .PlainSocketImpl.connectToAddress(Unknown Source) at .PlainSocketImpl.connect(Unknown Source) at .SocksSocketImpl.connect(Unknown Source) at .Socket.connect(Unknown Source) at .Socket.connect(Unknown Source) at .Socket.(Unknown Source) at .Socket.(Unknown Source) at Client.main(Client.java:10)從以上打印結(jié)果可以看出,Client與Server在成功地建立了3個(gè)連接后,就無法再創(chuàng)建其余的連接了,因?yàn)榉?wù)器的隊(duì)列已經(jīng)滿了。(2)把Server類的main()方法按如下方式修改:public static void main(String args)throws Exception Server server=new Server(); /Thread.sleep(60000*10); /睡眠10分鐘 server.service(); 作了以上修改,服務(wù)器與8 000端口綁定后,就會(huì)在一個(gè)while循環(huán)中不斷執(zhí)行serverSocket.accept()方法,該方法從隊(duì)列中取出連接請(qǐng)求,使得隊(duì)列能及時(shí)騰出空位,以容納新的連接請(qǐng)求。先運(yùn)行Server程序,然后再運(yùn)行Client程序,Client程序的打印結(jié)果如下:第1次連接成功第2次連接成功第3次連接成功第100次連接成功從以上打印結(jié)果可以看出,此時(shí)Client能順利與Server建立100次連接。3.1.3 設(shè)定綁定的IP地址如果主機(jī)只有一個(gè)IP地址,那么默認(rèn)情況下,服務(wù)器程序就與該IP地址綁定。ServerSocket的第4個(gè)構(gòu)造方法ServerSocket(int port, int backlog, InetAddress bindAddr)有一個(gè)bindAddr參數(shù),它顯式指定服務(wù)器要綁定的IP地址,該構(gòu)造方法適用于具有多個(gè)IP地址的主機(jī)。假定一個(gè)主機(jī)有兩個(gè)網(wǎng)卡,一個(gè)網(wǎng)卡用于連接到Internet, IP地址為4,還有一個(gè)網(wǎng)卡用于連接到本地局域網(wǎng),IP地址為。如果服務(wù)器僅僅被本地局域網(wǎng)中的客戶訪問,那么可以按如下方式創(chuàng)建ServerSocket:ServerSocket serverSocket=new ServerSocket(8000,10,InetAddress.getByName ();3.1.4 默認(rèn)構(gòu)造方法的作用ServerSocket有一個(gè)不帶參數(shù)的默認(rèn)構(gòu)造方法。通過該方法創(chuàng)建的ServerSocket不與任何端口綁定,接下來還需要通過bind()方法與特定端口綁定。這個(gè)默認(rèn)構(gòu)造方法的用途是,允許服務(wù)器在綁定到特定端口之前,先設(shè)置ServerSocket的一些選項(xiàng)。因?yàn)橐坏┓?wù)器與特定端口綁定,有些選項(xiàng)就不能再改變了。在以下代碼中,先把ServerSocket的SO_REUSEADDR選項(xiàng)設(shè)為true,然后再把它與8000端口綁定:ServerSocket serverSocket=new ServerSocket();serverSocket.setReuseAddress(true); /設(shè)置ServerSocket的選項(xiàng)serverSocket.bind(new InetSocketAddress(8000); /與8000端口綁定如果把以上程序代碼改為:ServerSocket serverSocket=new ServerSocket(8000);serverSocket.setReuseAddress(true); /設(shè)置ServerSocket的選項(xiàng)那么serverSocket.setReuseAddress(true)方法就不起任何作用了,因?yàn)镾O_ REUSEADDR選項(xiàng)必須在服務(wù)器綁定端口之前設(shè)置才有效。3.2 接收和關(guān)閉與客戶的連接ServerSocket的accept()方法從連接請(qǐng)求隊(duì)列中取出一個(gè)客戶的連接請(qǐng)求,然后創(chuàng)建與客戶連接的Socket對(duì)象,并將它返回。如果隊(duì)列中沒有連接請(qǐng)求,accept()方法就會(huì)一直等待,直到接收到了連接請(qǐng)求才返回。接下來,服務(wù)器從Socket對(duì)象中獲得輸入流和輸出流,就能與客戶交換數(shù)據(jù)。當(dāng)服務(wù)器正在進(jìn)行發(fā)送數(shù)據(jù)的操作時(shí),如果客戶端斷開了連接,那么服務(wù)器端會(huì)拋出一個(gè)IOException的子類SocketException異常:.SocketException: Connection reset by peer這只是服務(wù)器與單個(gè)客戶通信中出現(xiàn)的異常,這種異常應(yīng)該被捕獲,使得服務(wù)器能繼續(xù)與其他客戶通信。以下程序顯示了單線程服務(wù)器采用的通信流程:public void service() while (true) Socket socket=null;try socket = serverSocket.accept(); /從連接請(qǐng)求隊(duì)列中取出一個(gè)連接 System.out.println(New connection accepted + socket.getInetAddress() + : +socket.getPort(); /接收和發(fā)送數(shù)據(jù) catch (IOException e) /這只是與單個(gè)客戶通信時(shí)遇到的異常,可能是由于客戶端過早斷開連接引起的 /這種異常不應(yīng)該中斷整個(gè)while循環(huán) e.printStackTrace(); finally tryif(socket!=null)socket.close(); /與一個(gè)客戶通信結(jié)束后,要關(guān)閉Socket catch (IOException e) e.printStackTrace(); 與單個(gè)客戶通信的代碼放在一個(gè)try代碼塊中,如果遇到異常,該異常被catch代碼塊捕獲。try代碼塊后面還有一個(gè)finally代碼塊,它保證不管與客戶通信正常結(jié)束還是異常結(jié)束,最后都會(huì)關(guān)閉Socket,斷開與這個(gè)客戶的連接。3.3 關(guān)閉ServerSocketServerSocket的close()方法使服務(wù)器釋放占用的端口,并且斷開與所有客戶的連接。當(dāng)一個(gè)服務(wù)器程序運(yùn)行結(jié)束時(shí),即使沒有執(zhí)行ServerSocket的close()方法,操作系統(tǒng)也會(huì)釋放這個(gè)服務(wù)器占用的端口。因此,服務(wù)器程序并不一定要在結(jié)束之前執(zhí)行ServerSocket的close()方法。在某些情況下,如果希望及時(shí)釋放服務(wù)器的端口,以便讓其他程序能占用該端口,則可以顯式調(diào)用ServerSocket的close()方法。例如,以下代碼用于掃描165535之間的端口號(hào)。如果ServerSocket成功創(chuàng)建,意味著該端口未被其他服務(wù)器進(jìn)程綁定,否者說明該端口已經(jīng)被其他進(jìn)程占用:for(int port=1;portjava RandomPort監(jiān)聽的端口為:3000C:chapter03classesjava RandomPort監(jiān)聽的端口為:3004C:chapter03classesjava RandomPort監(jiān)聽的端口為:3005多數(shù)服務(wù)器會(huì)監(jiān)聽固定的端口,這樣才便于客戶程序訪問服務(wù)器。匿名端口一般適用于服務(wù)器與客戶之間的臨時(shí)通信,通信結(jié)束,就斷開連接,并且ServerSocket占用的臨時(shí)端口也被釋放。FTP(文件傳輸)協(xié)議就使用了匿名端口。如圖3-1所示,F(xiàn)TP協(xié)議用于在本地文件系統(tǒng)與遠(yuǎn)程文件系統(tǒng)之間傳送文件。圖3-1 FTP協(xié)議用于在本地文件系統(tǒng)與遠(yuǎn)程文件系統(tǒng)之間傳送文件FTP使用兩個(gè)并行的TCP連接:一個(gè)是控制連接,一個(gè)是數(shù)據(jù)連接??刂七B接用于在客戶和服務(wù)器之間發(fā)送控制信息,如用戶名和口令、改變遠(yuǎn)程目錄的命令或上傳和下載文件的命令。數(shù)據(jù)連接用于傳送文件。TCP服務(wù)器在21端口上監(jiān)聽控制連接,如果有客戶要求上傳或下載文件,就另外建立一個(gè)數(shù)據(jù)連接,通過它來傳送文件。數(shù)據(jù)連接的建立有兩種方式。(1)如圖3-2所示,TCP服務(wù)器在20端口上監(jiān)聽數(shù)據(jù)連接,TCP客戶主動(dòng)請(qǐng)求建立與該端口的連接。圖3-2 TCP服務(wù)器在20端口上監(jiān)聽數(shù)據(jù)連接(2)如圖3-3所示,首先由TCP客戶創(chuàng)建一個(gè)監(jiān)聽匿名端口的ServerSocket,再把這個(gè)ServerSocket監(jiān)聽的端口號(hào)(調(diào)用ServerSocket的getLocalPort()方法就能得到端口號(hào))發(fā)送給TCP服務(wù)器,然后由TCP服務(wù)器主動(dòng)請(qǐng)求建立與客戶端的連接。圖3-3 TCP客戶在匿名端口上監(jiān)聽數(shù)據(jù)連接以上第二種方式就使用了匿名端口,并且是在客戶端使用的,用于和服務(wù)器建立臨時(shí)的數(shù)據(jù)連接。在實(shí)際應(yīng)用中,在服務(wù)器端也可以使用匿名端口。3.5 ServerSocket選項(xiàng)ServerSocket有以下3個(gè)選項(xiàng)。l SO_TIMEOUT:表示等待客戶連接的超時(shí)時(shí)間。l SO_REUSEADDR:表示是否允許重用服務(wù)器所綁定的地址。l SO_RCVBUF:表示接收數(shù)據(jù)的緩沖區(qū)的大小。3.5.1 SO_TIMEOUT選項(xiàng)l 設(shè)置該選項(xiàng):public void setSoTimeout(int timeout) throws SocketExceptionl 讀取該選項(xiàng):public int getSoTimeout () throws IOExceptionSO_TIMEOUT表示ServerSocket的accept()方法等待客戶連接的超時(shí)時(shí)間,以毫秒為單位。如果SO_TIMEOUT的值為0,表示永遠(yuǎn)不會(huì)超時(shí),這是SO_TIMEOUT的默認(rèn)值。當(dāng)服務(wù)器執(zhí)行ServerSocket的accept()方法時(shí),如果連接請(qǐng)求隊(duì)列為空,服務(wù)器就會(huì)一直等待,直到接收到了客戶連接才從accept()方法返回。如果設(shè)定了超時(shí)時(shí)間,那么當(dāng)服務(wù)器等待的時(shí)間超過了超時(shí)時(shí)間,就會(huì)拋出SocketTimeoutException,它是InterruptedException的子類。如例程3-4所示的TimeoutTester把超時(shí)時(shí)間設(shè)為6秒鐘。例程3-4 TimeoutTester.javaimport java.io.*;import .*;public class TimeoutTester public static void main(String args)throws IOException ServerSocket serverSocket=new ServerSocket(8000); serverSocket.setSoTimeout(6000); /等待客戶連接的時(shí)間不超過6秒 Socket socket=serverSocket.accept(); socket.close(); System.out.println(服務(wù)器關(guān)閉); 運(yùn)行以上程序,過6秒鐘后,程序會(huì)從serverSocket.accept()方法中拋出Socket- TimeoutException:C:chapter03classesjava TimeoutTesterException in thread main .SocketTimeoutException: Accept timed out at .PlainSocketImpl.socketAccept(Native Method) at .PlainSocketImpl.accept(Unknown Source) at .ServerSocket.implAccept(Unknown Source) at .ServerSocket.accept(Unknown Source) at TimeoutTester.main(TimeoutTester.java:8)如果把程序中的“serverSocket.setSoTimeout(6000)”注釋掉,那么serverSocket. accept()方法永遠(yuǎn)不會(huì)超時(shí),它會(huì)一直等待下去,直到接收到了客戶的連接,才會(huì)從accept()方法返回。服務(wù)器執(zhí)行serverSocket.accept()方法時(shí),等待客戶連接的過程也稱為阻塞。本書第4章的4.1節(jié)(線程阻塞的概念)詳細(xì)介紹了阻塞的概念。3.5.2 SO_REUSEADDR選項(xiàng)l 設(shè)置該選項(xiàng):public void setResuseAddress(boolean on) throws SocketExceptionl 讀取該選項(xiàng):public boolean getResuseAddress() throws SocketException這個(gè)選項(xiàng)與Socket的SO_REUSEADDR選項(xiàng)相同,用于決定如果網(wǎng)絡(luò)上仍然有數(shù)據(jù)向舊的ServerSocket傳輸數(shù)據(jù),是否允許新的ServerSocket綁定到與舊的ServerSocket同樣的端口上。SO_REUSEADDR選項(xiàng)的默認(rèn)值與操作系統(tǒng)有關(guān),在某些操作系統(tǒng)中,允許重用端口,而在某些操作系統(tǒng)中不允許重用端口。當(dāng)ServerSocket關(guān)閉時(shí),如果網(wǎng)絡(luò)上還有發(fā)送到這個(gè)ServerSocket的數(shù)據(jù),這個(gè)ServerSocket不會(huì)立刻釋放本地端口,而是會(huì)等待一段時(shí)間,確保接收到了網(wǎng)絡(luò)上發(fā)送過來的延遲數(shù)據(jù),然后再釋放端口。許多服務(wù)器程序都使用固定的端口。當(dāng)服務(wù)器程序關(guān)閉后,有可能它的端口還會(huì)被占用一段時(shí)間,如果此時(shí)立刻在同一個(gè)主機(jī)上重啟服務(wù)器程序,由于端口已經(jīng)被占用,使得服務(wù)器程序無法綁定到該端口,服務(wù)器啟動(dòng)失敗,并拋出BindException:Exception in thread main .BindException: Address already in use: JVM_Bind為了確保一個(gè)進(jìn)程關(guān)閉了ServerSocket后,即使操作系統(tǒng)還沒釋放端口,同一個(gè)主機(jī)上的其他進(jìn)程還可以立刻重用該端口,可以調(diào)用ServerSocket的setResuse- Address(true)方法:if(!serverSocket.getResuseAddress()serverSocket.setResuseAddress(true);值得注意的是,serverSocket.setResuseAddress(true)方法必須在ServerSocket還沒有綁定到一個(gè)本地端口之前調(diào)用,否則執(zhí)行serverSocket.setResuseAddress(true)方法無效。此外,兩個(gè)共用同一個(gè)端口的進(jìn)程必須都調(diào)用serverSocket.setResuseAddress(true)方法,才能使得一個(gè)進(jìn)程關(guān)閉ServerSocket后,另一個(gè)進(jìn)程的ServerSocket還能夠立刻重用相同端口。3.5.3 SO_RCVBUF選項(xiàng)l 設(shè)置該選項(xiàng):public void setReceiveBufferSize(int size) throws SocketExceptionl 讀取該選項(xiàng):public int getReceiveBufferSize() throws SocketExceptionSO_RCVBUF表示服務(wù)器端的用于接收數(shù)據(jù)的緩沖區(qū)的大小,以字節(jié)為單位。一般說來,傳輸大的連續(xù)的數(shù)據(jù)塊(基于HTTP或FTP協(xié)議的數(shù)據(jù)傳輸)可以使用較大的緩沖區(qū),這可以減少傳輸數(shù)據(jù)的次數(shù),從而提高傳輸數(shù)據(jù)的效率。而對(duì)于交互式的通信(Telnet和網(wǎng)絡(luò)游戲),則應(yīng)該采用小的緩沖區(qū),確保能及時(shí)把小批量的數(shù)據(jù)發(fā)送給對(duì)方。SO_RCVBUF的默認(rèn)值與操作系統(tǒng)有關(guān)。例如,在Windows 2000中運(yùn)行以下代碼時(shí),顯示SO_RCVBUF的默認(rèn)值為8192:ServerSocket serverSocket=new ServerSocket(8000);System.out.println(serverSocket.getReceiveBufferSize(); /打印8192無論在ServerSocket綁定到特定端口之前或之后,調(diào)用setReceiveBufferSize()方法都有效。例外情況下是如果要設(shè)置大于64K的緩沖區(qū),則必須在ServerSocket綁定到特定端口之前進(jìn)行設(shè)置才有效。例如,以下代碼把緩沖區(qū)設(shè)為128K:ServerSocket serverSocket=new ServerSocket();int size=serverSocket.getReceiveBufferSize();if(size131072) serverSocket.setReceiveBufferSize(131072); /把緩沖區(qū)的大小設(shè)為128KserverSocket.bind(new InetSocketAddress(8000); /與8 000端口綁定執(zhí)行serverSocket.setReceiveBufferSize()方法,相當(dāng)于對(duì)所有由serverSocket.accept()方法返回的Socket設(shè)置接收數(shù)據(jù)的緩沖區(qū)的大小。3.5.4 設(shè)定連接時(shí)間、延遲和帶寬的相對(duì)重要性l public void setPerformancePreferences(int connectionTime,int latency,int bandwidth)該方法的作用與Socket的setPerformancePreferences()方法的作用相同,用于設(shè)定連接時(shí)間、延遲和帶寬的相對(duì)重要性,參見本書第2章的2.5.10節(jié)(設(shè)定連接時(shí)間、延遲和帶寬的相對(duì)重要性)。3.6 創(chuàng)建多線程的服務(wù)器在本書第1章的1.5.1節(jié)的例程1-2的EchoServer中,其service()方法負(fù)責(zé)接收客戶連接,以及與客戶通信。service()方法的處理流程如下:while (true) Socket socket=null; try socket = serverSocket.accept(); /接收客戶連接 /從Socket中獲得輸入流與輸出流,與客戶通信 catch (IOException e) e.printStackTrace(); finally tryif(socket!=null)socket.close(); /斷開連接 catch (IOException e) e.printStackTrace(); EchoServer接收到一個(gè)客戶連接,就與客戶進(jìn)行通信,通信完畢后斷開連接,然后再接收下一個(gè)客戶連接。假如同時(shí)有多個(gè)客戶請(qǐng)求連接,這些客戶就必須排隊(duì)等候EchoServer的響應(yīng)。EchoServer無法同時(shí)與多個(gè)客戶通信。許多實(shí)際應(yīng)用要求服務(wù)器具有同時(shí)為多個(gè)客戶提供服務(wù)的能力。HTTP服務(wù)器就是最明顯的例子。任何時(shí)刻,HTTP服務(wù)器都可能接收到大量的客戶請(qǐng)求,每個(gè)客戶都希望能快速得到HTTP服務(wù)器的響應(yīng)。如果長(zhǎng)時(shí)間讓客戶等待,會(huì)使網(wǎng)站失去信譽(yù),從而降低訪問量??梢杂貌l(fā)性能來衡量一個(gè)服務(wù)器同時(shí)響應(yīng)多個(gè)客戶的能力。一個(gè)具有好的并發(fā)性能的服務(wù)器,必須符合兩個(gè)條件:l 能同時(shí)接收并處理多個(gè)客戶連接;l 對(duì)于每個(gè)客戶,都會(huì)迅速給予響應(yīng)。服務(wù)器同時(shí)處理的客戶連接數(shù)目越多,并且對(duì)每個(gè)客戶作出響應(yīng)的速度越快,就表明并發(fā)性能越高。用多個(gè)線程來同時(shí)為多個(gè)客戶提供服務(wù),這是提高服務(wù)器的并發(fā)性能的最常用的手段。本節(jié)將按照3種方式來重新實(shí)現(xiàn)EchoServer,它們都使用了多線程。l 為每個(gè)客戶分配一個(gè)工作線程。l 創(chuàng)建一個(gè)線程池,由其中的工作線程來為客戶服務(wù)。l 利用JDK的Java類庫中現(xiàn)成的線程池,由它的工作線程來為客戶服務(wù)。3.6.1 為每個(gè)客戶分配一個(gè)線程服務(wù)器的主線程負(fù)責(zé)接收客戶的連接,每次接收到一個(gè)客戶連接,就會(huì)創(chuàng)建一個(gè)工作線程,由它負(fù)責(zé)與客戶的通信。以下是EchoServer的service()方法的代碼:public void service() while (true) Socket socket=null; try socket = serverSocket.accept(); /接收客戶連接 Thread workThread=new Thread(new Handler(socket); /創(chuàng)建一個(gè)工作線程 workThread.start(); /啟動(dòng)工作線程 catch (IOException e) e.printStackTrace(); 以上工作線程workThread執(zhí)行Handler的run()方法。Handler類實(shí)現(xiàn)了Runnable接口,它的run()方法負(fù)責(zé)與單個(gè)客戶通信,與客戶通信結(jié)束后,就會(huì)斷開連接,執(zhí)行Handler的run()方法的工作線程也會(huì)自然終止。如例程3-5所示是EchoServer類及Handler類的源程序。例程3-5 EchoServer.java(為每個(gè)任務(wù)分配一個(gè)線程)package multithread1;import java.io.*;import .*;public class EchoServer private int port=8000; private ServerSocket serverSocket; public EchoServer() throws IOException serverSocket = new ServerSocket(port); System.out.println(服務(wù)器啟動(dòng)); public void service() while (true) Socket socket=null; try socket = serverSocket.accept(); /接收客戶連接 Thread workThread=new Thread(new Handler(socket); /創(chuàng)建一個(gè)工作線程 workThread.start(); /啟動(dòng)工作線程 catch (IOException e) e.printStackTrace(); public static void main(String args)throws IOException new EchoServer().service(); class Handler implements Runnable /負(fù)責(zé)與單個(gè)客戶的通信 private Socket socket; public Handler(Socket socket) this.socket=socket; private PrintWriter getWriter(Socket socket)throws IOException private BufferedReader getReader(Socket socket)throws IOException public String echo(String msg) public void run() try System.out.println(New connection accepted + socket.getInetAddress() + : +socket.getPort(); BufferedReader br =getReader(socket); PrintWriter pw = getWriter(socket);String msg = null; while (msg = br.readLine() != null) /接收和發(fā)送數(shù)據(jù),直到通信結(jié)束 System.out.println(msg); pw.println(echo(msg); if (msg.equals(bye) break; catch (IOException e) e.printStackTrace(); finally try if(socket!=null)socket.close(); /斷開連接 catch (IOException e) e.printStackTrace(); 3.6.2 創(chuàng)建線程池在3.6.1節(jié)介紹的實(shí)現(xiàn)方式中,對(duì)每個(gè)客戶都分配一個(gè)新的工作線程。當(dāng)工作線程與客戶通信結(jié)束,這個(gè)線程就被銷毀。這種實(shí)現(xiàn)方式有以下不足之處。l 服務(wù)器創(chuàng)建和銷毀工作線程的開銷(包括所花費(fèi)的時(shí)間和系統(tǒng)資源)很大。如果服務(wù)器需要與許多客戶通信,并且與每個(gè)客戶的通信時(shí)間都很短,那么有可能服務(wù)器為客戶創(chuàng)建新線程的開銷比實(shí)際與客戶通信的開銷還要大。l 除了創(chuàng)建和銷毀線程的開銷之外,活動(dòng)的線程也消耗系統(tǒng)資源。每個(gè)線程本身都會(huì)占用一定的內(nèi)存(每個(gè)線程需要大約1M內(nèi)存),如果同時(shí)有大量客戶連接服務(wù)器,就必須創(chuàng)建大量工作線程,它們消耗了大量?jī)?nèi)存,可能會(huì)導(dǎo)致系統(tǒng)的內(nèi)存空間不足。l 如果線程數(shù)目固定,并且每個(gè)線程都有很長(zhǎng)的生命周期,那么線程切換也是相對(duì)固定的。不同操作系統(tǒng)有不同的切換周期,一般在20毫秒左右。這里所說的線程切換是指在Java虛擬機(jī),以及底層操作系統(tǒng)的調(diào)度下,線程之間轉(zhuǎn)讓CPU的使用權(quán)。如果頻繁創(chuàng)建和銷毀線程,那么將導(dǎo)致頻繁地切換線程,因?yàn)橐粋€(gè)線程被銷毀后,必然要把CPU轉(zhuǎn)讓給另一個(gè)已經(jīng)就緒的線程,使該線程獲得運(yùn)行機(jī)會(huì)。在這種情況下,線程之間的切換不再遵循系統(tǒng)的固定切換周期,切換線程的開銷甚至比創(chuàng)建及銷毀線程的開銷還大。線程池為線程生命周期開銷問題和系統(tǒng)資源不足問題提供了解決方案。線程池中預(yù)先創(chuàng)建了一些工作線程,它們不斷從工作隊(duì)列中取出任務(wù),然后執(zhí)行該任務(wù)。當(dāng)

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論