一区二区三区电影_国产伦精品一区二区三区视频免费_亚洲欧美国产精品va在线观看_国产精品一二三四

聯(lián)系我們 - 廣告服務(wù) - 聯(lián)系電話:
您的當(dāng)前位置: > 關(guān)注 > > 正文

java Socket用法詳解 Socket的構(gòu)造方法有以下幾種重載形式

來源:CSDN 時(shí)間:2022-12-23 09:58:57

在客戶/服務(wù)器通信模式中, 客戶端需要主動(dòng)創(chuàng)建與服務(wù)器連接的 Socket(套接字), 服務(wù)器端收到了客戶端的連接請(qǐng)求, 也會(huì)創(chuàng)建與客戶連接的 Socket. Socket可看做是通信連接兩端的收發(fā)器, 服務(wù)器與客戶端都通過 Socket 來收發(fā)數(shù)據(jù).

這篇文章首先介紹Socket類的各個(gè)構(gòu)造方法, 以及成員方法的用法, 接著介紹 Socket的一些選項(xiàng)的作用, 這些選項(xiàng)可控制客戶建立與服務(wù)器的連接, 以及接收和發(fā)送數(shù)據(jù)的行為.

一. 構(gòu)造Socket


(資料圖片僅供參考)

Socket的構(gòu)造方法有以下幾種重載形式:

Socket()  Socket(InetAddress address, int port) throws UnknowHostException, IOException  Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException  Socket(String host, int port) throws UnknowHostException, IOException  Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException  除了第一個(gè)不帶參數(shù)的構(gòu)造方法以外, 其他構(gòu)造方法都會(huì)試圖建立與服務(wù)器的連接, 如果連接成功, 就返回 Socket對(duì)象; 如果因?yàn)槟承┰蜻B接失敗, 就會(huì)拋出IOException .

1.1 使用無(wú)參數(shù)構(gòu)造方法, 設(shè)定等待建立連接的超時(shí)時(shí)間

Socket socket = new Socket(); SocketAddress remoteAddr = new InetSocketAddress("localhost",8000); socket.connect(remoteAddr, 60000); //等待建立連接的超時(shí)時(shí)間為1分鐘

以上代碼用于連接到本地機(jī)器上的監(jiān)聽8000端口的服務(wù)器程序, 等待連接的最長(zhǎng)時(shí)間為1分鐘. 如果在1分鐘內(nèi)連接成功則connet()方法順利返回; 如果在1分鐘內(nèi)出現(xiàn)某種異常, 則拋出該異常; 如果超過1分鐘后, 即沒有連接成功, 也沒有出現(xiàn)其他異常, 那么會(huì)拋出 SocketTimeoutException. Socket 類的 connect(SocketAddress endpoint, int timeout) 方法負(fù)責(zé)連接服務(wù)器, 參數(shù)endpoint 指定服務(wù)器的地址, 參數(shù)timeout 設(shè)定超時(shí)數(shù)據(jù), 以毫秒為單位. 如果參數(shù)timeout 設(shè)為0, 表示永遠(yuǎn)不會(huì)超時(shí), 默認(rèn)是不會(huì)超時(shí)的.

1.2 設(shè)定服務(wù)器的地址

除了第一個(gè)不帶參數(shù)的構(gòu)造方法, 其他構(gòu)造方法都需要在參數(shù)中設(shè)定服務(wù)器的地址, 包括服務(wù)器的IP地址或主機(jī)名, 以及端口:

Socket(InetAddress address, int port)              //第一個(gè)參數(shù)address 表示主機(jī)的IP地址 Socket(String host, int port)                              //第一個(gè)參數(shù)host 表示主機(jī)的名字

InetAddress 類表示服務(wù)器的IP地址, InetAddress 類提供了一系列靜態(tài)工廠方法, 用于構(gòu)造自身的實(shí)例, 例如:

//返回本地主機(jī)的IP地址 InetAddress addr1 = InetAddress.getLocalHost(); //返回代表 "222.34.5.7"的 IP地址 InetAddress addr2 = InetAddress.getByName("222.34.5.7"); //返回域名為"www.javathinker.org"的 IP地址 InetAddress addr3 = InetAddress.getByName("www.javathinker.org");

1.3 設(shè)定客戶端的地址

在一個(gè)Socket 對(duì)象中, 即包含遠(yuǎn)程服務(wù)器的IP 地址和端口信息, 也包含本地客戶端的IP 地址和端口信息. 默認(rèn)情況下, 客戶端的IP 地址來自于客戶程序所在的主機(jī), 客戶端的端口則由操作系統(tǒng)隨機(jī)分配. Socket類還有兩個(gè)構(gòu)造方法允許顯式地設(shè)置客戶端的IP 地址和端口:

//參數(shù)localAddr 和 localPort 用來設(shè)置客戶端的IP 地址和端口 Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException

如果一個(gè)主機(jī)同時(shí)屬于兩個(gè)以上的網(wǎng)絡(luò), 它就可能擁有兩個(gè)以上的IP 地址. 例如, 一個(gè)主機(jī)在Internet 網(wǎng)絡(luò)中的IP 地址為 "222.67.1.34", 在一個(gè)局域網(wǎng)中的IP 地址為 "112.5.4.3". 假定這個(gè)主機(jī)上的客戶程序希望和同一個(gè)局域網(wǎng)的一個(gè)服務(wù)器程序(地址為:"112.5.4.45: 8000")通信, 客戶端可按照如下方式構(gòu)造Socket 對(duì)象:

InetAddress remoteAddr1 = InetAddress.getByName("112.5.4.45"); InetAddress localAddr1 = InetAddress.getByName("112.5.4.3"); Socket socket1 = new Socket(remoteAddr1, 8000, localAddr1, 2345);   //客戶端使用端口2345

1.4 客戶連接服務(wù)器時(shí)可能拋出的異常

當(dāng)Socket 的構(gòu)造方法請(qǐng)求連接服務(wù)器時(shí), 可能會(huì)拋出下面的異常.

UnKnownHostException: 如果無(wú)法識(shí)別主機(jī)的名字或IP 地址, 就會(huì)拋出這種異常.  ConnectException: 如果沒有服務(wù)器進(jìn)程監(jiān)聽指定的端口, 或者服務(wù)器進(jìn)程拒絕連接, 就會(huì)拋出這種異常.  SocketTimeoutException: 如果等待連接超時(shí), 就會(huì)拋出這種異常.  BindException: 如果無(wú)法把Socket 對(duì)象與指定的本地IP 地址或端口綁定, 就會(huì)拋出這種異常. 以上4中異常都是IOException的直接或間接子類.      如圖2-1所示.

IOException------- UnknownHostException

|---- InterruptedIOException ----------- SocketTimeoutException

|---- SocketException              ----------- BindException

|---------- ConnectException

圖2-1 客戶端連接服務(wù)器時(shí)可能拋出的異常

二. 獲取Socket 的信息

在一個(gè)Socket 對(duì)象中同時(shí)包含了遠(yuǎn)程服務(wù)器的IP 地址和端口信息, 以及客戶本地的IP 地址和端口信息. 此外, 從Socket 對(duì)象中還可以獲得輸出流和輸入流, 分別用于向服務(wù)器發(fā)送數(shù)據(jù), 以及接收從服務(wù)器端發(fā)來的數(shù)據(jù). 以下方法用于獲取Socket的有關(guān)信息.

getInetAddress(): 獲得遠(yuǎn)程服務(wù)器的IP 地址.  getPort(): 獲得遠(yuǎn)程服務(wù)器的端口.  getLocalAddress(): 獲得客戶本地的IP 地址.  getLocalPort(): 獲得客戶本地的端口.  getInputStream(): 獲得輸入流. 如果Socket 還沒有連接, 或者已經(jīng)關(guān)閉, 或者已經(jīng)通過 shutdownInput() 方法關(guān)閉輸入流, 那么此方法會(huì)拋出IOException.  getOutputStream(): 獲得輸出流, 如果Socket 還沒有連接, 或者已經(jīng)關(guān)閉, 或者已經(jīng)通過 shutdownOutput() 方法關(guān)閉輸出流, 那么此方法會(huì)拋出IOException.  這里有個(gè)HTTPClient 類的例子, 代碼我是寫好了, 也測(cè)試過了, 因?yàn)槠蚓筒毁N了. 這個(gè)HTTPClient 類用于訪問網(wǎng)頁(yè) www.javathinker.org/index.jsp. 該網(wǎng)頁(yè)位于一個(gè)主機(jī)名(也叫域名)為 www.javathinker.org 的遠(yuǎn)程HTTP服務(wù)器上, 它監(jiān)聽 80 端口. 在HTTPClient 類中, 先創(chuàng)建了一個(gè)連接到該HTTP服務(wù)器的Socket對(duì)象, 然后發(fā)送符合HTTP 協(xié)議的請(qǐng)求, 接著接收從HTTP 服務(wù)器上發(fā)回的響應(yīng)結(jié)果.

三. 關(guān)閉Socket

當(dāng)客戶與服務(wù)器的通信結(jié)束, 應(yīng)該及時(shí)關(guān)閉Socket , 以釋放Socket 占用的包括端口在內(nèi)的各種資源. Socket 的 close() 方法負(fù)責(zé)關(guān)閉Socket. 當(dāng)一個(gè)Socket對(duì)象被關(guān)閉, 就不能再通過它的輸入流和輸出流進(jìn)行I/O操作, 否則會(huì)導(dǎo)致IOException.

為了確保關(guān)閉Socket 的操作總是被執(zhí)行, 強(qiáng)烈建議把這個(gè)操作放在finally 代碼塊中:

Socket socket = null; try{socket = new Socket(www.javathinker.org,80); //執(zhí)行接收和發(fā)送數(shù)據(jù)的操作 .......... }catch(IOException e){e.printStackTrace(); }finally{try{if(socket != null) socket.close(); }catch(IOException e){e.printStackTrace();} }

Socket 類提供了3 個(gè)狀態(tài)測(cè)試方法.

isClosed(): 如果Socket已經(jīng)連接到遠(yuǎn)程主機(jī), 并且還沒有關(guān)閉, 則返回true , 否則返回false .  isConnected(): 如果Socket曾經(jīng)連接到遠(yuǎn)程主機(jī), 則返回true , 否則返回false .  isBound(): 如果Socket已經(jīng)與一個(gè)本地端口綁定, 則返回true , 否則返回false .  如果要判斷一個(gè)Socket 對(duì)象當(dāng)前是否處于連接狀態(tài), 可采用以下方式:

boolean isConnected = socket.isConnected() && !socket.isClosed();

四. 半關(guān)閉Socket

進(jìn)程A 與進(jìn)程B 通過Socket 通信, 假定進(jìn)程A 輸出數(shù)據(jù), 進(jìn)程B 讀入數(shù)據(jù). 進(jìn)程A 如何告訴進(jìn)程B 所有數(shù)據(jù)已經(jīng)輸出完畢? 下文略......

五. 設(shè)置Socket 的選項(xiàng)

Socket 有以下幾個(gè)選項(xiàng).

TCP_NODELAY: 表示立即發(fā)送數(shù)據(jù).  SO_RESUSEADDR: 表示是否允許重用Socket 所綁定的本地地址.  SO_TIMEOUT: 表示接收數(shù)據(jù)時(shí)的等待超時(shí)數(shù)據(jù).  SO_LINGER: 表示當(dāng)執(zhí)行Socket 的 close()方法時(shí), 是否立即關(guān)閉底層的Socket.  SO_SNFBUF: 表示發(fā)送數(shù)據(jù)的緩沖區(qū)的大小.  SO_RCVBUF: 表示接收數(shù)據(jù)的緩沖區(qū)的大小.  SO_KEEPALIVE: 表示對(duì)于長(zhǎng)時(shí)間處于空閑狀態(tài)的Socket , 是否要自動(dòng)把它關(guān)閉.  OOBINLINE: 表示是否支持發(fā)送一個(gè)字節(jié)的TCP 緊急數(shù)據(jù).  5.1 TCP_NODELAY 選項(xiàng)

設(shè)置該選項(xiàng): public void setTcpNoDelay(boolean on) throws SocketException  讀取該選項(xiàng): public boolean getTcpNoDelay() throws SocketException  默認(rèn)情況下, 發(fā)送數(shù)據(jù)采用Negale 算法. Negale 算法是指發(fā)送方發(fā)送的數(shù)據(jù)不會(huì)立即發(fā)出, 而是先放在緩沖區(qū), 等緩存區(qū)滿了再發(fā)出. 發(fā)送完一批數(shù)據(jù)后, 會(huì)等待接收方對(duì)這批數(shù)據(jù)的回應(yīng), 然后再發(fā)送下一批數(shù)據(jù). Negale 算法適用于發(fā)送方需要發(fā)送大批量數(shù)據(jù), 并且接收方會(huì)及時(shí)作出回應(yīng)的場(chǎng)合, 這種算法通過減少傳輸數(shù)據(jù)的次數(shù)來提高通信效率.

如果發(fā)送方持續(xù)地發(fā)送小批量的數(shù)據(jù), 并且接收方不一定會(huì)立即發(fā)送響應(yīng)數(shù)據(jù), 那么Negale 算法會(huì)使發(fā)送方運(yùn)行很慢. 對(duì)于GUI 程序, 如網(wǎng)絡(luò)游戲程序(服務(wù)器需要實(shí)時(shí)跟蹤客戶端鼠標(biāo)的移動(dòng)), 這個(gè)問題尤其突出. 客戶端鼠標(biāo)位置改動(dòng)的信息需要實(shí)時(shí)發(fā)送到服務(wù)器上, 由于Negale 算法采用緩沖, 大大減低了實(shí)時(shí)響應(yīng)速度, 導(dǎo)致客戶程序運(yùn)行很慢.

TCP_NODELAY 的默認(rèn)值為 false, 表示采用 Negale 算法. 如果調(diào)用setTcpNoDelay(true)方法, 就會(huì)關(guān)閉 Socket的緩沖, 確保數(shù)據(jù)及時(shí)發(fā)送:

if(!socket.getTcpNoDelay()) socket.setTcpNoDelay(true);

如果Socket 的底層實(shí)現(xiàn)不支持TCP_NODELAY 選項(xiàng), 那么getTcpNoDelay() 和 setTcpNoDelay 方法會(huì)拋出 SocketException.

5.2 SO_RESUSEADDR 選項(xiàng)

設(shè)置該選項(xiàng): public void setResuseAddress(boolean on) throws SocketException  讀取該選項(xiàng): public boolean getResuseAddress() throws SocketException  當(dāng)接收方通過Socket 的close() 方法關(guān)閉Socket 時(shí), 如果網(wǎng)絡(luò)上還有發(fā)送到這個(gè)Socket 的數(shù)據(jù), 那么底層的Socket 不會(huì)立即釋放本地端口, 而是會(huì)等待一段時(shí)間, 確保接收到了網(wǎng)絡(luò)上發(fā)送過來的延遲數(shù)據(jù), 然后再釋放端口. Socket接收到延遲數(shù)據(jù)后, 不會(huì)對(duì)這些數(shù)據(jù)作任何處理. Socket 接收延遲數(shù)據(jù)的目的是, 確保這些數(shù)據(jù)不會(huì)被其他碰巧綁定到同樣端口的新進(jìn)程接收到.

客戶程序一般采用隨機(jī)端口, 因此出現(xiàn)兩個(gè)客戶程序綁定到同樣端口的可能性不大. 許多服務(wù)器程序都使用固定的端口. 當(dāng)服務(wù)器程序關(guān)閉后, 有可能它的端口還會(huì)被占用一段時(shí)間, 如果此時(shí)立刻在同一個(gè)主機(jī)上重啟服務(wù)器程序, 由于端口已經(jīng)被占用, 使得服務(wù)器程序無(wú)法綁定到該端口, 啟動(dòng)失敗. (第三篇文章會(huì)對(duì)此作出介紹).

為了確保一個(gè)進(jìn)程關(guān)閉Socket 后, 即使它還沒釋放端口, 同一個(gè)主機(jī)上的其他進(jìn)程還可以立即重用該端口, 可以調(diào)用Socket 的setResuseAddress(true) 方法:

if(!socket.getResuseAddress()) socket.setResuseAddress(true);

值得注意的是 socket.setResuseAddress(true) 方法必須在 Socket 還沒有綁定到一個(gè)本地端口之前調(diào)用, 否則執(zhí)行 socket.setResuseAddress(true) 方法無(wú)效. 因此必須按照以下方式創(chuàng)建Socket 對(duì)象, 然后再連接遠(yuǎn)程服務(wù)器:

Socket socket = new Socket();            //此時(shí)Socket對(duì)象未綁定本地端口,并且未連接遠(yuǎn)程服務(wù)器 socket.setReuseAddress(true); SocketAddress remoteAddr = new InetSocketAddress("localhost",8000); socket.connect(remoteAddr);              //連接遠(yuǎn)程服務(wù)器, 并且綁定匿名的本地端口

或者:

Socket socket = new Socket();              //此時(shí)Socke 對(duì)象為綁定本地端口, 并且未連接遠(yuǎn)程服務(wù)器 socket.setReuseAddress(true); SocketAddress localAddr = new InetSocketAddress("localhost",9000); SocketAddress remoteAddr = new InetSocketAddress("localhost",8000); socket.bind(localAddr);             //與本地端口綁定 socket.connect(remoteAddr); //連接遠(yuǎn)程服務(wù)器

此外, 兩個(gè)共用同一個(gè)端口的進(jìn)程必須都調(diào)用 socket.setResuseAddress(true) 方法, 才能使得一個(gè)進(jìn)程關(guān)閉 Socket后, 另一個(gè)進(jìn)程的 Socket 能夠立即重用相同端口.

5.3 SO_TIMEOUT 選項(xiàng)

設(shè)置該選項(xiàng): public void setSoTimeout(int milliseconds) throws SocketException  讀取該選項(xiàng): public int getSoTimeout() throws SocketException  當(dāng)通過Socket 的輸入流讀數(shù)據(jù)時(shí), 如果還沒有數(shù)據(jù), 就會(huì)等待. 例如, 在以下代碼中, in.read(buff) 方法從輸入流中讀入 1024個(gè)字節(jié):

byte[] buff = new byte[1024]; InputStream in = socket.getInputStream(); in.read(buff);

如果輸入流中沒有數(shù)據(jù), in.read(buff) 就會(huì)等待發(fā)送方發(fā)送數(shù)據(jù), 直到滿足以下情況才結(jié)束等待:

略...............

Socket 類的 SO_TIMEOUT 選項(xiàng)用于設(shè)定接收數(shù)據(jù)的等待超時(shí)時(shí)間, 單位為毫秒, 它的默認(rèn)值為 0, 表示會(huì)無(wú)限等待, 永遠(yuǎn)不會(huì)超時(shí). 以下代碼把接收數(shù)據(jù)的等待超時(shí)時(shí)間設(shè)為 3 分鐘:

if(socket.getSoTimeout() == 0) socket.setSoTimeout(60000 * 3);   //注意, 原書中這里的代碼錯(cuò)誤, 里面的方法名字都少了"So"

Socket 的 setSoTimeout() 方法必須在接收數(shù)據(jù)之前執(zhí)行才有效. 此外, 當(dāng)輸入流的 read()方法拋出 SocketTimeoutException 后, Socket 仍然是連接的, 可以嘗試再次讀數(shù)據(jù):

socket.setSoTimeout(180000); byte[] buff = new byte[1024]; InputStream in = socket.getInputStream(); int len = -1; do{try{len = in.read(buff); //處理讀到的數(shù)據(jù) //......... }catch(SocketTimeoutException e){//e.printStackTrace();  System.out.println("等待讀超時(shí)!"); len = 0; }     }while(len != -1);

例子ReceiveServer.java 和 SendClient.java 是一對(duì)簡(jiǎn)單的服務(wù)器/客戶程序. sendClient 發(fā)送字符串 "hello everyone" ,接著睡眠 1 分鐘, 然后關(guān)閉 Socket. ReceiveServer 讀取 SendClient 發(fā)送來的數(shù)據(jù), 直到抵達(dá)輸入流的末尾, 最后打印 SendClient 發(fā)送來的數(shù)據(jù).

ReceiveServer.java 略....... ,         SendClient.java 略..........

在 SendClient 發(fā)送字符串 "hello everyone" 后, 睡眠 1 分鐘. 當(dāng) SendClient 在睡眠時(shí), ReceiveServer 在執(zhí)行 in.read(buff) 方法, 不能讀到足夠的數(shù)據(jù)填滿 buff 緩沖區(qū), 因此會(huì)一直等待 SendClient 發(fā)送數(shù)據(jù). 如果在 ReceiveServer 類中 socket.setSoTimeout(20000) , 從而把等待接收數(shù)據(jù)的超時(shí)時(shí)間設(shè)為 20 秒, 那么 ReceiveServer 在等待數(shù)據(jù)時(shí), 每當(dāng)超過 20 秒, 就會(huì)拋出SocketTimeoutException . 等到 SendClient 睡眠 1 分鐘后, SendClient 調(diào)用 Socket 的 close() 方法關(guān)閉 Socket, 這意味著 ReceiveServer 讀到了輸入流的末尾, ReceiveServer 立即結(jié)束讀等待, read() 方法返回 -1 . ReceiveServer最后打印接收到的字符串 "hello everyone", 結(jié)果如下:

等待讀超時(shí)! 等待讀超時(shí)! hello everyone

5.4 SO_LINGER 選項(xiàng)

設(shè)置該選項(xiàng): public void setSoLinger(boolean on, int seconds) throws SocketException  讀取該選項(xiàng): public int getSoLinger() throws SocketException  SO_LINGER 選項(xiàng)用來控制 Socket 關(guān)閉時(shí)的行為. 默認(rèn)情況下, 執(zhí)行 Socket 的 close() 方法, 該方法會(huì)立即返回, 但底層的 Socket 實(shí)際上并不立即關(guān)閉, 它會(huì)延遲一段時(shí)間, 直到發(fā)送完所有剩余的數(shù)據(jù), 才會(huì)真正關(guān)閉 Socket, 斷開連接.

如果執(zhí)行以下方法:

socket.setSoLinger(true, 0);

那么執(zhí)行Socket 的close() 方法, 該方法也會(huì)立即返回, 并且底層的 Socket 也會(huì)立即關(guān)閉, 所有未發(fā)送完的剩余數(shù)據(jù)被丟棄.

如果執(zhí)行以下方法:

socket.setSoLinger(true, 3600);

那么執(zhí)行Socket 的 close() 方法, 該方法不會(huì)立即返回, 而是進(jìn)入阻塞狀態(tài). 同時(shí), 底層的 Socket 會(huì)嘗試發(fā)送剩余的數(shù)據(jù). 只有滿足以下兩個(gè)條件之一, close() 方法才返回:

⑴ 底層的 Socket 已經(jīng)發(fā)送完所有的剩余數(shù)據(jù);

⑵ 盡管底層的 Socket 還沒有發(fā)送完所有的剩余數(shù)據(jù), 但已經(jīng)阻塞了 3600 秒(注意這里是秒, 而非毫秒), close() 方法的阻塞時(shí)間超過 3600 秒, 也會(huì)返回, 剩余未發(fā)送的數(shù)據(jù)被丟棄.

值得注意的是, 在以上兩種情況內(nèi), 當(dāng)close() 方法返回后, 底層的 Socket 會(huì)被關(guān)閉, 斷開連接. 此外, setSoLinger(boolean on, int seconds) 方法中的 seconds 參數(shù)以秒為單位, 而不是以毫秒為單位.

如果未設(shè)置 SO_LINGER 選項(xiàng), getSoLinger() 返回的結(jié)果是 -1, 如果設(shè)置了 socket.setSoLinger(true, 80) , getSoLinger() 返回的結(jié)果是 80.

Tips: 當(dāng)程序通過輸出流寫數(shù)據(jù)時(shí), 僅僅表示程序向網(wǎng)絡(luò)提交了一批數(shù)據(jù), 由網(wǎng)絡(luò)負(fù)責(zé)輸送到接收方. 當(dāng)程序關(guān)閉 Socket, 有可能這批數(shù)據(jù)還在網(wǎng)絡(luò)上傳輸, 還未到達(dá)接收方. 這里所說的 "未發(fā)送完的數(shù)據(jù)" 就是指這種還在網(wǎng)絡(luò)上傳輸, 未被接收方接收的數(shù)據(jù).

例子 SimpleClient.java 與 SimpleServer.java 所示是一對(duì)簡(jiǎn)單的客戶/服務(wù)器程序. SimpleClient 類發(fā)送一萬(wàn)個(gè)字符給 SimpleServer, 然后調(diào)用Socket 的 close() 方法關(guān)閉 Socket.

SimpleServer 通過 ServerSocket 的 accept() 方法接受了 SimpleClient 的連接請(qǐng)求后, 并不立即接收客戶發(fā)送的數(shù)據(jù), 而是睡眠 5 秒鐘后再接收數(shù)據(jù). 等到 SimpleServer 開始接收數(shù)據(jù)時(shí), SimpleClient 有可能已經(jīng)執(zhí)行了 Socket 的close() 方法, 那么 SimpleServer 還能接收到 SimpleClient 發(fā)送的數(shù)據(jù)嗎?

SimpleClient.java 略..., SimpleServer.java 略......

SimpleClient.java中

System.out.println("開始關(guān)閉 Socket"); long begin = System.currentTimeMillis(); socket.close(); long end = System.currentTimeMillis(); System.out.println("關(guān)閉Socket 所用的時(shí)間為:" + (end - begin) + "ms");

下面分 3 種情況演示 SimpleClient 關(guān)閉 Socket 的行為.

⑴ 未設(shè)置 SO_LINGER 選項(xiàng), 當(dāng) SimpleClient 執(zhí)行 Socket 的close() 方法時(shí), 立即返回, SimpleClient 的打印結(jié)果如下:

開始關(guān)閉 Socket 關(guān)閉Socket 所用的時(shí)間為:0ms

等到 SimpleClient 結(jié)束運(yùn)行, SimpleServer 可能才剛剛結(jié)束睡眠, 開始接收 SimpleClient 發(fā)送的數(shù)據(jù). 此時(shí)盡管 SimpleClient 已經(jīng)執(zhí)行了 Socket 的 close() 方法, 并且 SimpleClient 程序本身也運(yùn)行結(jié)束了, 但從 SimpleServer 的打印結(jié)果可以看出, SimpleServer 仍然接收到了所有的數(shù)據(jù). 之所以出現(xiàn)這種情況, 是因?yàn)楫?dāng) SimpleClient 執(zhí)行了 Socket 的 close() 方法后, 底層的 Socket 實(shí)際上并沒有真正關(guān)閉, 與 SimpleServer 的連接依然存在. 底層的 Socket 會(huì)存在一段時(shí)間, 直到發(fā)送完所有的數(shù)據(jù).

⑵ 設(shè)置SO_LINGER 選項(xiàng), socket.setSoLinger(true, 0). 這次當(dāng) SimpleClient 執(zhí)行 Socket 的 close() 方法時(shí), 會(huì)強(qiáng)行關(guān)閉底層的 Socket, 所有未發(fā)送完的數(shù)據(jù)丟失. SimpleClient 的打印結(jié)果如下:

開始關(guān)閉 Socket 關(guān)閉Socket 所用的時(shí)間為:0ms

從打印結(jié)果看出, SimpleClient 執(zhí)行 Socket 的 close() 方法時(shí), 也立即返回. 當(dāng) SimpleServer 結(jié)束睡眠, 開始接收 SimpleClient 發(fā)送的數(shù)據(jù)時(shí), 由于 SimpleClient 已經(jīng)關(guān)閉底層 Socket, 斷開連接, 因此 SimpleServer 在讀數(shù)據(jù)時(shí)會(huì)拋出 SocketException:

java.net.SocketException: Connection reset

⑶ 設(shè)置SO_LINGER 選項(xiàng), socket.setSoLinger(true, 3600). 這次當(dāng) SimpleClient 執(zhí)行 Socket 的close() 方法時(shí), 會(huì)進(jìn)入阻塞狀態(tài), 知道等待了 3600 秒, 或者底層 Socket 已經(jīng)把所有未發(fā)送的剩余數(shù)據(jù)發(fā)送完畢, 才會(huì)從 close() 方法返回. SimpleClient 的打印結(jié)果如下:

開始關(guān)閉 Socket 關(guān)閉Socket 所用的時(shí)間為:5648ms

當(dāng) SimpleServer 結(jié)束了 5 秒鐘的睡眠, 開始接收 SimpleClient 發(fā)送的數(shù)據(jù)時(shí), SimpleClient 還在這些 Socket 的close() 方法, 并且處于阻塞狀態(tài). SimpleClient 與 SimpleServer 之間的連接依然存在, 因此 SimpleServer 能夠接收到 SimpleClient 發(fā)送的所有數(shù)據(jù).

5.5 SO_RCVBUF 選項(xiàng)

設(shè)置該選項(xiàng): public void setReceiveBufferSize(int size) throws SocketException  讀取該選項(xiàng): public int getReceiveBufferSize() throws SocketException  SO_RCVBUF 表示 Socket 的用于輸入數(shù)據(jù)的緩沖區(qū)的大小. 一般說來, 傳輸大的連續(xù)的數(shù)據(jù)塊(基于HTTP 或 FTP 協(xié)議的通信) 可以使用較大的緩沖區(qū), 這可以減少傳輸數(shù)據(jù)的次數(shù), 提高傳輸數(shù)據(jù)的效率. 而對(duì)于交互頻繁且單次傳送數(shù)據(jù)量比較小的通信方式(Telnet 和 網(wǎng)絡(luò)游戲), 則應(yīng)該采用小的緩沖區(qū), 確保小批量的數(shù)據(jù)能及時(shí)發(fā)送給對(duì)方. 這種設(shè)定緩沖區(qū)大小的原則也同樣適用于 Socket 的 SO_SNDBUF 選項(xiàng).

如果底層 Socket 不支持 SO_RCVBUF 選項(xiàng), 那么 setReceiveBufferSize() 方法會(huì)拋出 SocketException.

5.6 SO_SNDBUF 選項(xiàng)

設(shè)置該選項(xiàng): public void setSendBufferSize(int size) throws SocketException  讀取該選項(xiàng): public int getSendBufferSize() throws SocketException  SO_SNDBUF 表示 Socket 的用于輸出數(shù)據(jù)的緩沖區(qū)的大小. 如果底層 Socket 不支持 SO_SNDBUF 選項(xiàng), setSendBufferSize() 方法會(huì)拋出 SocketException.

5.7 SO_KEEPALIVE 選項(xiàng)

設(shè)置該選項(xiàng): public void setKeepAlive(boolean on) throws SocketException  讀取該選項(xiàng): public boolean getKeepAlive() throws SocketException //原書中這個(gè)方法返回的類型是int  當(dāng) SO_KEEPALIVE 選項(xiàng)為 true 時(shí), 表示底層的TCP 實(shí)現(xiàn)會(huì)監(jiān)視該連接是否有效. 當(dāng)連接處于空閑狀態(tài)(連接的兩端沒有互相傳送數(shù)據(jù)) 超過了 2 小時(shí)時(shí), 本地的TCP 實(shí)現(xiàn)會(huì)發(fā)送一個(gè)數(shù)據(jù)包給遠(yuǎn)程的 Socket. 如果遠(yuǎn)程Socket 沒有發(fā)回響應(yīng), TCP實(shí)現(xiàn)就會(huì)持續(xù)嘗試 11 分鐘, 直到接收到響應(yīng)為止. 如果在 12 分鐘內(nèi)未收到響應(yīng), TCP 實(shí)現(xiàn)就會(huì)自動(dòng)關(guān)閉本地Socket, 斷開連接. 在不同的網(wǎng)絡(luò)平臺(tái)上, TCP實(shí)現(xiàn)嘗試與遠(yuǎn)程Socket 對(duì)話的時(shí)限有所差別.

SO_KEEPALIVE 選項(xiàng)的默認(rèn)值為 false, 表示TCP 不會(huì)監(jiān)視連接是否有效, 不活動(dòng)的客戶端可能會(huì)永遠(yuǎn)存在下去, 而不會(huì)注意到服務(wù)器已經(jīng)崩潰.

以下代碼把 SO_KEEPALIVE 選項(xiàng)設(shè)為 true:

if(!socket.getKeepAlive()) socket.setKeepAlive(true);

5.8 OOBINLINE 選項(xiàng)

設(shè)置該選項(xiàng): public void setOOBInline(boolean on) throws SocketException  讀取該選項(xiàng): public boolean getOOBInline() throws SocketException //原書中這個(gè)方法返回的類型是int  當(dāng) OOBINLINE 為 true 時(shí), 表示支持發(fā)送一個(gè)字節(jié)的 TCP 緊急數(shù)據(jù). Socket 類的 sendUrgentData(int data) 方法用于發(fā)送一個(gè)字節(jié)的 TCP緊急數(shù)據(jù).

OOBINLINE 的默認(rèn)值為 false, 在這種情況下, 當(dāng)接收方收到緊急數(shù)據(jù)時(shí)不作任何處理, 直接將其丟棄. 如果用戶希望發(fā)送緊急數(shù)據(jù), 應(yīng)該把 OOBINLINE 設(shè)為 true:

socket.setOOBInline(true);

此時(shí)接收方會(huì)把接收到的緊急數(shù)據(jù)

責(zé)任編輯:

標(biāo)簽:

相關(guān)推薦:

精彩放送:

新聞聚焦
Top 主站蜘蛛池模板: 三河市| 康平县| 湖北省| 五常市| 宁远县| 新河县| 尖扎县| 博乐市| 合水县| 开平市| 定远县| 静宁县| 洛阳市| 元氏县| 辉县市| 合水县| 大港区| 连平县| 溆浦县| 普兰县| 旬邑县| 雷波县| 黎平县| 中卫市| 桦南县| 南召县| 亳州市| 闻喜县| 柏乡县| 班戈县| 乃东县| 浙江省| 洛南县| 湛江市| 育儿| 安溪县| 古丈县| 南川市| 拜泉县| 岱山县| 泗阳县|