JAVA套接字編程之TCP編程

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)信息,如果沒(méi)有收到確認(rèn)信息,則會(huì)再次重發(fā)剛才發(fā)送的信息。

TCP通信分為客戶端和服務(wù)器端,對(duì)應(yīng)的對(duì)象是分別是Socket和ServerSocket。

Socket類是java執(zhí)行客戶端TCP操作的基礎(chǔ)類,這個(gè)類本身使用代碼通過(guò)主機(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,更確切地說(shuō),是拋出BindException,它是IOException的子類。BindException一般由以下原因造成:

  • 端口已經(jīng)被其他服務(wù)器進(jìn)程占用;
  • 在某些操作系統(tǒng)中,如果沒(méi)有以超級(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èn)服務(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)程通過(guò)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)造方法中沒(méi)有指定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)中的客戶訪問(wèn),那么可以按如下方式創(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)造方法。通過(guò)該方法創(chuàng)建的ServerSocket不與任何端口綁定,接下來(lái)還需要通過(guò)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ì)列中沒(méi)有連接請(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í),即使沒(méi)有執(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)程綁定,否者說(shuō)明該端口已經(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還沒(méi)有和特定的端口綁定,isClose()方法的返回值也是false。

ServerSocket的isBound()方法判斷ServerSocket是否已經(jīng)與一個(gè)端口綁定,只要ServerSocket已經(jīng)與一個(gè)端口綁定,即使它已經(jīng)被關(guān)閉,isBound()方法也會(huì)返回true。

如果要確定一個(gè)ServerSocket已經(jīng)與特定端口綁定,并且還沒(méi)有被關(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è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)連接過(guò),返回true;否則返回false
isBound();            //如果Socket已經(jīng)與本地一個(gè)端口綁定,返回true;否則返回false

如果要確認(rèn)Socket的狀態(tài)是否處于連接中,可以通過(guò)下面語(yǔ)句。

//判斷當(dāng)前是否處于連接
boolean isConnection = socket.isConnedted() && !socket.isClosed();

參考博文

http://www.cnblogs.com/xujian2014/p/4660570.html

http://www.51cto.com/specbook/11/40196.htm

http://www.cnblogs.com/rond/p/3565113.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,615評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,826評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,227評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,447評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,992評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,807評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,001評(píng)論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評(píng)論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,243評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評(píng)論 1 287
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,709評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,996評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容