java網絡編程socket解析


轉載:http://www.blogjava.net/landon/archive/2013/07/02/401137.html

Java網絡編程精解筆記2:Socket詳解

Socket用法詳解
在C/S通信模式中,client需要主動創建於server連接的Socket(套接字).服務器端收到了客戶端的連接請求,也會創建與客戶連接的Socket.Socket可看做是通信兩端的收發器.server與client都通過Socket來收發數據.

1.構造Socket

1.Socket()
2.Socket(InetAddress address,int port) throws UnknownHostException,IOException
3.Socket(InetAddress addrss,int port,InetAddress localAddr,int localPort) throws IOException
4.Socket(String host,int port) throws UnknownHostException,IOException
5.Socket(String host,int port,InetAddress localAddr,int localPort) throws IOExcception

除了第一個不帶參數的構造方法外,其他構造方法都會試圖建立與服務器的連接.如果連接成功,就返回Socket對象.如果因為某些原因連接失敗,則拋出IOException.

2.設定等待建立連接的超時時間

1.客戶端的Socket構造方法請求與server連接時,可能要等待一段時間.默認會一直等待下去,直到連接成功或者出現異常.Socket構造方法請求連接時,受底層網絡傳輸速度的影響,可能處於長時間的等待狀態.
->希望限定等待連接的時間
->Socket socket = new Socket();
SocketAddress rermoteAddr = new InetSocketAddress("localhost",8000);
socket.connect(remoteAddr,60000);//設置等待建立連接的超時時間為1分鍾
->如果1分鍾之內連接成功,則connect順利返回.如果1分鍾之內出現異常,則拋出該異常.如果超過了1分鍾,即沒有連接成功,也沒有出現其他異常.則會拋出
SocketTimeoutException

2.Socket#connect(SocketAddress endpoint,int timeout).endpoint為服務器的地址,timeout設定超時時間.ms->
timeout為0,表示永遠不會超時.

3.設定服務器的地址.

Socket(InetAddress address,int port)
Socket(String host,int port)
InetAddress表示服務器的IP地址.->該類提供了一系列的靜態工廠方法.用於構造自身的實例.如
1.InetAdress addr1 = InetAdress.getLocalHost();
2.InetAddress addr2 = InetAddress.getByName("10.10.137.44");
3.InetAddress addr2 = InetAddress.getByName("www.javathinker.org");

4.設定客戶端的地址
1.在一個Socket對象中,即包含遠程服務器的IP和端口信息,也包含本地客戶端的IP地址和端口信息.默認情況下,客戶端的IP地址來自客戶程序所在的主機,而客戶端的端口則有操作系統隨機分配.
->Socket(InetAddress address,int port,InetAddress localAddress,int localPort) throws IOException
->Socket(String host,int port,InetAddress localAddress,int localPort) throws IOException
上兩個方法用來設置客戶端的ip端口和地址
->這種情況主要適用於一個主機同時屬於兩個以上的網絡,它可能擁有兩個以上的IP地址.如一個在Internet,一個在局域網.
->如果希望和局域網的服務器程序通訊,則可以以局域網的IP地址作為localAddress來構造Socket.

5.客戶連接服務器時可能拋出的異常

1.UnknownHostException:無法識別主機的名字或ip地址時,就會拋出此異常
2.ConnectException:如果沒有服務器監聽指定的端口;或者服務器進程拒接連接,則會拋出此異常.
3.SocketTimeoutException:如果等待連接超時,就會拋出此異常.
4.BindException:如無法把Socket對象與指定的本地IP地址或端口綁定則會拋出此異常.

IOException
-UnknownHostException
-InterruptedIOException
-SocketTimeoutException
-SocketException
-BindException
-ConnectException

6.獲取Socket的信息

1.同時包含了遠程服務器的IP和端口信息以及客戶本地的IP和端口信息.
2.獲取輸出流合輸入流,分別用於向服務器發送數據以及接收從服務端發來的數據.

1.getInetAddress():獲得遠程服務器的IP地址
2.getPort():獲得遠程服務器的端口
3.getLocalAddress():獲得客戶本地的IP地址
4.getLocalPort():獲得客戶本地的端口
5.getInputStream():獲得輸入流.如果Sokcet還沒有連接或已經關閉或者已經通過shutdownInput方法關閉輸入流,則才方法會拋出IOException.
6.getOutputStream(): 獲得輸入流..如果Sokcet還沒有連接或已經關閉或者已經通過shutdownOutput方法關閉輸出流,則才方法會拋出IOException.

7.關閉Socket

1.當client與Server通信結束,應該及時關閉Socktt,以釋放Socket占用的包括端口在內的各種資源.
2.Socket#close方法負責關閉socket.當一個Socket對象被關閉就不能通過其輸入和輸出流進行io操作.否則會導致IOException.
3.確保關閉Socket的操作就是被執行,建議把該操作放在finally代碼塊中.如
Socket socket = null;
try
{
socket = new Socket("www.thinker.org",80);
//執行接收和發送數據的操作
...
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
try
{
if(socket != null)
{
socket.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
}

4.Socket類提供了3個狀態測試方法.
1.isClosed()
1.public synchronized void close() throws IOException {
synchronized(closeLock) {
if (isClosed())
return;
if (created)
impl.close();
closed = true;
}
}
2.public boolean isClosed() {
synchronized(closeLock) {
return closed;// true if the socket has been closed
}
}

2.isConnected()
1.true if the socket was successfuly connected to a server
2. Note: Closing a socket doesn't clear its connection state, which means this method will return true for a closed socket
(see {@link #isClosed()}) if it was successfuly connected prior to being closed.
3.isBound()
1. true if the socket was successfuly bound to an address
2.Note: Closing a socket doesn't clear its connection state, which means this method will return true for a closed socket
(see {@link #isClosed()}) if it was successfuly bound prior to being closed.

4.判斷一個Socket對象是否處於連接狀態,用以下形式:
boolean isConnected = socket.isConnected() && !socket.isClosed()

8.半關閉Socket

A進程與B進程通過Socket通信.假定A輸出數據,B讀入數據.A如何告訴B所有數據已經輸出完畢:
1.A與B交換的是字符流,且一行一行的讀寫.可事先約定以一個特殊標志作為結束標志,如以"bye"作為結束標志.當A向B發送一行字符串"bye"時,B讀到這一行數據時,則停止讀數據.{@link EchoServer},{@link EchoClient}
2.進程A先發送消息,告訴B所發送正文的長度.->再發送正文.->B先獲知A發送的正文長度->接下來只要讀取完該長度的字符或者字節,就停止讀數據.
3.A發完所有數據后,關閉Socket->B讀取A發送的所有數據后->InputStream#read->該方法返回-1.->BufferedReader#readLine->返回null.
{@link HTTPClient}
4.Socket#close->輸入輸出流都被關閉->有時候希望僅關閉輸入流或輸出流之一->Socket半關閉方法->
shutdownInput():關閉輸入流
shutdownOutput():關閉輸出流
->B讀取數據時,如果A的輸出流已經關閉->B讀入所有數據后,就會讀到輸入流的末尾.
->先后調用Socket的shutdonwInput和shutdownOutput方法.僅僅是關閉了輸入流和輸出流,並不等價Socket#close.->通信結束后,依然要調用Socket的close方法.只有該方法才會釋放Socket占用的資源.如占用的本能地端口等.
5.Socket#isInputShutdown()->輸入流關閉,返回true.
Socket#isOutputShutdown()->輸出流關閉,返回true
6.client與Server通信時,如果有一方突然結束程序或者關閉了Socket或者單獨關閉了輸入流或輸出流.對另一方會造成什么影響.
{@link Sender} {@link Receiver}.

9.設置Socket選項

1.TCP_NODELAY:表示立即發送數據

1.public void setTcpNoDelay(boolean on) throws SocketException
2.public boolean getTcpNoDelay() throws SocketException
3.默認情況,發送數據采用Negale算法.即指發送方發送的數據不會立即發出,而是先放到緩沖區內.等緩沖區區慢了再發出.->發送完一批數據等待接收方對這批數據的回應.->再發送下一批數據.->適用於發送方需要發送大批量數據,且接收方會及時回應的場合->通過減少傳輸數據的次數來提高通信效率.
->對於發送方持續發送小批量數據,且接收方不一定立即發送響應->該算法會使發送方運行很慢->如實時網絡游戲
4.TCP_NODELAY默認值為false->即表示采用Negale算法.->setTcpNoDelay(true)->關閉Socket緩存,確保數據及時發送
5.if(!socket.getTcpNoDelay()){socket.setTcpNoDelay(true)}
6.Socket底層不支持該選項,則拋出SocketException.

2.SO_REUSEADDR:表示是否允許重用Socket所綁定的本地地址

1.public void setReuseAddress(boolen on) throws SocketException
2.public boolean getReuseAddress() throws SocketException
3.接收方通過Socket#close關閉Socket->如果網絡上還有發送到這個Socket的數據,那么底層的Socket不會立刻釋放本地端口->會等待一段時間->確保接收到了網絡上發送過來的延遲數據->釋放端口->Socket收到延遲數據后,不會對這些數據做任何處理->Socket接收延遲數據的目的->確保這些數據不會被其他恰巧綁定到同樣端口的新進程接收到.
4.客戶端程序一般采用隨機端口->出現兩個client程序綁定到同樣端口的可能性不大
5.server程序采用固定端口->server關閉后,其端口可能還會被占用一段時間->此時如果重啟程序,端口已經被占用->使得程序無法綁定到給端口->啟動失敗
6.確保一個進程關閉Socket后,即使其還未釋放端口->同一個主機上的其他進程還可以立即重用該端口->
if(!socket.getReuseAddress()){socket.setReuseAddress(true)}
7.該方法必須在Socket還未綁定到一個本地端口之前調用.否則無效.
1.Socket socket = new Socket();
socket.setResueAddress(true);
socket.connect(new InetSocketAddress("localhost",8080));
2.Socket socket = new Socket();
socket.setResueAddress(true);
socket.bind(new InetSocketAddress("localhost",9000));
socket.connect(new InetSocketAddress("remotehost",8000));
8.兩個公用一個端口的進程必須都調用socket.SetResueAddress(true)->才能使得一個進程關閉Socket后,另一個進程的Socket能立即重用相同端口.
9.當多個ServerSocket對象同時綁定一個端口時,系統會隨機選擇一個ServerSocket對象來接收客戶端請求->接收客戶端請求的ServerSocket對象必須關閉才能輪到其他的ServerSocket對象接收客戶端請求。如果不關閉這個ServerSocket對象,那么其他的ServerSocket對象將永遠無法接收客戶端請求

3.SO_TIMEOUT:表示接收數據時的等待時間

1.public void setSoTimeout(int milliseconds) throws SocketException
2.public int getSoTimeout() throws SocketException
3.通過Socket的輸入流讀數據時,如果還未有數據,則等待:
如:
byte[] buff = new byte[1024];
InputStream in = socket.getInputStream();
in.read(buff);
->輸入流沒有數據,則in.read(buff)就會等待發送方發送數據->結束等待條件:
1.輸入流中有1024個字節->read將其讀到buff中->返回讀到的字節數
2.距離輸入流末尾還有小雨1024個字節->read讀到buff中,返回讀到的字節數
3.讀到輸入流的末尾
4.連接已經斷開,拋出IOException
5.Socket#setSoTimeout設置了等待超時時間,超過這一時間則拋出SocketTimeoutException
4.該選項用於設定接收數據的等待超時時間,單位為毫秒->默認值為0,表示無限等待,永遠不會超時.
5.該方法必須在接收數據之前執行才有效.
6.輸入流的read方法拋出SocketTimeoutException后,Socket依然是連接的->可嘗試再次讀取數據->
socket.setTimeout(3 * 60 * 1000);
byte[] buff = new byte[1024];
InputStream in = socket.getInputStream();

int len = -1;

do
{
try
{
len = in.read(buff);
// 處理讀到的數據
...
}
catch(SocketTimeoutException e)
{
e.printStackTrace();
len = 0;
}
}
while(len != -1)

4.SO_LINGER:表示當執行Socket的close方法時,是否立即關閉底層Socket

1.public void setSoLinger(boolean on,int seconds) throws SocketException
2.public int getSoLinger() throws SocketException
3.該選項用來控制Socket關閉時的行為->默認執行Socket的close,該方法會立即返回.但是底層的Socket不立即關閉,會延遲一段時間,知道發送完所有剩余的數據->真正關閉socket->斷開連接.
4.socket.setSoLinger(true,0)->執行Socket#close時,該方法立即返回且底層的Socket也會立即關閉->所有未發送完的剩余數據被丟棄.
5.socket.setSoLinger(true,60)->
1.Socket#close->該方法不會立即返回->進入阻塞狀態
2.底層的Socket會嘗試發送剩余的數據->返回條件:
1.底層的Socket已經發送完所有剩余數據
2.盡管底層的Socket還沒有發送完所有的剩余數據->但是已經阻塞了60秒->也會返回->剩余未發送的數據將被丟棄
1.->close返回后->底層的Socket會被關閉,斷開連接
2.setSoLinger(boolean on,int seconds)->seconds參數以秒為單位->
6.程序通過輸出流寫數據時,->僅表示程序向網絡提交了一批數據->由網絡負責輸送到到接收方->程序關閉Socket時,有可能這批數據還在網絡上傳輸,未達到接收方->未發送完的數據指還在網絡上傳輸未被接收方接收的數據
{@link TestLingerClient} {@link TestLingerServer}

5.SO_SNDBUF:表示發送數據的緩沖區大小
1.public void setSendBufferSize(int size) throws SocketException
2.public int getSendBufferSize() throws SocketException
3.該選項用來表示Socket用於輸出數據的緩沖區的大小->底層Socket不支持該選項->set 拋出SocketException

6.SO_RCVBUF:表示接收數據的緩沖區大小
1.public void setReceiveBufferSize(int size) throws SocketException
2.public int getReceiveBufferSize() throws SocketException
3.該選項用來表示Socket的用於輸入數據的緩沖區的大小->傳輸大的連續的數據塊,如基於HTTP和FTP協議的通信,可以使用較大緩沖區->減少數據傳輸的次數->提高傳輸數據的效率->對於交互頻繁且單次傳送數據量比較小的通信方式如Telnet和網絡游戲,則應該采用交換緩沖區.確保小批量的數據能及時發送給對方.-->設定緩沖區大小的原則使用與SO_SNDBUf選項
4.底層Socket不支持該選項->set 拋出SocketException.


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

1.public void setKeepAlive(boolean on) throws SocketException
2.public boolean getKeepAlive() throws SocketException
3.該選項為true->底層的TCP實現會監視該連接是否有效->當連接處於空閑狀態(連接的兩端沒有互相傳送數據)->超過2小時->本地的TCP實現會發送一個數據包一個遠程的Socket->遠程Socket沒有發回響應->TCP實現持續嘗試11分鍾->直到接收到響應->12分鍾內未收到響應->TCP實現就會自動關閉本地Socket,斷開連接->不同的網絡平台,TCP實現嘗試與遠程Socket對話的實現會有所差別.
4.該選項為false->表示TCP不會監視連是否有效->不活動的client可能會永久存在下去->而不會注意server已經崩潰
5.if(!socket.getKeepAlive()){socket.setKeepAlive(true)}

8.OOBINLINE:表示是否支持發送一個字節的TCP緊急數據
//注OOB:out-of-band 帶外
1.public void setOOBInline(boolean on) throws SocketException
2.public int getOOBInline() throws SocketException
3.該選項為true,表示支持發送一個自己的TCP緊急數據->Socket#sendUrgentData(int data),用於發送一個字節的TCP緊急數據
4.該選項為false->接收方收到緊急數據時不做處理,直接丟棄->需要socket.setOOBInline(true)->接收方會將接收到的緊急數據與普通數據放在同樣的隊列->注:除非采用更高層次的協議,否則接收方處理緊急數據的能力非常有限->緊急數據到來時,接收方不會得到任何通知->因此很難區分普通數據與緊急數據->只好按照同樣的方式處理.


10.服務類型選項

1.用戶去郵局時,可選擇不同的服務->發送普通信 | 掛號信 | 快件->價格,發送速度及可靠性均不同.
2.Internet上傳輸數據也分為不同的服務器類型.->如發送視頻需要較高的寬帶,快速到達目的,保證接收方看到連續的畫面.
3.IP規定了4種服務類型,定性的描述服務的質量:
1.低成本->發送成本低.
2.高可靠性->保證把數據可靠的送達目的地.
3.最高吞吐量->一次性可以接收或發送大批量的數據
4.最小延遲->傳輸數據的速度要快,把數據快速送達目的地.
4.4種服務類型可以組合->即可進行或運算
IPTOS_LOWCOST (0x02)
IPTOS_RELIABILITY (0x04)
IPTOS_THROUGHPUT (0x08)
IPTOS_LOWDELAY (0x10)
{@link Socket#setTrafficClass}
5.public void setTrafficClass(int trafficClass) throws SocketException
public int getTrafficClass() throws SocketException

11.設置連接時間,延遲和帶寬的相對重要性(注意相對二字)
1.Socket#setPerformancePreferences(int connectionTime,int latency,int bandwidth)
2.3個參數為網絡傳輸數據的3項指標
1.connectionTime-表示用最少時間建立連接
2.latency-表示最小延遲
3.bandwidth-表示最高帶寬
->三項指標的相對重要性.->3項參數的整數之前的相對大小決定了響應參數的相對重要性.
如setPerformancePreferences(2,1,3)->則表示最高帶寬最重要,其實是最少連接時間,最后是最小延遲.

12.小結:
1.通信過程中,如果發送方沒有關閉Socket,就突然中止程序,則接收方在接收數據時會拋出SocketException.
2.發送方發送完數據后,應該及時關閉Socket或關閉Socket的輸出流,這樣,接收方就能順利讀到輸入流的末尾.

部分源代碼:

package com.game.landon.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;

/**
*
*測試Socket連接服務器可能拋出的異常
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-10-9
*
*/

public class ConnectTester
{
public static void main(String[] args)
{
//默認的host+port
String host = "localhost";
int port = 8000;

//通過main參數解析host+port
if(args.length > 1)
{
host = args[0];
port = Integer.parseInt(args[1]);
}

new ConnectTester().connect(host, port);
}

public void connect(String host,int port)
{
SocketAddress remoteAddress = new InetSocketAddress(host,port);
Socket socket = null;
String result = "";

try
{
long begin = System.currentTimeMillis();
socket = new Socket();//這里未指定任何參數
// socket.connect(remoteAddress,5000);//設置超時時間為5秒
socket.connect(remoteAddress,100);//超時時間設短,用來測試SocketTimeoutException
long end = System.currentTimeMillis();
result = (end - begin) + "ms";//計算連接所化的時間
}
catch(BindException e)//綁定異常
{
result = "Local address and port can't be binded";
//1.調用Socket#bind方法綁定本地IP|端口
//2.Socket構造方法中指定本地IP|端口
//->如果本地主機不具有IP地址或者端口已經被占用,則會拋出此異常
}
catch(UnknownHostException e)//無法識別主機server的ip地址
{
result = "Unknown host";//測試參數 unknownhost 80
}
catch(ConnectException e)//如果沒有服務器進程監聽指定的端口,或者服務器進程拒絕連接,就會拋出這種異常
{
result = "Connection refused";//測試參數1: localhost 7777(沒有服務器進程監聽7777端口) 測試2:server指定連接請求隊列的長度
}
catch(SocketTimeoutException e)//服務器超時就會拋出此異常
{
result = "Timeout";// 測試參數 www.javathinker.org 80
}
catch(IOException e)
{
result = "failure";
}
finally
{
try
{
if(socket != null)
{
socket.close();
}
}
catch(IOException e)
{
e.printStackTrace();
}
}

//打印結果
System.out.println(remoteAddress + " : " + result );
}
}

 

package com.game.landon.socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;

/**
*
*Socket連接超時
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-9
*
*/

public class ConnectTimeout
{
public static void main(String[] args)
{
try
{
Socket socket = new Socket();
SocketAddress serverAddr = new InetSocketAddress("localhost", 8000);

socket.connect(serverAddr, 60 * 1000);//指定1分鍾超時時間
}
catch(SocketTimeoutException timeoutException)
{
System.out.println("connect localhost:8000 timeout in 1minutes:" + timeoutException);
}
catch(IOException ioException)
{
System.out.println("connect localhost:8000 fail:" + ioException);
}
}
}

 

package com.game.landon.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;

/**
*
*測試連接到一個http服務器,然后發送http協議的請求,接着接收從http服務器發回的響應結果
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-10-9
*
*/

public class HTTPClient
{
String host = "www.javathinker.org";
int port = 80;
Socket socket;

public void createSocket() throws Exception
{
socket = new Socket(host,port);
}

//訪問網頁www.javathinker.org/index.jsp
public void communication() throws Exception
{
//組裝http請求協議
StringBuffer sb = new StringBuffer("GET " + "/index.jsp" + " 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;Window NT 5.0)\r\n");
sb.append("Connection:Keep-Alive\r\n\r\n");

//發出http請求->request
OutputStream socketOut = socket.getOutputStream();
// 發送數據時,先把字符串形式的請求信息轉換為字節數組,即字符串的編碼 sb.toString().getBytes()
socketOut.write(sb.toString().getBytes());
socket.shutdownOutput();//關閉輸出流

//接收響應結果->response
InputStream socketIn = socket.getInputStream();
// 接收數據時把接收到的字節寫到一個ByteArrayOutputSteam中,其是一個容量能夠自動增長的緩沖區.
//socketIn.read(buff)返回-1,則表示獨到了輸入流的末尾
// 問題,如果接收的網頁數據量很大,則先把這些數據全部保存在ByteArrayOutputSteam,很不明智,因為這些數據會占用大量內存.->
//更有效的做法是利用BufferReader來逐行讀取數據
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len = -1;

while((len = socketIn.read(buff)) != -1)
{
//將buff寫入buffer
buffer.write(buff, 0, len);
}

System.out.println(new String(buffer.toByteArray()));//把字節數組轉為字符串
socket.close();
}

//利用BufferReader逐行讀取數據
public void communication2() throws Exception
{
//組裝http請求協議
StringBuffer sb = new StringBuffer("GET " + "/index.jsp" + " 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;Window NT 5.0)\r\n");
sb.append("Connection:Keep-Alive\r\n\r\n");

//發出http請求->request
OutputStream socketOut = socket.getOutputStream();
// 發送數據時,先把字符串形式的請求信息轉換為字節數組,即字符串的編碼 sb.toString().getBytes()
socketOut.write(sb.toString().getBytes());
socket.shutdownOutput();//關閉輸出流

//接收響應結果->response
InputStream socketIn = socket.getInputStream();
// 問題,如果接收的網頁數據量很大,則先把這些數據全部保存在ByteArrayOutputSteam,很不明智,因為這些數據會占用大量內存.->
//更有效的做法是利用BufferReader來逐行讀取數據

BufferedReader br = new BufferedReader(new InputStreamReader(socketIn));
String data;

while((data = br.readLine()) != null)
{
System.out.println(data);
}

socket.close();
}

public static void main(Stringargs) throws Exception
{
HTTPClient client = new HTTPClient();
client.createSocket();
// client.communication();
client.communication2();
}
}

 

package com.game.landon.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;

/**
*
*發送郵件的SMTP客戶程序
*
*<pre>
*1.SMTP-Simple Mail Transfer Protocol,簡單郵件傳輸協議,應用層協議,建立在TCP/IP協議基礎之上.
*2.RFC821
*3.SMTP服務器默認監聽25端口.客戶程序請求發送郵件,服務器負責將郵件傳輸到目的地.
*4.client會發送一系列SMTP命令,服務器會做出響應,返回應答碼及對應答碼的描述
*<pre>
*
*<output>
Server>220 EX-01.hec.intra Microsoft ESMTP MAIL Service ready at Wed, 26 Jun 2013 12:56:47 +0800
Client>HELO PC
Server>250 EX-01.hec.intra Hello [10.130.137.44]
Client>MAIL FROM:<wenyong.lv@happyelements.com>
Server>550 5.7.1 Client does not have permissions to send as this sender
Client>RCPT TO:<wenyong.lv@happyelements.com>
Server>503 5.5.2 Need mail command
Client>DATA
Server>503 5.5.2 Need mail command
Client>Subject:hello
I just test smtp using java.
Client>.
Server>500 5.3.3 Unrecognized command
Client>QUIT
Server>500 5.3.3 Unrecognized command
*</output>
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-25
*
*/

public class MailSender
{
private String smtpServer = "EX-01.hec.intra";//公司郵箱服務器的域名
private int port = 25;

private PrintWriter getWriter(Socket socket) throws IOException
{
OutputStream socketOut = socket.getOutputStream();
return new PrintWriter(socketOut, true);
}

private BufferedReader getReader(Socket socket) throws IOException
{
InputStream socketIn = socket.getInputStream();
return new BufferedReader(new InputStreamReader(socketIn));
}

/**
*
* 發送一行字符串並接收服務器的響應數據
*
* @param str
* @param reader
* @param writer
*/
private void sendAndReceive(String str,BufferedReader reader,PrintWriter writer) throws IOException
{
if(str != null)
{
System.out.println("Client>" + str);
writer.println(str);//是println.需要發送\r\n
}

String response;

if((response = reader.readLine()) != null)
{
System.out.println("Server>" + response);
}
}

/**
*
* 發送郵件
*
* @param msg
*/
public void sendMail(Message msg)
{
Socket socket = null;

try
{
socket = new Socket(smtpServer,port);//連接至郵件服務器

BufferedReader reader = getReader(socket);
PrintWriter writer = getWriter(socket);

String localhost = InetAddress.getLocalHost().getHostName();

//因為連接成功時,SMTP服務器會返回一個應答碼為220的響應,表示就緒.
//214-幫助信息 220-服務就緒 221-服務關閉 250-郵件操作完成 354-開始輸入郵件內容,以.結束 421-服務未就緒,關閉傳輸通道
//501 命令參數格式錯誤 502 命令不支持 503 錯誤的命令序列 504 命令參數不支持
sendAndReceive(null, reader, writer);//為了接收服務器的響應數據

sendAndReceive("HELO " + localhost, reader, writer);//HELO | EHLO表示郵件發送者的主機地址
sendAndReceive("MAIL FROM:<" + msg.from + ">", reader, writer);//郵件發送者的郵件地址
sendAndReceive("RCPT TO:<" + msg.to + ">", reader, writer);//郵件接收者送者的郵件地址

//郵件內容
sendAndReceive("DATA", reader, writer);
writer.println(msg.data);

System.out.println("Client>" + msg.data);

// 發送完畢
sendAndReceive(".", reader, writer);

// 退出
sendAndReceive("QUIT", reader, writer);
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
try
{
if(socket != null)
{
socket.close();
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
}

public static void main(Stringargs)
{
Message message = new Message("wenyong.lv@happyelements.com",
"wenyong.lv@happyelements.com",
"hello",
"I just test smtp using java.");

new MailSender().sendMail(message);
}
}

/**
*
* 一封郵件消息
*
* @author landon
*
*/
class Message
{
/** 發送者地址 */
String from;
/** 接收者地址 */
String to;
/** 標題 */
String subject;
/** 正文 */
String content;
/** 數據<標題+正文> */
String data;

public Message(String from,String to,String subject,String content)
{
this.from = from;
this.to = to;
this.subject = subject;
this.content = content;

data = "Subject:" + subject + "\r\n" + content;
}

}

 

package com.game.landon.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;

/**
*
*有些SMTP服務器要求客戶提供身份認證信息.本例使用126郵箱進行測試,向qq郵箱測試,注意發送主題和內容要正規一些
*否則發送時會被認為是垃圾郵件,不被發送
*
*1.先發送EHLO
*2.發送AUTH LOGIN
*3.采用Base64編碼用戶名和口令.即可通過認證.
*
*{@link MailSender}
*Server>550 5.7.1 Client does not have permissions to send as this sender
*
*<output>
Server>220 126.com Anti-spam GT for Coremail System (126com[20121016])
Client>HELO PC
Server>250 OK
Client>AUTH LOGIN
Server>334 dXNlcm5hbWU6
Client>c210cGxhbmRvbg==
Server>334 UGFzc3dvcmQ6
Client>YTEyMzQ1Ng==
Server>235 Authentication successful
Client>MAIL FROM:<smtplandon@126.com>
Server>250 Mail OK
Client>RCPT TO:<340706410@qq.com>
Server>250 Mail OK
Client>DATA
Server>354 End data with <CR><LF>.<CR><LF>
Client>Subject:hello,我是stmplandon,測試一下stmp
I just test smtp using java.ok??
Client>.
Server>250 Mail OK queued as smtp4,jdKowECpUWU+gcpR0HQUCA--.1261S2 1372225854
Client>QUIT
Server>221 Bye
*</output>
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-26
*
*/

public class MailSenderWithAuth
{
private String smtpServer = "smtp.126.com";//使用126郵箱進行測試
private int port = 25;

private PrintWriter getWriter(Socket socket) throws IOException
{
OutputStream socketOut = socket.getOutputStream();
return new PrintWriter(socketOut, true);
}

private BufferedReader getReader(Socket socket) throws IOException
{
InputStream socketIn = socket.getInputStream();
return new BufferedReader(new InputStreamReader(socketIn));
}

/**
*
* 發送一行字符串並接收服務器的響應數據
*
* @param str
* @param reader
* @param writer
*/
private void sendAndReceive(String str,BufferedReader reader,PrintWriter writer) throws IOException
{
if(str != null)
{
System.out.println("Client>" + str);
writer.println(str);//是println.需要發送\r\n
}

String response;

if((response = reader.readLine()) != null)
{
System.out.println("Server>" + response);
}
}

/**
*
* 發送郵件
*
* @param msg
*/
public void sendMail(Message msg)
{
Socket socket = null;

try
{
socket = new Socket(smtpServer,port);//連接至郵件服務器

BufferedReader reader = getReader(socket);
PrintWriter writer = getWriter(socket);

String localhost = InetAddress.getLocalHost().getHostName();

//因為連接成功時,SMTP服務器會返回一個應答碼為220的響應,表示就緒.
//214-幫助信息 220-服務就緒 221-服務關閉 250-郵件操作完成 354-開始輸入郵件內容,以.結束 421-服務未就緒,關閉傳輸通道
//501 命令參數格式錯誤 502 命令不支持 503 錯誤的命令序列 504 命令參數不支持
sendAndReceive(null, reader, writer);//為了接收服務器的響應數據

sendAndReceive("HELO " + localhost, reader, writer);//HELO | EHLO表示郵件發送者的主機地址

sendAndReceive("AUTH LOGIN", reader, writer);//認證命令

// 新注冊的一個126賬號
String userName = new sun.misc.BASE64Encoder().encode("smtplandon".getBytes());
String pwd = new sun.misc.BASE64Encoder().encode("a123456".getBytes());

sendAndReceive(userName, reader, writer);
sendAndReceive(pwd, reader, writer);

sendAndReceive("MAIL FROM:<" + msg.from + ">", reader, writer);//郵件發送者的郵件地址
sendAndReceive("RCPT TO:<" + msg.to + ">", reader, writer);//郵件接收者送者的郵件地址

//郵件內容
sendAndReceive("DATA", reader, writer);
writer.println(msg.data);

System.out.println("Client>" + msg.data);

// 發送完畢
sendAndReceive(".", reader, writer);

// 退出
sendAndReceive("QUIT", reader, writer);
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
try
{
if(socket != null)
{
socket.close();
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
}

public static void main(Stringargs)
{
Message message = new Message("smtplandon@126.com",
"340706410@qq.com",
"hello,我是stmplandon,測試一下stmp",
"I just test smtp using java.ok??");

new MailSenderWithAuth().sendMail(message);
}
}

 

package com.game.landon.socket;

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

/**
*
*掃描1到1024的端口,用Socket連接這些端口,如果Socket對象創建成功,則說明在這些端口中有服務器程序在監聽
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-9-27
*
*/

public class PortScanner
{
public static void main(Stringargs)
{
String host = "localhost";

new PortScanner().scan(host);
}

/**
*
* 掃描指定的Host下的各端口的服務器程序
*
* @param host
*/
public void scan(String host)
{
Socket socket = null;

for(int port = 0;port < 1024;port++)
{
try
{
//該構造方法就會試圖建立與服務器的連接
socket = new Socket(host,port);
System.out.println("There is a Server on Port:" + port);
}
catch(IOException e)
{
System.out.println("Can't connect to port:" + port);
}
finally
{
try
{
if(socket != null)
{
socket .close();
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
}
}

 

package com.game.landon.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.TimeUnit;

/**
*
*接收數據的服務器程序,每隔1秒接收一行字符串.共接收20行字符串
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-14
*
*/

public class Receiver
{
private int port = 8000;
private ServerSocket serverSocket;

private static int stopWay = 1;

private final int NATURAL_STOP = 1;
private final int SUDDEN_STOP = 2;
private final int SOCKET_STOP = 3;
private final int INPUT_STOP = 4;

/** 關閉ServerSocket,再結束程序 */
private final int SERVERSOCKET_STOP = 5;

public Receiver() throws IOException
{
serverSocket = new ServerSocket(port);
System.out.println("server has started.");
}

private BufferedReader getReader(Socket socket) throws IOException
{
InputStream socketIn = socket.getInputStream();

return new BufferedReader(new InputStreamReader(socketIn));
}

public void receive() throws Exception
{
Socket socket = null;
socket = serverSocket.accept();

BufferedReader br = getReader(socket);

for(int i = 0;i < 20;i++)
{
//client socket close后,readLine則會拋出此異常
// Exception in thread "main" java.net.SocketException: Connection reset
// at java.net.SocketInputStream.read(Unknown Source)
// at sun.nio.cs.StreamDecoder.readBytes(Unknown Source)
// at sun.nio.cs.StreamDecoder.implRead(Unknown Source)
// at sun.nio.cs.StreamDecoder.read(Unknown Source)
// at java.io.InputStreamReader.read(Unknown Source)
// at java.io.BufferedReader.fill(Unknown Source)
// at java.io.BufferedReader.readLine(Unknown Source)
// at java.io.BufferedReader.readLine(Unknown Source)
// at com.game.landon.socket.Receiver.receive(Receiver.java:59)
String msg = br.readLine();
System.out.println("receive:" + msg);

TimeUnit.MILLISECONDS.sleep(1000);

if(i == 2)
{
//2.通過提前停止receiver.發現Sender依然會發送全部的20行字符.
//因為進入Receiver結束運行,但是底層的Socket並沒有立即釋放本地端口.OS檢測還沒有發送給Socket的數據,會使底層Socket繼續占用本地端口一段時間
if(stopWay == SUDDEN_STOP)
{
System.out.println("sudden stop");
System.exit(0);
}
else if(stopWay == SOCKET_STOP)
{
System.out.println("close socket and stop");
socket.close();
break;
}
else if(stopWay == INPUT_STOP)
{
System.out.println("shutdown the input and stop");
socket.shutdownInput();
break;
}
else if(stopWay == SERVERSOCKET_STOP)
{
System.out.println("close serverSocket and stop");
serverSocket.close();
break;
}
}
}

//1.server和client均已正常結束方式運行的話,因為二者sleep的時間不同.所以server可能再次read的時候會出現異常:
//Exception in thread "main" java.net.SocketException: Connection reset
//at java.net.SocketInputStream.read(Unknown Source)
//這樣的話,其實server可能會丟失讀了部分數據(Connection reset.Client的Socket已經close了->client的數據可能還在網絡傳輸,即還未被接收方接收).
//查一下是否是Socket選項問題
if(stopWay == NATURAL_STOP)
{
socket.close();
serverSocket.close();
}
}

public static void main(String[] args) throws Exception
{
if(args.length > 0)
{
stopWay = Integer.parseInt(args[0]);
}

new Receiver().receive();
}
}

 

package com.game.landon.socket;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;

/**
*
*讀取SendClient發送來的數據,直到抵達輸入流的末尾
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-18
*
*/

public class ReceiveServer
{
public static void main(Stringargs) throws Exception
{
ServerSocket serverSocket = new ServerSocket(8000);

Socket socket = serverSocket.accept();
// 設置接收數據的等待時間
socket.setSoTimeout(3 * 1000);

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

int len = -1;

do
{
try
{
//1.啟動ReceiveServer再啟動SendClient.->因為client至發送了helloworld.所以不能讀到足夠的數據填滿buff.->一直等待->client睡眠結束,關閉Socket
//->ReceiverServer讀到輸入流末尾->立即結束等待->read返回-1.
//2.啟動ReceiveServer再啟動SendClient->in.read一直在等待->在client隨眠期間,關掉client->拋出Exception in thread "main" java.net.SocketException: Connection reset
//3.socket.setSoTimeout(3 * 1000)->加上這個后,in.read則會超時拋出異常
len = in.read(buff);

if(len != -1)
{
bufferStream.write(buff, 0, len);
}
}
catch(SocketTimeoutException e)
{
System.err.println("read timeout");
len = 0;
}
}
while(len != -1);

System.out.println(new String(bufferStream.toByteArray()));
}
}

 

package com.game.landon.socket;

import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.TimeUnit;

/**
*
*發送字符串->sleep->關閉Socket
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-18
*
*/

public class SendClient
{
public static void main(Stringargs) throws Exception
{
Socket socket = new Socket("localhost",8000);
OutputStream out = socket.getOutputStream();

out.write("hello".getBytes());
out.write("world".getBytes());

// sleep
TimeUnit.MILLISECONDS.sleep(5 * 1000);

socket.close();
}
}

 

package com.game.landon.socket;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.concurrent.TimeUnit;

/**
*
* 發送數據的客戶程序,每隔500毫秒發送一行字符串.共發送20行字符串
*
* @author landon
* @since 1.6.0_35
* @version 1.0.0 2013-6-14
*
*/

public class Sender
{
private String host = "localhost";
private int port = 8000;
private Socket socket;

/** 結束通信的方式 */
private static int stopWay;

/** 自然結束 */
private final int NATURAL_STOP = 1;
/** 突然終止程序 */
private final int SUDDEN_STOP = 2;
/** 關閉Socket,再結束程序 */
private final int SOCKET_STOP = 3;
/** 關閉輸出流,再結束程序 */
private final int OUTPUT_STOP = 4;

public static void main(Stringargs) throws Exception
{
if(args.length > 0)
{
stopWay = Integer.parseInt(args[0]);
}

new Sender().send();
}

public Sender() throws IOException
{
socket = new Socket(host, port);
}

private PrintWriter getWriter(Socket socket) throws IOException
{
return new PrintWriter(socket.getOutputStream(), true);
}

public void send() throws Exception
{
PrintWriter pw = getWriter(socket);

for(int i = 0;i < 20;i++)
{
String msg = "hello_" + i;
pw.println(msg);

System.out.println("send:" + msg);

TimeUnit.MILLISECONDS.sleep(500);

if(i == 2)
{
//1.sender突然中止,server會拋出:Exception in thread "main" java.net.SocketException: Connection reset
//at java.net.SocketInputStream.read(Unknown Source)
if(stopWay == SUDDEN_STOP)
{
System.out.println("sudden stop");
System.exit(0);
}
else if(stopWay == SOCKET_STOP)
{
System.out.println("socket close");
socket.close();
break;
}
else if(stopWay == OUTPUT_STOP)//2.如果send以這種方式運行,則server會出現:receive:null receive:null receive:null.
//因為已經shutdownOutput.server調用readLine方法時讀到了輸入流的末尾,因為返回null
{
System.out.println("socket shutdown outputstream");
socket.shutdownOutput();
break;
}
}
}

if(stopWay == NATURAL_STOP)
{
socket.close();
}
}
}

 

package com.game.landon.socket;

import java.net.Socket;

/**
*
*simple client,用來測試服務器的連接請求隊列的長度
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-10-9
*
*/

public class SimpleClient
{
public static void main(Stringargs) throws Exception
{
Socket s1 = new Socket("localhost",8000);
System.out.println("第一次連接成功");
Socket s2 = new Socket("localhost",8000);
System.out.println("第二次連接成功");
//這里會拋出異常
//Exception in thread "main" java.net.ConnectException: Connection refused: connect
Socket s3 = new Socket("localhost",8000);
System.out.println("第三次連接成功");

}
}

 

package com.game.landon.socket;

import java.net.ServerSocket;

/**
*
*一個SimpleServer,用來測試服務器的連接請求隊列的長度
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-10-9
*
*/

public class SimpleServer
{
public static void main(Stringargs) throws Exception
{
//設置連接請求隊列的長度為2
ServerSocket serverSocket = new ServerSocket(8000,2);//ServerSocket(int port,int backlog)
Thread.sleep(6 * 60 * 1000);//sleep 6分鍾

// 個人認為這個連接請求隊列只有在server端將連接的socket斷掉后,才會從隊列移除(屬個人猜測)
}
}

 

package com.game.landon.socket;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
*
*測試BindException
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-10-9
*
*/

public class TestBindException
{
public static void main(Stringargs) throws Exception
{
Socket socket = new Socket();
//直接運行程序,即拋出Exception in thread "main" java.net.BindException: Cannot assign requested address: JVM_Bind
// socket.bind(new InetSocketAddress(InetAddress.getByName("10.10.0.0"),5678));

// 拋出異常:Exception in thread "main" java.net.BindException: Address already in use: JVM_Bind
socket.bind(new InetSocketAddress("127.0.0.1", 3306));//3306為mysql所占端口

// Socket socket = new Socket("localhost",80,InetAddress.getByName("10.10.0.0"),5678);
}
}

 

package com.game.landon.socket;

import java.io.OutputStream;
import java.net.Socket;

/**
*
*測試SO_LINGER選項的一個client.發送100個字符(10000個的話控制台就顯示不出來了)給Server.然后調用close關閉Socket
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-18
*
*/

public class TestLingerClient
{
public static void main(Stringargs) throws Exception
{
Socket socket = new Socket("localhost",8000);

// socket.setSoLinger(true, 0);
socket.setSoLinger(true, 60);

OutputStream out = socket.getOutputStream();
StringBuilder builder = new StringBuilder();

for(int i = 0;i < 100;i++)
{
builder.append(i);
}

//1.注釋掉兩句setSoLinger的代碼->啟動Server再啟動client.
//1.close方法立即返回 輸出close socket cost Time:0 ms
//2.因為server執行了sleep->client已經執行了close且client程序本身也結束了->但是server依然受到了全部所有的數據.
//因為client執行了Socket#close后,底層的Socekt其實並沒有真正關閉,與server的連接仍然存在.底層的Socket會存在一段時間,知道發送完所有的數據.
//2.socket.setSoLinger(true, 0)->先后啟動server|client.->client執行Socket#close時會強制關閉底層Socket.->所有未發送數據丟失.->Server
//結束休眠后,讀數據拋出異常->Exception in thread "main" java.net.SocketException: Connection reset
//3.socket.setSoLinger(true, 60)->先后啟動server|client->client執行Socket#close會阻塞狀態,直到等待了60秒.->或者底層已經將所有未發送的數據
//發送完畢,才會從close返回。
//close socket cost Time:1651 ms->server結束休眠后,因為client還在執行close並處於阻塞狀態.client與server之前的連接依然存在.所以可以收到所有數據.
out.write(builder.toString().getBytes());//發送100個字符

long begin = System.currentTimeMillis();
socket.close();
long end = System.currentTimeMillis();

System.out.println("close socket cost Time:" + (end - begin) + " ms");
}
}

 

package com.game.landon.socket;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
*
*測試SO_LINGER選項的一個簡單server.接收連接請求后,不立即接收client發送的數據,而是睡眠5秒再接收數據.
*等到其開始接收數據時,client可能已經執行了close方法.server還會接收到client發送的數據嗎?
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-18
*
*/

public class TestLingerServer
{
public static void main(String[] args) throws Exception
{
ServerSocket serverSocket = new ServerSocket(8000);
Socket socket = serverSocket.accept();

Thread.sleep(5000);//睡眠5秒再讀輸入流

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

int len = -1;

do
{
len = in.read(buff);

if(len != -1)
{
buffer.write(buff, 0, len);
}
}
while (len != -1);

System.out.println(new String(buffer.toByteArray()));//字節數組轉為字符串
}
}

 

package com.game.landon.socket;

/**
*
*測試String字符串長度問題
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-6-18
*
*/

public class TestStringMax
{
public static void main(String[] args)
{
StringBuilder builder = new StringBuilder();

for(int i = 0;i < 10000;i++)
{
builder.append(i);
}

// 打印出了長度
System.out.println(builder.toString().length());
// 但是字符串卻無法打印,原因是控制台設置的原因->Window->Preferences->Run/Debug->Console->Fixed with Console->Maximum Character Width
System.out.println(builder.toString());
}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM