《Java TCP/IP Socket編程》讀書筆記(15)

來源:互聯網
上載者:User
4.4 控制預設的行為4.4.1 Keep-alive

如果一段時間內沒有資料交換,通訊的每個終端可能都會懷疑對方是否還處於活躍狀態。TCP協議提供了一種keep-alive的機制,該機制在經過一段不啟用時間後,將向另一個終端發送一個探測訊息。如果另一個終端還出於活躍狀態,它將回複一個確認訊息。如果經過幾次嘗試後依然沒有收到另一終端的確認訊息,則終止發送探測資訊,關閉通訊端,並在下一次嘗試I/O操作時拋出一個異常。注意,應用程式只要在探測資訊失敗時才能察覺到keep-alive機制的工作。

4.4.2 發送和接收緩衝區的大小

Socket, DatagramSocket: 設定和擷取發送接收緩衝區大小

int getReceiveBufferSize()

void setReceiveBufferSize(int size)

int getSendBufferSize()

void setSendBufferSize(int size)

ServerSocket上指定接收緩衝區大小。不過,這實際上是為accept()方法所建立的新Socket執行個體設定接收緩衝區大小。為什麼可以只設定接收緩衝區大小而不設定發送緩衝區的大小呢?當接收了一個新的Socket,它就可以立刻開始接收資料,因此需要在accept()方法完成串連之前設定好緩衝區的大小。另一方面,由於可以控制什麼時候在新接受的通訊端上發送資料,因此在發送之前還有時間設定發送緩衝區的大小。

ServerSocket: 設定/擷取所接受通訊端的接收緩衝區大小

getReceiveBufferSize()和setReceiveBufferSize()方法用於擷取和設定由accept()方法建立的Socket執行個體的接收緩衝區的大小(位元組)。

4.4.3 逾時

Socket, ServerSocket, DatagramSocket: 設定/擷取I/O逾時時間

getSoTimeout()和setSoTimeout()方法分別用於擷取和設定讀/接收資料操作以及accept操作的最長阻塞時間。逾時設定為0表示該操作永不逾時。如果阻塞超過了逾時時間長度,則拋出一個異常。

4.4.4 地址重用

在某些情況下,可能希望能夠將多個通訊端綁定到同一個通訊端地址。對於UDP多播的情況,在同一個主機上可能有多個應用程式加入了相同的多播組。對於TCP,當一個串連關閉後,通訊的一端(或兩端)必須在"Time-Wait"狀態上等待一段時間,以對傳輸途中丟失的資料包進行清理。不幸的是,通訊終端可能無法等到Time-Wait結束。對於這兩種情況,都需要能夠與正在使用的地址進行綁定的能力,這就要求實現地址重用。

Socket, ServerSocket, DatagramSocket: 設定/擷取地址重用

getReuseAddress()和setReuseAddress()方法用於擷取和設定地址重用許可。設定為true表示啟用了地址重用功能。

4.4.5 消除緩衝延遲

TCP協議將資料緩衝起來直到足夠多時一次發送,以避免發送過小的資料包而浪費網路資源。雖然這個功能有利於網路,但應用程式可能對所造成的緩衝延遲不能容忍。好在可以人為禁用緩衝功能。

getTcpNoDelay()和setTcpNoDelay()方法用於擷取和設定是否消除緩衝延遲。將值設定為true表示禁用緩衝延遲功能。

4.4.6 緊急資料

Java中的緊急資料幾乎沒什麼用,因為緊急位元組與常規位元組按照傳輸的順序混在了一起。實際上,Java接收者並不能區分其是否在接收緊急資料。

4.4.7 關閉後停留

當調用通訊端的close()方法後,即使通訊端的緩衝區中還有沒有發送的資料,它也將立即返回。這樣不發送完所有資料可能導致的問題是主機將在後面的某個時刻發生故障。其實可以選擇讓close()方法"停留"或阻塞一段時間,直到所有資料都已經發送並確認,或發生了逾時。

Socket: 在close()方法停留

如果調用setSoLinger()並將其設定為true,那麼後面再調用的close()方法將阻塞等待,直到遠程終端對所有資料都返回了確認資訊,或者發生了指定的逾時(秒)。如果發生了逾時,TCP串連將強行關閉。如果開啟了停留功能,getSoLinger()方法將返回指定的逾時時間,否則返回-1。

4.4.8 廣播許可

一些作業系統要求顯示地對廣播許可進行請求。你可以對廣播許可進行控制。正如前面所介紹的,DatagramSockets類提供了廣播服務功能。

DatagramSocket: 設定/擷取廣播許可

getBroadcast()和setBroadcast()方法分別用於擷取和設定廣播許可。設定為true表示允許廣播。在Java中,預設情況下是允許進行廣播的。

4.4.9 通訊等級

有的網路對滿足服務條件的資料包提供了增強服務或"額外的保險"。一個資料包的通訊等級(traffic class)由資料包在網路中傳輸時其內部的一個值來指定。

Socket, DatagramSocket: 設定/擷取通訊等級

int getTrafficClass()

void setTrafficClass(int tc)

通訊等級通常由一個整數或一組位標記指定。位標記的數量和意義則取決於所使用的IP協議版本。

4.4.10 基於效能的協議選擇

TCP協議並不是通訊端惟一可選的協議。使用什麼樣的協議取決於應用程式的側重點在是什麼。 Java允許開發人員根據不同效能特徵對於應用程式的重要程度,為具體實現給出"建議"。底層的網路系統可能會根據這些建議,在一組能夠提供同等的資料流服務,同時又具有不同的效能特徵的不同協議中做出選擇。

Socket, ServerSocket: 指定協議參數選擇

void setPerformancePreferences(int 

connectionTime, int latency, int bandwidth)

通訊端的績效參數由三個整數表示,分別代表連線時間,延遲和頻寬。具體的數值並不重要,Java將比較各種標準的相關參數值,並返回與之最匹配的可用協議。例如,如果connectionTime和latency都等於0,bandwidth等於1,那麼則將選擇能夠使頻寬最大的協議。注意,要使這個方法生效,必須在通訊端建立串連之前調用。

4.5 關閉串連

Socket類的shutdownInput()和shutdownOutput()方法能夠將輸入輸出資料流相互獨立地關閉。調用shutdownInput()後,通訊端的輸入資料流將無法使用。任何沒有發送的資料都將毫無提示地被丟棄,任何想從通訊端的輸入資料流讀取資料的操作都將返回-1。當Socket調用shutdownOutput() 方法後,通訊端的輸出資料流將無法再發送資料,任何嘗試向輸出資料流寫資料的操作都將拋出一個IOException異常。在調用shutdownOutput()之前寫出的資料可能能夠被遠程通訊端讀取,之後,在遠程通訊端輸入資料流上的讀操作將返回-1。應用程式調用shutdownOutput()後還能繼續從通訊端讀取資料,類似的,在調用shutdownInput()後也能夠繼續寫資料。

下面考慮另一種協議。假設你需要一個壓縮伺服器,將接收到的位元組流壓縮後,發回給用戶端。這種情況下應該由哪一端來關閉串連呢?由於從用戶端發來的位元組流的長度是任意的,用戶端需要關閉串連以通知伺服器要壓縮的位元組流已經發送完畢。那麼用戶端應該什麼時候調用close()方法呢?如果用戶端在其發送完最後一個位元組後立即調用通訊端的close(),它將無法接收到壓縮後資料的最後一些位元組。或許用戶端可以像回顯協議那樣,在接收完所有壓縮後的資料才關閉串連。但不幸的是,這樣一來伺服器和用戶端都不知道到底有多少資料要接收,因此這也不可行.

用戶端向伺服器發送待壓縮的位元組,發送完成後調用shutdownOutput()關閉輸出資料流,並從伺服器讀取壓縮後的位元組流。伺服器反覆地擷取未壓縮的資料,並將壓縮後的資料發回給用戶端,直到用戶端執行了停機操作,導致伺服器的read操作返回-1,這表示資料流的結束。然後伺服器關閉串連並退出。


  下面是壓縮協議的代碼

package com.suifeng.tcpip.chapter4.compress;import java.io.IOException;import java.io.InputStream;import java.net.Socket;import java.util.logging.Logger;import java.util.zip.GZIPOutputStream;public class CompressProtocol implements Runnable{private static final int BUFFER_SIZE = 1024;private Logger logger;private Socket socket;public CompressProtocol(Logger logger, Socket socket) {super();this.logger = logger;this.socket = socket;}public void handleCompress(Socket socket,Logger logger) {try{InputStream in = socket.getInputStream();GZIPOutputStream out = new GZIPOutputStream(socket.getOutputStream());byte[] buffer = new byte[BUFFER_SIZE];int bytesRead;while((bytesRead = in.read(buffer)) != -1){out.write(buffer, 0, bytesRead);}out.finish();logger.info("Client "+socket.getRemoteSocketAddress()+" finished");}catch (IOException e){logger.warning("Excetion in compress protocol");}try{socket.close();}catch (IOException e){logger.info("close socket ERROR:"+e.getMessage());}}@Overridepublic void run(){handleCompress(socket, logger);}}

壓縮用戶端

package com.suifeng.tcpip.chapter4.compress;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

/**
 * 壓縮用戶端
 * @author Suifeng
 *
 */
public class CompressClient
{
public static final int BUFFER_SIZE = 256;

public static void main(String[] args) throws IOException
{
if(args.length != 3)
{
throw new IllegalArgumentException("Parameters:<Server> <Port> <File>");
}

String server = args[0];
int port = Integer.parseInt(args[1]);
String filename = args[2];

FileInputStream fileIn = new FileInputStream(filename);
FileOutputStream fileOut = new FileOutputStream(filename+".gz");

Socket socket = new Socket(InetAddress.getByName(server),port);

sendBytes(socket, fileIn);

InputStream in = socket.getInputStream();

int bytesRead ;
byte[] buffer = new byte[BUFFER_SIZE];

while((bytesRead = in.read(buffer))!=-1)
{
fileOut.write(buffer,0,bytesRead);
System.out.print("R");
}

System.out.println("");

socket.close();

fileIn.close();
fileOut.close();
}

public static void sendBytes(Socket socket, FileInputStream fileIn) throws IOException
{
OutputStream out = socket.getOutputStream();

int bytesRead ;
byte[] buffer = new byte[BUFFER_SIZE];

while((bytesRead = fileIn.read(buffer))!=-1)
{
out.write(buffer,0,bytesRead);
System.out.print("W");
}

// 單獨關閉輸出,通知伺服器端輸入已完畢
socket.shutdownOutput();
}

}


壓縮伺服器

package com.suifeng.tcpip.chapter4.compress;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

import com.suifeng.tcpip.chapter4.EchoProtocol;

public class CompressServer
{

/**
* @param args
* @throws IOException 
*/
public static void main(String[] args) throws IOException
{
if(args.length != 1)
{
throw new IllegalArgumentException("Parameter:<Port>");
}

int serverPort = Integer.parseInt(args[0]);

ServerSocket serverSocket = new ServerSocket(serverPort);

Executor service = Executors.newCachedThreadPool();

Logger logger = Logger.getLogger("practical");
logger.info("Server is started!!!!");

while(true)
{
Socket socket = serverSocket.accept();
service.execute(new CompressProtocol(logger,socket));
}

}

}

啟動伺服器端



啟動用戶端



查看伺服器端



查看壓縮結果



經過壓縮,5KB的test.txt檔案壓縮到1KB


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.