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就表示一个数据报。DatagramSocket并非Socket的子类,除了接口相似外,它们没有任何关系。
UDP也提供了Channel方式的编程,可以使用阻塞方式,也可以使用非阻塞模式以及Selector模式(监听端口的那一端实际上也只有一个DatagramChannel,所以只会注册一个Channel到Selector,个人觉得DatagramChannel没有必要使用selector)。
// 用于接收和发送数据报
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);
}
// 通道方式发送数据报,继承了AbstractSelectableChannel,故可以入SocketChannel一样可以选择阻塞模式或Selector模式
public abstract class DatagramChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, MulticastChannel {
public static DatagramChannel open(); // 创建一个未绑定本地地址和端口的DatagramChannel
// 绑定本地地址端口,接收数据时必须已经绑定了端口,第一次发送数据时如果未绑定端口会自动绑定随机端口,以后发送数据时端口就已经绑定了
public abstract DatagramChannel bind(SocketAddress local);
public abstract SocketAddress getLocalAddress();
public abstract DatagramSocket socket(); // 该通道对应的socket
public final int validOps(); // 标识此通道所支持的操作
// 连接如同DatagramSocket的connect,连接之后可调用write方法,否则只能调用send方法
public abstract DatagramChannel connect(SocketAddress remote);
public abstract boolean isConnected();
public abstract DatagramChannel disconnect();
public abstract SocketAddress getRemoteAddress();
// 接收与发送数据报
public abstract SocketAddress receive(ByteBuffer dst);
public abstract int send(ByteBuffer src, SocketAddress target);
public abstract int read(ByteBuffer dst);
public abstract long read(ByteBuffer[] dsts, int offset, int length);
public final long read(ByteBuffer[] dsts);
public abstract int write(ByteBuffer src);
public abstract long write(ByteBuffer[] srcs, int offset, int length);
public final long write(ByteBuffer[] srcs);
}
示例代码如下:
package com.java.test.server;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
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.testSocket();
MyClient.testChannel();
}
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(datagramPacket.getSocketAddress() + ": " + new String(data));
}
}
}
}
private static class MyClient {
public static void testSocket() throws Exception {
SocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 9997);
DatagramSocket datagramSocket = new DatagramSocket();
datagramSocket.connect(socketAddress);
byte[] data = "socket-data1".getBytes();
DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
datagramSocket.send(datagramPacket);
data = "socket-data2".getBytes();
datagramPacket = new DatagramPacket(data, data.length);
datagramSocket.send(datagramPacket);
}
public static void testChannel() throws Exception {
SocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 9997);
DatagramChannel datagramChannel = DatagramChannel.open();
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
byteBuffer.put("channel-data".getBytes());
byteBuffer.flip();
datagramChannel.send(byteBuffer, socketAddress);
byteBuffer.clear();
}
}
}
Netty框架
在Java中,一般不直接使用JDK自带的Socket编程框架,而是使用JBOSS的开源框架Netty进行高效的网络编程。
Netty是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。这里不对Netty做详细介绍。
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);
}