JAVA套接字之TCP編程
1 TCP協(xié)議
TCP是面向諒解的協(xié)議笆呆。所謂連接衷戈,就是兩個(gè)對(duì)等實(shí)體為進(jìn)行數(shù)據(jù)通信而進(jìn)行的一種結(jié)合。面向連接服務(wù)是在數(shù)據(jù)交換之前稠通,必須先建立連接礁遵。當(dāng)數(shù)據(jù)交換結(jié)束后,則應(yīng)終止這個(gè)連接采记。
面向連接服務(wù)具有:連接建立佣耐、數(shù)據(jù)傳輸和連接釋放這三個(gè)階段。在傳送數(shù)據(jù)時(shí)是按序傳送的唧龄。
當(dāng)一臺(tái)計(jì)算機(jī)需要與另一臺(tái)遠(yuǎn)程計(jì)算機(jī)連接時(shí)兼砖,TCP協(xié)議會(huì)讓他們建立一個(gè)連接:用于發(fā)送和接收數(shù)據(jù)的虛擬鏈路奸远。TCP協(xié)議負(fù)責(zé)收集信息包,并將其按適當(dāng)?shù)拇涡蚍藕脗魉头硇诮邮斩耸盏胶笤賹⑵湔_的還原懒叛。為了保證數(shù)據(jù)包在傳送中準(zhǔn)確無(wú)誤,TCP使用了重發(fā)機(jī)制:當(dāng)一個(gè)通信實(shí)體發(fā)送一個(gè)消息給另一個(gè)通信實(shí)體后需要收到另一個(gè)實(shí)體的確認(rèn)信息耽梅,如果沒有收到確認(rèn)信息薛窥,則會(huì)再次重發(fā)剛才發(fā)送的信息。
TCP通信分為客戶端和服務(wù)器端眼姐,對(duì)應(yīng)的對(duì)象是分別是Socket和ServerSocket诅迷。
Socket類是java執(zhí)行客戶端TCP操作的基礎(chǔ)類,這個(gè)類本身使用代碼通過主機(jī)操作系統(tǒng)的本地TCP棧進(jìn)行通信众旗。Socket類的方法會(huì)建立和銷毀連接罢杉,設(shè)置各種Socket選項(xiàng)。
ServerSocket類是java執(zhí)行服務(wù)器端操作的基礎(chǔ)類贡歧,該類運(yùn)行于服務(wù)器滩租,監(jiān)聽入棧TCP連接,每個(gè)socket服務(wù)器監(jiān)聽服務(wù)器的某個(gè)端口利朵,當(dāng)遠(yuǎn)程主機(jī)的客戶端嘗試連接此端口時(shí)律想,服務(wù)器就被喚醒,并返回一個(gè)表示兩臺(tái)主機(jī)之間socket的正常的Socket對(duì)象绍弟。
ServerSocket和Socket通信流程:
2 ServerSocket
2.1 構(gòu)造函數(shù)
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地址姥份。
2.1.1 綁定端口
除了第一個(gè)不帶參數(shù)的構(gòu)造方法以外郭脂,其他構(gòu)造方法都會(huì)使服務(wù)器與特定的端口綁定年碘,該端口由參數(shù)port指定。例如展鸡,ServerSocket serverSocket = new SererSocket(80);
如果運(yùn)行時(shí)無(wú)法綁定到80端口屿衅,以上代碼會(huì)拋出IOException,更確切地說莹弊,是拋出BindException涤久,它是IOException的子類。BindException一般由以下原因造成:
- 端口已經(jīng)被其他服務(wù)器進(jìn)程占用忍弛;
- 在某些操作系統(tǒng)中响迂,如果沒有以超級(jí)管理員用戶身份來(lái)運(yùn)行服務(wù)器程序,那么操作系統(tǒng)不允許服務(wù)器綁定到1~1023之間的端口细疚。
- 如果把port設(shè)置為0蔗彤,表示由操作系統(tǒng)來(lái)為服務(wù)器分配一個(gè)任意可用的端口。由操作系統(tǒng)分配的端口也稱為匿名端口。對(duì)于多數(shù)服務(wù)器然遏,會(huì)使用明確的端口贫途,而不會(huì)使用匿名端口,因?yàn)榭蛻舫绦蛐枰孪戎婪?wù)器的端口待侵,才能方便的訪問服務(wù)器丢早。在某些場(chǎng)合,匿名端口有著特殊的用途秧倾。
2.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 sock = new Socket("192.168.32.105",80);
就意味著在遠(yuǎn)程主機(jī)的80端口上凫碌,監(jiān)聽到一個(gè)客戶端的連接請(qǐng)求。管理客戶端請(qǐng)求的任務(wù)是由操作系統(tǒng)完成的胃榕。操作系統(tǒng)把這些請(qǐng)求連接存儲(chǔ)在一個(gè)先入先出的隊(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ù)用來(lái)顯式設(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)造方法中沒有指定backlog俯萎。
2.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:
ServerSocket serverSocket = new ServerSocket(80,10,InetAddress.getByName ("192.168.3.4"));
2.1.4 默認(rèn)構(gòu)造方法的作用
ServerSocket有一個(gè)不帶參數(shù)的默認(rèn)構(gòu)造方法跷叉。通過該方法創(chuàng)建的ServerSocket不與任何端口綁定,接下來(lái)還需要通過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è)置才有效赂弓。
2.2 接收和關(guān)閉與客戶的連接
ServerSocket的accept()方法從連接請(qǐng)求隊(duì)列中取出一個(gè)客戶的連接請(qǐng)求绑榴,然后創(chuàng)建與客戶連接的Socket對(duì)象,并將它返回盈魁。如果隊(duì)列中沒有連接請(qǐng)求翔怎,accept()方法就會(huì)一直等待,直到接收到了連接請(qǐng)求才返回杨耙。
接下來(lái)赤套,服務(wù)器從Socket對(duì)象中獲得輸入流和輸出流,就能與客戶交換數(shù)據(jù)珊膜。當(dāng)服務(wù)器正在進(jìn)行發(fā)送數(shù)據(jù)的操作時(shí)容握,如果客戶端斷開了連接,那么服務(wù)器端會(huì)拋出一個(gè)IOException的子類SocketException異常:
java.net.SocketException: Connection reset by peer
這只是服務(wù)器與單個(gè)客戶通信中出現(xiàn)的異常辅搬,這種異常應(yīng)該被捕獲唯沮,使得服務(wù)器能繼續(xù)與其他客戶通信脖旱。
2.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ù)器的端口,以便讓其他程序占用這個(gè)端口彭则,則可以顯式調(diào)用close()方法鳍刷。例如以下代碼用于掃描1~65535之間的端口號(hào)。如果ServerSocket成功創(chuàng)建俯抖,意味著該端口未被其他服務(wù)器進(jìn)程綁定输瓜,否者說明該端口已經(jīng)被其他進(jìn)程占用:
for(int i = 1; i < 65535; i++){
try{
ServerSocket serverSocket = new ServerSocket(i);
serverSocket.close();
}catch(Exception e){
System.out.println("端口" + i + "已經(jīng)被其他服務(wù)器進(jìn)程占用");
}
}
以上程序代碼創(chuàng)建了一個(gè)ServerSocket對(duì)象后,就馬上關(guān)閉它芬萍,以便及時(shí)釋放它占用的端口尤揣,從而避免程序臨時(shí)占用系統(tǒng)的大多數(shù)端口。
ServerSocket的isClose()方法判斷ServerSocket是否關(guān)閉柬祠,只有執(zhí)行了ServerSocket的close()方法北戏,isClose()方法的返回值為true,即使ServerSocket還沒有和特定的端口綁定漫蛔,isClose()方法的返回值也是false嗜愈。
ServerSocket的isBound()方法判斷ServerSocket是否已經(jīng)與一個(gè)端口綁定,只要ServerSocket已經(jīng)與一個(gè)端口綁定莽龟,即使它已經(jīng)被關(guān)閉芝硬,isBound()方法也會(huì)返回true。
如果要確定一個(gè)ServerSocket已經(jīng)與特定端口綁定轧房,并且還沒有被關(guān)閉拌阴,則可以采用以下方式:
boolean isOpen = serverSocket.isBound() && !serverSocket.isClosed();
2.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,它使用的就是匿名端口捺信。
多數(shù)服務(wù)器會(huì)監(jiān)聽固定的端口酌媒,這樣才便于客戶程序訪問服務(wù)器。匿名端口一般適用于服務(wù)器與客戶之間的臨時(shí)通信迄靠,通信結(jié)束秒咨,就斷開連接,并且ServerSocket占用的臨時(shí)端口也被釋放掌挚。
3 Socket
3.1 構(gòu)造函數(shù)
Socket()
Socket(InetAddress address, int port)throws UnknownHostException, IOException
Socket(InetAddress address, int port, InetAddress localAddress, int localPort)throws IOException
Socket(String host, int port)throws UnknownHostException, IOException
Socket(String host, int port, InetAddress localAddress, int localPort)throws IOException
除了第一種不帶參數(shù)以外雨席,其他構(gòu)造函數(shù)會(huì)嘗試建立與服務(wù)器的連接。如果失敗會(huì)拋出IOException錯(cuò)誤吠式,如果成功則返回Socket對(duì)象陡厘。
InetAddress是一個(gè)用于記錄主機(jī)的類抽米,其靜態(tài)getHostByName(String msg)可以返回一個(gè)實(shí)例,其靜態(tài)方法getLocalHost()也可以獲得當(dāng)前主機(jī)的IP地址糙置,并返回一個(gè)實(shí)例云茸。Socket(String host, int port, InetAddress localAddress, int localPort)構(gòu)造函數(shù)的參數(shù)分別為目標(biāo)IP、目標(biāo)端口谤饭、綁定本地IP查辩、綁定本地端口。
3.2 Socket方法
getInetAddress(); 遠(yuǎn)程服務(wù)端的IP地址
getPort(); 遠(yuǎn)程服務(wù)端的端口
getLocalAddress() 本地客戶端的IP地址
getLocalPort() 本地客戶端的端口
getInputStream(); 獲得輸入流
getOutStream(); 獲得輸出流
3.3 Socket狀態(tài)
isClosed(); //連接是否已關(guān)閉网持,若關(guān)閉宜岛,返回true;否則返回false
isConnect(); //如果曾經(jīng)連接過功舀,返回true萍倡;否則返回false
isBound(); //如果Socket已經(jīng)與本地一個(gè)端口綁定,返回true辟汰;否則返回false
如果要確認(rèn)Socket的狀態(tài)是否處于連接中列敲,可以通過下面語(yǔ)句。
//判斷當(dāng)前是否處于連接
boolean isConnection = socket.isConnedted() && !socket.isClosed();
參考博文
http://www.cnblogs.com/xujian2014/p/4660570.html