




已閱讀5頁,還剩25頁未讀, 繼續(xù)免費閱讀
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)
文檔簡介
在客戶/服務(wù)器通信模式中,服務(wù)器端需要創(chuàng)建監(jiān)聽特定端口的ServerSocket,ServerSocket負(fù)責(zé)接收客戶連接請求。本章首先介紹ServerSocket類的各個構(gòu)造方法,以及成員方法的用法,接著介紹服務(wù)器如何用多線程來處理與多個客戶的通信任務(wù)。本章提供線程池的一種實現(xiàn)方式。線程池包括一個工作隊列和若干工作線程。服務(wù)器程序向工作隊列中加入與客戶通信的任務(wù),工作線程不斷從工作隊列中取出任務(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指定客戶連接請求隊列的長度,參數(shù)bindAddr指定服務(wù)器要綁定的IP地址。3.1.1 綁定端口除了第一個不帶參數(shù)的構(gòu)造方法以外,其他構(gòu)造方法都會使服務(wù)器與特定端口綁定,該端口由參數(shù)port指定。例如,以下代碼創(chuàng)建了一個與80端口綁定的服務(wù)器:ServerSocket serverSocket=new ServerSocket(80);如果運行時無法綁定到80端口,以上代碼會拋出IOException,更確切地說,是拋出BindException,它是IOException的子類。BindException一般是由以下原因造成的:l 端口已經(jīng)被其他服務(wù)器進程占用;l 在某些操作系統(tǒng)中,如果沒有以超級用戶的身份來運行服務(wù)器程序,那么操作系統(tǒng)不允許服務(wù)器綁定到11023之間的端口。如果把參數(shù)port設(shè)為0,表示由操作系統(tǒng)來為服務(wù)器分配一個任意可用的端口。由操作系統(tǒng)分配的端口也稱為匿名端口。對于多數(shù)服務(wù)器,會使用明確的端口,而不會使用匿名端口,因為客戶程序需要事先知道服務(wù)器的端口,才能方便地訪問服務(wù)器。在某些場合,匿名端口有著特殊的用途,本章3.4節(jié)會對此作介紹。3.1.2 設(shè)定客戶連接請求隊列的長度當(dāng)服務(wù)器進程運行時,可能會同時監(jiān)聽到多個客戶的連接請求。例如,每當(dāng)一個客戶進程執(zhí)行以下代碼:Socket socket=new Socket(,80);就意味著在遠(yuǎn)程主機的80端口上,監(jiān)聽到了一個客戶的連接請求。管理客戶連接請求的任務(wù)是由操作系統(tǒng)來完成的。操作系統(tǒng)把這些連接請求存儲在一個先進先出的隊列中。許多操作系統(tǒng)限定了隊列的最大長度,一般為50。當(dāng)隊列中的連接請求達到了隊列的最大容量時,服務(wù)器進程所在的主機會拒絕新的連接請求。只有當(dāng)服務(wù)器進程通過ServerSocket的accept()方法從隊列中取出連接請求,使隊列騰出空位時,隊列才能繼續(xù)加入新的連接請求。對于客戶進程,如果它發(fā)出的連接請求被加入到服務(wù)器的隊列中,就意味著客戶與服務(wù)器的連接建立成功,客戶進程從Socket構(gòu)造方法中正常返回。如果客戶進程發(fā)出的連接請求被服務(wù)器拒絕,Socket構(gòu)造方法就會拋出ConnectionException。ServerSocket構(gòu)造方法的backlog參數(shù)用來顯式設(shè)置連接請求隊列的長度,它將覆蓋操作系統(tǒng)限定的隊列的最大長度。值得注意的是,在以下幾種情況中,仍然會采用操作系統(tǒng)限定的隊列的最大長度:l backlog參數(shù)的值大于操作系統(tǒng)限定的隊列的最大長度;l backlog參數(shù)的值小于或等于0;l 在ServerSocket構(gòu)造方法中沒有設(shè)置backlog參數(shù)。以下例程3-1的Client.java和例程3-2的Server.java用來演示服務(wù)器的連接請求隊列的特性。例程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); /連接請求隊列的長度為3 System.out.println(服務(wù)器啟動); public void service() while (true) Socket socket=null; try socket = serverSocket.accept(); /從連接請求隊列中取出一個連接 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進行100次連接。在Server類中,把連接請求隊列的長度設(shè)為3。這意味著當(dāng)隊列中有了3個連接請求時,如果Client再請求連接,就會被Server拒絕。下面按照以下步驟運行Server和Client程序。(1)把Server類的main()方法中的“server.service();”這行程序代碼注釋掉。這使得服務(wù)器與8 000端口綁定后,永遠(yuǎn)不會執(zhí)行serverSocket.accept()方法。這意味著隊列中的連接請求永遠(yuǎn)不會被取出。先運行Server程序,然后再運行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個連接后,就無法再創(chuàng)建其余的連接了,因為服務(wù)器的隊列已經(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端口綁定后,就會在一個while循環(huán)中不斷執(zhí)行serverSocket.accept()方法,該方法從隊列中取出連接請求,使得隊列能及時騰出空位,以容納新的連接請求。先運行Server程序,然后再運行Client程序,Client程序的打印結(jié)果如下:第1次連接成功第2次連接成功第3次連接成功第100次連接成功從以上打印結(jié)果可以看出,此時Client能順利與Server建立100次連接。3.1.3 設(shè)定綁定的IP地址如果主機只有一個IP地址,那么默認(rèn)情況下,服務(wù)器程序就與該IP地址綁定。ServerSocket的第4個構(gòu)造方法ServerSocket(int port, int backlog, InetAddress bindAddr)有一個bindAddr參數(shù),它顯式指定服務(wù)器要綁定的IP地址,該構(gòu)造方法適用于具有多個IP地址的主機。假定一個主機有兩個網(wǎng)卡,一個網(wǎng)卡用于連接到Internet, IP地址為4,還有一個網(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有一個不帶參數(shù)的默認(rèn)構(gòu)造方法。通過該方法創(chuàng)建的ServerSocket不與任何端口綁定,接下來還需要通過bind()方法與特定端口綁定。這個默認(rèn)構(gòu)造方法的用途是,允許服務(wù)器在綁定到特定端口之前,先設(shè)置ServerSocket的一些選項。因為一旦服務(wù)器與特定端口綁定,有些選項就不能再改變了。在以下代碼中,先把ServerSocket的SO_REUSEADDR選項設(shè)為true,然后再把它與8000端口綁定:ServerSocket serverSocket=new ServerSocket();serverSocket.setReuseAddress(true); /設(shè)置ServerSocket的選項serverSocket.bind(new InetSocketAddress(8000); /與8000端口綁定如果把以上程序代碼改為:ServerSocket serverSocket=new ServerSocket(8000);serverSocket.setReuseAddress(true); /設(shè)置ServerSocket的選項那么serverSocket.setReuseAddress(true)方法就不起任何作用了,因為SO_ REUSEADDR選項必須在服務(wù)器綁定端口之前設(shè)置才有效。3.2 接收和關(guān)閉與客戶的連接ServerSocket的accept()方法從連接請求隊列中取出一個客戶的連接請求,然后創(chuàng)建與客戶連接的Socket對象,并將它返回。如果隊列中沒有連接請求,accept()方法就會一直等待,直到接收到了連接請求才返回。接下來,服務(wù)器從Socket對象中獲得輸入流和輸出流,就能與客戶交換數(shù)據(jù)。當(dāng)服務(wù)器正在進行發(fā)送數(shù)據(jù)的操作時,如果客戶端斷開了連接,那么服務(wù)器端會拋出一個IOException的子類SocketException異常:.SocketException: Connection reset by peer這只是服務(wù)器與單個客戶通信中出現(xiàn)的異常,這種異常應(yīng)該被捕獲,使得服務(wù)器能繼續(xù)與其他客戶通信。以下程序顯示了單線程服務(wù)器采用的通信流程:public void service() while (true) Socket socket=null;try socket = serverSocket.accept(); /從連接請求隊列中取出一個連接 System.out.println(New connection accepted + socket.getInetAddress() + : +socket.getPort(); /接收和發(fā)送數(shù)據(jù) catch (IOException e) /這只是與單個客戶通信時遇到的異常,可能是由于客戶端過早斷開連接引起的 /這種異常不應(yīng)該中斷整個while循環(huán) e.printStackTrace(); finally tryif(socket!=null)socket.close(); /與一個客戶通信結(jié)束后,要關(guān)閉Socket catch (IOException e) e.printStackTrace(); 與單個客戶通信的代碼放在一個try代碼塊中,如果遇到異常,該異常被catch代碼塊捕獲。try代碼塊后面還有一個finally代碼塊,它保證不管與客戶通信正常結(jié)束還是異常結(jié)束,最后都會關(guān)閉Socket,斷開與這個客戶的連接。3.3 關(guān)閉ServerSocketServerSocket的close()方法使服務(wù)器釋放占用的端口,并且斷開與所有客戶的連接。當(dāng)一個服務(wù)器程序運行結(jié)束時,即使沒有執(zhí)行ServerSocket的close()方法,操作系統(tǒng)也會釋放這個服務(wù)器占用的端口。因此,服務(wù)器程序并不一定要在結(jié)束之前執(zhí)行ServerSocket的close()方法。在某些情況下,如果希望及時釋放服務(wù)器的端口,以便讓其他程序能占用該端口,則可以顯式調(diào)用ServerSocket的close()方法。例如,以下代碼用于掃描165535之間的端口號。如果ServerSocket成功創(chuàng)建,意味著該端口未被其他服務(wù)器進程綁定,否者說明該端口已經(jīng)被其他進程占用:for(int port=1;portjava RandomPort監(jiān)聽的端口為:3000C:chapter03classesjava RandomPort監(jiān)聽的端口為:3004C:chapter03classesjava RandomPort監(jiān)聽的端口為:3005多數(shù)服務(wù)器會監(jiān)聽固定的端口,這樣才便于客戶程序訪問服務(wù)器。匿名端口一般適用于服務(wù)器與客戶之間的臨時通信,通信結(jié)束,就斷開連接,并且ServerSocket占用的臨時端口也被釋放。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使用兩個并行的TCP連接:一個是控制連接,一個是數(shù)據(jù)連接??刂七B接用于在客戶和服務(wù)器之間發(fā)送控制信息,如用戶名和口令、改變遠(yuǎn)程目錄的命令或上傳和下載文件的命令。數(shù)據(jù)連接用于傳送文件。TCP服務(wù)器在21端口上監(jiān)聽控制連接,如果有客戶要求上傳或下載文件,就另外建立一個數(shù)據(jù)連接,通過它來傳送文件。數(shù)據(jù)連接的建立有兩種方式。(1)如圖3-2所示,TCP服務(wù)器在20端口上監(jiān)聽數(shù)據(jù)連接,TCP客戶主動請求建立與該端口的連接。圖3-2 TCP服務(wù)器在20端口上監(jiān)聽數(shù)據(jù)連接(2)如圖3-3所示,首先由TCP客戶創(chuàng)建一個監(jiān)聽匿名端口的ServerSocket,再把這個ServerSocket監(jiān)聽的端口號(調(diào)用ServerSocket的getLocalPort()方法就能得到端口號)發(fā)送給TCP服務(wù)器,然后由TCP服務(wù)器主動請求建立與客戶端的連接。圖3-3 TCP客戶在匿名端口上監(jiān)聽數(shù)據(jù)連接以上第二種方式就使用了匿名端口,并且是在客戶端使用的,用于和服務(wù)器建立臨時的數(shù)據(jù)連接。在實際應(yīng)用中,在服務(wù)器端也可以使用匿名端口。3.5 ServerSocket選項ServerSocket有以下3個選項。l SO_TIMEOUT:表示等待客戶連接的超時時間。l SO_REUSEADDR:表示是否允許重用服務(wù)器所綁定的地址。l SO_RCVBUF:表示接收數(shù)據(jù)的緩沖區(qū)的大小。3.5.1 SO_TIMEOUT選項l 設(shè)置該選項:public void setSoTimeout(int timeout) throws SocketExceptionl 讀取該選項:public int getSoTimeout () throws IOExceptionSO_TIMEOUT表示ServerSocket的accept()方法等待客戶連接的超時時間,以毫秒為單位。如果SO_TIMEOUT的值為0,表示永遠(yuǎn)不會超時,這是SO_TIMEOUT的默認(rèn)值。當(dāng)服務(wù)器執(zhí)行ServerSocket的accept()方法時,如果連接請求隊列為空,服務(wù)器就會一直等待,直到接收到了客戶連接才從accept()方法返回。如果設(shè)定了超時時間,那么當(dāng)服務(wù)器等待的時間超過了超時時間,就會拋出SocketTimeoutException,它是InterruptedException的子類。如例程3-4所示的TimeoutTester把超時時間設(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); /等待客戶連接的時間不超過6秒 Socket socket=serverSocket.accept(); socket.close(); System.out.println(服務(wù)器關(guān)閉); 運行以上程序,過6秒鐘后,程序會從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)不會超時,它會一直等待下去,直到接收到了客戶的連接,才會從accept()方法返回。服務(wù)器執(zhí)行serverSocket.accept()方法時,等待客戶連接的過程也稱為阻塞。本書第4章的4.1節(jié)(線程阻塞的概念)詳細(xì)介紹了阻塞的概念。3.5.2 SO_REUSEADDR選項l 設(shè)置該選項:public void setResuseAddress(boolean on) throws SocketExceptionl 讀取該選項:public boolean getResuseAddress() throws SocketException這個選項與Socket的SO_REUSEADDR選項相同,用于決定如果網(wǎng)絡(luò)上仍然有數(shù)據(jù)向舊的ServerSocket傳輸數(shù)據(jù),是否允許新的ServerSocket綁定到與舊的ServerSocket同樣的端口上。SO_REUSEADDR選項的默認(rèn)值與操作系統(tǒng)有關(guān),在某些操作系統(tǒng)中,允許重用端口,而在某些操作系統(tǒng)中不允許重用端口。當(dāng)ServerSocket關(guān)閉時,如果網(wǎng)絡(luò)上還有發(fā)送到這個ServerSocket的數(shù)據(jù),這個ServerSocket不會立刻釋放本地端口,而是會等待一段時間,確保接收到了網(wǎng)絡(luò)上發(fā)送過來的延遲數(shù)據(jù),然后再釋放端口。許多服務(wù)器程序都使用固定的端口。當(dāng)服務(wù)器程序關(guān)閉后,有可能它的端口還會被占用一段時間,如果此時立刻在同一個主機上重啟服務(wù)器程序,由于端口已經(jīng)被占用,使得服務(wù)器程序無法綁定到該端口,服務(wù)器啟動失敗,并拋出BindException:Exception in thread main .BindException: Address already in use: JVM_Bind為了確保一個進程關(guān)閉了ServerSocket后,即使操作系統(tǒng)還沒釋放端口,同一個主機上的其他進程還可以立刻重用該端口,可以調(diào)用ServerSocket的setResuse- Address(true)方法:if(!serverSocket.getResuseAddress()serverSocket.setResuseAddress(true);值得注意的是,serverSocket.setResuseAddress(true)方法必須在ServerSocket還沒有綁定到一個本地端口之前調(diào)用,否則執(zhí)行serverSocket.setResuseAddress(true)方法無效。此外,兩個共用同一個端口的進程必須都調(diào)用serverSocket.setResuseAddress(true)方法,才能使得一個進程關(guān)閉ServerSocket后,另一個進程的ServerSocket還能夠立刻重用相同端口。3.5.3 SO_RCVBUF選項l 設(shè)置該選項:public void setReceiveBufferSize(int size) throws SocketExceptionl 讀取該選項: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ù)的效率。而對于交互式的通信(Telnet和網(wǎng)絡(luò)游戲),則應(yīng)該采用小的緩沖區(qū),確保能及時把小批量的數(shù)據(jù)發(fā)送給對方。SO_RCVBUF的默認(rèn)值與操作系統(tǒng)有關(guān)。例如,在Windows 2000中運行以下代碼時,顯示SO_RCVBUF的默認(rèn)值為8192:ServerSocket serverSocket=new ServerSocket(8000);System.out.println(serverSocket.getReceiveBufferSize(); /打印8192無論在ServerSocket綁定到特定端口之前或之后,調(diào)用setReceiveBufferSize()方法都有效。例外情況下是如果要設(shè)置大于64K的緩沖區(qū),則必須在ServerSocket綁定到特定端口之前進行設(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)于對所有由serverSocket.accept()方法返回的Socket設(shè)置接收數(shù)據(jù)的緩沖區(qū)的大小。3.5.4 設(shè)定連接時間、延遲和帶寬的相對重要性l public void setPerformancePreferences(int connectionTime,int latency,int bandwidth)該方法的作用與Socket的setPerformancePreferences()方法的作用相同,用于設(shè)定連接時間、延遲和帶寬的相對重要性,參見本書第2章的2.5.10節(jié)(設(shè)定連接時間、延遲和帶寬的相對重要性)。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接收到一個客戶連接,就與客戶進行通信,通信完畢后斷開連接,然后再接收下一個客戶連接。假如同時有多個客戶請求連接,這些客戶就必須排隊等候EchoServer的響應(yīng)。EchoServer無法同時與多個客戶通信。許多實際應(yīng)用要求服務(wù)器具有同時為多個客戶提供服務(wù)的能力。HTTP服務(wù)器就是最明顯的例子。任何時刻,HTTP服務(wù)器都可能接收到大量的客戶請求,每個客戶都希望能快速得到HTTP服務(wù)器的響應(yīng)。如果長時間讓客戶等待,會使網(wǎng)站失去信譽,從而降低訪問量??梢杂貌l(fā)性能來衡量一個服務(wù)器同時響應(yīng)多個客戶的能力。一個具有好的并發(fā)性能的服務(wù)器,必須符合兩個條件:l 能同時接收并處理多個客戶連接;l 對于每個客戶,都會迅速給予響應(yīng)。服務(wù)器同時處理的客戶連接數(shù)目越多,并且對每個客戶作出響應(yīng)的速度越快,就表明并發(fā)性能越高。用多個線程來同時為多個客戶提供服務(wù),這是提高服務(wù)器的并發(fā)性能的最常用的手段。本節(jié)將按照3種方式來重新實現(xiàn)EchoServer,它們都使用了多線程。l 為每個客戶分配一個工作線程。l 創(chuàng)建一個線程池,由其中的工作線程來為客戶服務(wù)。l 利用JDK的Java類庫中現(xiàn)成的線程池,由它的工作線程來為客戶服務(wù)。3.6.1 為每個客戶分配一個線程服務(wù)器的主線程負(fù)責(zé)接收客戶的連接,每次接收到一個客戶連接,就會創(chuàng)建一個工作線程,由它負(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)建一個工作線程 workThread.start(); /啟動工作線程 catch (IOException e) e.printStackTrace(); 以上工作線程workThread執(zhí)行Handler的run()方法。Handler類實現(xiàn)了Runnable接口,它的run()方法負(fù)責(zé)與單個客戶通信,與客戶通信結(jié)束后,就會斷開連接,執(zhí)行Handler的run()方法的工作線程也會自然終止。如例程3-5所示是EchoServer類及Handler類的源程序。例程3-5 EchoServer.java(為每個任務(wù)分配一個線程)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ù)器啟動); public void service() while (true) Socket socket=null; try socket = serverSocket.accept(); /接收客戶連接 Thread workThread=new Thread(new Handler(socket); /創(chuàng)建一個工作線程 workThread.start(); /啟動工作線程 catch (IOException e) e.printStackTrace(); public static void main(String args)throws IOException new EchoServer().service(); class Handler implements Runnable /負(fù)責(zé)與單個客戶的通信 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é)介紹的實現(xiàn)方式中,對每個客戶都分配一個新的工作線程。當(dāng)工作線程與客戶通信結(jié)束,這個線程就被銷毀。這種實現(xiàn)方式有以下不足之處。l 服務(wù)器創(chuàng)建和銷毀工作線程的開銷(包括所花費的時間和系統(tǒng)資源)很大。如果服務(wù)器需要與許多客戶通信,并且與每個客戶的通信時間都很短,那么有可能服務(wù)器為客戶創(chuàng)建新線程的開銷比實際與客戶通信的開銷還要大。l 除了創(chuàng)建和銷毀線程的開銷之外,活動的線程也消耗系統(tǒng)資源。每個線程本身都會占用一定的內(nèi)存(每個線程需要大約1M內(nèi)存),如果同時有大量客戶連接服務(wù)器,就必須創(chuàng)建大量工作線程,它們消耗了大量內(nèi)存,可能會導(dǎo)致系統(tǒng)的內(nèi)存空間不足。l 如果線程數(shù)目固定,并且每個線程都有很長的生命周期,那么線程切換也是相對固定的。不同操作系統(tǒng)有不同的切換周期,一般在20毫秒左右。這里所說的線程切換是指在Java虛擬機,以及底層操作系統(tǒng)的調(diào)度下,線程之間轉(zhuǎn)讓CPU的使用權(quán)。如果頻繁創(chuàng)建和銷毀線程,那么將導(dǎo)致頻繁地切換線程,因為一個線程被銷毀后,必然要把CPU轉(zhuǎn)讓給另一個已經(jīng)就緒的線程,使該線程獲得運行機會。在這種情況下,線程之間的切換不再遵循系統(tǒng)的固定切換周期,切換線程的開銷甚至比創(chuàng)建及銷毀線程的開銷還大。線程池為線程生命周期開銷問題和系統(tǒng)資源不足問題提供了解決方案。線程池中預(yù)先創(chuàng)建了一些工作線程,它們不斷從工作隊列中取出任務(wù),然后執(zhí)行該任務(wù)。當(dāng)
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 高一第3章數(shù)學(xué)試卷
- 汾陽6年級數(shù)學(xué)試卷
- 肋骨骨折術(shù)后護理
- 2024年11月浙江海鹽湖商村鎮(zhèn)銀行股份有限公司招考人員筆試歷年參考題庫附帶答案詳解
- 2025至2030乘用車行業(yè)發(fā)展趨勢分析與未來投資戰(zhàn)略咨詢研究報告
- 2024年南充市順慶區(qū)和平路街道社區(qū)衛(wèi)生服務(wù)中心招聘筆試真題
- 2025至2030草藥和有機睫毛膏行業(yè)市場深度研究與戰(zhàn)略咨詢分析報告
- 福清市初三數(shù)學(xué)試卷
- 分?jǐn)?shù)乘法五下數(shù)學(xué)試卷
- 高考新教材數(shù)學(xué)試卷
- 社會語言學(xué)視角下網(wǎng)絡(luò)流行用語研究
- 《拍賣概論》考試題庫(精煉版)
- DL-T5434-2021電力建設(shè)工程監(jiān)理規(guī)范
- 設(shè)計投標(biāo)服務(wù)方案
- “一帶一路”倡議與國際合作課件
- 貨物供應(yīng)方案及運輸方案
- 中醫(yī)養(yǎng)生健康小妙招的課件
- 拉鏈采購合同
- 紀(jì)檢監(jiān)察大數(shù)據(jù)平臺建設(shè)方案
- 09J202-1 坡屋面建筑構(gòu)造(一)-2
- 2024年山東兗礦能源集團股份有限公司招聘筆試參考題庫含答案解析
評論
0/150
提交評論