2011-9-26 21:25:34

 

2011-9-26 21:25:34


將上傳註釋掉

停止報警 功能沒有

連接失敗


E/ProxyInterface( 3298): The operation timed out
 
這個也使用單例  安全

讓第一次連接的時候


套接字或插座(socket)是一種軟件形式的抽象,用於表達兩臺機器間一個連接的“終端”。針對一個特定的連接,每臺機器上都有一個“套接字”,

可以想象它們之間有一條虛擬的“線纜”。JAVA有兩個基於數據流的套接字類:ServerSocket,服務器用它“偵聽”進入的連接;Socket,

客戶端用它初始一次連接。偵聽套接字只能接收新的連接請求,不能接收實際的數據包。

套接字是基於TCP/IP實現的,它是用來提供一個訪問TCP的服務接口,或者說套接字socket是TCP的應用編程接口API,通過它應用層就可以訪問TCP提供的服務。

在JAVA中,我們用ServerSocket、Socket類創建一個套接字連接,從套接字得到的結果是一個InputStream以及OutputStream對象,

以便將連接作爲一個IO流對象對待。通過IO流可以從流中讀取數據或者寫數據到流中,讀寫IO流會有異常IOException產生。

  套接字底層是基於TCP的,所以socket的超時和TCP超時是相同的。下面先討論套接字讀寫緩衝區,接着討論連接建立超時、
 
  讀寫超時以及JAVA套接字編程的嵌套異常捕獲和一個超時例子程序的抓包示例。

1 socket讀寫緩衝區
  一旦創建了一個套接字實例,操作系統就會爲其分配緩衝區以存放接收和要發送的數據。

 

 

 

  JAVA可以設置讀寫緩衝區的大小-setReceiveBufferSize(int size), setSendBufferSize(int size)。

  向輸出流寫數據並不意味着數據實際上已經被髮送,它們只是被複制到了發送緩衝區隊列SendQ,就是在Socket的OutputStream上調用flush()方法,也不能保證數據能夠立即發送到網絡。真正的數據發送是由操作系統的TCP協議棧模塊從緩衝區中取數據發送到網絡來完成的。

  當有數據從網絡來到時,TCP協議棧模塊接收數據並放入接收緩衝區隊列RecvQ,輸入流InputStream通過read方法從RecvQ中取出數據。

2 socket連接建立超時
  socket連接建立是基於TCP的連接建立過程。TCP的連接需要通過3次握手報文來完成,開始建立TCP連接時需要發送同步SYN報文,然後等待確認報文SYN+ACK,
 
  最後再發送確認報文ACK。TCP連接的關閉通過4次揮手來完成,主動關閉TCP連接的一方發送FIN報文,等待對方的確認報文;被動關閉的一方也發送FIN報文,然等待確認報文。

 


  正在等待TCP連接請求的一端有一個固定長度的連接隊列,該隊列中的連接已經被TCP接受(即三次握手已經完成),但還沒有被應用層所接受。TCP接受一個連接是將其放入這個連接隊列,
 
  而應用層接受連接是將其從該隊列中移出。應用層可以通過設置backlog變量來指明該連接隊列的最大長度,即已被TCP接受而等待應用層接受的最大連接數。


  當一個連接請求SYN到達時,TCP確定是否接受這個連接。如果隊列中還有空間,TCP模塊將對SYN進行確認並完成連接的建立。但應用層只有在三次握手中的第三個報文收到後纔會知道這個新連接。如果隊列沒有空間,TCP將不理會收到的SYN。

  如果應用層不能及時接受已被TCP接受的連接,這些連接可能佔滿整個連接隊列,新的連接請求可能不被響應而會超時。如果一個連接請求SYN發送後,一段時間後沒有收到確認SYN+ACK,TCP會重傳這個連接請求SYN兩次,每次重傳的時間間隔加倍,在規定的時間內仍沒有收到SYN+ACK,TCP將放棄這個連接請求,連接建立就超時了。

  JAVA Socket連接建立超時和TCP是相同的,如果TCP建立連接時三次握手超時,那麼導致Socket連接建立也就超時了。可以設置Socket連接建立的超時時間-

connect(SocketAddress endpoint, int timeout)

如果在timeout內,連接沒有建立成功,在TimeoutException異常被拋出。如果timeout的值小於三次握手的時間,那麼Socket連接永遠也不會建立。

  不同的應用層有不同的連接建立過程,Socket的連接建立和TCP一樣-僅僅需要三次握手就完成連接,但有些應用程序需要交互很多信息後才能成功建立連接,比如Telnet協議,在TCP三次握手完成後,需要進行選項協商之後,Telnet連接才建立完成。

3 socket讀超時
  如果輸入緩衝隊列RecvQ中沒有數據,read操作會一直阻塞而掛起線程,直到有新的數據到來或者有異常產生。調用setSoTimeout(int timeout)可以設置超時時間,如果到了超時時間仍沒有數據,read會拋出一個SocketTimeoutException,程序需要捕獲這個異常,但是當前的socket連接仍然是有效的。

  如果對方進程崩潰、對方機器突然重啓、網絡斷開,本端的read會一直阻塞下去,這時設置超時時間是非常重要的,否則調用read的線程會一直掛起。

  TCP模塊把接收到的數據放入RecvQ中,直到應用層調用輸入流的read方法來讀取。如果RecvQ隊列被填滿了,這時TCP會根據滑動窗口機制通知對方不要繼續發送數據,本端停止接收從對端發送來的數據,直到接收者應用程序調用輸入流的read方法後騰出了空間。

4 socket寫超時
  socket的寫超時是基於TCP的超時重傳。超時重傳是TCP保證數據可靠性傳輸的一個重要機制,其原理是在發送一個數據報文後就開啓一個計時器,在一定時間內如果沒有得到發送報文的確認ACK,那麼就重新發送報文。如果重新發送多次之後,仍沒有確認報文,就發送一個復位報文RST,然後關閉TCP連接。首次數據報文發送與復位報文傳輸之間的時間差大約爲9分鐘,也就是說如果9分鐘內沒有得到確認報文,就關閉連接。但是這個值是根據不同的TCP協議棧實現而不同。

  如果發送端調用write持續地寫出數據,直到SendQ隊列被填滿。如果在SendQ隊列已滿時調用write方法,則write將被阻塞,直到SendQ有新的空閒空間爲止,也就是說直到一些字節傳輸到了接收者套接字的RecvQ中。如果此時RecvQ隊列也已經被填滿,所有操作都將停止,直到接收端調用read方法將一些字節傳輸到應用程序。

  當Socket的write發送數據時,如果網線斷開、對端進程崩潰或者對端機器重啓動,TCP模塊會重傳數據,最後超時而關閉連接。下次如再調用write會導致一個異常而退出。

  Socket寫超時是基於TCP協議棧的超時重傳機制,一般不需要設置write的超時時間,也沒有提供這種方法。

5 雙重嵌套異常捕獲
   如果ServerSocket、Socket構造失敗,只需要僅僅捕獲這個構造失敗異常而不需要調用套接字的close方法來釋放資源(必須保證構造失敗後不會留下任何需要清除的資源),因爲這時套接字內部資源沒有被成功分配。如果構造成功,必須進入一個try finally語句塊裏調用close釋放套接字。請參照下面例子程序。

 


view plaincopy to clipboardprint?
import java.net.*; 
import java.io.*; 
public class SocketClientTest 

  public static final int PORT = 8088; 
  public static void main( String[] args ) throws Exception 
  { 
    InetAddress addr = InetAddress.getByName( "127.0.0.1" ); 
    Socket socket = new Socket(); 
    try 
    { 
      socket.connect( new InetSocketAddress( addr, PORT ), 30000 ); 
      socket.setSendBufferSize(100); 
       
      BufferedWriter out = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream() ) ); 
      int i = 0; 
       
      while( true ) 
      { 
        System.out.println( "client sent --- hello *** " + i++ ); 
        out.write( "client sent --- hello *** " + i ); 
        out.flush(); 
         
        Thread.sleep( 1000 ); 
      } 
    } 
    finally 
    { 
      socket.close(); 
    } 
  } 

import java.net.*;
import java.io.*;
public class SocketClientTest
{
  public static final int PORT = 8088;
  public static void main( String[] args ) throws Exception
  {
    InetAddress addr = InetAddress.getByName( "127.0.0.1" );
    Socket socket = new Socket();
    try
    {
      socket.connect( new InetSocketAddress( addr, PORT ), 30000 );
      socket.setSendBufferSize(100);
     
      BufferedWriter out = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream() ) );
      int i = 0;
     
      while( true )
      {
        System.out.println( "client sent --- hello *** " + i++ );
        out.write( "client sent --- hello *** " + i );
        out.flush();
       
        Thread.sleep( 1000 );
      }
    }
    finally
    {
      socket.close();
    }
  }
}
 

 

view plaincopy to clipboardprint?
import java.io.*; 
import java.net.ServerSocket; 
import java.net.Socket; 
public class SocketServerTest 

  public static final int PORT = 8088; 
  public static final int BACKLOG = 2; 
  public static void main( String[] args ) throws IOException 
  { 
    ServerSocket server = new ServerSocket( PORT, BACKLOG ); 
    System.out.println("started: " + server); 
    try 
    { 
      Socket socket = server.accept(); 
      try 
      { 
        BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); 
        String info = null; 
         
        while( ( info = in.readLine() ) != null ) 
        { 
          System.out.println( info ); 
        } 
      } 
      finally 
      { 
        socket.close(); 
      } 
    } 
    finally 
    { 
      server.close(); 
    } 
  } 

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServerTest
{
  public static final int PORT = 8088;
  public static final int BACKLOG = 2;
  public static void main( String[] args ) throws IOException
  {
    ServerSocket server = new ServerSocket( PORT, BACKLOG );
    System.out.println("started: " + server);
    try
    {
      Socket socket = server.accept();
      try
      {
        BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) );
        String info = null;
       
        while( ( info = in.readLine() ) != null )
        {
          System.out.println( info );
        }
      }
      finally
      {
        socket.close();
      }
    }
    finally
    {
      server.close();
    }
  }
}

 

  執行上面的程序,在程序運行一會兒之後,斷開client和server之間的網絡連接,在機器上輸出如下:

 

Server上的輸出:


Echoing:client sent -----hello0


Echoing:client sent -----hello1

Echoing:client sent -----hello2

Echoing:client sent -----hello3

Echoing:client sent -----hello4

Echoing:client sent -----hello5

Echoing:client sent -----hello6

 

---->> 斷開了網絡連接之後沒有數據輸出

 

Client上的輸出:

socket default timeout = 0

socket = Socket[addr=/10.15.9.99,port=8088,localport=4691]

begin to read

client sent --- hello *** 0

client sent --- hello *** 1

client sent --- hello *** 2

client sent --- hello *** 3

client sent --- hello *** 4

client sent --- hello *** 5

client sent --- hello *** 6

client sent --- hello *** 7

client sent --- hello *** 8 

client sent --- hello *** 9

client sent --- hello *** 10

 

 ---->> 斷開網絡連接後客戶端進程掛起

 

java.net.SocketException : Connection reset by peer: socket write error

    at java.net.SocketOutputStream.socketWrite0( Native Method )

    at java.net.SocketOutputStream.socketWrite( SocketOutputStream.java:92 )

    at java.net.SocketOutputStream.write( SocketOutputStream.java:136 )

    at sun.nio.cs.StreamEncoder.writeBytes( StreamEncoder.java:202 )

    at sun.nio.cs.StreamEncoder.implFlushBuffer( StreamEncoder.java:272 )

    at sun.nio.cs.StreamEncoder.implFlush( StreamEncoder.java:276 )

    at sun.nio.cs.StreamEncoder.flush( StreamEncoder.java:122 )

    at java.io.OutputStreamWriter.flush( OutputStreamWriter.java:212 )

    at java.io.BufferedWriter.flush( BufferedWriter.java:236 )

    at com.xtera.view.SocketClientTest.main( SocketClientTest.java:99 )

 

  當hello6被髮送到server端後,網絡連接被斷開,這時server端不能接收任何數據而掛起。client端仍然繼續發送數據,實際上hello7、hello8、hello9、hello10都被複制到SendQ隊列中,write方法立即返回。當client的SendQ隊列被填滿之後,write方法就被阻塞。TCP模塊在發送報文hello7之後,沒有收到確認而超時重傳,再重傳幾次之後關閉了TCP連接,同時導致被阻塞的write方法異常返回。

  通過抓包工具,我們可以看到超時重傳的報文。

 

 

 

 

 

 

 

發佈了363 篇原創文章 · 獲贊 11 · 訪問量 37萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章