JavaSE(十)網絡

TCP協議

  TCP的連接過程需要三次握手,首先服務器處於監聽狀態,客戶端發起SYN報文並進入SYN_SEND狀態,服務器監聽到該SYN併爲該請求分配資源,資源分配成功後服務器連接狀態變爲SYN_RCVD,同時向客戶端發送ACK+SYN,客戶端收到該ACK+SYN後分配資源,分配資源成功後客戶端狀態變爲ESTABLESHED,並向服務器發送ACK,服務器收到這個ACK後服務器狀態變爲ESTABLESHED,這樣TCP連接建立成功。SYN報文攜帶着初始化的序列號,告訴對方自己的發送序列起始值。
  TCP連接的斷開需要四次揮手,TCP是雙工通信,兩端都能發送和接受,第一次揮手爲A端發送FIN報文並進入FIN_WAIT_1狀態,第二次揮手爲B端接到報文後關閉讀併發送ACK報文,同時進入CLOSE_WAIT狀態,A端收到應答進入FIN_WAIT_2狀態,第三次揮手爲B端關閉寫發送FIN報文進入LAST_ACK狀態,第四次揮手爲A端接收到FIN報文後發送ACK報文並進入TIME_WAIT狀態,B端收到應答後進入到CLOSED狀態,A端過一段時間進入CLOSED狀態。
  對於一些小包,其協議頭部佔比比較大,這樣業務數據的網絡利用率就比較低,爲了更好利用網絡,把一些小包集合成大包(包長度爲MSS的包,也就是一個包允許的最大長度)再發送,然而,如果某個包尚未達到MSS就沒有業務數據了,是否就一直不發送了呢?當然不能,Nagle算法就解決了這個問題,如果當前一個包長度達到了MSS就直接發送出去,否則就等待之前發送的包的應答都返回再發送當前包(這時候可能該包長度小於MSS),這樣這個連接上最多隻會存在一個包的長度小於MSS。Nagle算法適用於網絡阻塞嚴重但實時性要求不是那麼高的應用。
  連接的關閉分爲主動關閉和被動關閉,如四次揮手中的首先發起關閉的一端就是主動關閉,另一端就是被動關閉。正常關閉時,通過四次握手,雙方都能進入closed狀態,但如果某一方由於宕機等原因,可能導致另一方無法被動關閉,爲了解決這個問題,需要引入心跳機制。TCP爲我們提供了心跳機制,默認是關閉的,當打開時,如果一段時間沒有和另一方有數據交換,那麼就發送一個包,該包對方一定會響應,如果沒有響應就表示對方已斷線,本方會斷開連接釋放資源。我們常用的方式是自己在邏輯層實現心跳機制,如一端定時發送一個心跳包到對方,如果對方有應答就表示對方沒有掛掉,而另一方可以通過相同的方式檢測對方是否掛掉,也可以通過一定時間內是否收到了對方的心跳包來檢測對方是否已經掛掉(如設置個定時器,檢查每隔連接的標誌,如果被設置就設置爲未設置,如果未設置就表示連接已經斷開,在收到心跳包時將對應的連接設置爲已設置,也可以每次收到心跳包記錄下收到時間,然後定時檢查)。
  InetAddress是基於IP協議的網絡上的一個主機的地址,也就是IP地址的封裝。SocketAddress是一個抽象的標記類,沒有實現任何方法,表示網絡上的一個服務地址,針對不同的網絡協議,服務地址有不同的表現形式,所以應該根據網絡協議的特性來定義其子類,對於通用的IP協議,JDK爲我們提供了其子類InetSocketAddress,它封裝了一個ip地址和一個端口。

// 對應一個主機地址。
public class InetAddress implements java.io.Serializable {
  public static InetAddress getByName(String host) throws UnknownHostException; // 通過主機名或ip文本形式(如"192.168.15.1")構建一個地址,該地址的合法性會通過dns檢驗,如果dns檢查不到會拋出異常。
  public static InetAddress[] getAllByName(String host) throws UnknownHostException; // 返回host對應的所有主機,因爲一個域名可能對應多個主機,如www.baidu.com對應多個ip。
  public static InetAddress getLocalHost() throws UnknownHostException; // 返回本地主機。
  public String getHostAddress(); // 文本形式的ip地址。
  public String getHostName(); // 主機名。
  public boolean isReachable(int timeout) throws IOException; // 主機是否可達。
}
   
// 對應一個主機地址和端口號。
public class InetSocketAddress  extends SocketAddress {  
  InetSocketAddress public InetSocketAddress(int port);
  public InetSocketAddress(InetAddress addr, int port);
  public InetSocketAddress(String hostname, int port);
  public final int getPort();
  public final InetAddress getAddress();
  public final String getHostName();
}    

Socket編程

  Socket是基於TCP協議的一組接口,是爲了方便TCP協議編程,本座不把socket編程放在應用層,而將其認爲是簡化TCP編程的框架,因爲Socket並不會引入自己的協議頭。
  網絡通信就是在一個線路的兩端相互傳遞信息,所以首先需要在通信的兩端建立連接,服務器端會監聽某個端口等待客戶端的連接請求,當連接上時,客戶端和服務器會各持有一個打開的套接字文件,兩端通過對套接字文件的讀寫操作來進行通信。

Socket

  在java中,socket編程有早期的BIO方案和新起的NIO方案以及AIO方案,任一方案的客戶端與任一方案的服務端都可以組合使用,服務端選用的方案對於客戶端是透明的,客戶端選用的方案對於服務端也是透明的。
  BIO方案的客戶端用Socket去連接服務器,當服務器連接上時,通過socket的輸入流和輸出流同服務器進行交互,對輸入流的讀和對輸出流的寫都是阻塞的讀寫,如果想在與服務器交互的同時幹其他事,需要新建線程來負責與服務器交互。NIO方案的客戶端通過SocketChannel去連接服務器,當連接上時,由通道與服務器進行交互,SocketChannel支持阻塞和非阻塞兩種模式來控制讀寫通道時是否阻塞。NIO客戶端的阻塞模式同BIO使用上沒有什麼區別(可以看成NIO是BIO的優化版本,詳見文件與IO),而非阻塞模式在讀寫不了通道時及時返回並處理其他事情,如果客戶端需要跟多個服務器交互(也就是客戶端有多個SocketChannel),那麼阻塞模式需要爲每一個SocketChannel新建一個線程來與服務器通信,而非阻塞模式下可以在一個線程中輪詢處理所有SocketChannel,或者通過selector來對所有SocketChannel進行管理。
  BIO方案的服務器通過新建一個ServerSocket來對端口進行監聽,當有連接到來時,將連接保存在一個集合中,通過ServerSocket的accept方法從連接的集合中取出一個連接並新建一個與之對應的Socket返回,如果集合爲空,就阻塞線程直到有新的連接到來並創建一個與之對應的Socket返回,通過返回的socket可以同發出連接請求的客戶端進行通信(這個socket和客戶端的socket很相似,但該socket的遠程地址和端口是連接過來的socket綁定的地址和端口,本地地址和端口是同serverSocket一樣的地址和端口),通常需要處理所有客戶端的通信請求,所以需要循環調用accept方法來獲取新的Socket,由於對Socket的讀寫是阻塞的,所以通常需要在獲得Socket後開啓新線程來與客戶端進行通信,主線程只負責調用accept來獲取客戶端的連接並將監聽到的連接分派給新的線程來處理。NIO直接使用通道和BIO的用法差不多,首先需要打開一個ServerSocketChannel進行監聽,並將監聽到的連接保存在一個集合中,然後通過ServerSocketChannel的accept方法從集合中取出一個連接封裝成SocketChannel,如果集合爲空,需要看serverSocketChannel是否是阻塞模式,如果是就阻塞線程直到有客戶端連接爲止,如果不是就直接返回null。通常NIO是在非阻塞模式下通過selector來進行管理,首先需要打開一個Selector,然後把所有通道感興趣的事件註冊到selector上(Selector感興趣的事件有SelectionKey.OP_READ/OP_WRITE/OP_ACCEPT/OP_CONNECTION),在最開始的時候,只有一個打開的ServerSocketChannel通道,而該類型通道只能註冊監聽OP_ACCEPT事件,循環調用selector的select方法,該方法會把準備就緒的各通道的各事件(SelectionKey)放在一個就緒集合中, 並返回放入集合的SelectionKey的個數(如果沒有就緒的通道事件就阻塞,如果就緒集合已經有這個SelectionKey,那麼此次放入不計數),再通過selector的selectedKey方法得到這個就緒集合,並遍歷這個集合進行處理,如果是連接事件,那麼可以通過serverSocketChannel的accept獲取SocketChannel並將該通道感興趣的事件(如讀寫)註冊到selector中(如果不通過accept獲取SocketChannel,那麼每次調用selector的select方法時,ServerSocketChannel的OP_ACCEPT事件都會是就緒的),在對每一個準備就緒的事件處理後,應該從就緒集合中移除該SelectionKey,否則就緒集合中會一直存在該事件(儘管該事件可能已經不是就緒的了,如果該事件再次就緒,selector的select方法也不會統計該事件)。AIO通過AsynchronousServerSocketChannel來進行監聽,其accept方法可以使用將來式或者回調式來獲取客戶端的連接並創建AsynchronousSocketChannel,並可通過將來式或者回調式來處理AsynchronousSocketChannel。

// 通過工廠創建socket
public abstract class SocketFactory {
    public static SocketFactory getDefault(); // 獲取工廠實例

	// 與Socket的構造方法對應
    public Socket createSocket() throws IOException;
    public abstract Socket createSocket(String host, int port) throws IOException, UnknownHostException;
    public abstract Socket createSocket(String host, int port, InetAddress localAddr, int localPort) throws IOException, UnknownHostException;
    public abstract Socket createSocket(InetAddress address, int port) throws IOException;
    public abstract Socket createSocket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException;
}

// 套接字,一個套接字對應兩個SocketAddress,一個本地綁定的SocketAddress,一個遠程連接的SocketAddress。
public class Socket implements java.io.Closeable {  
  public Socket(); // 創建一個套接字,未連接遠端主機,很多套接字設置必須在連接主機之前完成,該構造函數沒有連接主機,其他構造方法都有連接遠程主機。
  public Socket(String host, int port) throws UnknownHostException, IOException; // 創建一個流套接字並將其連接到指定IP地址的指定端口號。
  public Socket(InetAddress address, int port) throws IOException; // 創建一個流套接字並將其連接到指定IP地址的指定端口號。
  public Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException; // 創建一個套接字並綁定到本地某網卡的指定端口,將其連接到指定遠程主機上的指定遠程端口。
  public Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException; // 創建一個套接字並綁定到本地某網卡的指定端口,將其連接到指定遠程主機上的指定遠程端口。
  public void bind(SocketAddress bindpoint) throws IOException; // 將套接字綁定到本地地址。
  public void connect(SocketAddress endpoint) throws IOException; // 將此套接字連接到服務器。
  public void connect(SocketAddress endpoint, int timeout) throws IOException; // 將此套接字連接到服務器並設置超時時間。
  public InetAddress getInetAddress(); // 返回套接字連接的地址。
  public InetAddress getLocalAddress(); // 返回套接字綁定的本地地址。
  public int getPort(); // 返回套接字連接的端口。
  public int getLocalPort(); // 返回套接字綁定的本地端口。
  public SocketAddress getRemoteSocketAddress(); // 返回套接字連接的SocketAddress。
  public SocketAddress getLocalSocketAddress(); // 返回套接字綁定的本地SocketAddress。
  public SocketChannel getChannel(); // 當且僅當通過SocketChannel.open或ServerSocketChannel.accept方法創建了通道本身時,套接字才具有一個通道。
  public InputStream getInputStream() throws IOException; 
  public OutputStream getOutputStream() throws IOException;
  public void setTcpNoDelay(boolean on) throws SocketException; // 是否啓用TCP_NODELAY,默認爲false,表示採用Nagle算法。
  public boolean getTcpNoDelay() throws SocketException;
  public void setSoLinger(boolean on, int linger) throws SocketException; // 是否啓用SO_LINGER,未啓用時關閉套接字那麼未發送的數據包將被丟棄,若啓用將在linger秒時間內儘可能發送,如果到時還沒發送的丟棄。
                                // 也就是調用close函數的時候,如果有數據包未發送,那麼close函數可能會最多等待linger秒才返回。
  public int getSoLinger() throws SocketException; // 如果啓用了SO_LINGER返回逗留時間,否則返回-1。
  public void sendUrgentData(int data) throws IOException; // 直接發送一個字節的緊急數據,data的低位一個字節,該字節不通過緩衝區。
  public void setOOBInline(boolean on) throws SocketException ; // 只有設爲true才能通過sendUrgentData發送數據。
  public boolean getOOBInline() throws SocketException;
  public synchronized void setSoTimeout(int timeout) throws SocketException; // 設置通過inputStream讀取數據的阻塞超時時間,單位毫秒,如果爲0表示無限長時間。
  public synchronized int getSoTimeout() throws SocketException;
  public synchronized void setSendBufferSize(int size) throws SocketException; // 設置寫緩衝區的大小,往socket輸出流寫數據只是寫到緩衝區,實際的發送是由操作系統內核網絡模塊從緩衝區讀數據發送的。
  public synchronized int getSendBufferSize() throws SocketException;
  public synchronized void setReceiveBufferSize(int size) throws SocketException; // 設置讀緩衝區的大小,從socket輸入流讀數據只是從緩衝區讀,實際網絡來的數據是由操作系統內核網絡模塊寫到緩衝區的。
  public synchronized int getReceiveBufferSize() throws SocketException;
  public void setKeepAlive(boolean on) throws SocketException; // 設置是否啓用SO_KEEPALIVE心跳機制。
  public boolean getKeepAlive() throws SocketException; // 測試是否啓用SO_KEEPALIVE。
  public void setReuseAddress(boolean on) throws SocketException; // 在一個監聽某個端口的套接字完全關閉前不能複用該端口,但如果該套接字設置了該項,而新的套接字也設置了該項,
                                // 那麼在原套接字進入TIME_WAIT時,新套接字就可以複用該端口。
  public boolean getReuseAddress() throws SocketException;
  public synchronized void close() throws IOException; // 關閉套接字。
  public boolean isClosed(); // 套接字的關閉狀態。
  public void shutdownInput/shutdownOutPut() throws IOException; // 是否關閉套接字連接的半讀/半寫,close套接字就像是關閉了讀寫。
  public boolean isInputShutdown/isOutputShutdown();
  public boolean isConnected(); // 套接字的連接狀態,只要連接之後就算套接字關閉也返回true。
  public boolean isBound(); // 套接字的綁定狀態,只要綁定之後就算套接字關閉也返回true。
  public static synchronized void setSocketImplFactory(SocketImplFactory fac) throws IOException; // 爲套接字實際工作的對象socketImpl指定工廠類。
  public void setPerformancePreferences(int connectionTime, int latency, int bandwidth); // 設置此套接字的性能偏好,三個參數的值分別表示程序要求的短連接時間、低延遲和高帶寬的相對重要性。
}

// 通過工廠創建serverSocket
public abstract class ServerSocketFactory {
    public static ServerSocketFactory getDefault(); // 獲取工廠實例
    
    // 與ServerSocket的構造方法對應
    public ServerSocket createServerSocket() throws IOException;
    public abstract ServerSocket createServerSocket(int port) throws IOException;
    public abstract ServerSocket createServerSocket(int port, int backlog) throws IOException;
    public abstract ServerSocket createServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException;
}
    
// 服務器端用於監聽客戶端連接的socket,並非Socket的子類。
public class ServerSocket implements java.io.Closeable {  
  public ServerSocket() throws IOException; // 沒有進行端口綁定(其他構造函數都綁定了端口),有很多配置需要在綁定端口前完成,所以需要先用此構造,然後配置,最後bind。
  public ServerSocket(int port, int backlog, InetAddress bindAddr); // bindAddr是針對多個網卡的情況,backlog是監聽到但未被取走(調用accept取走)的連接的最大個數。
  public ServerSocket(int port, int backlog) throws IOException;
  public ServerSocket(int port) throws IOException;
  public void bind(SocketAddress endpoint) throws IOException;
  public void bind(SocketAddress endpoint, int backlog) throws IOException;
  public InetAddress getInetAddress(); // 獲取綁定的網卡IP地址。
  public int getLocalPort(); // 獲取本地綁定的端口號。
  public SocketAddress getLocalSocketAddress(); // 獲取本地綁定的IP和端口信息。
  public Socket accept() throws IOException; // 從監聽到的連接集合中取出一個來封裝成Socket,如果集合爲空會阻塞等待。返回的socket的遠程地址是客戶端地址,本地端口是ServerSocket的端口。
  public void close() throws IOException;
  public boolean isBound();
  public boolean isClosed();
  public synchronized void setSoTimeout(int timeout) throws SocketException; // 這個超時時間是accept的等待時間。
  public synchronized int getSoTimeout() throws IOException;
  public void setReuseAddress(boolean on) throws SocketException;
  public boolean getReuseAddress() throws SocketException;
  public synchronized int getReceiveBufferSize();
  public ServerSocketChannel getChannel(); // 當且僅當通過SocketServerChannel.open創建的channel包含的ServerSocket時,該方法纔不反回null。
  public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) throws SocketException;
}
    
// 客戶端通道繼承了SelectableChannel。
public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel,  ScatteringByteChannel, GatheringByteChannel, NetworkChannel {  
  public static SocketChannel open(); // 打開一個通道。
  public static SocketChannel open(SocketAddress remote); // 打開通道並連接遠程服務器。
  public abstract boolean connect(SocketAddress remote) throws IOException; // 如果是阻塞模式直到連接完成再返回true,如果是非阻塞模式,如果連接成功就返回true,否則異步進行連接並返回false。
  public abstract boolean finishConnect() throws IOException; // 在非阻塞模式下,該方法檢查是否連接完成,並在連接完成後做了一些操作,對於非阻塞的連接應該循環此函數來保證以後的操作中連接是建立起的。
  public abstract boolean isConnected(); // 是否已經連接上。
  public abstract boolean isConnectionPending(); // 是否正在進行連接操作,處在connect和finishConnect返回true之間的狀態,只有調用了finishConnect此函數才能檢查到完成。
  public final int validOps(); // 返回該通道支持的操作,默認的客戶端通道支持連接、讀取和寫入。
  public abstract SocketChannel bind(SocketAddress local); // 綁定本地端口。
  public abstract SocketChannel shutdownInput/shutdownOutput() throws IOException; //
  public final SelectableChannel configureBlocking(boolean block); // 配置通道是否爲阻塞模式。
  public abstract Socket socket(); // 對應的socket。
  public abstract SocketAddress getRemoteAddress() throws IOException; // 遠程連接的地址。
  public abstract SocketAddress getLocalAddress() throws IOException; // 本地綁定的地址。
  public abstract int read(ByteBuffer dst) throws IOException;
  public abstract long read(ByteBuffer[] dsts, int offset, int length) throws IOException; 
  public final long read(ByteBuffer[] dsts) throws IOException;
  public abstract int write(ByteBuffer src) throws IOException;
  public abstract long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
  public final long write(ByteBuffer[] srcs) throws IOException;
}
    
public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel {
  public static ServerSocketChannel open() throws IOException; 
  public final int validOps(); // 返回該通道支持的操作,默認的服務器通道支持接收。
  public abstract ServerSocket socket(); // 與其關聯的服務器套接字。
  public abstract SocketChannel accept() throws IOException; // 如果阻塞模式阻塞直到有連接,如果非阻塞模式沒有就返回null,無論該通道是什麼模式,返回的socketChannel都是阻塞模式。
}
    
// 該類和Selector是一對好基友。
public abstract class SelectableChannel extends AbstractInterruptibleChannel implements Channel {  
  public abstract int validOps(); // 操作集。
  public final SelectableChannel configureBlocking(boolean block); // 配置通道是否爲阻塞模式。
  public final SelectionKey register(Selector sel, int ops, Object att); // 向指定的選擇器註冊此通道感興趣的事件。
  public final SelectionKey keyFor(Selector sel); // 返回該通道向選擇器註冊的鍵。
  public final boolean isRegistered(); // 此通道是否向任何選擇器註冊過。
}    
    
// 這其實應該是nio的類。
public abstract class Selector implements Closeable {  
  public static Selector open() throws IOException;
  public abstract Set keys(); // 返回此選擇器中的所有鍵集。
  public abstract Set selectedKeys(); // 返回選擇器中已選擇的鍵集。
  public abstract int selectNow() throws IOException; // 無論選擇的個數是多少立即返回。
  public abstract int select(long timeout) throws IOException; // 選擇的個數大於0或超時返回。
  public abstract int select() throws IOException; // 選擇的個數大於0才返回。
  public abstract void close() throws IOException;
} 

// 服務端AIO
public abstract class AsynchronousServerSocketChannel implements AsynchronousChannel, NetworkChannel {
    public static AsynchronousServerSocketChannel open(AsynchronousChannelGroup group) throws IOException; // group爲共享資源管理,其中包括共享的線程池
    public static AsynchronousServerSocketChannel open() throws IOException;
    public final AsynchronousServerSocketChannel bind(SocketAddress local) throws IOException;
    public abstract <T> AsynchronousServerSocketChannel setOption(SocketOption<T> name, T value) throws IOException;
    public abstract <A> void accept(A attachment, CompletionHandler<AsynchronousSocketChannel,? super A> handler); // 新開一個線程來獲取到連接並回調處理連接
    public abstract Future<AsynchronousSocketChannel> accept(); // 將來時獲取連接
    public abstract SocketAddress getLocalAddress() throws IOException;
}

public abstract class AsynchronousSocketChannel implements AsynchronousByteChannel, NetworkChannel {
    public static AsynchronousSocketChannel open(AsynchronousChannelGroup group) throws IOException; // AsynchronousChannelGroup管理共享資源,比如線程池
    public static AsynchronousSocketChannel open() throws IOException; 
    public abstract AsynchronousSocketChannel bind(SocketAddress local) throws IOException;
    public abstract <T> AsynchronousSocketChannel setOption(SocketOption<T> name, T value) throws IOException;
    public abstract AsynchronousSocketChannel shutdownInput() throws IOException;
    public abstract AsynchronousSocketChannel shutdownOutput() throws IOException;
    public abstract SocketAddress getRemoteAddress() throws IOException;
    public abstract <A> void connect(SocketAddress remote, A attachment, CompletionHandler<Void,? super A> handler);
    public abstract Future<Void> connect(SocketAddress remote);
    public abstract <A> void read(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler<Integer,? super A> handler);
    public final <A> void read(ByteBuffer dst,  A attachment, CompletionHandler<Integer,? super A> handler);
    public abstract Future<Integer> read(ByteBuffer dst);
    public abstract <A> void read(ByteBuffer[] dsts, int offset, int length, long timeout, TimeUnit unit, A attachment, CompletionHandler<Long,? super A> handler);
    public abstract <A> void write(ByteBuffer src, long timeout, TimeUnit unit, A attachment,  CompletionHandler<Integer,? super A> handler);
    public final <A> void write(ByteBuffer src, A attachment, CompletionHandler<Integer,? super A> handler);
    public abstract Future<Integer> write(ByteBuffer src);
    public abstract <A> void write(ByteBuffer[] srcs, int offset, int length,  long timeout, TimeUnit unit, A attachment, CompletionHandler<Long,? super A> handler);
    public abstract SocketAddress getLocalAddress() throws IOException;
} 

  BIO方式服務端示例:

package com.java.test.server;

import java.net.ServerSocket;
import javax.net.ServerSocketFactory;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ServerSocketTest {
    private static int serverPort = 9000;
    private static int threadNo = 200;

    public static void main(String[] vars) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(threadNo);
        ServerSocket serverSocket = new ServerSocketFactory.getDefault().createServerSocket(serverPort);
        while (true) {
            Socket socket = serverSocket.accept();
            executorService.submit(new DealService(socket));
        }
    }

    public static class DealService implements Runnable {
        private Socket socket;

        public DealService(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            System.out.println("------deal---------");
        }
    }
}

  NIO阻塞方式服務端示例:

package com.java.test.server;

import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ServerSocketChannelTest {
    private static int serverPort = 9000;
    private static int threadNo = 200;

    public static void main(String[] vars) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(threadNo);
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(serverPort));

        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            executorService.submit(new DealService(socketChannel)); 
        }
    }

    public static class DealService implements Runnable {
        private SocketChannel socketChannel;

        public DealService(SocketChannel socketChannel) {
            this.socketChannel = socketChannel;
        }

        @Override
        public void run() {
            System.out.println("------deal---------");
        }
    }
}

  NIO Selector方式服務端示例:

package com.java.test.server;

import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class SelectorTest {
    private static int serverPort = 9000;

    public static void main(String[] vars) throws Exception {
        Selector selector = Selector.open();

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(serverPort));
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            if (selector.select() > 0) {
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();

                    if (selectionKey.isAcceptable()) {
                        SocketChannel socketChannel = ((ServerSocketChannel)selectionKey.channel()).accept();
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector, SelectionKey.OP_READ);
                    } else if (selectionKey.isReadable()) {
                    	SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                        System.out.println("----deal---");
                    }

                    iterator.remove();
                }
            }
        }
    }
}

  AIO回調式服務端示例:

package com.java.test.server;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AIOServerSocketChannel {
    private static int serverPort = 9000;
    private static int threadNo = 200;

    private static CountDownLatch serverStatus = new CountDownLatch(1);

    public static void main(String[] vars) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(threadNo);
        AsynchronousChannelGroup asynchronousChannelGroup = AsynchronousChannelGroup.withThreadPool(executorService);
        AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open(asynchronousChannelGroup);
        serverSocketChannel.bind(new InetSocketAddress(serverPort));
        serverSocketChannel.accept(serverSocketChannel, new ConnectHandler());

        serverStatus.await(); // 保證服務器主程序不退出
    }

    public static class ConnectHandler implements CompletionHandler<AsynchronousSocketChannel, AsynchronousServerSocketChannel> {
        @Override
        public void completed(AsynchronousSocketChannel asyncSocketChannel, AsynchronousServerSocketChannel serverSocketChannel) {
            serverSocketChannel.accept(serverSocketChannel, new ConnectHandler()); //當前連接建立成功後,接收下一個請求建立新的連接

            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            asyncSocketChannel.read(byteBuffer, byteBuffer, new ReadHandler(asyncSocketChannel));
        }

        @Override
        public void failed(Throwable exc, AsynchronousServerSocketChannel attachment) {
            System.out.println("----error-----");
        }
    }

    public static class ReadHandler implements CompletionHandler<Integer, ByteBuffer> {
        private AsynchronousSocketChannel asyncSocketChannel;

        public ReadHandler(AsynchronousSocketChannel asyncSocketChannel) {
            this.asyncSocketChannel = asyncSocketChannel;
        }

        @Override
        public void completed(Integer result, ByteBuffer byteBuffer) {
            byteBuffer.flip();
            byte[] bytes = new byte[byteBuffer.remaining()];
            byteBuffer.get(bytes);
            
            System.out.println("---deal---");

            byteBuffer.compact();
            asyncSocketChannel.read(byteBuffer, byteBuffer, new ReadHandler(asyncSocketChannel));
        }

        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {
            try {
                asyncSocketChannel.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

  AIO將來式服務端示例:

package com.java.test.server;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class AIOServerSocketChannel {
    private static int serverPort = 9000;
    private static int threadNo = 200;
    private static int bufferSize = 1024;

    public static void main(String[] vars) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(threadNo);
        AsynchronousChannelGroup asynchronousChannelGroup = AsynchronousChannelGroup.withThreadPool(executorService);
        AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open(asynchronousChannelGroup);
        serverSocketChannel.bind(new InetSocketAddress(serverPort));

        Set<Holder> holders = new HashSet<>();
        Set<Holder> newHolders = new HashSet<>();

        Future<AsynchronousSocketChannel> socketChannelFuture = serverSocketChannel.accept();
        while (true) {
            if (socketChannelFuture.isDone()) {
                AsynchronousSocketChannel socketChannel = socketChannelFuture.get();
                ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize);
                holders.add(Holder.create(socketChannel.read(byteBuffer), socketChannel, byteBuffer));
                socketChannelFuture = serverSocketChannel.accept();
            }

            Iterator<Holder> iterator = holders.iterator();
            while (iterator.hasNext()) {
                Holder holder = iterator.next();
                if (holder.future.isDone()) {
                    iterator.remove();
                    if (holder.future.get() == -1) {
                        continue;
                    }

                    holder.byteBuffer.flip();
                    byte[] bytes = new byte[holder.byteBuffer.remaining()];
                    holder.byteBuffer.get(bytes);
                    System.out.println("---" + new String(bytes));

                    holder.byteBuffer.compact();
                    newHolders.add(Holder.create(holder.socketChannel.read(holder.byteBuffer), holder.socketChannel, holder.byteBuffer));
                }
            }
            holders.addAll(newHolders);
            newHolders.clear();
        }
    }

    public static class Holder {
        public Future<Integer> future;
        public AsynchronousSocketChannel socketChannel;
        public ByteBuffer byteBuffer;

        public static Holder create(Future<Integer> future, AsynchronousSocketChannel socketChannel, ByteBuffer byteBuffer) {
            Holder holder = new Holder();
            holder.future = future;
            holder.socketChannel = socketChannel;
            holder.byteBuffer = byteBuffer;

            return holder;
        }
    }
}

SSL/TLS

  安全套接層SSL(Secure Sockets Layer)及其繼任者傳輸層安全TLS(Transport Layer Security)是爲網絡通信提供安全及數據完整性的一種協議,它們在傳輸層之上對網絡連接進行加密。SSL/TLS主要解決了數據加密、數據完整性以及身份認證問題。SSL/TLS協議位於應用層與TCP之間的層級,與應用層完全解耦,所以可以對各種應用層協議提供安全可靠的底層支持,如SSL/TLS + HTTP = HTTPS、SSL/TLS + FTP = FTPS(非SFTP)。 SSL/TLS協議的具體過程:

  • client_hello。客戶端以明文發起請求,包含支持的SSL/TLS版本,加密套件候選列表,壓縮算法候選列表,隨機數RandomC,擴展字段等信息。
  • server_hello + server_certificate + sever_hello_done。服務端返回協商的信息結果,包括選擇的SSL/TLS版本,選擇的加密套件,選擇的壓縮算法,隨機數RandomS,同時發送服務端的數字證書到客戶端。如果需要對客戶端進行身份認證,這裏還會發送一個client_certificate_request給客戶端,要求客戶端發送客戶端的數字證書到服務端。
  • 客戶端證書校驗。客戶端對服務器的數字證書進行一系列的校驗。
  • client_key_exchange。客戶端產生一個隨機數Pre-master並用服務器證書中的公鑰加密後發送給服務器。同時明確後續數據傳輸的對稱密鑰secretKey = f(RandomC, RandomS, Pre-master)。如果需要對客戶端進行身份認證(收到服務器client_certificate_request請求),該階段先會發送client_certificate與certificate_verify_message給服務端。
  • server_key_exchange。服務端通過自己的私鑰解密得到Pre-master,同樣可以獲取對稱密鑰secretKey = f(RandomC, RandomS, Pre-master),如果需要對客戶端進行身份認證,這裏還會進行客戶端的證書校驗。
  • 通過secretKey加密通信

  SSLSocket連接是在建立了Socket連接之後,通過Socket連接進行SSL握手通信的,如果用普通Socket去連接SSLServerSocket,那麼可以建立Socket連接,但之後SSLServerSocket要求SSL握手時客戶端必須通過Socket手動模擬SSL握手過程,否則握手就會失敗,SSL連接也會建立失敗,當客戶端用SSLSocket連接時,底層會自動進行SSL握手,最後得到的SSLSocket都是SSL握手成功的Socket,SSLSocket與SSLServerSocket握手成功後使用方法與Socket以及ServerSocket別無二致。
  SSLContext用於獲取SSLSocket與SSLServerSocket,以及創建SSLEngine。在使用SSLContext前,SSLContext必須已經初始化且不能重複初始化,初始化時需要告知SSLContext最受信任的證書以及代表自己身份的證書。在獲取到SSLSocket與SSLServerSocket之後,使用方式同Socket以及ServerSocket完全相同。
  通過SSLSocket與SSLServerSocket進行SSL編程非常簡單,SSL握手以及數據加解密過程對於開發者都是透明的。但這種方式只支持BIO,對於NIO的SSL編程,Java並沒有提供類似的SSLSocketChannel與SSLServerSocketChannel類(Java維護者太懶了),需要在建立Socket連接後,通過Socket連接手動傳輸握手協議相關數據,用以完成握手過程,握手結束後還需要在每次通信時手動進行加解密。手動實現SSL握手以及加解密太過繁瑣,Java提供了SSLEngine類幫助實現這一過程,但SSL握手以及加解密過程對於開發者而言依然不是透明的。SSLEngine與網絡通信完全獨立(所以SSLEngine可以通過任何方式實現SSL通信過程,無論BIO、NIO還是AIO),它有多個狀態,如握手過程中的某個狀態或已完成握手狀態,其wrap方法會根據當前狀態封裝出數據(握手階段會封裝出相應的握手數據,數據傳輸階段會加密數據),然後通過socket將封裝數據發送出去,當通過socket接收到數據時,其unwrap方法會根據當前狀態解密數據(握手階段會在底層處理掉握手數據,數據傳輸階段會解密數據)。SSLEngine不僅可以爲NIOSocket提供SSL,也可以爲BIO和AIO提供SSL(但BIO使用SSLSocket和SSLServerSocket更簡單)。爲了簡便,可以用BIO實現客戶端,爲了高效,可以用NIO實現服務端。

// 安全上下文,可以用於獲取客戶端/服務端安全套接字,以及作爲SSLEngine的工廠
public class SSLContext {
    public static synchronized void setDefault(SSLContext context); // 設置默認的SSLContext
    public static synchronized SSLContext getDefault(); // 返回默認的SSLContext,如果未設置,會先調用getInstance("Default")進行設置再返回,個人測試getInstance("Default")獲取的SSLContext不好使用。
    public static SSLContext getInstance(String protocol); // 協議如:SSL、SSLv3、TLS、TLSv1.2、DTLSv1.2(UDP安全層)等

	// 初始化此上下文,已初始化的SSLContext不能再調用此函數,km爲代表當前應用的證書,tm爲受信任的證書,只有km和tm數組中的第一個元素纔有意義
	// 最常見的方式是通過KeyManagerFactory以及TrustManagerFactory來得到參數,但也可以通過實現X509KeyManager以及X509TrustManager來構建參數
    public final void init(KeyManager[] km, TrustManager[] tm, SecureRandom sr); 

    public final SSLSocketFactory getSocketFactory();  // 通過上下文獲取客戶端SSLSocketFactory
    public final SSLServerSocketFactory getServerSocketFactory(); // 通過上下文獲取服務端SSLServerSocketFactory
    public final SSLEngine createSSLEngine(); // 根據上下文生成SSLEngine
    
    public final String getProtocol(); // 獲取協議
    public final SSLSessionContext getServerSessionContext(); // 獲取服務方SSLSession上下文,可以對生成的SSLSession的默認屬性進行設置(如緩存大小、超時值)
    public final SSLSessionContext getClientSessionContext(); // 獲取客戶端SSLSession上下文
    public final SSLParameters getDefaultSSLParameters(); // 返回此SSL上下文默認設置的SSLParameters的副本
    public final SSLParameters getSupportedSSLParameters(); // 返回此SSL上下文支持設置的SSLParameters的副本
}

// 通過工廠模式生成KeyManager的實現類,KeyManager對應KeyStore.PrivateKeyEntry條目
public class KeyManagerFactory {
    public static final String getDefaultAlgorithm(); // 默認SunX509
    public static final KeyManagerFactory getInstance(String algorithm); // 根據算法創建工廠實例,一般算法取getDefaultAlgorithm()
    
    public final String getAlgorithm();
    public final void init(KeyStore ks, char[] keyEntryPasswd); // 用ks中的第一個KeyStore.PrivateKeyEntry條目初始化
    public final KeyManager[] getKeyManagers(); // 根據工廠獲取keyManagers
}

// 通過工廠模式生成生成最受信任的證書庫,證書庫中的證書可以驗證接收到的證書是否受信任
public class TrustManagerFactory {
    public static final String getDefaultAlgorithm(); // 默認SunX509
    public static final TrustManagerFactory getInstance(String algorithm); // 根據算法創建工廠實例,一般算法取getDefaultAlgorithm()
    
    public final String getAlgorithm();
    public final void init(KeyStore ks); // 用ks中的所有KeyStore.TrustedCertificateEntry條目以及所有KeyStore.PrivateKeyEntry對應證書鏈的鏈首證書初始化
    public final TrustManager[] getTrustManagers();
}

// 代表一個X509規範的KeyManager,可用於自定義KeyManager的實現類,該接口只適合客戶端和服務端SSLSocket而不適用於SSLEngine
// 對於SSLEngine需要使用其擴展類X509ExtendedKeyManager
public interface X509KeyManager extends KeyManager {
    String[] getServerAliases(String keyType, Principal[] issuers); // 獲取匹配的別名,keyType如RSA
    String chooseServerAlias(String keyType, Principal[] issuers, Socket socket); // 選擇一個別名作爲getCertificateChain與getPrivateKey的參數
    String[] getClientAliases(String[] keyType, Principal[] issuers);
    String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket);
    
    X509Certificate[] getCertificateChain(String alias); // 根據選擇的別名返回被驗證的證書鏈,其實現也可以無視alias參數
    PrivateKey getPrivateKey(String alias); // 根據選擇的別名返回被驗證的證書對應的私鑰
}

// 作爲X509KeyManager的功能擴展,X509KeyManager針對的SSLSocket進行別名選擇,擴展後添加了針對SSLEngine的別名選擇
public abstract class X509ExtendedKeyManager implements X509KeyManager {
    public String chooseEngineClientAlias(String[] keyType,, Principal[] issuers, SSLEngine engine); // 在Engine處於客戶端模式時,返回選擇的別名作爲getCertificateChain與getPrivateKey的參數
    public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) ;
}

// 代表一個X509規範的TrustManager,可用於自定義TrustManager的實現類,SSLSocket與SSLEngine方式都會回調該接口的方法
// 如果需要針對SSLSocket和SSLEngine使用不同的處理方式,可以使用實現它的虛擬類X509ExtendedTrustManager
// 使用X509ExtendedTrustManager後就不會回調到X509TrustManager的check方法,而是針對SSLSocket或SSLEngine回調X509ExtendedTrustManager的check方法
public interface X509TrustManager extends TrustManager {
    void checkClientTrusted(X509Certificate[] chain, String authType); // 服務器端實現的對客戶端傳來的證書鏈進行驗證,authType如RSA,驗證失敗拋出CertificateException異常
    void checkServerTrusted(X509Certificate[] chain, String authType); // 客戶端實現的對服務端傳來的證書鏈進行驗證,authType如RSA,驗證失敗拋出CertificateException異常
    X509Certificate[] getAcceptedIssuers(); // 返回所有用於驗證證書的可信任證書
}

public abstract class X509ExtendedTrustManager implements X509TrustManager {
    public abstract void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException;
    public abstract void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException;
    public abstract void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException;
    public abstract void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException;
}

// 用於創建SSLSocket的工廠
public abstract class SSLSocketFactory extends SocketFactory {
    public abstract String[] getSupportedCipherSuites(); // 支持的密碼套件
    public abstract String[] getDefaultCipherSuites(); // 默認可選擇的密碼套件,在握手過程中可以選擇的密碼套件,它是支持的密碼套件的子集
}

// 用於創建SSLServerSocket的工廠
public abstract class SSLServerSocketFactory extends ServerSocketFactory {
    public abstract String[] getSupportedCipherSuites(); // 支持的密碼套件
    public abstract String[] getDefaultCipherSuites(); // 默認可選擇的密碼套件,在握手過程中可以選擇的密碼套件,它是支持的密碼套件的子集
}

// SSLSocket擴展自Socket,它如同Socket一樣使用,額外添加了許多獲取SSL相關信息的接口
public abstract class SSLSocket extends Socket {
    public abstract String[] getSupportedCipherSuites(); // 支持的密碼套件
    public abstract String[] getEnabledCipherSuites(); // 可選擇的密碼套件,它是支持的密碼套件的子集
    public abstract void setEnabledCipherSuites(String[] suites);
    public abstract String[] getSupportedProtocols(); 
    public abstract String[] getEnabledProtocols();
    public abstract void setEnabledProtocols(String[] protocols);
    
    public abstract void setNeedClientAuth(boolean need); // 是否要求客戶端認證
    public abstract boolean getNeedClientAuth();
    public abstract void setWantClientAuth(boolean want);
    public abstract boolean getWantClientAuth();
    public abstract void setUseClientMode(boolean mode); // 設置爲客戶端模式還是服務端模式
    public abstract boolean getUseClientMode();
    
    public abstract void setEnableSessionCreation(boolean flag); // 控制此套接字是否可以建立新的SSL會話
    public abstract boolean getEnableSessionCreation();
    public abstract void startHandshake(); // 開始握手
    public abstract void addHandshakeCompletedListener(HandshakeCompletedListener listener); // 對握手完成進行監聽
    public abstract void removeHandshakeCompletedListener(HandshakeCompletedListener listener);
    public SSLSession getHandshakeSession();
    public abstract SSLSession getSession();
    
    public SSLParameters getSSLParameters();
    public void setSSLParameters(SSLParameters params);
}

// SSLServerSocket擴展自ServerSocket,它如同ServerSocket一樣使用,額外添加了許多獲取SSL相關信息的接口
public abstract class SSLServerSocket extends ServerSocket {
    public abstract String[] getEnabledCipherSuites();
    public abstract String[] getSupportedCipherSuites();
    public abstract void setEnabledCipherSuites(String[] suites);
    public abstract String[] getSupportedProtocols();
    public abstract String[] getEnabledProtocols();
    public abstract void setEnabledProtocols(String[] protocols);
    
    public abstract void setNeedClientAuth(boolean need); // 是否要求進行客戶端認證
    public abstract boolean getNeedClientAuth();
    public abstract void setWantClientAuth(boolean want);
    public abstract boolean getWantClientAuth();
    public abstract void setUseClientMode(boolean mode);
    public abstract boolean getUseClientMode();
    
    public abstract void setEnableSessionCreation(boolean flag);
    public abstract boolean getEnableSessionCreation();
    
    public SSLParameters getSSLParameters();
    public void setSSLParameters(SSLParameters params);
}

// SSLEngine模擬了SSL協議的全套過程,過程中涉及到的傳出和接收數據由wrap與unwrap函數模擬
// 該類與網絡通信沒有任何關係,所以wrap和unwrap的數據需要通過手動進行網絡編程來傳出與接收數據
public abstract class SSLEngine {    
    public abstract void beginHandshake(); // 啓動握手(初始或重新協商)
    public abstract HandshakeStatus getHandshakeStatus(); // 獲取握手狀態,在不同的握手狀態需要不同的處理方式
    
    // 對網絡數據與應用數據進行相互轉換,網絡數據是握手數據或加密後的數據,應用數據是加密前或解密後的數據
    public SSLEngineResult wrap(ByteBuffer src, ByteBuffer dst);
    public SSLEngineResult wrap(ByteBuffer[] srcs, ByteBuffer dst);
    public abstract SSLEngineResult wrap(ByteBuffer[] srcs, int offset, int length, ByteBuffer dst);
    public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer dst);
    public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts);
    public abstract SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts, int offset, int length);
    
    public abstract Runnable getDelegatedTask(); // 當握手狀態爲HandshakeStatus.NEED_TASK時,可以獲取到一個異步任務執行
    
    public abstract void closeInbound();
    public abstract boolean isInboundDone();
    public abstract void closeOutbound();
    public abstract boolean isOutboundDone(); 
    
    public abstract String[] getSupportedCipherSuites();
    public abstract String[] getEnabledCipherSuites();
    public abstract void setEnabledCipherSuites(String[] suites);
    public abstract String[] getSupportedProtocols();
    public abstract String[] getEnabledProtocols();
    public abstract void setEnabledProtocols(String[] protocols);
    
    public abstract void setUseClientMode(boolean mode);
    public abstract boolean getUseClientMode();
    public abstract void setNeedClientAuth(boolean need);
    public abstract boolean getNeedClientAuth();
    public abstract void setWantClientAuth(boolean want);
    public abstract boolean getWantClientAuth();
    
    public abstract void setEnableSessionCreation(boolean flag);
    public abstract boolean getEnableSessionCreation();
    public SSLSession getHandshakeSession();
    public abstract SSLSession getSession();
    public String getPeerHost();
    public int getPeerPort();
    
    public SSLParameters getSSLParameters();
    public void setSSLParameters(SSLParameters params);
}

// SSLSession代表了握手最後的結果
public interface SSLSession {
    SSLSessionContext getSessionContext(); // 返回此會話綁定的上下文
    byte[] getId(); // 返回分配給此會話的標識符
    
    String getCipherSuite(); // 協商後使用的密碼套件
    String getProtocol(); // 協商後使用的協議
    
    int getPacketBufferSize(); // 獲取使用此會話時預期的最大SSL / TLS數據包的當前大小
    int getApplicationBufferSize(); // 獲取使用此會話時預期的最大應用程序數據的當前大小
    
    Certificate[] getPeerCertificates(); // 對等端的證書鏈
    Certificate[] getLocalCertificates(); // 本地發送的證書鏈
    Principal getPeerPrincipal(); // 對端的證書主體
    Principal getLocalPrincipal(); // 本端發送的證書主體
    String getPeerHost(); // 對端的主機
    int getPeerPort(); // 對端的端口號
    
    long getCreationTime(); // 會話創建時間
    long getLastAccessedTime(); // 會話最後訪問時間    
    void invalidate(); // 使會話無效
    boolean isValid(); // 會話是否有效
}

  BIO方式SSL服務端示例:

package com.java.test.server;

import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.Socket;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SSLServerSocketTest {
    private static int serverPort = 9000;
    private static int threadNo = 200;
    private static String trustStroe = "e:/pki/keytool/server.trust";
    private static String keyStore = "e:/pki/keytool/server.store";
    private static String trustPasswd = "123456";
    private static String keyStorePasswd = "123456";
    private static String keyEntryPasswd = "654321";

    public static void main(String[] vars) throws Throwable {
        ExecutorService executorService = Executors.newFixedThreadPool(threadNo);

        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(createKeyStore(keyStore, keyStorePasswd), keyEntryPasswd.toCharArray());

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(createKeyStore(trustStroe, trustPasswd));

        SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());

        SSLServerSocketFactory socketFactory = sslContext.getServerSocketFactory();
        SSLServerSocket serverSocket = (SSLServerSocket) socketFactory.createServerSocket(serverPort);
        serverSocket.setNeedClientAuth(true);

        while (true) {
            Socket socket = serverSocket.accept();
            executorService.submit(new DealService(socket));
        }
    }

    public static KeyStore createKeyStore(String keyStoreFile, String passwd) throws Exception {
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(new FileInputStream(keyStoreFile), passwd.toCharArray());
        return keyStore;
    }

    public static class DealService implements Runnable {
        private Socket socket;

        public DealService(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try {
                InputStream is = socket.getInputStream();
                byte[] data = new byte[1024];
                int i = is.read(data);
                System.out.println("---------------" + new String(Arrays.copyOf(data, i)));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

  NIO方式SSL服務端示例:

package com.java.test.server;

import javax.net.ssl.*;
import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class SSLServerSocketChannelTest {
    private static final int SERVER_PORT = 9000;
    private static final String TRUST_STORE = "e:/pki/keytool/server.trust";
    private static final String KEY_STORE = "e:/pki/keytool/server.store";
    private static final String TRUST_STORE_PASSWD = "123456";
    private static final String KEY_STORE_PASSWD = "123456";
    private static final String KEY_ENTRY_PASSWD = "654321";

    // 保存每一個socket連接的關聯信息,注意連接斷開時(包括異常斷開)資源的釋放
    private static Map<SocketChannel, SSLSocketExtraInfo> socketsExtraInfo = new ConcurrentHashMap<>();

    public static void main(String[] vars) throws Throwable {
        SSLContext sslContext = createSSLContext(KEY_STORE, KEY_STORE_PASSWD, KEY_ENTRY_PASSWD, TRUST_STORE, TRUST_STORE_PASSWD);

        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(SERVER_PORT));
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            if (selector.select() > 0) {
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();

                    if (selectionKey.isAcceptable()) {
                        SocketChannel socketChannel = ((ServerSocketChannel)selectionKey.channel()).accept();
                        doHandShake(socketChannel, sslContext, selector);
                    } else if (selectionKey.isReadable()) {
                        SocketChannel sc = (SocketChannel) selectionKey.channel();
                        SSLSocketExtraInfo extraInfo = socketsExtraInfo.get(sc);

                        sc.read(extraInfo.netIn);
                        extraInfo.netIn.flip();
                        SSLEngineResult engineResult = extraInfo.sslEngine.unwrap(extraInfo.netIn, extraInfo.appIn);
                        extraInfo.netIn.compact();

                        if (engineResult.getStatus() == SSLEngineResult.Status.OK) {
                            extraInfo.appIn.flip();
                            String receivedData = Charset.forName("UTF8").newDecoder().decode(extraInfo.appIn).toString();
                            System.out.println("deal: " + receivedData); // 處理接收到的數據
                            extraInfo.appIn.compact();
                        }
                    }

                    iterator.remove();
                }
            }
        }
    }

    private static void doHandShake(SocketChannel sc, SSLContext sslContext, Selector selector) throws Exception {
        SSLSocketExtraInfo extraInfo = SSLSocketExtraInfo.createInstance(sslContext, false);
        socketsExtraInfo.put(sc, extraInfo);

        extraInfo.sslEngine.beginHandshake(); // 開始握手
        SSLEngineResult.HandshakeStatus hsStatus;
        do {
            hsStatus = extraInfo.sslEngine.getHandshakeStatus();
            switch (hsStatus) {
                case FINISHED:
                    break;
                case NEED_TASK:
                    extraInfo.sslEngine.getDelegatedTask().run();
                    break;
                case NEED_UNWRAP:
                    sc.read(extraInfo.netIn);
                    extraInfo.netIn.flip();
                    do {
                        extraInfo.sslEngine.unwrap(extraInfo.netIn, extraInfo.appIn);
                    } while (extraInfo.netIn.remaining() > 0);
                    extraInfo.netIn.clear();
                    break;
                case NEED_WRAP:
                    extraInfo.sslEngine.wrap(extraInfo.appOut, extraInfo.netOut);
                    extraInfo.netOut.flip();
                    sc.write(extraInfo.netOut);
                    extraInfo.netOut.clear();
                    break;
                case NOT_HANDSHAKING:
                    sc.configureBlocking(false);
                    sc.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                    break;
            }
        } while (hsStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING);
    }

    // 根據密鑰庫與信任證書庫創建SSLContext
    public static SSLContext createSSLContext(String keyStoreFile, String keyStorePasswd, String keyEntryPasswd, String trustStroeFile, String trustStorePasswd) throws Exception {
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(new FileInputStream(keyStoreFile), keyStorePasswd.toCharArray());
        trustStore.load(new FileInputStream(trustStroeFile), trustStorePasswd.toCharArray());

        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keyStore, keyEntryPasswd.toCharArray());
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(trustStore);

        SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());

        return sslContext;
    }

    // Socket連接的關聯信息
    static class SSLSocketExtraInfo {
        public SSLEngine sslEngine;
        public ByteBuffer appOut;
        public ByteBuffer appIn;
        public ByteBuffer netOut;
        public ByteBuffer netIn;

        public static SSLSocketExtraInfo createInstance(SSLContext sslContext, boolean clientMode) {
            SSLSocketExtraInfo sslSocketExtraInfo = new SSLSocketExtraInfo();

            sslSocketExtraInfo.sslEngine = sslContext.createSSLEngine();
            sslSocketExtraInfo.sslEngine.setUseClientMode(clientMode);
            sslSocketExtraInfo.sslEngine.setNeedClientAuth(true);

            SSLSession session = sslSocketExtraInfo.sslEngine.getSession();
            int appBufferMax = session.getApplicationBufferSize();
            int netBufferMax = session.getPacketBufferSize();

            sslSocketExtraInfo.appOut = ByteBuffer.allocate(appBufferMax);
            sslSocketExtraInfo.appIn = ByteBuffer.allocate(appBufferMax);
            sslSocketExtraInfo.netOut = ByteBuffer.allocateDirect(netBufferMax);
            sslSocketExtraInfo.netIn = ByteBuffer.allocateDirect(netBufferMax);

            return  sslSocketExtraInfo;
        }
    }
}

UDP編程

  UDP是用戶數據報文協議的簡稱,與TCP一樣是傳輸層協議,UDP編程較TCP編程簡單。UDP並非可靠的協議,但在當代比較優秀的網絡環境下,也是相當可靠的了。
  在UDP編程中最常用的是DatagramSocket與DatagramPacket,DatagramSocket負責報文的發送和接收,它綁定了本地地址和端口,但並不需要與接收方進行連接,接收方的地址和端口信息包含在數據報中,DatagramPacket就表示一個數據報。

// 用於接收和發送數據報
public class DatagramSocket implements java.io.Closeable {
    public DatagramSocket(); // 綁定到一個隨機端口
    public DatagramSocket(int port);
    public DatagramSocket(SocketAddress bindaddr);
    public DatagramSocket(int port, InetAddress laddr);
    
    public synchronized void bind(SocketAddress addr); // 構造函數一般都已經綁定了本地地址和端口,不可重複綁定
    public boolean isBound();
    public SocketAddress getLocalSocketAddress();
    public InetAddress getLocalAddress();
    public int getLocalPort();
   
	// 發送報文時,如果DatagramSocket已經連接到了某個地址,那麼發送報文時的DatagramPacket就不需要指定遠程地址信息,如果DatagramPacket指定了另外一個地址,會報錯
	// 接收報文時,如果DatagramSocket已經連接到了某個地址,那麼只會接收指定地址的報文
    public void connect(InetAddress address, int port);
    public void connect(SocketAddress addr);
    public void disconnect();
    public boolean isConnected();    
    public SocketAddress getRemoteSocketAddress();
    public InetAddress getInetAddress();
    public int getPort();    
    
    public void close();
    public boolean isClosed();
    
    public void send(DatagramPacket p); // 發送數據報
    public synchronized void receive(DatagramPacket p); // p用於接收報文,該函數會阻塞知道接收到報文
    
    public synchronized void setSoTimeout(int timeout);
    public synchronized int getSoTimeout();
    public synchronized void setSendBufferSize(int size);
    public synchronized int getSendBufferSize();
    public synchronized void setReceiveBufferSize(int size);
    public synchronized int getReceiveBufferSize();
    
    public synchronized void setBroadcast(boolean on); // 是否啓用了廣播,如果未啓用,向廣播地址(主機標識段爲全1的IP地址)發送報文會報錯
    public synchronized boolean getBroadcast();

	// 通過DatagramChannel進行UDP通信
    public DatagramChannel getChannel();
}

// 代表一個數據報
public final class DatagramPacket {
    public DatagramPacket(byte buf[], int offset, int length); // 
    public DatagramPacket(byte buf[], int length); // 
    public DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port);
    public DatagramPacket(byte buf[], int offset, int length, SocketAddress address);
    public DatagramPacket(byte buf[], int length, InetAddress address, int port);
    public DatagramPacket(byte buf[], int length, SocketAddress address);
    
    // DatagramSocket發送報文時,需手動設置接收端的地址
    // DatagramSocket接收報文時,被DatagramSocket自動設置爲發送方的地址
    public synchronized void setSocketAddress(SocketAddress address);
    public synchronized void setAddress(InetAddress iaddr);
    public synchronized void setPort(int iport);
    public synchronized SocketAddress getSocketAddress();
    public synchronized InetAddress getAddress();
    public synchronized int getPort();
    
    public synchronized byte[] getData();
    public synchronized int getLength();
    public synchronized int getOffset();
    public synchronized void setData(byte[] buf, int offset, int length);
    public synchronized void setData(byte[] buf);
    public synchronized void setLength(int length);
}

  示例代碼如下:

package com.java.test.server;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;

public class UDPSocketTest {
    public static void main(String[] vars) throws Exception {
        new Thread() {
            @Override
            public void run() {
                try {
                    MyServer.main();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();


        MyClient.main();
    }

    private static class MyServer {
        private static final int LISTEN_PORT = 9997;
        private static final int DATAGRAM_DATA_SIZE = 1024;

        public static void main() throws Exception {
            try (DatagramSocket datagramSocket = new DatagramSocket(LISTEN_PORT)) {
                byte[] packet = new byte[DATAGRAM_DATA_SIZE];
                DatagramPacket datagramPacket = new DatagramPacket(packet, packet.length);
                while (true) {
                    datagramSocket.receive(datagramPacket);
                    byte[] data = datagramPacket.getData();
                    System.out.println(new String(data));
                }
            }
        }
    }

    private static class MyClient {
        public static void main() throws Exception {
            DatagramSocket datagramSocket = new DatagramSocket();
            SocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 9997);

            datagramSocket.connect(socketAddress);

            byte[] data = "abc".getBytes();
            DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
            datagramSocket.send(datagramPacket);

            data = "dec".getBytes();
            datagramPacket = new DatagramPacket(data, data.length);
            datagramSocket.send(datagramPacket);
        }
    }
}

Http客戶端編程

  統一資源標識符URI用一個符合特定語法的字符串來表示一個資源,其字符串語法符合[scheme:]schemeSpacificPart[#fragment],其中[]中的內容可有可無,包含scheme:的叫着絕對URI,否則叫着相對RUI。統一資源定位符URL也是用一個符合特定語法規則的字符串來表示一個資源,而URL字符串的特殊規則是建立在URI字符串的特殊規則之上,所以URL是一個特殊的URI,其特殊之處在於URL字符串更加特別,它需要包含協議(訪問資源的方式)、位置(本地或網絡位置)和資源名等,也就是說有資源的定位信息,當然這樣一個資源當你通過資源定位位置去尋找時不一定真的存在。統一資源名稱URN也是URI的一個特例,它只定義了資源名而不包含定位信息,我們可以脫離定位信息討論資源。
  Java中的Http客戶端主要有URLConnection和HttpClient。

URLConnection

  URLConnection方式首先通過一個URL對象的openConnection()方法獲取一個URLConnection實例(一個HttpURLConnection或者HttpsURLConnection實例),然後對URLConnection實例進行設置(如超時時間,請求頭,請求方式),再通過URLConnection實例的connect()方法與服務器建立連接(對URLConnection實例的設置一定要在connect之前進行,否則報錯,有很多方法默認是先調用了connect方法的,如getOutputStream、getHeader系列方法),如果需要向服務器傳遞數據(如post請求),通過URLConnection實例的輸出流寫入數據,最後通過URLConnection實例的getResponseCode()方法獲取響應碼,並通過URLConnection實例的輸入流獲取服務端返回值。
  Https連接比Http連接多了SSL握手過程,通過設置SSLSocketFactory來進行底層的SSL握手管理(SSLSocketFactory獲取過程與SSLSocket編程部分相同),另外https還會進行主機驗證,通過設置HostnameVerifier來進行主機名驗證管理。默認的SSLSocketFactory和HostnameVerifier都已經設置,訪問https://www.baidu.com時不需要做任何處理,就像沒有走HTTPS協議一樣,因爲默認的SSLSocketFactory和HostnameVerifier能夠驗證通過https://www.baidu.com,如果自己的自簽名主機就無法通過默認的驗證。

public abstract class URLConnection {
	// FileNameMap接口只有一個getContentTypeFor方法,該方法傳入一個文件名,返回對應的MimeType(一般根據文件名後綴,如abc.html返回text/html),可以用來根據文件名設置請求的默認MimeType。
    public static synchronized FileNameMap getFileNameMap(); 
    public static void setFileNameMap(FileNameMap map); // 如果需要在原基礎上添加文件名與MimeType對應關係,在新建的FileNameMap的getContentTypeFor方法中,先判斷是不是新文件名(新後綴),如果是就返回對應的MimeType,否則就返回老FileNameMap(老FileNameMap作爲全局靜態變量)的getContentTypeFor方法。
    public static String guessContentTypeFromName(String fname); // getFileNameMap.getContentTypeFor(fname);
    public static String guessContentTypeFromStream(InputStream is);// 根據輸入流猜測ContentType

	public URL getURL();	
    abstract public void connect() throws IOException; // 與服務器建立連接,對連接的設置應該在此之前。    

	// 請求頭,一個關鍵字爲key的請求頭可以對應多個值
    public void setRequestProperty(String key, String value); // 如果key沒有值就添加值,如果key有值,就更改key的最後一個值
    public void addRequestProperty(String key, String value); // 爲指定的key添加值
    public String getRequestProperty(String key); // 獲取key的最後一個值
    public Map<String,List<String>> getRequestProperties(); // 獲取所有key及其所有值

	// 獲取響應頭,與請求頭類似,但只有get方法
    public String getHeaderField(String name);
    public Map<String,List<String>> getHeaderFields();
    public int getHeaderFieldInt(String name, int Default);
    public long getHeaderFieldLong(String name, long Default);
    public String getHeaderFieldKey(int n); // 獲取第n個key值
    public String getHeaderField(int n); // 等同於getHeaderField(getHeaderFieldKey(n));
    public long getExpiration();  // getHeaderFieldLong("Expires")  過期時間
    public long getLastModified(); // getHeaderFieldLong("Last-Modified")  服務器該請求內容(如html文件)最後修改時間
    public long getDate(); // getHeaderFieldLong("Date") 服務端響應日期

	// 數據通信
    public InputStream getInputStream(); // 獲取服務端數據
    public OutputStream getOutputStream(); // 向服務器傳輸數據
    public void setDoInput(boolean doinput);
    public boolean getDoInput();
    public void setDoOutput(boolean dooutput);  // 是否可以調用getOutputStream
    public boolean getDoOutput();
    
    // 超時
    public void setConnectTimeout(int timeout);
    public int getConnectTimeout(); // 連接超時時間
    public void setReadTimeout(int timeout); 
    public int getReadTimeout(); // 讀InputStream的超時時間
    
    // 緩存
    public void setUseCaches(boolean usecaches);
    public boolean getUseCaches();
    public boolean getDefaultUseCaches(); // 是否使用緩存
    public void setDefaultUseCaches(boolean defaultusecaches);
    public void setIfModifiedSince(long ifmodifiedsince);
    public long getIfModifiedSince(); // getLastModifiled獲取的是本次請求返回的數據的最後修改時間,getModifiedSince是緩存內容的最後修改時間(獲取緩存內容的請求返回的LastModified時間),每次請求時服務器先判斷該時間之後內容是否修改過,沒有修改過直接通知客戶端從本地緩存獲取,節約網絡資源。
    
    public int getContentLength(); // getHeaderFieldInt("Content-Length")  請求資源的長度
    public long getContentLengthLong(); // getHeaderFieldLong("Content-Length")  請求資源的長度
    public String getContentType(); // getHeaderField("Content-Type")  請求資源的mimetype
    public String getContentEncoding(); // getHeaderField("Content-Encoding")  請求資源的編碼格式
    public Object getContent() throws IOException; // 獲取資源
}

public abstract class HttpURLConnection extends URLConnection {
    public void setRequestMethod(String method) throws ProtocolException; // 請求方式如POST、GET
    public String getRequestMethod();

    public int getResponseCode() throws IOException; // 響應碼
    public String getResponseMessage() throws IOException; // 響應碼對應的消息
    
    public abstract boolean usingProxy(); // 指示連接是否通過代理

	// 重定向
    public static void setFollowRedirects(boolean set); // 所有HttpURLConnection默認是否開啓重定向,當收到3xx響應碼時自動轉向新的連接
    public static boolean getFollowRedirects();
    public void setInstanceFollowRedirects(boolean followRedirects); // 當前HttpURLConnection是否開啓重定向
    public boolean getInstanceFollowRedirects();
    
    // 每個HttpURLConnection實例都可用於生成單個請求,但是其他實例可以透明地共享連接到HTTP服務器的基礎網絡,
    // 調用輸入輸出流的close方法釋放資源不會涉及到斷開基礎網絡的持久連接(socket連接),而disconnect可能會關閉socket連接,
    // 近期服務器不太可能有其他請求時,才通過disconnect釋放資源,否則不需要釋放底層連接。
    public abstract void disconnect(); 
}

public abstract class HttpsURLConnection extends HttpURLConnection {
    public abstract String getCipherSuite(); // 獲取最終協商的密碼套件
    public abstract Certificate[] getLocalCertificates(); // 返回在握手期間發送到服務器的證書
    public abstract Certificate[] getServerCertificates(); // 返回在握手期間從服務器接收到的證書
    public Principal getPeerPrincipal(); 
    public Principal getLocalPrincipal(); 
    
    // HostnameVerifier管理
    public static void setDefaultHostnameVerifier(HostnameVerifier verifier); // 必須在openConnection之前設置纔有效
    public static HostnameVerifier getDefaultHostnameVerifier();    
    public void setHostnameVerifier(HostnameVerifier verifier);
    public HostnameVerifier getHostnameVerifier();
    
    // SSLSocketFactory管理
    public static void setDefaultSSLSocketFactory(SSLSocketFactory factory); // 必須在openConnection之前設置纔有效
    public static SSLSocketFactory getDefaultSSLSocketFactory();
    public void setSSLSocketFactory(SSLSocketFactory factory);
    public SSLSocketFactory getSSLSocketFactory();    
}    

// 用於驗證主機名,有一些實現類
// com.sun.deploy.security.CertificateHostnameVerifier,基於X509證書主體來驗證hostname
// sun.net.www.protocol.https.DefaultHostnameVerifier, 驗證函數直接返回false
// org.apache.http.conn.ssl.NoopHostnameVerifier(位於httpClient包中),驗證函數直接返回true,提供了單例NoopHostnameVerifier.INSTANCE
public interface HostnameVerifier {
    boolean verify(String hostname, SSLSession session); // 用於驗證當前session是否匹配hostname
}

  基於POST的代碼示例如下:

package com.test;

import com.alibaba.fastjson.JSONObject;

import javax.net.ssl.*;
import java.io.InputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class HttpURLConnectionTest {
    private static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded";
    private static final String CONTENT_TYPE_JSON = "application/json";
    private static final String HOST = "http://localhost:8080/a";

    public static void main(String[] vars) throws Exception {
        Map<String, Object> params = new HashMap<>();
        params.put("abc", "sfesf");
        params.put("def", 5);
        post(HOST, params, "utf-8", "json");
    }

    private static void post(String urlStr, Map<String, Object> params, String encode, String dataFormat) throws Exception {
        String postData;
        String contentType;
        if (dataFormat.equals("form")) { // form格式post
            contentType = CONTENT_TYPE_FORM;
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String, Object> param:params.entrySet()) {
                if (sb.length() != 0) {
                    sb.append("&");
                }

                sb.append(URLEncoder.encode(param.getKey(), encode));
                sb.append("=");
                sb.append(URLEncoder.encode(param.getValue().toString(), encode));
            }
            postData = sb.toString();
        } else if (dataFormat.equals("json")) { // json格式post
            contentType = CONTENT_TYPE_JSON;
            postData = JSONObject.toJSONString(params);
        } else {
            throw new Exception("不支持的格式");
        }
        byte[] postDataBytes = postData.getBytes(encode);

        URL url = new URL(urlStr);
        HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
        urlConnection.setRequestMethod("POST");
        urlConnection.setRequestProperty("Content-Type", contentType);
        urlConnection.setRequestProperty("Content", String.valueOf(postDataBytes.length));
        urlConnection.setConnectTimeout(2000);
        urlConnection.setReadTimeout(5000);
        urlConnection.setHostnameVerifier(MyHostnameVerifier.getInstance());
        urlConnection.setSSLSocketFactory(getSSLSocketFactory());
        urlConnection.setDoOutput(true);
        urlConnection.getOutputStream().write(postDataBytes);

        urlConnection.connect();
        InputStream is = urlConnection.getInputStream();

        byte[] data = new byte[1024];
        int i = is.read(data);
        System.out.println(new String(Arrays.copyOf(data, i)));
    }

    // 獲取SSLSocketFactory,詳見SSLSocket一節
    private static SSLSocketFactory getSSLSocketFactory() throws Exception {
        KeyManager keyManager = null; //
        TrustManager trustManager = null; //

        SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        sslContext.init(
                new KeyManager[] { keyManager },
                new TrustManager[] { trustManager },
                new SecureRandom()
        );

        return sslContext.getSocketFactory();
    }

    static class MyHostnameVerifier implements HostnameVerifier {
        private static MyHostnameVerifier instance = new MyHostnameVerifier();

        private MyHostnameVerifier() {}

        public static MyHostnameVerifier getInstance() {
            return instance;
        }

        @Override
        public boolean verify(String s, SSLSession sslSession) {
            return true;
        }
    }
}

HttpClient

  HttpCore組件對HTTP協議做了最基礎的封裝,它本身不提供客戶端、代理、服務器的功能,但可以基於它來實現這些功能。HttpClient就是基於HttpCore實現的Http客戶端,用HttpClient可以與Http服務器進行通信。HttpClient與HttpCore都是由apache出品的httpcomponents下的子項目。
  訪問站點,首先通過HttpClientBuilder的create方法創建一個HttpClientBuilder實例,再通過HttpClientBuilder實例的build方法創建HttpClient實例,然後需要構建一個Http請求HttpRequestBase(其實現類有HttpPost、HttpGet、HttpPut、HttpDelete等,對於實現了HttpEntityEnclosingRequest的請求可以通過其setEntity方法設置請求體),通過HttpClient實例的execute方法發出請求並得到服務器的響應結果HttpResponse,最後可以對HttpResponse進行分析。
  HttpPost、HttpGet、HttpPut、HttpPatch、HttpOptions、HttpTrace、HttpDelete、HttpHead都是HttpRequestBase的子類,其中HttpPost、HttpPut、HttpPatch實現了HttpEntityEnclosingRequest接口(實際上它們繼承自HttpEntityEnclosingRequestBase類,HttpEntityEnclosingRequest類是HttpRequestBase子類且實現了HttpEntityEnclosingRequest接口),實現了HttpEntityEnclosingRequest接口意味着可以向請求中添加請求體。
  瀏覽器具有cookie保持等功能,HttpClient也有這樣的功能,通過HttpClient對象發出請求時會附帶上該HttpClient對象保持的cookie信息(當然是與請求地址相同作用域的cookie),而且還會將服務端響應的新cookie(響應頭爲set-cookie的)添加到該HttpClient對象上,下次通過該HttpClient對象發送請求時新cookie就會一起發送。如果多個HttpClient對象(如HttpClient池)希望共享cookie信息,可以通過共享同一個HttpContext對象來實現(可以通過HttpCoreContext的靜態方法create()來創建HttpContext實例對象),所有HttpClient對象在發送請求時都附帶上該HttpContext對象作爲參數。實際上通過HttpClient發送請求時,會綜合發送HttpClient對象與HttpContext對象的cookie並集,而服務端響應的設置cookie,HttpClient對象與HttpContex對象都會進行設置。

// 用於創建一個HttpClient,先通過其靜態方法create創建一個HttpClientBuilder實例,然後對builder進行一系列配置,最後調用build方法創建HttpClient
// 另外有HttpClients也可用來創建一些默認配置的HttpClient
public class HttpClientBuilder {
    public static HttpClientBuilder create(); // 創建一個HttpClientBuilder實例
    public CloseableHttpClient build(); // 根據HttpClientBuilder配置創建HttpClient
    
    // 如果設置了連接管理器,那麼HttpClientBuilder中與連接相關的設置將會無效
    public final HttpClientBuilder setConnectionManager(final HttpClientConnectionManager connManager);
    public final HttpClientBuilder setConnectionManagerShared(final boolean shared); // ConnectionManager是否可被多個HttpClient實例共享

	// Https相關設置,HostnameVerifier用於主機驗證,SSLContext用於握手過程
	// setSSLSocketFactory會使setSSLHostnameVerifier與setSSLContext設置無效,但如果設置了ConnectionManager,這些方法均無效
    public final HttpClientBuilder setSSLHostnameVerifier(final HostnameVerifier hostnameVerifier);
    public final HttpClientBuilder setSSLContext(final SSLContext sslContext);
    public final HttpClientBuilder setSSLSocketFactory(final LayeredConnectionSocketFactory sslSocketFactory); // 同時包含了設置SSLContext與HostnameVerifier,一般使用LayeredConnectionSocketFactory的實現類SSLConnectionSocketFactory來構建參數
    
    // 在發送請求前和接收響應後可以對請求和響應分別進行攔截處理
    // 系統有一個HttpRequestInterceptor列表和一個HttpResponseInterceptor列表,發送請求前會依次調用HttpRequestInterceptor列表中各元素的process方法,接收響應後會一次調用HttpResponseInterceptor列表中各元素的process方法
    public final HttpClientBuilder addInterceptorFirst(final HttpResponseInterceptor itcp); // 添加到HttpResponseInterceptor列表開頭
    public final HttpClientBuilder addInterceptorLast(final HttpResponseInterceptor itcp); // 添加到HttpResponseInterceptor列表結尾
    public final HttpClientBuilder addInterceptorFirst(final HttpRequestInterceptor itcp); // 添加到HttpRequestInterceptor列表開頭
    public final HttpClientBuilder addInterceptorLast(final HttpRequestInterceptor itcp); // 添加到HttpRequestInterceptor列表結尾
	
	// 如果設置了ConnectionManager,本段方法將無效
    public final HttpClientBuilder setDefaultSocketConfig(final SocketConfig config); // 默認的socket配置
    public final HttpClientBuilder setDefaultConnectionConfig(final ConnectionConfig config); // 默認的連接配置
    public final HttpClientBuilder setMaxConnTotal(final int maxConnTotal); // 最大連接數
    public final HttpClientBuilder setMaxConnPerRoute(final int maxConnPerRoute); // 每個Route的最大連接數
    public final HttpClientBuilder setConnectionTimeToLive(final long connTimeToLive, final TimeUnit connTimeToLiveTimeUnit); // 連接池中連接的最大存活時間
    
    public final HttpClientBuilder setDefaultHeaders(final Collection<? extends Header> defaultHeaders); // 通過該Builder構建的HttpClient發送請求時都會附帶默認請求頭,請求頭的實現類有BasicHeader
    public final HttpClientBuilder setDefaultRequestConfig(final RequestConfig config); // 設置默認的請求配置
    
    public final HttpClientBuilder disableCookieManagement(); // 禁用cookie管理,當響應頭爲set-cookie時,並不會設置本地的cookie值,也不會自動將本地cookie以請求頭的方式發送到服務端(但可以手動設置Cookie請求頭髮送到服務端)
    public final HttpClientBuilder setUserAgent(final String userAgent); // 設置user-agent請求頭(一般用於識別客戶端操作系統、瀏覽器版本等信息)
    public final HttpClientBuilder disableDefaultUserAgent(); // 不設置UserAgent時依然包含user-agent請求頭(value爲默認值),調用此方法後就不會包含user-agent請求頭(前提是未設置UserAgent)  
    public final HttpClientBuilder setRedirectStrategy(final RedirectStrategy redirectStrategy); // 重定向策略
    public final HttpClientBuilder disableRedirectHandling();  // 關閉重定向
    public final HttpClientBuilder setProxy(final HttpHost proxy); // 設置代理主機
    public final HttpClientBuilder setDnsResolver(final DnsResolver dnsResolver); // 覆蓋操作系統默認的域名解析,自定義域名解析獲取InetAddress    
    
    public final HttpClientBuilder useSystemProperties(); // 使用系統屬性進行默認配置

	// 設置在發送請求前和接收響應後的處理類,調用該方法會讓很多其他設置無效,個人不建議手動調用此方法
    public final HttpClientBuilder setHttpProcessor(final HttpProcessor httpprocessor);
    // HttpRoutePlanne可以根據HttpHost、HttpRequest和HttpContext構建出HttpRoute,不設置能夠很好地處理路由問題(包括代理),個人不建議手動調用此方法
    public final HttpClientBuilder setRoutePlanner(final HttpRoutePlanner routePlanner);
    
    ...
}

// HttpClientConnectionManage最常用的實現類爲PoolingHttpClientConnectionManager
public class PoolingHttpClientConnectionManager implements HttpClientConnectionManager, ConnPoolControl<HttpRoute>, Closeable {
	// 構造函數	
    public PoolingHttpClientConnectionManager(
            final Registry<ConnectionSocketFactory> socketFactoryRegistry, // ConnectionSocketFactory的註冊中心(應用協議名如http與ConnectionSocketFactory的鍵值對),會根據Http請求的協議名,選取相應的ConnectionSocketFactory來創建Socket連接(或者SSLSocket連接)
            final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory, // 工廠負責根據HttpRout和ConnectionConfig創建HttpConnection
            final SchemePortResolver schemePortResolver, // SchemePortResolver可以根據HttpHost設置訪問的端口號
            final DnsResolver dnsResolver, // 自定義的dns解析方案
            final long timeToLive, final TimeUnit timeUnit); // 持久連接的最長生命週期,超過該生命週期的連接永遠不會被使用      
            
	public int getMaxTotal(); // 獲取連接池最大連接數
    public void setMaxTotal(final int max); // 設置連接池最大連接數
    public int getDefaultMaxPerRoute(); // 獲取每個路由的最大默認連接數
    public void setDefaultMaxPerRoute(final int max); // 設置每個路由的最大默認連接數
    public int getMaxPerRoute(final HttpRoute route); // 獲取指定路由的最大連接數
    public void setMaxPerRoute(final HttpRoute route, final int max); // 設置指定路由的最大連接數 
    public int getValidateAfterInactivity(); // 當某個連接處在非活動狀態的毫秒數超過該值時,需要重新驗證連接的可用性才能出租給連接的申請者
    public void setValidateAfterInactivity(final int ms);
    
    public SocketConfig getDefaultSocketConfig(); // 獲取默認的Socket配置
    public void setDefaultSocketConfig(final SocketConfig defaultSocketConfig);
    public SocketConfig getSocketConfig(final HttpHost host); // 針對某個指定的HttpHost進行的Socket配置
    public void setSocketConfig(final HttpHost host, final SocketConfig socketConfig);
    public ConnectionConfig getDefaultConnectionConfig(); // 獲取默認的Http連接配置
    public void setDefaultConnectionConfig(final ConnectionConfig defaultConnectionConfig);
    public ConnectionConfig getConnectionConfig(final HttpHost host); // 針對某個指定的HttpHost進行的Http連接配置 
    public void setConnectionConfig(final HttpHost host, final ConnectionConfig connectionConfig);
    
    public ConnectionRequest requestConnection(final HttpRoute route, final Object state); // 從連接池中申請連接
    public void connect(final HttpClientConnection managedConn, final HttpRoute route, final int connectTimeout, final HttpContext context); // 進行連接
    public void releaseConnection(final HttpClientConnection managedConn, final Object state, final long keepalive, final TimeUnit timeUnit); // 釋放連接
    public void upgrade(final HttpClientConnection managedConn, final HttpRoute route, final HttpContext context);
    public void routeComplete(final HttpClientConnection managedConn, final HttpRoute route, final HttpContext context);
            
    public void shutdown/close(); // 關閉所有連接,close直接調用了shutdown方法
    public void closeIdleConnections(final long idleTimeout, final TimeUnit timeUnit); // 關閉連接池中空閒了指定時間的連接
    public void closeExpiredConnections(); // 關閉連接池中所有失效的連接
    
    public PoolStats getTotalStats(); // 獲取連接池的統計信息,如空閒連接數、正在使用的連接數等
    public PoolStats getStats(final HttpRoute route); // 獲取連接池中,屬於某個HttpRoute的連接統計信息
    public Set<HttpRoute> getRoutes(); // 連接池中的所有HttpRoute
}

// 一個客戶端,負責發送請求並獲取響應
public interface HttpClient {
	// HttpHost包括三要素,協議、主機地址(IP或域名)、端口,除了有多個構造函數可以直接創建HttpHost對象外,另外有靜態方法create可以根據一個字符串如http://www.baidu.com:8080解析出三要素,構造函數不能通過一個字符串對三要素進行這樣的解析,所以構造函數不要在一個參數中包含多個要素。
	// HttpRequest表示一個請求內容,而其子類HttpUriRequest比HttpRequest多包含了URI信息,所以傳入HttpUriRequest參數就不需要再傳入HttpHost參數了,HttpRequestBase(HttpPost、HttpGet、HttpDelete等的父類)實現了HttpUriRequest。
    HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context); 
    HttpResponse execute(HttpHost target, HttpRequest request);
    HttpResponse execute(HttpUriRequest request, HttpContext context);
    HttpResponse execute(HttpUriRequest request); 
	
	// 與返回HttpResponse的方法一一對應,只是返回HttpResopnse的方法是直接對服務端響應的HttpResponse進行處理,而以下方法是回調一個處理器對服務端的響應HttpResponse進行處理,並返回處理結果
    <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context);
    <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler);
    <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context);
    <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler);
}

// 代表了請求和響應的實體內容,具體實現類有StringEntity、UrlEncodedFormEntity(爲StringEntiry的實現類)
// 作爲響應的HtpEntity時,只需要HttpEntity接口方法,作爲請求的HttpEntity時,需要自己構建HttpEntity,如StringEntity
public interface HttpEntity {
    long getContentLength(); // 內容長度
    Header getContentType(); // 內容的contentType
    Header getContentEncoding(); // 內容的編碼格式
    InputStream getContent(); // 以流的方式返回內容
    ...
}

// 對HttpEntity進行處理的工具函數,主要用於獲取相應內容
public final class EntityUtils {
    public static String toString(HttpEntity entity, String/Charset defaultCharset); 
    public static byte[] toByteArray(HttpEntity entity); 
    
    ...
}
    
// HttpRequestBase與HttpResponse都實現了該接口,主要包括協議版本以及Header操作
public interface HttpMessage {
    ProtocolVersion getProtocolVersion(); // 獲取協議版本,包括協議名稱,主版本號,次版本號
    
    // Header相當於一個鍵值對,常用的Header子類爲BasicHeader
    // HttpMessage包含許多Header,允許存在若干個name相同的Header
    boolean containsHeader(String name); // 是否包含名爲name的Header
    Header[] getAllHeaders();
    HeaderIterator headerIterator();
    Header[] getHeaders(String name);
    HeaderIterator headerIterator(String name);
    
    Header getFirstHeader(String name); // 第一個名爲name的Header
    Header getLastHeader(String name); // 最後一個名爲name的Header
    void addHeader(Header header); 
    void addHeader(String name, String value);
    void setHeader(String name, String value); // 修改第一個名爲name的header的值爲value,如果不存在名爲name()的header,那麼等同於addHeader(name, value)
    void setHeader(Header header); // 等同setHeader(header,getName(), header.getValue())
    void setHeaders(Header[] headers); // 用headers替換原來的所有headers,此後getAllHeaders返回的就是這裏設置的headers
    void removeHeader(Header header); // 移除指定的header(如果同一個header被add了多次,那麼只會移除第一個)
    void removeHeaders(String name); // 移除所有名爲name的Header
}

// AbstractExecutionAwareRequest是AbstractHttpMessage的子類
public abstract class HttpRequestBase extends AbstractExecutionAwareRequest implements HttpUriRequest, Configurable {    
    ProtocolVersion getProtocolVersion(); // 獲取請求版本信息
    public void setProtocolVersion(final ProtocolVersion version); // 設置請求版本信息
    public URI getURI(); // 獲取請求地址
    public void setURI(final URI uri);
    public abstract String getMethod(); // 獲取請求方法,如Post
    public RequestLine getRequestLine(); // RequestLine包括ProtocolVersion、Method、URI信息
    
    public RequestConfig getConfig(); // 請求配置信息
    public void setConfig(final RequestConfig config);
    public void releaseConnection(); // 釋放連接
}

// 需要傳遞實體內容的請求,如post、put請求
public abstract class HttpEntityEnclosingRequestBase extends HttpRequestBase implements HttpEntityEnclosingRequest { 
    public HttpEntity getEntity();
    public void setEntity(final HttpEntity entity);
}

// 服務端對請求的響應
public interface HttpResponse extends HttpMessage {
    StatusLine getStatusLine(); // StatusLine包括協議版本ProtocolVersion、返回碼StatusCode及其描述ReasonPhrase
    Locale getLocale(); // 國際化相關信息
    HttpEntity getEntity(); // 響應內容 
    
    ...對HttpResponse的設置,一般在應用中很少用...
}

// 通過RequestConfig.Builder構建一個請求配置(不涉及到請求的具體內容),一旦構建成功,將無法更改配置信息
// Builder中除build方法外,只存在set方法,而RequestConfig中只存在相應的get方法
public class RequestConfig implements Cloneable {    
    public static RequestConfig.Builder custom();  // 用於獲取RequestConfig.Builder對象來構建RequestConfig實例

	...一系列get方法,用於查看RequestConfig的配置信息,與RequestConfig.Builder中的配置一一對應...

	// 用於構建請求配置RequestConfig
    public static class Builder {
        public RequestConfig build();

        public Builder setConnectionRequestTimeout(final int connectionRequestTimeout); // 連接池獲取到連接的超時時間
        public Builder setConnectTimeout(final int connectTimeout); // 建立連接的超時時間
        public Builder setSocketTimeout(final int socketTimeout); // 獲取數據的超時時間    

        public Builder setRedirectsEnabled(final boolean redirectsEnabled); // 是否開啓重定向,只有在HttpClientBuilder與Request都開啓重定向時纔會開啓重定向
        public Builder setMaxRedirects(final int maxRedirects); // 客戶端發起一次請求允許的最大重定向次數
    
        public Builder setProxy(final HttpHost proxy); // 設置請求的代理主機        
        public Builder setLocalAddress(final InetAddress localAddress); // 設置請求的本地地址
        
        ...其他不常用設置...
    }
}

  編程示例如下:

package com.java.test.server;

import com.alibaba.fastjson.JSONObject;
import org.apache.http.*;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.*;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;

public class HttpClientTest {
    private static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded";
    private static final String CONTENT_TYPE_JSON = "application/json";

    public static void main(String[] vars) throws Throwable {
        Map<String, Object> params = new HashMap<>();
        params.put("abc", "sfesf");
        params.put("def", 5);
        post("http://localhost:8090/a", params, "utf-8", "json");

    }

    private static void post(String urlStr, Map<String, Object> params, String encode, String dataFormat) throws Exception {
        HttpEntity httpEntity;
        if (dataFormat.equals("json")) {
            StringEntity stringEntity = new StringEntity(JSONObject.toJSONString(params));
            stringEntity.setContentType(CONTENT_TYPE_JSON);
            stringEntity.setContentEncoding(encode);
            httpEntity = stringEntity;
        } else if (dataFormat.equals("form")) {
            List<NameValuePair> nameValuePairList = new ArrayList<>();
            for (Map.Entry<String, Object> entry: params.entrySet()) {
                nameValuePairList.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString()));
            }
            UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(nameValuePairList);
            urlEncodedFormEntity.setContentType(CONTENT_TYPE_FORM);
            urlEncodedFormEntity.setContentEncoding(encode);
            httpEntity = urlEncodedFormEntity;
        } else {
            throw new Exception("不支持的格式");
        }

        HttpPost httpPost = new HttpPost(urlStr);
        httpPost.setEntity(httpEntity);
        httpPost.setConfig(RequestConfig.custom()
                .setConnectTimeout(5000)
                .setSocketTimeout(4000)
                .build());

        HttpResponse httpResponse = MyHttpClient.INSTANCE.execute(httpPost);
        System.out.println(EntityUtils.toString(httpResponse.getEntity()));
    }

    private static class MyHttpClient {
        public static final HttpClient INSTANCE;

        static {
            try {
                HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

                Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("https", new SSLConnectionSocketFactory(getSSLContext(), NoopHostnameVerifier.INSTANCE))
                        .register("http", new PlainConnectionSocketFactory())
                        .build();
                PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
                connectionManager.setMaxTotal(50);
                connectionManager.setDefaultMaxPerRoute(10);
                httpClientBuilder.setConnectionManager(connectionManager);
                httpClientBuilder.setDefaultHeaders(Arrays.asList(new BasicHeader("TokenId", "aaaaaaa")));

                INSTANCE = httpClientBuilder.build();
            } catch (Exception e) {
                throw new RuntimeException();
            }
        }

        private static SSLContext getSSLContext() throws Exception {
            X509TrustManager x509TrustManager = new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

                }

                @Override
                public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            };

            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
            sslContext.init(null, new X509TrustManager[]{ x509TrustManager }, new SecureRandom());

            return sslContext;
        }
    }
}

RMI編程

  遠程方法調用RMI使得對遠程對象方法的調用就像調用本地方法一樣簡單,但它是針對Java語言設計的,所以客戶端和服務端的實現都必須是java代碼(由於客戶端和服務器底層是通過JRMP協議通信,所以用其他語言也可以實現JRMP協議框架,協議通信不僅能讓客戶端和服務端解耦,還能無視彼此的編程語言)。在相當長一段時間內,RMI都被當做過時的框架,它只用來學習研究而不在實際項目中使用(《Java核心編程》都持有這樣的觀點),但隨着微服務的崛起,RMI被重新賦予了強大的生命力(dubbo便支持使用rmi協議)。
  RMI分爲客戶端、服務端、註冊中心3個部分,註冊中心提供服務的註冊與發現服務,JRE中有rmiregistry命令工具(該命令的參數爲註冊中心的端口號)可以啓動一個註冊中心,也可以由LocateRegistry的createRegistry方法創建一個註冊中心。無論是啓動還是創建註冊中心,在其服務註冊功能啓動後,註冊中心首先會創建一個註冊中心的代理對象註冊到註冊中心以便服務端和客戶端通過該代理進行服務註冊和發現。
  服務端首先通過LocateRegistry的靜態方法getRegistry獲取註冊中心的本地代理,然後通過該代理的bind或rebind方法向註冊中心註冊自己的服務。註冊服務首先需要定義服務接口及其實現類,服務接口必須繼承Remote接口表示這是一個遠程調用接口,而且所有的服務方法都需要聲明RemoteException異常,服務接口需要發佈給客戶端(把所有的服務接口打包成一個jar包發佈出去供客戶端使用是一個好方式),服務的實現類不但需要實現服務接口,還必須繼承UnicastRemoteObject類。
  客戶端首先也通過LocateRegistry的靜態方法getRegistry獲取一個註冊中心的本地代理,然後通過其lookup方法根據服務名獲取服務的本地代理,客戶端通過服務的本地代理與服務端通信(與服務端的通信由框架完成,客戶端只需要像使用普通對象一樣使用服務的本地代理)。
  服務發現與註冊的底層機制。服務註冊的過程,實際上是把命名的服務對象序列化後發送給註冊中心進行保存的過程,這要求註冊的服務對象必須是可序列化的,在客戶端獲取服務時,也是通過反序列化獲取的服務對象,那麼服務的執行最終是在客戶端執行反序列得到的服務對象的方法,例如服務端將ServiceImpl對象通過bind方法註冊到註冊中心,客戶端通過lookup方法從註冊中心獲取到ServiceImpl的序列化字節碼並將其反序列化爲ServiceImpl對象(這要求客戶端必須存在ServiceImpl類才能反序列化,所以服務器需要對客戶端發佈服務實現類的包),所以客戶端的服務調用實際上是對ServiceImpl對象方法的調用,方法內代碼的執行實際發生在客戶端。爲了能讓代碼的執行發生在服務端,在對ServiceImpl進行服務註冊的時候就不應該直接註冊ServiceImpl對象,而應該註冊其代理對象(代理對象也被叫着存根stub,在服務註冊過程中,服務端的骨架Skeleton會保留服務名與真實服務對象的對應關係),這樣客戶端lookup實際上是反序列化獲得了ServiceImpl的代理對象(這時客戶端不必存在ServiceImpl類,而應該存在ServiceImpl的代理類,默認情況下服務器註冊的所有服務代理都是Java動態代理的代理類對象,而Java動態代理的代理類已經包含在了JRE中,所以客戶端不必引入額外的ServiceImpl代理類了, 可以通過ServiceImpl的接口如Service來作爲該代理對象的訪問句柄,服務器只需要發佈接口而不用發佈服務的實現類),當客戶端通過該代理對象訪問服務時,代理對象會向服務器發送請求(通過網絡將服務名以及方法名、方法參數等信息發發送給服務器,所以方法參數也必須是可序列化的),服務器接收到請求後會交給骨架Skeleton找到相應的對象進行處理,方法的返回值通過反序列化發送回客戶端(所以方法的返回值也必須是可序列化的),客戶端通過服務代理對象返回給上層應用。因爲服務方法是由服務器定義的,所以方法的參數類型與返回值類型也由服務器定義(參數與返回值都必須可序列化),因而服務器發佈給客戶端的包至少應該包括服務接口及其參數和返回值的定義。早期版本需要手動編寫代理類(實際上Java提供了rmic工具根據服務實現類class文件來生成代理類,默認名爲ServiceImpl_stub),新版本中只要被註冊的服務對象所屬類繼承了UnicastRemoteObject類,那麼在bind的時候會自動生成其代理對象進行註冊(所以存根是在服務端生成的)。註冊中心在啓動時就註冊了一個註冊中心的代理對象,通過LocateRegistry.getRegistry(…)可以獲得該代理對象。
  通過LocateRegistry獲取註冊中心代理,然後通過該代理Registry獲取相應服務,而Naming提供了兩者的封裝,通過其靜態方法可以直接獲取某個註冊中心的相應服務

// 定位到一個註冊中心
public final class LocateRegistry {
    public static Registry getRegistry(String host, int port) throws RemoteException; // 根據主機與端口獲取註冊中心服務(註冊中心本身也是遠程對象)
    public static Registry getRegistry()throws RemoteException; // 默認本地主機的Registry.REGISTRY_PORT端口
    public static Registry getRegistry(int port) throws RemoteException; // 默認本地主機
    public static Registry getRegistry(String host) throws RemoteException; // 默認端口Registry.REGISTRY_PORT
    public static Registry createRegistry(int port) throws RemoteException; // 創建一個註冊中心
}    

// 註冊中心,本身就是一個匿名的遠程對象,在創建自己時就被註冊了自己
public interface Registry extends Remote {
    public static final int REGISTRY_PORT = 1099; // rmi協議默認端口1099
    public Remote lookup(String name) throws RemoteException, NotBoundException, AccessException; // 根據服務名查找服務
    public void bind(String name, Remote obj) throws RemoteException, AlreadyBoundException, AccessException; // 用指定服務名註冊遠程對象,不能重複註冊同一個服務名
    public void unbind(String name) throws RemoteException, NotBoundException, AccessException; // 根據服務名註銷服務
    public void rebind(String name, Remote obj) throws RemoteException, AccessException; // 先註銷服務再註冊
    public String[] list() throws RemoteException, AccessException; // 列出所有已註冊的服務名
}

// 對LocateRegistry與Registry的封裝,其name爲"rmi://host:port/服務名稱"
public final class Naming {
    public static Remote lookup(String name);
    public static void bind(String name, Remote obj);
    public static void unbind(String name);
    public static void rebind(String name, Remote obj);
    public static String[] list(String name);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章