在客戶/服務器通訊模式中,客戶端須要主動創建與服務器鏈接的Socket,服務器端收到客戶端的鏈接請求,也會建立與客戶端鏈接的Socket。Socket能夠看作是通訊鏈接兩端的收發器,客戶端和服務店都經過Socket來收發數據。java
一、構造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() 函數來綁定提供的本地地址及端口。app
public Socket(InetAddress address,int port)throws IOException建立一個流套接字並將其鏈接到指定 IP 地址的指定端口號。socket
public Socket(InetAddress address,int port,InetAddress localAddr,int localPort)throws IOException建立一個套接字並將其鏈接到指定遠程地址上的指定遠程端口。socket 會經過調用 bind() 函數來綁定提供的本地地址及端口。函數
除第一個外,其餘4個構造方法都會試圖和服務器創建鏈接,如何鏈接成功則返回Socket對象,若是鏈接失敗就會拋出IOException。測試
實例1:掃描主機上1到1024的端口,判斷這些端口是否被服務器監聽package com.hanfeng.socket; google
import java.io.IOException; spa
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對象:
<span style="white-space:pre"> </span>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的直接或間接子類,以下圖
實例:捕獲各類異常
/**
*
* @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);
}
}
二、獲取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();
}
}
三、關閉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();
四、半關閉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()
五、設置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); 此時接收方會把接收到的緊急數據與普通數據放在一樣的隊列中。值得注意的是,除非使用一些更高層次的協議,不然接收方處理緊急數據的能力很是有限,當緊急數據到來時,接收方不會獲得任何通知,所以接收方很難區分普通數據與緊急數據,只好按照一樣的方式處理它們。
在客戶/服務器通訊模式中,客戶端須要主動創建與服務器鏈接的Socket,服務器端收到客戶端的鏈接請求,也會建立與客戶端鏈接的Socket。Socket能夠看作是通訊鏈接兩端的收發器,客戶端和服務店都經過Socket來收發數據。
一、構造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的端口,判斷這些端口是否被服務器監聽
[java] view plain copy
1. package com.hanfeng.socket;
2.
3. import java.io.IOException;
4. import java.net.Socket;
5.
6. /**
7. * 掃描主機上1到1024的端口,判斷這些端口是否被服務器監聽
8. *
9. * @author hanfeng
10. * @data 2012-8-24 上午10:25:57
11. */
12. public class PortScanner {
13.
14. public static void main(String[] args) {
15. String host = "localhost";
16. if (args.length > 0) {
17. host = args[0];
18. }
19. new PortScanner().scan(host);
20. }
21.
22. public void scan(String host) {
23. Socket socket = null;
24.
25. for (int port = 1; port < 1024; port++) {
26. try {
27. socket = new Socket(host, port);
28. System.out.println("鏈接到端口:" + port);
29. } catch (IOException e) {
30. System.out.println("沒法鏈接到端口:" + port);
31. } finally {
32. try {
33. if (socket != null) {
34. socket.close();
35. }
36. } catch (Exception e) {
37. e.printStackTrace();
38. }
39. }
40. }
41. }
42.
43. }
1.1 設置等待鏈接超時時間
[java] view plain copy
1. public void scan(String host) {
2. Socket socket = null;
3. socket = new Socket();
4. SocketAddress address = new InetSocketAddress(host, 1158);
5. try {
6. socket.connect(address, 60000);
7. System.out.println("鏈接成功!");
8. } catch (IOException e) {
9. System.out.println("鏈接超時!");
10. e.printStackTrace();
11. }
12. }
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");
[java] view plain copy
1. public static void main(String[] args) {
2. try {
3. InetAddress address = InetAddress.getLocalHost();
4. System.out.println("本機IP地址:"+address);
5. InetAddress address2 = InetAddress.getByName("222.34.5.7");
6. System.out.println("返回遠程IP地址:"+address2);
7. InetAddress address3 = InetAddress.getByName("www.google.com");
8. System.out.println("返回域名IP地址:"+address3);
9. } catch (UnknownHostException e) {
10. e.printStackTrace();
11. }
12. }
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對象:
[java] view plain copy
1. <span style="white-space:pre"> </span>InetAddress remoteAddr = InetAddress.getByName("112.5.4.45");
2. InetAddress localAddr = InetAddress.getByName("112.5.4.3");
3. Socket socket = new Socket(remoteAddr,8000,localAddr,2345);
1.4客戶端鏈接服務器可能拋出的異常
UnknowHostException:若是沒法識別主機的名字或IP地址,就會拋出異常
ConnectException:若是沒有服務器進行監聽指定的端口,或則服務器進程拒絕鏈接,就會拋出異常
SocketTimeoutException:等待鏈接超時,拋出異常
BindExcrption:若是沒法把Socket對象與具體的本地IP地址或端口綁定,就會拋出異常
以上4個異常都是IOException的直接或間接子類,以下圖
實例:捕獲各類異常
[java] view plain copy
1. package com.hanfeng.socket;
2.
3. import java.io.IOException;
4. import java.net.BindException;
5. import java.net.ConnectException;
6. import java.net.InetSocketAddress;
7. import java.net.Socket;
8. import java.net.SocketAddress;
9. import java.net.SocketTimeoutException;
10. import java.net.UnknownHostException;
11.
12. /**
13. *
14. * @author hanfeng
15. * @data 2012-8-24 上午11:16:32
16. */
17. public class ConnectTest {
18.
19. public static void main(String[] args) {
20. String host = "www.google.com";
21. int port = 80;
22. if (args.length>1) {
23. host = args[0];
24. port = Integer.parseInt(args[1]);
25. }
26. new ConnectTest().connect(host, port);
27. }
28.
29. public void connect(String host,int port){
30. SocketAddress address = new InetSocketAddress(host, port);
31. Socket socket = null;
32. String result = "";
33.
34. try {
35. long begin = System.currentTimeMillis();//計算開始鏈接的時間
36. socket = new Socket();//開始創建鏈接
37. socket.connect(address, 6000);//設置鏈接超時時間
38. long end = System.currentTimeMillis();// 計算機鏈接結束的時間
39. result = (end-begin)+"ms";
40. } catch (BindException e) {
41. result = "IP地址或端口綁定異常!";
42. } catch (UnknownHostException e) {
43. result = "未識別主機地址!";
44. }catch (SocketTimeoutException e) {
45. result = "鏈接超時!";
46. }catch (ConnectException e) {
47. result = "拒絕鏈接!";
48. }catch (Exception e) {
49. result = "失敗啦!";
50. }finally{
51. if (socket!=null) {
52. try {
53. socket.close();
54. } catch (IOException e) {
55. e.printStackTrace();
56. }
57. }
58. }
59. System.out.println("遠程地址信息==>"+address+":"+result);
60. }
61. }
二、獲取Socket信息
如下方法用於獲取Socket的有關信息:
getInetAddress():得到遠程服務器的IP地址。
getPort():得到遠程服務器的端口。
getLocalAddress():得到客戶本地的IP地址。
getLocalPort():得到客戶本地的端口。
getInputStream():得到輸入流。若是Socket尚未鏈接,或者已經關閉,或者已經經過shutdownInput()方法關閉輸入流,那麼此方法會拋出IOException。
getOutputStream():得到輸出流。若是Socket尚未鏈接,或者已經關閉,或者已經經過shutdownOutput()方法關閉輸出流,那麼此方法會拋出IOException。
實例:HTTPClient類訪問網頁
[java] view plain copy
1. package com.hanfeng.socket;
2.
3. import java.io.BufferedReader;
4. import java.io.ByteArrayOutputStream;
5. import java.io.InputStream;
6. import java.io.InputStreamReader;
7. import java.io.OutputStream;
8. import java.net.Socket;
9.
10.
11. public class HttpClientDemo {
12. String host ="www.google.com";
13. int port = 80;
14. Socket socket = null;
15.
16. public void createSocket() throws Exception{
17. socket = new Socket(host, port);
18. }
19.
20. public void communicate() throws Exception{
21. StringBuffer sb=new StringBuffer("GET "+" HTTP/1.1\r\n");
22. // sb.append("Host: www.javathinker.org\r\n");
23. // sb.append("Accept: */*\r\n");
24. // sb.append("Accept-Language: zh-cn\r\n");
25. // sb.append("Accept-Encoding: gzip, deflate\r\n");
26. // sb.append("User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)\r\n");
27. // sb.append("Connection: Keep-Alive\r\n\r\n");
28.
29. //發出HTTP請求
30. OutputStream socketOut = socket.getOutputStream();
31. socketOut.write(sb.toString().getBytes());
32. socket.shutdownOutput();
33.
34. //接收響應結果
35. InputStream socketInput = socket.getInputStream();
36. BufferedReader buffer = new BufferedReader(new InputStreamReader(socketInput));
37. String data = null;
38. while ((data=buffer.readLine())!=null) {
39. System.out.println(data);
40. }
41. }
42.
43. /**
44. * @param args
45. * @throws Exception
46. */
47. public static void main(String[] args) throws Exception {
48. HttpClientDemo httpClient = new HttpClientDemo();
49. httpClient.createSocket();
50. httpClient.communicate();
51. }
52.
53. }
三、關閉Socket
當客戶與服務器的通訊結束,應該及時關閉Socket,以釋放Socket佔用的包括端口在內的各類資源。Socket的close()方法負責關閉Socket。當一個Socket對象被關閉,就不能在經過他的輸入流和輸出流進行I/O操做,不然會致使IOException。
爲了確保關閉Socket的操做總被執行,建議把這個操做放在finally代碼塊中。
[java] view plain copy
1. finally {
2. try {
3. if(socket!=null)socket.close();
4. } catch (IOException e) {
5. e.printStackTrace();
6. }
7. }
Socket類提供了3個狀態測試方法
isClosed():若是Socket已經鏈接到遠程主機,而且尚未關閉,則返回true
isConnected():若是Socket曾經鏈接到遠程主機,則返回true
isBound():若是Socket已經與一個本地端口綁定,則返回true
若是要判斷一個Socket對象當前是否處於鏈接狀態,能夠採用如下方式:
boolean isConnected = socket.isConnected()&&!socket.isClosed();
四、半關閉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()
五、設置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); 此時接收方會把接收到的緊急數據與普通數據放在一樣的隊列中。值得注意的是,除非使用一些更高層次的協議,不然接收方處理緊急數據的能力很是有限,當緊急數據到來時,接收方不會獲得任何通知,所以接收方很難區分普通數據與緊急數據,只好按照一樣的方式處理它們。