熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

Java網絡編程從入門到精通(18):Socket類的getter和setter方法(2)

2022-06-13   來源: Java核心技術 

  用於獲得和設置Socket選項的getter和setter方法

  Socket選擇可以指定Socket類發送和接受數據的方式在JDK中共有個Socket選擇可以設置個選項都定義在SocketOptions接口中定義如下

       public final static int TCP_NODELAY = x;

    public final static int SO_REUSEADDR = x;

    public final static int SO_LINGER = x;

    public final static int SO_TIMEOUT = x;

    public final static int SO_SNDBUF = x;

    public final static int SO_RCVBUF = x;

    public final static int SO_KEEPALIVE = x;

    public final static int SO_OOBINLINE = x;

  有趣的是個選項除了第一個沒在SO前綴外其他個選項都以SO作為前綴其實這個SO就是Socket Option的縮寫因此在Java中約定所有以SO為前綴的常量都表示Socket選項當然也有例外如TCP_NODELAY在Socket類中為每一個選項提供了一對get和set方法分別用來獲得和設置這些選項

   TCP_NODELAY

   public boolean getTcpNoDelay() throws SocketException
public void setTcpNoDelay(boolean on) throws SocketException

  在默認情況下客戶端向服務器發送數據時會根據數據包的大小決定是否立即發送當數據包中的數據很少時如只有個字節而數據包的頭卻有幾十個字節(IP頭+TCP頭)時系統會在發送之前先將較小的包合並到軟大的包後一起將數據發送出去在發送下一個數據包時系統會等待服務器對前一個數據包的響應當收到服務器的響應後再發送下一個數據包這就是所謂的Nagle算法在默認情況下Nagle算法是開啟的

  這種算法雖然可以有效地改善網絡傳輸的效率但對於網絡速度比較慢而且對實現性的要求比較高的情況下(如游戲Telnet等)使用這種方式傳輸數據會使得客戶端有明顯的停頓現象因此最好的解決方案就是需要Nagle算法時就使用它不需要時就關閉它而使用setTcpToDelay正好可以滿足這個需求當使用setTcpNoDelay(true)將Nagle算法關閉後客戶端每發送一次數據無論數據包的大小都會將這些數據發送出去

    SO_REUSEADDR

   public boolean getReuseAddress() throws SocketException           
public void setReuseAddress(boolean on) throws SocketException

  通過這個選項可以使多個Socket對象綁定在同一個端口上其實這樣做並沒有多大意義但當使用close方法關閉Socket連接後Socket對象所綁定的端口並不一定馬上釋放系統有時在Socket連接關閉才會再確認一下是否有因為延遲面未到達的數據包這完全是在底層處理的也就是說對用戶是透明的因此在使用Socket類時完全不會感覺到

  這種處理機制對於隨機綁定端口的Socket對象沒有什麼影響但對於綁定在固定端口的Socket對象就可能會拋出Address already in use JVM_Bind例外因此使用這個選項可以避免個例外的發生

   package mynet;

import *;
import javaio*;

public class Test
{
    public static void main(String[] args)
    {
        Socket socket = new Socket();
        Socket socket = new Socket();
        try
        {
            socketsetReuseAddress(true);
            socketbind(new InetSocketAddress( ));
            Systemoutprintln(socketgetReuseAddress():
                    + socketgetReuseAddress());
            socketbind(new InetSocketAddress( ));
        }
        catch (Exception e)
        {
            Systemoutprintln(error: + egetMessage());
            try
            {
                socketsetReuseAddress(true);
                socketbind(new InetSocketAddress( ));
                Systemoutprintln(socketgetReuseAddress():
                        + socketgetReuseAddress());
                Systemoutprintln(端口第二次綁定成功!);
            }
            catch (Exception e)
            {
                Systemoutprintln(egetMessage());
            }
        }
    }
}

  上面的代碼的運行結果如下

   socketgetReuseAddress():true
error:Address already in use: JVM_Bind
socketgetReuseAddress():true
端口第二次綁定成功!

  使用SO_REUSEADDR選項時有兩點需要注意

    必須在調用bind方法之前使用setReuseAddress方法來打開SO_REUSEADDR選項因此要想使用SO_REUSEADDR選項就不能通過Socket類的構造方法來綁定端口

    必須將綁定同一個端口的所有的Socket對象的SO_REUSEADDR選項都打開才能起作用如在例程socket和socket都使用了setReuseAddress方法打開了各自的SO_REUSEADDR選項

    SO_LINGER

   public int getSoLinger() throws SocketException
public void setSoLinger(boolean on int linger) throws SocketException

  這個Socket選項可以影響close方法的行為在默認情況下當調用close方法後將立即返回如果這時仍然有未被送出的數據包那麼這些數據包將被丟棄如果將linger參數設為一個正整數n時(n的值最大是在調用close方法後將最多被阻塞n秒在這n秒內系統將盡量將未送出的數據包發送出去如果超過了n秒如果還有未發送的數據包這些數據包將全部被丟棄而close方法會立即返回如果將linger設為和關閉SO_LINGER選項的作用是一樣的

  如果底層的Socket實現不支持SO_LINGER都會拋出SocketException例外當給linger參數傳遞負數值時setSoLinger還會拋出一個IllegalArgumentException例外可以通過getSoLinger方法得到延遲關閉的時間如果返回則表明SO_LINGER是關閉的例如下面的代碼將延遲關閉的時間設為分鐘

   if(socketgetSoLinger() == ) socketsetSoLinger(true );

    SO_TIMEOUT

   public int getSoTimeout() throws SocketException
public void setSoTimeout(int timeout) throws SocketException

  這個Socket選項在前面已經討論過可以通過這個選項來設置讀取數據超時當輸入流的read方法被阻塞時如果設置timeout(timeout的單位是毫秒)那麼系統在等待了timeout毫秒後會拋出一個InterruptedIOException例外在拋出例外後輸入流並未關閉你可以繼續通過read方法讀取數據

  如果將timeout設為就意味著read將會無限等待下去直到服務端程序關閉這個Socket這也是timeout的默認值如下面的語句將讀取數據超時設為

   socketsetSoTimeout( * );

  當底層的Socket實現不支持SO_TIMEOUT選項時這兩個方法將拋出SocketException例外不能將timeout設為負數否則setSoTimeout方法將拋出IllegalArgumentException例外

    SO_SNDBUF

   public int getSendBufferSize() throws SocketException
public void setSendBufferSize(int size) throws SocketException

  在默認情況下輸出流的發送緩沖區是個字節(K)這個值是Java所建議的輸出緩沖區的大小如果這個默認值不能滿足要求可以用setSendBufferSize方法來重新設置緩沖區的大小但最好不要將輸出緩沖區設得太小否則會導致傳輸數據過於頻繁從而降低網絡傳輸的效率

  如果底層的Socket實現不支持SO_SENDBUF選項這兩個方法將會拋出SocketException例外必須將size設為正整數否則setSendBufferedSize方法將拋出IllegalArgumentException例外

    SO_RCVBUF

   public int getReceiveBufferSize() throws SocketException
public void setReceiveBufferSize(int size) throws SocketException

  在默認情況下輸入流的接收緩沖區是個字節(K)這個值是Java所建議的輸入緩沖區的大小如果這個默認值不能滿足要求可以用setReceiveBufferSize方法來重新設置緩沖區的大小但最好不要將輸入緩沖區設得太小否則會導致傳輸數據過於頻繁從而降低網絡傳輸的效率

  如果底層的Socket實現不支持SO_RCVBUF選項這兩個方法將會拋出SocketException例外必須將size設為正整數否則setReceiveBufferSize方法將拋出IllegalArgumentException例外

    SO_KEEPALIVE

   public boolean getKeepAlive() throws SocketException
public void setKeepAlive(boolean on) throws SocketException

  如果將這個Socket選項打開客戶端Socket每隔段的時間(大約兩個小時)就會利用空閒的連接向服務器發送一個數據包這個數據包並沒有其它的作用只是為了檢測一下服務器是否仍處於活動狀態如果服務器未響應這個數據包在大約分鐘後客戶端Socket再發送一個數據包如果在分鐘內服務器還沒響應那麼客戶端Socket將關閉如果將Socket選項關閉客戶端Socket在服務器無效的情況下可能會長時間不會關閉SO_KEEPALIVE選項在默認情況下是關閉的可以使用如下的語句將這個SO_KEEPALIVE選項打開

   socketsetKeepAlive(true);

    SO_OOBINLINE

    public boolean getOOBInline() throws SocketException
 public void setOOBInline(boolean on) throws SocketException

  如果這個Socket選項打開可以通過Socket類的sendUrgentData方法向服務器發送一個單字節的數據這個單字節數據並不經過輸出緩沖區而是立即發出雖然在客戶端並不是使用OutputStream向服務器發送數據但在服務端程序中這個單字節的數據是和其它的普通數據混在一起的因此在服務端程序中並不知道由客戶端發過來的數據是由OutputStream還是由sendUrgentData發過來的下面是sendUrgentData方法的聲明

   public void sendUrgentData(int data) throws IOException

  雖然sendUrgentData的參數data是int類型但只有這個int類型的低字節被發送其它的三個字節被忽略下面的代碼演示了如何使用SO_OOBINLINE選項來發送單字節數據

   package mynet;

import *;
import javaio*;

class Server
{
    public static void main(String[] args) throws Exception
    {
        ServerSocket serverSocket = new ServerSocket();
        Systemoutprintln(服務器已經啟動端口號);
        while (true)
        {
            Socket socket = serverSocketaccept();
            socketsetOOBInline(true);
            InputStream in = socketgetInputStream();
            InputStreamReader inReader = new InputStreamReader(in);
            BufferedReader bReader = new BufferedReader(inReader);
            Systemoutprintln(bReaderreadLine());
            Systemoutprintln(bReaderreadLine());
            socketclose();
        }
    }
}
public class Client
{
    public static void main(String[] args) throws Exception
    {
        Socket socket = new Socket( );
        socketsetOOBInline(true);
        OutputStream out = socketgetOutputStream();
        OutputStreamWriter outWriter = new OutputStreamWriter(out);
        outWriterwrite();              // 向服務器發送字符C
        outWriterwrite(hello world\r\n);
        socketsendUrgentData();        // 向服務器發送字符A
        socketsendUrgentData();        // 向服務器發送字符B
        outWriterflush();
        socketsendUrgentData();       // 向服務器發送漢字
        socketsendUrgentData();
        socketsendUrgentData();       // 向服務器發送漢字
        socketsendUrgentData();
        socketclose();
    }
}

  由於運行上面的代碼需要一個服務器類因此在加了一個類名為Server的服務器類關於服務端套接字的使用方法將會在後面的文章中詳細討論在類Server類中只使用了ServerSocket類的accept方法接收客戶端的請求並從客戶端傳來的數據中讀取兩行字符串並顯示在控制台上

  測試

  由於本例使用了因Server和Client類必須在同一台機器上運行

  運行Server

   java mynetServer

  運行Client

   java mynetClient

  在服務端控制台的輸出結果

   服務器已經啟動端口號
ABChello world
中國

  在ClienT類中使用了sendUrgentData方法向服務器發送了字符A)和B但發送B時實際發送的是由於sendUrgentData只發送整型數的低字節因此實際發送的是十進制整型的二進制形式如圖所示



  十進制整型的二進制形式

  從圖可以看出雖然分布在了兩個字節上但它的低字節仍然是

  在Client類中使用flush將緩沖區中的數據發送到服務器我們可以從輸出結果發現一個問題在Client類中先後向服務器發送了Chello worldrnAB而在服務端程序的控制台上顯示的卻是ABChello world這種現象說明使用sendUrgentData方法發送數據後系統會立即將這些數據發送出去而使用write發送數據必須要使用flush方法才會真正發送數據

  在Client類中向服務器發送中國字符串由於是由兩個字節組成的是由兩個字節組成的因此可分別發送這四個字節來傳送中國字符串

  注意在使用setOOBInline方法打開SO_OOBINLINE選項時要注意是必須在客戶端和服務端程序同時使用setOOBInline方法打開這個選項否則無法命名用sendUrgentData來發送數據


From:http://tw.wingwit.com/Article/program/Java/hx/201311/26387.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.