轉載:http://www.blogjava.net/landon/archive/2013/07/02/401137.htmlhtml
Java網絡編程精解筆記2:Socket詳解java
Socket用法詳解
在C/S通訊模式中,client須要主動建立於server鏈接的Socket(套接字).服務器端收到了客戶端的鏈接請求,也會建立與客戶鏈接的Socket.Socket可看作是通訊兩端的收發器.server與client都經過Socket來收發數據.
1.構造Socketmysql
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.設定等待創建鏈接的超時時間sql
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()); }}