JAVA網絡編程之Socket用法

在客戶/服務器通信模式中,客戶端需要主動建立與服務器連接的Socket,服務器端收到客戶端的連接請求,也會創建與客戶端連接的Socket。Socket可以看做是通信連接兩端的收發器,客戶端和服務店都通過Socket來收發數據。

1、構造Socket

public Socket() 通過系統默認類型的 SocketImpl 創建未連接套接字 

public Socket(String host,int port) throws UnknownHostException,IOException  創建一個流套接字並將其連接到指定主機上的指定端口號。

public Socket(String host,int port,InetAddress localAddr,int localPort) throws IOException 創建一個套接字並將其連接到指定遠程主機上的指定遠程端口。socket 會通過調用 bind() 函數來綁定提供的本地地址及端口。

public Socket(InetAddress address,int port)throws IOException創建一個流套接字並將其連接到指定 IP 地址的指定端口號。

public Socket(InetAddress address,int port,InetAddress localAddr,int localPort)throws IOException創建一個套接字並將其連接到指定遠程地址上的指定遠程端口。socket 會通過調用 bind() 函數來綁定提供的本地地址及端口。

除第一個外,其他4個構造方法都會試圖和服務器建立連接,如何連接成功則返回Socket對象,如果連接失敗就會拋出IOException。

實例1:掃描主機上1到1024的端口,判斷這些端口是否被服務器監聽

package com.hanfeng.socket;

import java.io.IOException;
import java.net.Socket;

/**
 * 掃描主機上1到1024的端口,判斷這些端口是否被服務器監聽
 * 
 * @author hanfeng
 * @data 2012-8-24 上午10:25:57
 */
public class PortScanner {

	public static void main(String[] args) {
		String host = "localhost";
		if (args.length > 0) {
			host = args[0];
		}
		new PortScanner().scan(host);
	}

	public void scan(String host) {
		Socket socket = null;

		for (int port = 1; port < 1024; port++) {
			try {
				socket = new Socket(host, port);
				System.out.println("連接到端口:" + port);
			} catch (IOException e) {
				System.out.println("無法連接到端口:" + port);
			} finally {
				try {
					if (socket != null) {
						socket.close();
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

}
1.1 設置等待連接超時時間

public void scan(String host) {
		Socket socket = null;		
		socket = new Socket();
		SocketAddress address = new InetSocketAddress(host, 1158);
		try {
			socket.connect(address, 60000);
			System.out.println("連接成功!");
		} catch (IOException e) {
			System.out.println("連接超時!");
			e.printStackTrace();
		}		
	}
1.2設置服務器的IP地址

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

InetAddress類表示IP地址,其用法如下:
//返回本地主機的IP地址
InetAddress addr1=InetAddress.getLocalHost();
//返回代表"222.34.5.7"的IP地址
InetAddress addr2=InetAddress.getByName("222.34.5.7");
//返回域名爲"www.google.com"的IP地址
InetAddress addr3=InetAddress.getByName("www.google.com");

public static void main(String[] args) {
		try {
			InetAddress address = InetAddress.getLocalHost();
			System.out.println("本機IP地址:"+address);
			InetAddress address2 = InetAddress.getByName("222.34.5.7");
			System.out.println("返回遠程IP地址:"+address2);
			InetAddress address3 = InetAddress.getByName("www.google.com");
			System.out.println("返回域名IP地址:"+address3);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}		
	}
1.3 設置客戶端的IP地址

在一個Socket對象中,既包含遠程服務器的IP地址和端口信息,也包含本地客戶端的IP地址和端口信息。默認情況下,客戶端的IP地址來自於客戶程序所在的主機,客戶端的端口則由操作系統隨機分配。Socket類還有兩個構造方法允許顯式的設置客戶端的IP地址和端口:
Socket(InetAddress address, int port, InetAddress localAddr, int localPort)throws IOException
Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException

實例:一個主機在Internet網絡中的IP地址爲”222.67.1.34“,在一個局域網中的IP地址爲”112.5.4.3“。假定這個主機上的客戶程序希望和同一個局域網上的一個服務器程序(112.5.4.45:8000)通信,則客戶端可按照如下方式進行構造Socket對象:

	InetAddress remoteAddr = InetAddress.getByName("112.5.4.45");
	InetAddress localAddr = InetAddress.getByName("112.5.4.3");
	Socket socket = new Socket(remoteAddr,8000,localAddr,2345);
1.4客戶端連接服務器可能拋出的異常

UnknowHostException:如果無法識別主機的名字或IP地址,就會拋出異常

ConnectException:如果沒有服務器進行監聽指定的端口,或則服務器進程拒絕連接,就會拋出異常

SocketTimeoutException:等待連接超時,拋出異常

BindExcrption:如果無法把Socket對象與具體的本地IP地址或端口綁定,就會拋出異常

以上4個異常都是IOException的直接或間接子類,如下圖

實例:捕獲各種異常

package com.hanfeng.socket;

import java.io.IOException;
import java.net.BindException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

/**
 * 
 * @author hanfeng
 * @data 2012-8-24 上午11:16:32
 */
public class ConnectTest {
	
	public static void main(String[] args) {
		String host = "www.google.com";
		int port = 80;
		if (args.length>1) {
			host = args[0];
			port = Integer.parseInt(args[1]);
		}
		new ConnectTest().connect(host, port);
	}
	
	public void connect(String host,int port){
		SocketAddress address = new InetSocketAddress(host, port);
		Socket socket = null;
		String result = "";
		
		try {
		long begin = System.currentTimeMillis();//計算開始連接的時間
		socket = new Socket();//開始建立連接		
		socket.connect(address, 6000);//設置連接超時時間
		long end = System.currentTimeMillis();// 計算機連接結束的時間
		result = (end-begin)+"ms";
		} catch (BindException e) {
			result = "IP地址或端口綁定異常!";
		} catch (UnknownHostException e) {
			result = "未識別主機地址!";
		}catch (SocketTimeoutException e) {
			result = "連接超時!";
		}catch (ConnectException e) {
			result = "拒絕連接!";
		}catch (Exception e) {
			result =  "失敗啦!";
		}finally{
			if (socket!=null) {
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		System.out.println("遠程地址信息==>"+address+":"+result);
	}
}
2、獲取Socket信息

以下方法用於獲取Socket的有關信息:
getInetAddress():獲得遠程服務器的IP地址。
getPort():獲得遠程服務器的端口。
getLocalAddress():獲得客戶本地的IP地址。
getLocalPort():獲得客戶本地的端口。
getInputStream():獲得輸入流。如果Socket還沒有連接,或者已經關閉,或者已經通過shutdownInput()方法關閉輸入流,那麼此方法會拋出IOException。
getOutputStream():獲得輸出流。如果Socket還沒有連接,或者已經關閉,或者已經通過shutdownOutput()方法關閉輸出流,那麼此方法會拋出IOException。

實例:HTTPClient類訪問網頁

package com.hanfeng.socket;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;


public class HttpClientDemo {
	String host ="www.google.com";
	int port = 80;
	Socket socket = null;
	
	public void createSocket() throws Exception{
		socket = new Socket(host, port);
	}
	
	public void communicate() throws Exception{
	    StringBuffer sb=new StringBuffer("GET "+" HTTP/1.1\r\n");
//	    sb.append("Host: www.javathinker.org\r\n");
//	    sb.append("Accept: */*\r\n");
//	    sb.append("Accept-Language: zh-cn\r\n");
//	    sb.append("Accept-Encoding: gzip, deflate\r\n");
//	    sb.append("User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)\r\n");
//	    sb.append("Connection: Keep-Alive\r\n\r\n");
		
		//發出HTTP請求
		OutputStream socketOut = socket.getOutputStream();
		socketOut.write(sb.toString().getBytes());
		socket.shutdownOutput();
		
		//接收響應結果
		InputStream socketInput = socket.getInputStream();
		BufferedReader buffer = new BufferedReader(new InputStreamReader(socketInput));
		String data = null;
		while ((data=buffer.readLine())!=null) {
			System.out.println(data);
		}
	}

	/**
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		HttpClientDemo httpClient = new HttpClientDemo();
		httpClient.createSocket();
		httpClient.communicate();
	}

}
3、關閉Socket

當客戶與服務器的通信結束,應該及時關閉Socket,以釋放Socket佔用的包括端口在內的各種資源。Socket的close()方法負責關閉Socket。當一個Socket對象被關閉,就不能在通過他的輸入流和輸出流進行I/O操作,否則會導致IOException。

爲了確保關閉Socket的操作總被執行,建議把這個操作放在finally代碼塊中。

finally {
        try {
            if(socket!=null)socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
Socket類提供了3個狀態測試方法

isClosed():如果Socket已經連接到遠程主機,並且還沒有關閉,則返回true

isConnected():如果Socket曾經連接到遠程主機,則返回true

isBound():如果Socket已經與一個本地端口綁定,則返回true

如果要判斷一個Socket對象當前是否處於連接狀態,可以採用以下方式:

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

4、半關閉Socket

4.1 有的時候,可能僅僅希望關閉輸出流或輸入流之一。此時可以採用Socket類提供的半關閉方法:

shutdownInput():關閉輸入流。

shutdownOutput(): 關閉輸出流。

4.2 先後調用Socket的shutdownInput()和shutdownOutput()方法,僅僅關閉了輸入流和輸出流,並不等價於調用Socket的close()方法。在通信結束後,仍然要調用Socket的close()方法,因爲只有該方法纔會釋放Socket佔用的資源,比如佔用的本地端口等。

4.3 Socket類還提供了兩個狀態測試方法,用來判斷輸入流和輸出流是否關閉:

public boolean isInputShutdown()

public boolean isOutputShutdown()

5、設置Socket的選項

Socket有以下幾個選項:

n TCP_NODELAY:表示立即發送數據。

n SO_RESUSEADDR:表示是否允許重用Socket所綁定的本地地址。

n SO_TIMEOUT:表示接收數據時的等待超時時間。

n SO_LINGER:表示當執行Socket的close()方法時,是否立即關閉底層的Socket。

n SO_SNFBUF:表示發送數據的緩衝區的大小。

n SO_RCVBUF:表示接收數據的緩衝區的大小。

n SO_KEEPALIVE:表示對於長時間處於空閒狀態的Socket,是否要自動把它關閉。

n OOBINLINE:表示是否支持發送一個字節的TCP緊急數據。

1. TCP_NODELAY選項

1) 設置該選項:public void setTcpNoDelay(boolean on) throws SocketException

2) 讀取該選項:public boolean getTcpNoDelay() throws SocketException

3) TCP_NODEALY的默認值爲false,表示採用Negale算法。如果調用setTcpNoDelay(true)方法,就會關閉Socket的緩衝,確保數據及時發送:

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

4) 如果Socket的底層實現不支持TCP_NODELAY選項,那麼getTcpNoDelay()和setTcpNoDelay()方法會拋出SocketException。

2. SO_RESUSEADDR選項

1) 設置該選項:public void setResuseAddress(boolean on) throws SocketException

2) 讀取該選項:public boolean getResuseAddress() throws SocketException

3) 爲了確保一個進程關閉了Socket後,即使它還沒釋放端口,同一個主機上的其他進程還可以立刻重用該端口,可以調用Socket的setResuseAddress(true)方法:

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

4) 值得注意的是socket.setResuseAddress(true)方法必須在Socket還沒有綁定到一個本地端口之前調用,否則執行socket.setResuseAddress(true)方法無效。因此必須按照以下方式創建Socket對象,然後再連接遠程服務器:

Socket socket = new Socket(); //此時Socket對象未綁定到本地端口,並且未連接遠程服務器

socket.setResuseAddress(true);

SocketAddress remoteAddr = new InetSocketAddress("remotehost",8000);

socket.connect(remoteAddr); //連接遠程服務器,並且綁定匿名的本地端口

或者:

Socket socket = new Socket(); //此時Socket對象未綁定到本地端口,並且未連接遠程服務器

socket.setResuseAddress(true);

SocketAddress localAddr = new InetSocketAddress("localhost",9000);

SocketAddress remoteAddr = new InetSocketAddress("remotehost",8000);

socket.bind(localAddr); //與本地端口綁定

socket.connect(remoteAddr); //連接遠程服務器,並且綁定匿名的本地端口

3. SO_TIMEOUT選項

1) 設置該選項:public void setSoTimeout(int milliseconds) throws SocketException

2) 讀取該選項:public int getSoTimeOut() throws SocketException

3) 當通過Socket的輸入流讀數據時,如果還沒有數據,就會等待。Socket類的SO_TIMEOUT選項用於設定接收數據的等待超時時間,單位爲毫秒,它的默認值爲0,表示會無限等待,永遠不會超時。

4) Socket的setSoTimeout()方法必須在接收數據之前執行纔有效。此外,當輸入流的read()方法拋出SocketTimeoutException後,Socket仍然是連接的,可以嘗試再次讀取數據。

4. SO_LINGER選項

1) 設置該選項:public void setSoLinger(boolean on, int seconds) throws SocketException

2) 讀取該選項:public int getSoLinger() throws SocketException

3) SO_LINGER選項用來控制Socket關閉時的行爲。

l socket.setSoLinger(true,0):執行Socket的close()方法時,該方法也會立即返回,但底層的Socket也會立即關閉,所有未發送完的剩餘數據被丟棄。

l socket.setSoLinger(true,3600):執行Socket的close()方法時,該方法不會立即返回,而進入阻塞狀態,同時,底層的Socket會嘗試發送剩餘的數據。只有滿足以下兩個條件之一,close()方法才返回:

n 底層的Socket已經發送完所有的剩餘數據。

n 儘管底層的Socket還沒有發送完所有的剩餘數據,但已經阻塞了3600秒。close()方法的阻塞時間超過3600秒,也會返回,剩餘未發送的數據被丟棄。

以上兩種情況內,當close()方法返回後,底層的Socket會被關閉,斷開連接。

4) setSoLinger(boolean on ,int second)方法中的seconds參數以秒爲單位,而不是以毫秒爲單位。

5. SO_RCVBUF選項

1) 設置該選項:public void setReceiveBufferSize(int size) throws SocketException

2) 讀取該選項:public int getReceiveBufferSize() throws SocketException

3) SO_RCVBUF表示Socket的用於輸入數據的緩衝區的大小。

4) 如果底層Socket不支持SO_RCVBUF選項,那麼setReceiveBufferSize()方法會拋出SocketException。

6. SO_SNDBUF選項

1) 設置該選項:public void setSendBufferSize(int size) throws SocketException

2) 讀取該選項:public int getSendBufferSize() throws SocketException

3) SO_SNDBUF表示Socket的用於輸出數據的緩衝區的大小。

4) 如果底層Socket不支持SO_SNDBUF選項,setSendBufferSize()方法會拋出SocketException。

7. SO_KEEPALIVE選項

1) 設置該選項:public void setKeepAlive(boolean on) throws SocketException

2) 讀取該選項:public int getKeepAlive() throws SocketException

3) 當SO_KEEPALIVE選項爲true,表示底層的TCP實現會監視該連接是否有效。

4) SO_KEEPALIVE選項的默認值爲false,表示TCP不會監視連接是否有效,不活動的客戶端可能會永久存在下去,而不會注意到服務器已經崩潰。

8. OOBINLINE選項

1) 設置該選項:public void setOOBInline(int size) throws SocketException

2) 讀取該選項:public int getOOBInline () throws SocketException

3) 當OOBINLINE爲true時,表示支持發送一個字節的TCP緊急數據。Socket類的sendUrgentDate(int data)方法用於發送一個字節的TCP緊急數據。

4) OOBINLINE的默認值爲false,在這種情況下,當接收方收到緊急數據時不作任何處理,直接將其丟棄。如果用戶希望發送緊急數據,應該把OOBINLINE設爲true:socket.setOOBInline(true); 此時接收方會把接收到的緊急數據與普通數據放在同樣的隊列中。值得注意的是,除非使用一些更高層次的協議,否則接收方處理緊急數據的能力非常有限,當緊急數據到來時,接收方不會得到任何通知,因此接收方很難區分普通數據與緊急數據,只好按照同樣的方式處理它們。


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