这里分析一下IPC模型中的Server端的实现。该Server类的实现有点复杂,而且涉及到网络中字节流缓冲区的操作问题,及其字节数据的反序列化。
Server类
该Server是服务端的抽象实现,定义了一个抽象的IPC服务。 该IPC服务器接收Client发送的参数值,并返回响应值。同时,作为IPC模型的服务端,它要维护Client端到Server端的一组连接。
首先看Server类定义的几个属性:
- private String bindAddress; // 服务端绑定的地址
- private int port; // 服务端监听端口
- private int handlerCount; // 处理线程的数量
- private Class<? extends Writable> paramClass; // 调用的参数的类,必须实现Writable序列化接口
- private int maxIdleTime; // 当一个客户端断开连接后的最大空闲时间
- private int thresholdIdleConnections; // 可维护的最大连接数量
- int maxConnectionsToNuke; // the max number of connections to nuke during a cleanup
- protected RpcMetrics rpcMetrics; // 维护RPC统计数据
- private Configuration conf; // 配置类实例
- private int maxQueueSize; // 处理器Handler实例队列大小
- private int socketSendBufferSize; // Socket Buffer大小
- private final boolean tcpNoDelay; // if T then disable Nagle's Algorithm
- volatile private boolean running = true; // Server是否运行
- private BlockingQueue<Call> callQueue; // 维护调用实例的队列
- private List<Connection> connectionList = Collections.synchronizedList(new LinkedList<Connection>()); // 维护客户端连接的列表
- private Listener listener = null; // 监听Server Socket的线程,为处理器Handler线程创建任务
- private Responder responder = null; // 响应客户端RPC调用的线程,向客户端调用发送响应信息
- private int numConnections = 0; // 连接数量
- private Handler[] handlers = null; // 处理器Handler线程数组
一个Server实例的构造基本上基于上面的属性信息的,构造方法对一个Server实例进行初始化,包括一些静态信息如绑定地址、维护连接数量、队列等,还有一些用来处理Server端事务的线程等等。
先对Server类中定义的几个内部类来分析,这些类都是与Server端一些重要的事务的处理类。然后再分析Server类提供的全部基本操作。
- Server.Call内部类
该类Server端使用队列维护的调用实体类,如下所示:
- private static class Call {
- private int id; // 客户端调用Call的ID
- private Writable param; // 客户端调用传递的参数
- private Connection connection; // 到客户端的连接实例
- private long timestamp; // 向客户端调用发送响应的时间戳
- private ByteBuffer response; // 向客户端调用响应的字节缓冲区
- public Call(int id, Writable param, Connection connection) {
- this.id = id;
- this.param = param;
- this.connection = connection;
- this.timestamp = System.currentTimeMillis();
- this.response = null;
- }
- @Override
- public String toString() {
- return param.toString() + " from " + connection.toString();
- }
- public void setResponse(ByteBuffer response) {
- this.response = response;
- }
- }
- Server.Connection内部类
该类表示服务端一个连接的抽象,主要是读取从Client发送的调用,并把读取到的调用Client.Call实例加入到待处理的队列。
看如何构造一个Server.Connection对象:
- public Connection(SelectionKey key, SocketChannel channel, long lastContact) {
- this.channel = channel; // Socket通道
- this.lastContact = lastContact; // 最后连接时间
- this.data = null;
- this.dataLengthBuffer = ByteBuffer.allocate(4); //
- this.socket = channel.socket(); // 获取到与通道channel关联的Socket
- InetAddress addr = socket.getInetAddress(); // 获取Socket地址
- if (addr == null) {
- this.hostAddress = "*Unknown*";
- } else {
- this.hostAddress = addr.getHostAddress();
- }
- this.remotePort = socket.getPort(); // 获取到远程连接的端口号
- this.responseQueue = new LinkedList<Call>(); // 服务端待处理调用的队列
- if (socketSendBufferSize != 0) {
- try {
- socket.setSendBufferSize(socketSendBufferSize); // 设置Socket Buffer大小
- } catch (IOException e) {
- LOG.warn("Connection: unable to set socket send buffer size to " + socketSendBufferSize);
- }
- }
- }
另外,Server.Connection内部类中还定义了如下几个属性:
- private boolean versionRead = false; // 是否初始化签名,并读取了版本信息
- private boolean headerRead = false; // 是否读取了头信息
- private int dataLength; // 数据长度
- ConnectionHeader header = new ConnectionHeader(); // 连接头信息
- Class<?> protocol; // 协议类
- Subject user = null; // 用户的Subject信息
该内部类中,readAndProcess()方法读取远程过程调用的数据,从一个Server.Connection的Socket通道中读取数据,并将调用任务加入到callQueue,转交给Handler线程去处理。下面看下该方法的实现:
- public int readAndProcess() throws IOException, InterruptedException {
- while (true) {
- int count = -1;
- // 从通道channel中读取字节,加入到dataLengthBuffer字节缓冲区
- if (dataLengthBuffer.remaining() > 0) {
- count = channelRead(channel, dataLengthBuffer); // 如果通道已经达到了流的末尾,会返回-1的
- if (count < 0 || dataLengthBuffer.remaining() > 0) // 读取不成功,直接返回读取的字节数(读取失败可能返回0或-1)
- return count;
- }
- if (!versionRead) { // 如果版本号信息还没有读取
- ByteBuffer versionBuffer = ByteBuffer.allocate(1);
- count = channelRead(channel, versionBuffer); // 读取版本号信息
- if (count <= 0) { // 没有从通道channel中读取到版本号信息,直接返回
- return count;
- }
- int version = versionBuffer.get(0); // 读取到了版本号信息,从字节缓冲区中获取出来
- dataLengthBuffer.flip(); // 反转dataLengthBuffer缓冲区
- // 如果读取到的版本号信息不匹配,返回-1(HEADER = ByteBuffer.wrap("hrpc".getBytes()),CURRENT_VERSION = 3)
- if (!HEADER.equals(dataLengthBuffer) || version != CURRENT_VERSION) {
- //Warning is ok since this is not supposed to happen.
- LOG.warn("Incorrect header or version mismatch from "
- + hostAddress + ":" + remotePort
- + " got version " + version
- + " expected version " + CURRENT_VERSION);
- return -1;
- }
- // 成功读取到了版本号信息,清空dataLengthBuffer以便重用,同时设置versionRead为true
- dataLengthBuffer.clear();
- versionRead = true;
- continue;
- }
- if (data == null) {
- dataLengthBuffer.flip();
- dataLength = dataLengthBuffer.getInt(); // 读取数据长度信息,以便分配data字节缓冲区
- if (dataLength == Client.PING_CALL_ID) { // 如果是Client端的ping调用,不需要处理数据,清空dataLengthBuffer,返回
- dataLengthBuffer.clear();
- return 0;
- }
- data = ByteBuffer.allocate(dataLength); // 分配data数据缓冲区,准备接收调用参数数据
- incRpcCount(); // 增加RPC调用统计计数
- }
- count = channelRead(channel, data); // 从通道channel中读取字节到data字节缓冲区中
- if (data.remaining() == 0) { // 如果data已经如期读满
- dataLengthBuffer.clear(); // 清空dataLengthBuffer
- data.flip(); // 反转dat字节缓冲区,准备从data缓冲区读取数据
- if (headerRead) { // 如果头信息已经读过了,读取到的一定是RPC调用参数数据
- processData(); // 调用:处理读取到的调用数据,通过反序列化操作从网络字节流中冲重构调用参数数据对象,并构造Server.Call对象,同时加入callQueue队列,等待Server.Handler线程进行处理
- data = null;
- return count; // 处理完成后返回
- } else { // 如果头信息未读
- processHeader(); // 读取版本号后面的连接头信息
- headerRead = true; // 设置连接头信息已经读取过
- data = null; // 重置data字节缓冲区,以备下一个连接到来时缓冲字节
- // 通过调用processHeader()方法,已经将用户的Subject信息从header中读取到user中
- try {
- authorize(user, header); // 为客户端到来的连接进行授权
- if (LOG.isDebugEnabled()) {
- LOG.debug("Successfully authorized " + header);
- }
- } catch (AuthorizationException ae) {
- authFailedCall.connection = this;
- setupResponse(authFailedResponse, authFailedCall,
- Status.FATAL, null,
- ae.getClass().getName(), ae.getMessage());
- responder.doRespond(authFailedCall);
- // Close this connection
- return -1;
- }
- continue;
- }
- }
- return count;
- }
- }
上面方法是接收调用数据的核心方法,实现了如何从SocketChannel通道中读取数据。其中processHeader方法与processData方法已经在上面种详细分析了,不再多说。
另外,作为Server.Connection是连接到客户端的,与客户端调用进行通信,所以一个连接定义了关闭的操作,关闭的时候需要关闭与客户端Socket关联的SocketChannel通道。
- Server.Listener内部类
该类是继承自Thread线程类,用来监听服务器Socket,并未Handler处理器线程创建处理任务。从一个Listener线程类的构造来它需要初始化哪些必要信息:
- /*
- * 构造一个Listener实例,初始化线程数据
- */
- public Listener() throws IOException {
- address = new InetSocketAddress(bindAddress, port); // 根据bindAddress和port创建一个Socket地址
- acceptChannel = ServerSocketChannel.open(); // 创建一个Server Socket通道(ServerSocketChannel)
- acceptChannel.configureBlocking(false); // 设置Server Socket通道为非阻塞模式
- bind(acceptChannel.socket(), address, backlogLength); // 绑定
- port = acceptChannel.socket().getLocalPort(); // Socket绑定端口
- selector= Selector.open(); // 创建一个选择器(使用选择器,可以使得指定的通道多路复用)
- acceptChannel.register(selector, SelectionKey.OP_ACCEPT); // 向通道acceptChannel注册上述selector选择器,选择器的键为Server Socket接受的操作集合
- this.setName("IPC Server listener on " + port); // 设置监听线程名称
- this.setDaemon(true); // 设置为后台线程
- }
该线程类定义的方法如下所示:
- /**
- * 根据连接的空闲时间来清除connectionList中维护的连接
- */
- private void cleanupConnections(boolean force);
- /**
- * 根据key获取到与该key关联的连接,并关闭它
- */
- private void closeCurrentConnection(SelectionKey key, Throwable e);
- /**
- * 根据key关联的Server Socket通道,接收该通道上Client端到来的连接
- */
- void doAccept(SelectionKey key) throws IOException, OutOfMemoryError {
- Connection c = null;
- ServerSocketChannel server = (ServerSocketChannel) key.channel(); // 获取到Server Socket 通道
- for (int i=0; i<10; i++) { // 选择的该通道最多接受10个连接
- SocketChannel channel = server.accept(); // Client Socket通道
- if (channel==null) return;
- channel.configureBlocking(false); // 设置为非阻塞模式
- channel.socket().setTcpNoDelay(tcpNoDelay); // 设置TCP连接是否延迟
- SelectionKey readKey = channel.register(selector, SelectionKey.OP_READ); // 向选择器selector注册读操作集合,返回键
- c = new Connection(readKey, channel, System.currentTimeMillis()); // 创建连接
- readKey.attach(c); // 使连接实例与注册到选择器selector相关的读操作集合键相关联
- synchronized (connectionList) {
- connectionList.add(numConnections, c); // 加入Server端连接维护列表
- numConnections++; // 修改连接计数
- }
- if (LOG.isDebugEnabled())
- LOG.debug("Server connection from " + c.toString() +
- "; # active connections: " + numConnections +
- "; # queued calls: " + callQueue.size());
- }
- }
上面方法,是一个收集来自客户端的连接的实现。下面看一下监听线程的线程体部分实现:
- @Override
- public void run() {
- LOG.info(getName() + ": starting");
- SERVER.set(Server.this); // 设置当前监听线程本地变量的拷贝
- while (running) { // 如果服务器正在运行中
- SelectionKey key = null;
- try {
- selector.select(); // 选择一组key集合,这些选择的key相关联的通道已经为I/O操作做好准备
- Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
- while (iter.hasNext()) {
- key = iter.next(); // 迭代出一个key
- iter.remove();
- try {
- if (key.isValid()) {
- if (key.isAcceptable()) // 如果该key对应的通道已经准备好接收新的Socket连接
- doAccept(key); // 调用,接收与该key关联的通道上的连接
- else if (key.isReadable()) // 如果该通道为读取数据做好准备
- doRead(key); // 从通道读取数据,主要调用了Server.Connection的readAndProcess方法来读取数据,并设置该连接的最后连接时间
- }
- } catch (IOException e) {
- }
- key = null;
- }
- } catch (OutOfMemoryError e) {
- // we can run out of memory if we have too many threads
- // log the event and sleep for a minute and give
- // some thread(s) a chance to finish
- LOG.warn("Out of Memory in server select", e);
- closeCurrentConnection(key, e);
- cleanupConnections(true);
- try {
- Thread.sleep(60000);
- } catch (Exception ie) {
- }
- } catch (InterruptedException e) {
- if (running) { // unexpected -- log it
- LOG.info(getName() + " caught: "
- + StringUtils.stringifyException(e));
- }
- } catch (Exception e) {
- closeCurrentConnection(key, e);
- }
- cleanupConnections(false);
- }
- LOG.info("Stopping " + this.getName());
- // 跳出while循环,即running=false,服务器已经不再运行,需要关闭通道、选择器、全部连接
- synchronized (this) {
- try {
- acceptChannel.close(); // 关闭通道
- selector.close(); // 关闭通道选择器
- } catch (IOException e) {
- }
- selector = null;
- acceptChannel = null;
- // 关闭全部连接
- while (!connectionList.isEmpty()) {
- closeConnection(connectionList.remove(0)); // 关闭一个连接
- }
- }
- }
可见,Server.Listener主要负责两个阶段的任务:当服务器运行时,不断地通过选择器来选择继续的通道,处理基于该选择的通道上通信;当服务器不再运行以后,需要关闭通道、选择器、全部链接,释放一切资源。
- Server.Handler内部类
该类是一个处理线程类,负责处理客户端的全部调用。
该类的源代码如下所示:
- private class Handler extends Thread {
- public Handler(int instanceNumber) {
- this.setDaemon(true); // 作为后台线程运行
- this.setName("IPC Server handler " + instanceNumber + " on "+ port);
- }
- @Override
- public void run() {
- LOG.info(getName() + ": starting");
- SERVER.set(Server.this); // 设置当前处理线程的本地变量的拷贝
- ByteArrayOutputStream buf = new ByteArrayOutputStream(10240); // 存放响应信息的缓冲区
- while (running) {
- try {
- final Call call = callQueue.take(); // 出队操作,获取到一个调用Server.Call call
- if (LOG.isDebugEnabled())
- LOG.debug(getName() + ": has #" + call.id + " from " + call.connection);
- String errorClass = null;
- String error = null;
- Writable value = null;
- CurCall.set(call); // 设置当前线程本地变量拷贝的值为出队得到的一个call调用实例
- try {
- // 根据调用Server.Call关联的连接Server.Connection,所对应的用户Subject,来执行IPC调用过程
- value = Subject.doAs(call.connection.user,
- new PrivilegedExceptionAction<Writable>() {
- @Override
- public Writable run() throws Exception {
- // 执行调用
- return call(call.connection.protocol, call.param, call.timestamp);
- }
- });
- } catch (PrivilegedActionException pae) {
- Exception e = pae.getException();
- LOG.info(getName() + ", call " + call + ": error: " + e, e);
- errorClass = e.getClass().getName();
- error = StringUtils.stringifyException(e);
- } catch (Throwable e) {
- LOG.info(getName() + ", call " + call + ": error: " + e, e);
- errorClass = e.getClass().getName();
- error = StringUtils.stringifyException(e);
- }
- CurCall.set(null); // 当前Handler线程处理完成一个调用call,回收当前线程的局部变量拷贝
- // 处理当前获取到的调用的响应
- setupResponse(buf, call, (error == null) ? Status.SUCCESS : Status.ERROR, value, errorClass, error);
- responder.doRespond(call); // 将调用call加入到响应队列中,等待客户端读取响应信息
- } catch (InterruptedException e) {
- if (running) { // unexpected -- log it
- LOG.info(getName() + " caught: " + StringUtils.stringifyException(e));
- }
- } catch (Exception e) {
- LOG.info(getName() + " caught: " + StringUtils.stringifyException(e));
- }
- }
- LOG.info(getName() + ": exiting");
- }
- }
该线程主要的任务是:真正地实现了处理来自客户端的调用,并设置每个相关调用的响应。关于响应的实现,有个具体实现的线程类Server.Responder。
- Server.Responder内部类
该线程类实现发送RPC响应到客户端。
我们先对该线程类中方法进行阅读分析,然后再看线程体的实现过程。
- /**
- * 处理一个通道上调用的响应数据
- * 如果一个通道空闲,返回true
- */
- private boolean processResponse(LinkedList<Call> responseQueue, boolean inHandler) throws IOException {
- boolean error = true;
- boolean done = false; // 一个通道channel有更多的数据待读取
- int numElements = 0;
- Call call = null;
- try {
- synchronized (responseQueue) {
- // 如果该通道channel空闲,处理响应完成
- numElements = responseQueue.size();
- if (numElements == 0) {
- error = false;
- return true; // 完成响应的处理,返回
- }
- // 从队列中取出第一个调用call
- call = responseQueue.removeFirst();
- SocketChannel channel = call.connection.channel; // 获取该调用对应的通道channel
- if (LOG.isDebugEnabled()) {
- LOG.debug(getName() + ": responding to #" + call.id + " from " + call.connection);
- }
- // Send as much data as we can in the non-blocking fashion
- int numBytes = channelWrite(channel, call.response); // 向通道channel中写入响应信息(响应信息位于call.response字节缓冲区中)
- if (numBytes < 0) { // 如果写入字节数为0,说明已经没有字节可写,返回
- return true;
- }
- if (!call.response.hasRemaining()) { // 如果call.response字节缓冲区中没有响应字节数据,说明已经全部写入到相关量的通道中
- call.connection.decRpcCount(); // 该调用call对应的RPC连接计数减1
- if (numElements == 1) { // 最后一个调用已经处理完成
- done = true; // 该通道channel没有更多的数据
- } else {
- done = false; // 否则,还存在尚未处理的调用,要向给通道发送数据
- }
- if (LOG.isDebugEnabled()) {
- LOG.debug(getName() + ": responding to #" + call.id + " from " + call.connection + " Wrote " + numBytes + " bytes.");
- }
- } else { // 如果call.response字节缓冲区中还存在未被写入通道响应字节数据
- call.connection.responseQueue.addFirst(call); // 如果不能够将全部的响应字节数据写入到通道中,需要暂时插入到Selector选择其队列中
- if (inHandler) { // 如果指定:现在就对调用call进行处理(该调用的响应还没有进行处理)
- call.timestamp = System.currentTimeMillis(); // 设置调用时间戳
- incPending(); // 增加未被处理响应信息的调用计数
- try {
- writeSelector.wakeup(); // 唤醒阻塞在该通道writeSelector上的线程
- channel.register(writeSelector, SelectionKey.OP_WRITE, call); // 调用call注册通道writeSelector
- } catch (ClosedChannelException e) {
- done = true;
- } finally {
- decPending(); // 经过上面处理,不管在处理过程中正常处理,或是发生通道已关闭异常,最后,都将设置该调用完成,更新计数
- }
- }
- if (LOG.isDebugEnabled()) {
- LOG.debug(getName() + ": responding to #" + call.id + " from " + call.connection + " Wrote partial " + numBytes + " bytes.");
- }
- }
- error = false; // 设置出错标志:完成
- }
- } finally {
- if (error && call != null) {
- LOG.warn(getName() + ", call " + call + ": output error");
- done = true; // error. no more data for this channel.
- closeConnection(call.connection);
- }
- }
- return done;
- }
上面方法主要实现的是,处理响应队列responseQueue中的全部调用Call,对应的响应数据。关于处理响应的调用队列,是指类似call.connection.responseQueue的响应队列,可以理解为某个通道上调用的集合所对应的待处理响应数据的队列。
看下面的doRespond方法:
- void doRespond(Call call) throws IOException {
- synchronized (call.connection.responseQueue) {
- call.connection.responseQueue.addLast(call); // 将执行完成的调用加入队列,准备响应客户端
- if (call.connection.responseQueue.size() == 1) {
- processResponse(call.connection.responseQueue, true); // 如果队列中只有一个调用,直接进行处理
- }
- }
- }
当某个通道上可写的时候,可以执行异步写响应数据的操作,实现方法为:
- private void doAsyncWrite(SelectionKey key) throws IOException {
- Call call = (Call) key.attachment();
- if (call == null) {
- return;
- }
- if (key.channel() != call.connection.channel) {
- throw new IOException("doAsyncWrite: bad channel");
- }
- synchronized (call.connection.responseQueue) {
- if (processResponse(call.connection.responseQueue, false)) { // 调用processResponse处理与调用关联的响应数据
- try {
- key.interestOps(0);
- } catch (CancelledKeyException e) {
- LOG.warn("Exception while changing ops : " + e);
- }
- }
- }
- }
再看doPurge方法:
- /**
- * 如果未被处理响应的调用在队列中滞留超过指定时限,要定时清除掉
- */
- private void doPurge(Call call, long now) throws IOException {
- LinkedList<Call> responseQueue = call.connection.responseQueue;
- synchronized (responseQueue) {
- Iterator<Call> iter = responseQueue.listIterator(0);
- while (iter.hasNext()) {
- call = iter.next();
- if (now > call.timestamp + PURGE_INTERVAL) {
- closeConnection(call.connection);
- break;
- }
- }
- }
- }
最后,看一个Responder线程启动后,是如何工作的,在线程体run方法中可以看到:
- @Override
- public void run() {
- LOG.info(getName() + ": starting");
- SERVER.set(Server.this);
- long lastPurgeTime = 0; // 最后一次清除过期调用的时间
- while (running) { // 如果服务器处于运行状态
- try {
- waitPending(); // 等待一个通道中,接收到来的调用进行注册
- writeSelector.select(PURGE_INTERVAL); // 设置超时时限
- Iterator<SelectionKey> iter = writeSelector.selectedKeys().iterator();
- while (iter.hasNext()) { // 迭代选择器writeSelector选择的key集合
- SelectionKey key = iter.next();
- iter.remove();
- try {
- if (key.isValid() && key.isWritable()) { // 如果合法,并且通道可写
- doAsyncWrite(key); // 执行异步写操作,向通道中写入调用执行的响应数据
- }
- } catch (IOException e) {
- LOG.info(getName() + ": doAsyncWrite threw exception " + e);
- }
- }
- long now = System.currentTimeMillis();
- if (now < lastPurgeTime + PURGE_INTERVAL) {
- continue;
- }
- lastPurgeTime = now;
- // 如果存在一些一直没有被发送出去的调用,这是时间限制为lastPurgeTime + PURGE_INTERVAL
- // 则这些调用被视为过期调用,进行清除
- LOG.debug("Checking for old call responses.");
- ArrayList<Call> calls;
- synchronized (writeSelector.keys()) {
- calls = new ArrayList<Call>(writeSelector.keys().size());
- iter = writeSelector.keys().iterator();
- while (iter.hasNext()) {
- SelectionKey key = iter.next();
- Call call = (Call) key.attachment();
- if (call != null&& key.channel() == call.connection.channel) {
- calls.add(call);
- }
- }
- }
- for (Call call : calls) {
- try {
- doPurge(call, now); // 执行清除
- } catch (IOException e) {
- LOG.warn("Error in purging old calls " + e);
- }
- }
- } catch (OutOfMemoryError e) {
- LOG.warn("Out of Memory in server select", e);
- try {
- Thread.sleep(60000);
- } catch (Exception ie) {
- }
- } catch (Exception e) {
- LOG.warn("Exception in Responder " + StringUtils.stringifyException(e));
- }
- }
- LOG.info("Stopping " + this.getName());
- }
通过线程执行可以看到,调用的相应数据的处理,是在服务器运行过程中处理的,而且分为两种情况:
1、一种情况是:如果某些调用超过了指定的时限而一直未被处理,这些调用被视为过期,服务器不会再为这些调用处理,而是直接清除掉;
2、另一种情况是:如果所选择的通道上,已经注册的调用是合法的,并且通道可写,会直接将调用的相应数据写入到通道,等待客户端读取。
上面实现的Server的内部类,基本上定义了一个Server应该实现的基本操作,下面再看Server类中就比较容易了。
启动服务器:
- public synchronized void start() throws IOException {
- responder.start(); // 启动调用的响应数据处理线程
- listener.start(); // 启动监听线程
- handlers = new Handler[handlerCount]; // 启动多个处理器线程
- for (int i = 0; i < handlerCount; i++) {
- handlers[i] = new Handler(i);
- handlers[i].start();
- }
- }
停止服务器:
- public synchronized void stop() {
- LOG.info("Stopping server on " + port);
- running = false;
- if (handlers != null) { // 先中断全部处理器线程
- for (int i = 0; i < handlerCount; i++) {
- if (handlers[i] != null) {
- handlers[i].interrupt();
- }
- }
- }
- listener.interrupt(); // 终止监听器线程
- listener.doStop();
- responder.interrupt(); // 终止响应数据处理线程
- notifyAll();
- if (this.rpcMetrics != null) {
- this.rpcMetrics.shutdown();
- }
- }
通过对Server类的分析,应该能够了解IPC模型的Server端需要做哪些基本的事情。