對(duì)于即時(shí)類應(yīng)用或者即時(shí)類的游戲吗购,HTTP協(xié)議很多時(shí)候無法滿足于我們的需求。這會(huì)砸狞,Socket對(duì)于我們來說就非常實(shí)用了捻勉。下面是本次學(xué)習(xí)的筆記。主要分異常類型刀森、交互原理踱启、Socket、ServerSocket研底、多線程這幾個(gè)方面闡述。
異常類型
在了解Socket的內(nèi)容之前斩郎,先要了解一下涉及到的一些異常類型窿侈。以下四種類型都是繼承于IOException跺讯,所以很多之后直接彈出IOException即可暂雹。
UnkownHostException:? ? 主機(jī)名字或IP錯(cuò)誤
ConnectException:? ? 服務(wù)器拒絕連接现斋、服務(wù)器沒有啟動(dòng)倦西、(超出隊(duì)列數(shù),拒絕連接)
SocketTimeoutException:? ? ??連接超時(shí)
BindException:? ? Socket對(duì)象無法與制定的本地IP地址或端口綁定
交互過程
Socket與ServerSocket的交互程剥,下面的圖片我覺得已經(jīng)說的很詳細(xì)很清楚了劝枣。
在客戶/服務(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)造ServerSocket
ServerSocket的構(gòu)造方法有以下幾種重載形式:
◆ServerSocket()throws IOException
◆ServerSocket(int port) throws IOException
◆ServerSocket(int port, int backlog) throws IOException
◆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ù)器:
[java]view plaincopy?
?
ServerSocket?serverSocket=new?ServerSocket(80);??
◆端口已經(jīng)被其他服務(wù)器進(jìn)程占用沙廉;如果運(yùn)行時(shí)無法綁定到80端口拘荡,以上代碼會(huì)拋出IOException,更確切地說撬陵,是拋出BindException珊皿,它是IOException的子類。BindException一般是由以下原因造成的:
◆在某些操作系統(tǒng)中巨税,如果沒有以超級(jí)用戶的身份來運(yùn)行服務(wù)器程序蟋定,那么操作系統(tǒng)不允許服務(wù)器綁定到1~1023之間的端口。
如果把參數(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í)行以下代碼:
[java]view plaincopy?
?
Socket?socket=new?Socket(www.javathinker.org,80);??
就意味著在遠(yuǎn)程www.javathinker.org主機(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)度:
◆backlog參數(shù)的值大于操作系統(tǒng)限定的隊(duì)列的最大長(zhǎng)度;
◆backlog參數(shù)的值小于或等于0;
◆在ServerSocket構(gòu)造方法中沒有設(shè)置backlog參數(shù)。
以下例程3-1的Client.java和例程3-2的Server.java用來演示服務(wù)器的連接請(qǐng)求隊(duì)列的特性。
例程3-1? Client.java
[java]view plaincopy?
?
import?java.net.*;??
public?class?Client?{??
public?static?void?main(String?args[])throws?Exception{??
final?int?length=100;??
String?host="localhost";??
int?port=8000;??
Socket[]?sockets=new?Socket[length];??
for(int?i=0;i
sockets[i]=new?Socket(host,?port);??
System.out.println("第"+(i+1)+"次連接成功");??
????}??
Thread.sleep(3000);??
for(int?i=0;i
sockets[i].close();//斷開連接??
????}???
??}??
}??
[java]view plaincopy?
?
import?java.io.*;??
import?java.net.*;??
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();??
??}??
}??
例程3-2? Server.java
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ù)器與8000端口綁定后愿待,永遠(yuǎn)不會(huì)執(zhí)行serverSocket.accept()方法鸳君。這意味著隊(duì)列中的連接請(qǐng)求永遠(yuǎn)不會(huì)被取出。先運(yùn)行Server程序,然后再運(yùn)行Client程序系草,Client程序的打印結(jié)果如下:
[java]view plaincopy?
?
第1次連接成功??
第2次連接成功??
第3次連接成功??
Exception?in?thread"main"?java.net.ConnectException:?Connection?refused:?connect??????????
????????at?java.net.PlainSocketImpl.socketConnect(Native?Method)??
????????at?java.net.PlainSocketImpl.doConnect(Unknown?Source)??
????????at?java.net.PlainSocketImpl.connectToAddress(Unknown?Source)??
????????at?java.net.PlainSocketImpl.connect(Unknown?Source)??
????????at?java.net.SocksSocketImpl.connect(Unknown?Source)??
????????at?java.net.Socket.connect(Unknown?Source)??
????????at?java.net.Socket.connect(Unknown?Source)??
????????at?java.net.Socket.(Unknown?Source)??
????????at?java.net.Socket.(Unknown?Source)??
at?Client.main(Client.java:10)??
(2)把Server類的main()方法按如下方式修改:從以上打印結(jié)果可以看出啰扛,Client與Server在成功地建立了3個(gè)連接后煞茫,就無法再創(chuàng)建其余的連接了钦扭,因?yàn)榉?wù)器的隊(duì)列已經(jīng)滿了膀斋。
[java]view plaincopy?
?
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é)果如下:
[java]view plaincopy?
?
第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地址為222.67.5.94庵朝,還有一個(gè)網(wǎng)卡用于連接到本地局域網(wǎng),IP地址為192.168.3.4等缀。如果服務(wù)器僅僅被本地局域網(wǎng)中的客戶訪問氧急,那么可以按如下方式創(chuàng)建ServerSocket:
[java]view plaincopy?
?
ServerSocket?serverSocket=new?ServerSocket(8000,10,InetAddress.getByName?("192.168.3.4"));??
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端口綁定:
[java]view plaincopy?
?
ServerSocket?serverSocket=new?ServerSocket();??
serverSocket.setReuseAddress(true);??????//設(shè)置ServerSocket的選項(xiàng)??
serverSocket.bind(new?InetSocketAddress(8000));???//與8000端口綁定??
如果把以上程序代碼改為:
[java]view plaincopy?
?
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異常:
[java]view plaincopy?
?
java.net.SocketException:?Connection?reset?by?peer??
這只是服務(wù)器與單個(gè)客戶通信中出現(xiàn)的異常猾蒂,這種異常應(yīng)該被捕獲仰迁,使得服務(wù)器能繼續(xù)與其他客戶通信充坑。
以下程序顯示了單線程服務(wù)器采用的通信流程:
[java]view plaincopy?
?
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?{??
try{??
if(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)閉ServerSocket
ServerSocket的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()方法持舆。例如,以下代碼用于掃描1~65535之間的端口號(hào)伪窖。如果ServerSocket成功創(chuàng)建逸寓,意味著該端口未被其他服務(wù)器進(jìn)程綁定,否者說明該端口已經(jīng)被其他進(jìn)程占用:
[java]view plaincopy?
?
for(int?port=1;port<=65535;port++){??
try{??
ServerSocket?serverSocket=new?ServerSocket(port);??
serverSocket.close();//及時(shí)關(guān)閉ServerSocket??
}catch(IOException?e){??
System.out.println("端口"+port+"?已經(jīng)被其他服務(wù)器進(jìn)程占用");??
}??
}??
以上程序代碼創(chuàng)建了一個(gè)ServerSocket對(duì)象后覆山,就馬上關(guān)閉它竹伸,以便及時(shí)釋放它占用的端口,從而避免程序臨時(shí)占用系統(tǒng)的大多數(shù)端口汹买。
ServerSocket的isClosed()方法判斷ServerSocket是否關(guān)閉,只有執(zhí)行了ServerSocket的close()方法聊倔,isClosed()方法才返回true晦毙;否則,即使ServerSocket還沒有和特定端口綁定耙蔑,isClosed()方法也會(huì)返回false见妒。
ServerSocket的isBound()方法判斷ServerSocket是否已經(jīng)與一個(gè)端口綁定,只要ServerSocket已經(jīng)與一個(gè)端口綁定甸陌,即使它已經(jīng)被關(guān)閉须揣,isBound()方法也會(huì)返回true盐股。
如果需要確定一個(gè)ServerSocket已經(jīng)與特定端口綁定,并且還沒有被關(guān)閉耻卡,則可以采用以下方式:
[java]view plaincopy?
?
boolean?isOpen=serverSocket.isBound()?&&?!serverSocket.isClosed();??
3.4? 獲取ServerSocket的信息
ServerSocket的以下兩個(gè)get方法可分別獲得服務(wù)器綁定的IP地址疯汁,以及綁定的端口:
◆public InetAddress getInetAddress()
◆public int getLocalPort()
前面已經(jīng)講到,在構(gòu)造ServerSocket時(shí)卵酪,如果把端口設(shè)為0幌蚊,那么將由操作系統(tǒng)為服務(wù)器分配一個(gè)端口(稱為匿名端口),程序只要調(diào)用getLocalPort()方法就能獲知這個(gè)端口號(hào)溃卡。如例程3-3所示的RandomPort創(chuàng)建了一個(gè)ServerSocket溢豆,它使用的就是匿名端口。
#p#
例程3-3? RandomPort.java
[java]view plaincopy?
?
import?java.io.*;??
import?java.net.*;??
public?class?RandomPort{??
public?static?void?main(String?args[])throws?IOException{??
ServerSocket?serverSocket=new?ServerSocket(0);??
System.out.println("監(jiān)聽的端口為:"+serverSocket.getLocalPort());???????
??}??
}??
多次運(yùn)行RandomPort程序瘸羡,可能會(huì)得到如下運(yùn)行結(jié)果:
[java]view plaincopy?
?
C:\chapter03\classes>java?RandomPort??
監(jiān)聽的端口為:3000??
C:\chapter03\classes>java?RandomPort??
監(jiān)聽的端口為:3004??
C:\chapter03\classes>java?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ù)連接∥砉祝控制連接用于在客戶和服務(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)消返。
◆SO_TIMEOUT:表示等待客戶連接的超時(shí)時(shí)間载弄。
◆SO_REUSEADDR:表示是否允許重用服務(wù)器所綁定的地址。
◆SO_RCVBUF:表示接收數(shù)據(jù)的緩沖區(qū)的大小撵颊。
3.5.1? SO_TIMEOUT選項(xiàng)
◆設(shè)置該選項(xiàng):public void setSoTimeout(int timeout) throws SocketException
◆讀取該選項(xiàng):public int getSoTimeout () throws IOException
SO_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秒鐘。
#p#
例程3-4? TimeoutTester.java
[java]view plaincopy?
?
import?java.io.*;??
import?java.net.*;??
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:
[java]view plaincopy?
?
C:\chapter03\classes>java?TimeoutTester??
Exception?in?thread"main"?java.net.SocketTimeoutException:?Accept?timed?out????????
????????at?java.net.PlainSocketImpl.socketAccept(Native?Method)??
????????at?java.net.PlainSocketImpl.accept(Unknown?Source)??
????????at?java.net.ServerSocket.implAccept(Unknown?Source)??
????????at?java.net.ServerSocket.accept(Unknown?Source)??
at?TimeoutTester.main(TimeoutTester.java:8)??
如果把程序中的“serverSocket.setSoTimeout(6000)”注釋掉,那么serverSocket. accept()方法永遠(yuǎn)不會(huì)超時(shí)序仙,它會(huì)一直等待下去突颊,直到接收到了客戶的連接,才會(huì)從accept()方法返回潘悼。
Tips:服務(wù)器執(zhí)行serverSocket.accept()方法時(shí)律秃,等待客戶連接的過程也稱為阻塞。本書第4章的4.1節(jié)(線程阻塞的概念)詳細(xì)介紹了阻塞的概念治唤。
3.5.2? SO_REUSEADDR選項(xiàng)
◆設(shè)置該選項(xiàng):public void setResuseAddress(boolean on) throws SocketException
◆讀取該選項(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:
[java]view plaincopy?
?
Exception?in?thread?"main"?java.net.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)方法:
[java]view plaincopy?
?
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)
◆設(shè)置該選項(xiàng):public void setReceiveBufferSize(int size) throws SocketException
◆讀取該選項(xiàng):public int getReceiveBufferSize() throws SocketException
SO_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:
[java]view plaincopy?
?
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:
[java]view plaincopy?
?
ServerSocket?serverSocket=new?ServerSocket();??
int?size=serverSocket.getReceiveBufferSize();??
if(size<131072)?serverSocket.setReceiveBufferSize(131072);??//把緩沖區(qū)的大小設(shè)為128K????????
serverSocket.bind(new?InetSocketAddress(8000));?????//與8000端口綁定??
3.5.4? 設(shè)定連接時(shí)間、延遲和帶寬的相對(duì)重要性執(zhí)行serverSocket.setReceiveBufferSize()方法俱尼,相當(dāng)于對(duì)所有由serverSocket.accept()方法返回的Socket設(shè)置接收數(shù)據(jù)的緩沖區(qū)的大小抖单。
◆public void setPerformancePreferences(int connectionTime,int latency,int bandwidth)
該方法的作用與Socket的setPerformancePreferences()方法的作用相同,用于設(shè)定連接時(shí)間遇八、延遲和帶寬的相對(duì)重要性矛绘。
轉(zhuǎn)載自http://www.51cto.com/specbook/11/40196.htm