Hadoop-0.20.0源代码分析(11)

这里分析一下IPC模型中的Server端的实现。该Server类的实现有点复杂,而且涉及到网络中字节流缓冲区的操作问题,及其字节数据的反序列化。

Server类

该Server是服务端的抽象实现,定义了一个抽象的IPC服务。 该IPC服务器接收Client发送的参数值,并返回响应值。同时,作为IPC模型的服务端,它要维护Client端到Server端的一组连接。

首先看Server类定义的几个属性:

[java]  view plain copy
  1. private String bindAddress;                     // 服务端绑定的地址  
  2. private int port;                               // 服务端监听端口  
  3. private int handlerCount;                       // 处理线程的数量  
  4. private Class<? extends Writable> paramClass;   // 调用的参数的类,必须实现Writable序列化接口  
  5. private int maxIdleTime;                        // 当一个客户端断开连接后的最大空闲时间   
  6. private int thresholdIdleConnections;           // 可维护的最大连接数量  
  7. int maxConnectionsToNuke;                       // the max number of connections to nuke during a cleanup    
  8. protected RpcMetrics  rpcMetrics;               // 维护RPC统计数据  
  9. private Configuration conf;                     // 配置类实例  
  10.   
  11. private int maxQueueSize;                       // 处理器Handler实例队列大小   
  12. private int socketSendBufferSize;               // Socket Buffer大小  
  13. private final boolean tcpNoDelay;               // if T then disable Nagle's Algorithm  
  14.   
  15. volatile private boolean running = true;        // Server是否运行  
  16. private BlockingQueue<Call> callQueue;          // 维护调用实例的队列  
  17.   
  18. private List<Connection> connectionList = Collections.synchronizedList(new LinkedList<Connection>()); // 维护客户端连接的列表  
  19. private Listener listener = null;               // 监听Server Socket的线程,为处理器Handler线程创建任务  
  20. private Responder responder = null;             // 响应客户端RPC调用的线程,向客户端调用发送响应信息  
  21. private int numConnections = 0;                 // 连接数量  
  22. private Handler[] handlers = null;              // 处理器Handler线程数组  

一个Server实例的构造基本上基于上面的属性信息的,构造方法对一个Server实例进行初始化,包括一些静态信息如绑定地址、维护连接数量、队列等,还有一些用来处理Server端事务的线程等等。

先对Server类中定义的几个内部类来分析,这些类都是与Server端一些重要的事务的处理类。然后再分析Server类提供的全部基本操作。

  • Server.Call内部类

该类Server端使用队列维护的调用实体类,如下所示:

[java]  view plain copy
  1. private static class Call {  
  2.   private int id;                               // 客户端调用Call的ID  
  3.   private Writable param;                       // 客户端调用传递的参数  
  4.   private Connection connection;                // 到客户端的连接实例  
  5.   private long timestamp;                       // 向客户端调用发送响应的时间戳  
  6.   private ByteBuffer response;                  // 向客户端调用响应的字节缓冲区  
  7.   
  8.   public Call(int id, Writable param, Connection connection) {   
  9.     this.id = id;  
  10.     this.param = param;  
  11.     this.connection = connection;  
  12.     this.timestamp = System.currentTimeMillis();  
  13.     this.response = null;  
  14.   }  
  15.     
  16.   @Override  
  17.   public String toString() {  
  18.     return param.toString() + " from " + connection.toString();  
  19.   }  
  20.   
  21.   public void setResponse(ByteBuffer response) {  
  22.     this.response = response;  
  23.   }  
  24. }  

  • Server.Connection内部类

该类表示服务端一个连接的抽象,主要是读取从Client发送的调用,并把读取到的调用Client.Call实例加入到待处理的队列。

看如何构造一个Server.Connection对象:

[java]  view plain copy
  1. public Connection(SelectionKey key, SocketChannel channel, long lastContact) {  
  2.   this.channel = channel; // Socket通道  
  3.   this.lastContact = lastContact; // 最后连接时间  
  4.   this.data = null;  
  5.   this.dataLengthBuffer = ByteBuffer.allocate(4); //   
  6.   this.socket = channel.socket(); // 获取到与通道channel关联的Socket  
  7.   InetAddress addr = socket.getInetAddress(); // 获取Socket地址  
  8.   if (addr == null) {  
  9.     this.hostAddress = "*Unknown*";  
  10.   } else {  
  11.     this.hostAddress = addr.getHostAddress();  
  12.   }  
  13.   this.remotePort = socket.getPort(); // 获取到远程连接的端口号  
  14.   this.responseQueue = new LinkedList<Call>(); // 服务端待处理调用的队列  
  15.   if (socketSendBufferSize != 0) {  
  16.     try {  
  17.       socket.setSendBufferSize(socketSendBufferSize); // 设置Socket Buffer大小  
  18.     } catch (IOException e) {  
  19.       LOG.warn("Connection: unable to set socket send buffer size to " + socketSendBufferSize);  
  20.     }  
  21.   }  
  22. }   

另外,Server.Connection内部类中还定义了如下几个属性:

[java]  view plain copy
  1. private boolean versionRead = false// 是否初始化签名,并读取了版本信息  
  2. private boolean headerRead = false// 是否读取了头信息  
  3. private int dataLength; // 数据长度  
  4. ConnectionHeader header = new ConnectionHeader(); // 连接头信息  
  5. Class<?> protocol; // 协议类  
  6. Subject user = null// 用户的Subject信息  

该内部类中,readAndProcess()方法读取远程过程调用的数据,从一个Server.Connection的Socket通道中读取数据,并将调用任务加入到callQueue,转交给Handler线程去处理。下面看下该方法的实现:

[java]  view plain copy
  1. public int readAndProcess() throws IOException, InterruptedException {  
  2.     while (true) {  
  3.         int count = -1;  
  4.         // 从通道channel中读取字节,加入到dataLengthBuffer字节缓冲区  
  5.         if (dataLengthBuffer.remaining() > 0) {  
  6.             count = channelRead(channel, dataLengthBuffer); // 如果通道已经达到了流的末尾,会返回-1的  
  7.             if (count < 0 || dataLengthBuffer.remaining() > 0// 读取不成功,直接返回读取的字节数(读取失败可能返回0或-1)  
  8.                 return count;  
  9.         }  
  10.   
  11.         if (!versionRead) { // 如果版本号信息还没有读取  
  12.             ByteBuffer versionBuffer = ByteBuffer.allocate(1);  
  13.             count = channelRead(channel, versionBuffer); // 读取版本号信息  
  14.             if (count <= 0) { // 没有从通道channel中读取到版本号信息,直接返回  
  15.                 return count;  
  16.             }  
  17.             int version = versionBuffer.get(0); // 读取到了版本号信息,从字节缓冲区中获取出来  
  18.   
  19.             dataLengthBuffer.flip(); // 反转dataLengthBuffer缓冲区  
  20.             // 如果读取到的版本号信息不匹配,返回-1(HEADER = ByteBuffer.wrap("hrpc".getBytes()),CURRENT_VERSION = 3)  
  21.             if (!HEADER.equals(dataLengthBuffer) || version != CURRENT_VERSION) {   
  22.                 //Warning is ok since this is not supposed to happen.  
  23.                 LOG.warn("Incorrect header or version mismatch from "  
  24.                         + hostAddress + ":" + remotePort  
  25.                         + " got version " + version  
  26.                         + " expected version " + CURRENT_VERSION);  
  27.                 return -1;  
  28.             }  
  29.             // 成功读取到了版本号信息,清空dataLengthBuffer以便重用,同时设置versionRead为true  
  30.             dataLengthBuffer.clear();  
  31.             versionRead = true;  
  32.             continue;  
  33.         }  
  34.   
  35.         if (data == null) {  
  36.             dataLengthBuffer.flip();   
  37.             dataLength = dataLengthBuffer.getInt(); // 读取数据长度信息,以便分配data字节缓冲区  
  38.   
  39.             if (dataLength == Client.PING_CALL_ID) { // 如果是Client端的ping调用,不需要处理数据,清空dataLengthBuffer,返回  
  40.                 dataLengthBuffer.clear();  
  41.                 return 0;   
  42.             }  
  43.             data = ByteBuffer.allocate(dataLength); // 分配data数据缓冲区,准备接收调用参数数据  
  44.             incRpcCount(); // 增加RPC调用统计计数  
  45.         }  
  46.   
  47.         count = channelRead(channel, data); // 从通道channel中读取字节到data字节缓冲区中  
  48.   
  49.         if (data.remaining() == 0) { // 如果data已经如期读满  
  50.             dataLengthBuffer.clear(); // 清空dataLengthBuffer  
  51.             data.flip(); // 反转dat字节缓冲区,准备从data缓冲区读取数据  
  52.             if (headerRead) { // 如果头信息已经读过了,读取到的一定是RPC调用参数数据  
  53.                 processData(); // 调用:处理读取到的调用数据,通过反序列化操作从网络字节流中冲重构调用参数数据对象,并构造Server.Call对象,同时加入callQueue队列,等待Server.Handler线程进行处理  
  54.                 data = null;  
  55.                 return count; // 处理完成后返回  
  56.             } else { // 如果头信息未读  
  57.                 processHeader(); // 读取版本号后面的连接头信息  
  58.                 headerRead = true// 设置连接头信息已经读取过  
  59.                 data = null// 重置data字节缓冲区,以备下一个连接到来时缓冲字节  
  60.   
  61.                 // 通过调用processHeader()方法,已经将用户的Subject信息从header中读取到user中  
  62.                 try {  
  63.                     authorize(user, header); // 为客户端到来的连接进行授权  
  64.   
  65.                     if (LOG.isDebugEnabled()) {  
  66.                         LOG.debug("Successfully authorized " + header);  
  67.                     }  
  68.                 } catch (AuthorizationException ae) {  
  69.                     authFailedCall.connection = this;  
  70.                     setupResponse(authFailedResponse, authFailedCall,  
  71.                             Status.FATAL, null,  
  72.                             ae.getClass().getName(), ae.getMessage());  
  73.                     responder.doRespond(authFailedCall);  
  74.   
  75.                     // Close this connection  
  76.                     return -1;  
  77.                 }  
  78.   
  79.                 continue;  
  80.             }  
  81.         }  
  82.         return count;  
  83.     }  
  84. }  

上面方法是接收调用数据的核心方法,实现了如何从SocketChannel通道中读取数据。其中processHeader方法与processData方法已经在上面种详细分析了,不再多说。

另外,作为Server.Connection是连接到客户端的,与客户端调用进行通信,所以一个连接定义了关闭的操作,关闭的时候需要关闭与客户端Socket关联的SocketChannel通道。

  • Server.Listener内部类

该类是继承自Thread线程类,用来监听服务器Socket,并未Handler处理器线程创建处理任务。从一个Listener线程类的构造来它需要初始化哪些必要信息:

[java]  view plain copy
  1. /* 
  2.  * 构造一个Listener实例,初始化线程数据  
  3.  */  
  4.   public Listener() throws IOException {  
  5.     address = new InetSocketAddress(bindAddress, port);      // 根据bindAddress和port创建一个Socket地址  
  6.     acceptChannel = ServerSocketChannel.open();               // 创建一个Server Socket通道(ServerSocketChannel)  
  7.     acceptChannel.configureBlocking(false);                   // 设置Server Socket通道为非阻塞模式  
  8.     bind(acceptChannel.socket(), address, backlogLength);     // 绑定  
  9.     port = acceptChannel.socket().getLocalPort();             // Socket绑定端口  
  10.     selector= Selector.open();                                // 创建一个选择器(使用选择器,可以使得指定的通道多路复用)  
  11.     acceptChannel.register(selector, SelectionKey.OP_ACCEPT); // 向通道acceptChannel注册上述selector选择器,选择器的键为Server Socket接受的操作集合  
  12.     this.setName("IPC Server listener on " + port);           // 设置监听线程名称  
  13.     this.setDaemon(true);                                     // 设置为后台线程  
  14.   }  

该线程类定义的方法如下所示:

[java]  view plain copy
  1. /**  
  2.  * 根据连接的空闲时间来清除connectionList中维护的连接 
  3.  */  
  4. private void cleanupConnections(boolean force);  
  5.   
  6. /**  
  7.  * 根据key获取到与该key关联的连接,并关闭它 
  8.  */  
  9. private void closeCurrentConnection(SelectionKey key, Throwable e);  
  10.   
  11. /**  
  12.  * 根据key关联的Server Socket通道,接收该通道上Client端到来的连接 
  13.  */  
  14. void doAccept(SelectionKey key) throws IOException,  OutOfMemoryError {  
  15.   Connection c = null;  
  16.   ServerSocketChannel server = (ServerSocketChannel) key.channel(); // 获取到Server Socket 通道  
  17.   for (int i=0; i<10; i++) { // 选择的该通道最多接受10个连接  
  18.     SocketChannel channel = server.accept(); // Client Socket通道  
  19.     if (channel==nullreturn;  
  20.     channel.configureBlocking(false); // 设置为非阻塞模式  
  21.     channel.socket().setTcpNoDelay(tcpNoDelay); // 设置TCP连接是否延迟  
  22.     SelectionKey readKey = channel.register(selector, SelectionKey.OP_READ); // 向选择器selector注册读操作集合,返回键  
  23.     c = new Connection(readKey, channel, System.currentTimeMillis()); // 创建连接  
  24.     readKey.attach(c); // 使连接实例与注册到选择器selector相关的读操作集合键相关联  
  25.     synchronized (connectionList) {  
  26.       connectionList.add(numConnections, c); // 加入Server端连接维护列表  
  27.       numConnections++; // 修改连接计数  
  28.     }  
  29.     if (LOG.isDebugEnabled())  
  30.       LOG.debug("Server connection from " + c.toString() +  
  31.           "; # active connections: " + numConnections +  
  32.           "; # queued calls: " + callQueue.size());  
  33.   }  
  34. }  

上面方法,是一个收集来自客户端的连接的实现。下面看一下监听线程的线程体部分实现:

[java]  view plain copy
  1. @Override  
  2. public void run() {  
  3.     LOG.info(getName() + ": starting");  
  4.     SERVER.set(Server.this); // 设置当前监听线程本地变量的拷贝  
  5.     while (running) { // 如果服务器正在运行中  
  6.         SelectionKey key = null;  
  7.         try {  
  8.             selector.select(); // 选择一组key集合,这些选择的key相关联的通道已经为I/O操作做好准备  
  9.             Iterator<SelectionKey> iter = selector.selectedKeys().iterator();  
  10.             while (iter.hasNext()) {  
  11.                 key = iter.next(); // 迭代出一个key  
  12.                 iter.remove();  
  13.                 try {  
  14.                     if (key.isValid()) {  
  15.                         if (key.isAcceptable()) // 如果该key对应的通道已经准备好接收新的Socket连接  
  16.                             doAccept(key); // 调用,接收与该key关联的通道上的连接  
  17.                         else if (key.isReadable()) // 如果该通道为读取数据做好准备  
  18.                             doRead(key); // 从通道读取数据,主要调用了Server.Connection的readAndProcess方法来读取数据,并设置该连接的最后连接时间  
  19.                     }  
  20.                 } catch (IOException e) {  
  21.                 }  
  22.                 key = null;  
  23.             }  
  24.         } catch (OutOfMemoryError e) {  
  25.             // we can run out of memory if we have too many threads  
  26.             // log the event and sleep for a minute and give   
  27.             // some thread(s) a chance to finish  
  28.             LOG.warn("Out of Memory in server select", e);  
  29.             closeCurrentConnection(key, e);  
  30.             cleanupConnections(true);  
  31.             try {  
  32.                 Thread.sleep(60000);  
  33.             } catch (Exception ie) {  
  34.             }  
  35.         } catch (InterruptedException e) {  
  36.             if (running) { // unexpected -- log it  
  37.                 LOG.info(getName() + " caught: "  
  38.                         + StringUtils.stringifyException(e));  
  39.             }  
  40.         } catch (Exception e) {  
  41.             closeCurrentConnection(key, e);  
  42.         }  
  43.         cleanupConnections(false);  
  44.     }  
  45.     LOG.info("Stopping " + this.getName());  
  46.   
  47.     // 跳出while循环,即running=false,服务器已经不再运行,需要关闭通道、选择器、全部连接  
  48.     synchronized (this) {  
  49.         try {  
  50.             acceptChannel.close(); // 关闭通道  
  51.             selector.close(); // 关闭通道选择器  
  52.         } catch (IOException e) {  
  53.         }  
  54.   
  55.         selector = null;  
  56.         acceptChannel = null;  
  57.   
  58.         // 关闭全部连接  
  59.         while (!connectionList.isEmpty()) {  
  60.             closeConnection(connectionList.remove(0)); // 关闭一个连接  
  61.         }  
  62.     }  
  63. }  

可见,Server.Listener主要负责两个阶段的任务:当服务器运行时,不断地通过选择器来选择继续的通道,处理基于该选择的通道上通信;当服务器不再运行以后,需要关闭通道、选择器、全部链接,释放一切资源。

  • Server.Handler内部类

该类是一个处理线程类,负责处理客户端的全部调用。

该类的源代码如下所示:

[java]  view plain copy
  1. private class Handler extends Thread {  
  2.     public Handler(int instanceNumber) {  
  3.         this.setDaemon(true); // 作为后台线程运行  
  4.         this.setName("IPC Server handler " + instanceNumber + " on "+ port);  
  5.     }  
  6.   
  7.     @Override  
  8.     public void run() {  
  9.         LOG.info(getName() + ": starting");  
  10.         SERVER.set(Server.this); // 设置当前处理线程的本地变量的拷贝  
  11.         ByteArrayOutputStream buf = new ByteArrayOutputStream(10240); // 存放响应信息的缓冲区  
  12.         while (running) {  
  13.             try {  
  14.                 final Call call = callQueue.take(); // 出队操作,获取到一个调用Server.Call call  
  15.   
  16.                 if (LOG.isDebugEnabled())  
  17.                     LOG.debug(getName() + ": has #" + call.id + " from " + call.connection);  
  18.   
  19.                 String errorClass = null;  
  20.                 String error = null;  
  21.                 Writable value = null;  
  22.   
  23.                 CurCall.set(call); // 设置当前线程本地变量拷贝的值为出队得到的一个call调用实例  
  24.                 try {  
  25.                     // 根据调用Server.Call关联的连接Server.Connection,所对应的用户Subject,来执行IPC调用过程  
  26.                     value = Subject.doAs(call.connection.user,  
  27.                             new PrivilegedExceptionAction<Writable>() {  
  28.                                 @Override  
  29.                                 public Writable run() throws Exception {  
  30.                                     // 执行调用  
  31.                                     return call(call.connection.protocol, call.param, call.timestamp);  
  32.   
  33.                                 }  
  34.                             });  
  35.   
  36.                 } catch (PrivilegedActionException pae) {  
  37.                     Exception e = pae.getException();  
  38.                     LOG.info(getName() + ", call " + call + ": error: " + e, e);  
  39.                     errorClass = e.getClass().getName();  
  40.                     error = StringUtils.stringifyException(e);  
  41.                 } catch (Throwable e) {  
  42.                     LOG.info(getName() + ", call " + call + ": error: " + e, e);  
  43.                     errorClass = e.getClass().getName();  
  44.                     error = StringUtils.stringifyException(e);  
  45.                 }  
  46.                 CurCall.set(null); // 当前Handler线程处理完成一个调用call,回收当前线程的局部变量拷贝  
  47.                 // 处理当前获取到的调用的响应  
  48.                 setupResponse(buf, call, (error == null) ? Status.SUCCESS : Status.ERROR, value, errorClass, error);  
  49.                 responder.doRespond(call); // 将调用call加入到响应队列中,等待客户端读取响应信息  
  50.             } catch (InterruptedException e) {  
  51.                 if (running) { // unexpected -- log it  
  52.                     LOG.info(getName() + " caught: " + StringUtils.stringifyException(e));  
  53.                 }  
  54.             } catch (Exception e) {  
  55.                 LOG.info(getName() + " caught: " + StringUtils.stringifyException(e));  
  56.             }  
  57.         }  
  58.         LOG.info(getName() + ": exiting");  
  59.     }  
  60.   
  61. }  

该线程主要的任务是:真正地实现了处理来自客户端的调用,并设置每个相关调用的响应。关于响应的实现,有个具体实现的线程类Server.Responder。

  • Server.Responder内部类

该线程类实现发送RPC响应到客户端。

我们先对该线程类中方法进行阅读分析,然后再看线程体的实现过程。

[java]  view plain copy
  1. /**  
  2.  * 处理一个通道上调用的响应数据 
  3.  * 如果一个通道空闲,返回true 
  4.  */  
  5. private boolean processResponse(LinkedList<Call> responseQueue, boolean inHandler) throws IOException {  
  6.     boolean error = true;  
  7.     boolean done = false// 一个通道channel有更多的数据待读取  
  8.     int numElements = 0;  
  9.     Call call = null;  
  10.     try {  
  11.         synchronized (responseQueue) {   
  12.             // 如果该通道channel空闲,处理响应完成  
  13.             numElements = responseQueue.size();  
  14.             if (numElements == 0) {  
  15.                 error = false;  
  16.                 return true// 完成响应的处理,返回  
  17.             }  
  18.             // 从队列中取出第一个调用call  
  19.             call = responseQueue.removeFirst();  
  20.             SocketChannel channel = call.connection.channel; // 获取该调用对应的通道channel  
  21.             if (LOG.isDebugEnabled()) {  
  22.                 LOG.debug(getName() + ": responding to #" + call.id + " from " + call.connection);  
  23.             }  
  24.             // Send as much data as we can in the non-blocking fashion  
  25.             int numBytes = channelWrite(channel, call.response); // 向通道channel中写入响应信息(响应信息位于call.response字节缓冲区中)  
  26.             if (numBytes < 0) { // 如果写入字节数为0,说明已经没有字节可写,返回  
  27.                 return true;  
  28.             }  
  29.             if (!call.response.hasRemaining()) { // 如果call.response字节缓冲区中没有响应字节数据,说明已经全部写入到相关量的通道中  
  30.                 call.connection.decRpcCount(); // 该调用call对应的RPC连接计数减1  
  31.                 if (numElements == 1) { // 最后一个调用已经处理完成  
  32.                     done = true// 该通道channel没有更多的数据  
  33.                 } else {  
  34.                     done = false// 否则,还存在尚未处理的调用,要向给通道发送数据  
  35.                 }  
  36.                 if (LOG.isDebugEnabled()) {  
  37.                     LOG.debug(getName() + ": responding to #" + call.id + " from " + call.connection + " Wrote " + numBytes + " bytes.");  
  38.                 }  
  39.             } else { // 如果call.response字节缓冲区中还存在未被写入通道响应字节数据  
  40.                 call.connection.responseQueue.addFirst(call); // 如果不能够将全部的响应字节数据写入到通道中,需要暂时插入到Selector选择其队列中  
  41.                 if (inHandler) { // 如果指定:现在就对调用call进行处理(该调用的响应还没有进行处理)  
  42.                     call.timestamp = System.currentTimeMillis(); // 设置调用时间戳  
  43.                     incPending(); // 增加未被处理响应信息的调用计数  
  44.                     try {  
  45.                         writeSelector.wakeup(); // 唤醒阻塞在该通道writeSelector上的线程  
  46.                         channel.register(writeSelector, SelectionKey.OP_WRITE, call); // 调用call注册通道writeSelector  
  47.                     } catch (ClosedChannelException e) {  
  48.                         done = true;  
  49.                     } finally {  
  50.                         decPending(); // 经过上面处理,不管在处理过程中正常处理,或是发生通道已关闭异常,最后,都将设置该调用完成,更新计数  
  51.                     }  
  52.                 }  
  53.                 if (LOG.isDebugEnabled()) {  
  54.                     LOG.debug(getName() + ": responding to #" + call.id + " from " + call.connection + " Wrote partial " + numBytes + " bytes.");  
  55.                 }  
  56.             }  
  57.             error = false// 设置出错标志:完成  
  58.         }  
  59.     } finally {  
  60.         if (error && call != null) {  
  61.             LOG.warn(getName() + ", call " + call + ": output error");  
  62.             done = true;  // error. no more data for this channel.  
  63.             closeConnection(call.connection);  
  64.         }  
  65.     }  
  66.     return done;  
  67. }  

上面方法主要实现的是,处理响应队列responseQueue中的全部调用Call,对应的响应数据。关于处理响应的调用队列,是指类似call.connection.responseQueue的响应队列,可以理解为某个通道上调用的集合所对应的待处理响应数据的队列。

看下面的doRespond方法:

[java]  view plain copy
  1. void doRespond(Call call) throws IOException {  
  2.     synchronized (call.connection.responseQueue) {  
  3.         call.connection.responseQueue.addLast(call); // 将执行完成的调用加入队列,准备响应客户端  
  4.         if (call.connection.responseQueue.size() == 1) {  
  5.             processResponse(call.connection.responseQueue, true); // 如果队列中只有一个调用,直接进行处理  
  6.         }  
  7.     }  
  8. }  

当某个通道上可写的时候,可以执行异步写响应数据的操作,实现方法为:

[java]  view plain copy
  1. private void doAsyncWrite(SelectionKey key) throws IOException {  
  2.     Call call = (Call) key.attachment();  
  3.     if (call == null) {  
  4.         return;  
  5.     }  
  6.     if (key.channel() != call.connection.channel) {  
  7.         throw new IOException("doAsyncWrite: bad channel");  
  8.     }  
  9.   
  10.     synchronized (call.connection.responseQueue) {  
  11.         if (processResponse(call.connection.responseQueue, false)) { // 调用processResponse处理与调用关联的响应数据  
  12.             try {  
  13.                 key.interestOps(0);  
  14.             } catch (CancelledKeyException e) {  
  15.                 LOG.warn("Exception while changing ops : " + e);  
  16.             }  
  17.         }  
  18.     }  
  19. }  

再看doPurge方法:

[java]  view plain copy
  1. /** 
  2.  * 如果未被处理响应的调用在队列中滞留超过指定时限,要定时清除掉  
  3.  */  
  4. private void doPurge(Call call, long now) throws IOException {  
  5.     LinkedList<Call> responseQueue = call.connection.responseQueue;  
  6.     synchronized (responseQueue) {  
  7.         Iterator<Call> iter = responseQueue.listIterator(0);  
  8.         while (iter.hasNext()) {  
  9.             call = iter.next();  
  10.             if (now > call.timestamp + PURGE_INTERVAL) {  
  11.                 closeConnection(call.connection);  
  12.                 break;  
  13.             }  
  14.         }  
  15.     }  
  16. }  

最后,看一个Responder线程启动后,是如何工作的,在线程体run方法中可以看到:

[java]  view plain copy
  1. @Override  
  2. public void run() {  
  3.     LOG.info(getName() + ": starting");  
  4.     SERVER.set(Server.this);  
  5.     long lastPurgeTime = 0// 最后一次清除过期调用的时间  
  6.     while (running) { // 如果服务器处于运行状态  
  7.         try {  
  8.             waitPending(); // 等待一个通道中,接收到来的调用进行注册  
  9.             writeSelector.select(PURGE_INTERVAL); // 设置超时时限  
  10.             Iterator<SelectionKey> iter = writeSelector.selectedKeys().iterator();  
  11.             while (iter.hasNext()) { // 迭代选择器writeSelector选择的key集合  
  12.                 SelectionKey key = iter.next();  
  13.                 iter.remove();  
  14.                 try {  
  15.                     if (key.isValid() && key.isWritable()) { // 如果合法,并且通道可写  
  16.                         doAsyncWrite(key); // 执行异步写操作,向通道中写入调用执行的响应数据  
  17.                     }  
  18.                 } catch (IOException e) {  
  19.                     LOG.info(getName() + ": doAsyncWrite threw exception " + e);  
  20.                 }  
  21.             }  
  22.             long now = System.currentTimeMillis();  
  23.             if (now < lastPurgeTime + PURGE_INTERVAL) {  
  24.                 continue;  
  25.             }  
  26.             lastPurgeTime = now;  
  27.             // 如果存在一些一直没有被发送出去的调用,这是时间限制为lastPurgeTime + PURGE_INTERVAL  
  28.             // 则这些调用被视为过期调用,进行清除  
  29.             LOG.debug("Checking for old call responses.");  
  30.             ArrayList<Call> calls;  
  31.             synchronized (writeSelector.keys()) {  
  32.                 calls = new ArrayList<Call>(writeSelector.keys().size());  
  33.                 iter = writeSelector.keys().iterator();  
  34.                 while (iter.hasNext()) {  
  35.                     SelectionKey key = iter.next();  
  36.                     Call call = (Call) key.attachment();  
  37.                     if (call != null&& key.channel() == call.connection.channel) {  
  38.                         calls.add(call);  
  39.                     }  
  40.                 }  
  41.             }  
  42.   
  43.             for (Call call : calls) {  
  44.                 try {  
  45.                     doPurge(call, now); // 执行清除  
  46.                 } catch (IOException e) {  
  47.                     LOG.warn("Error in purging old calls " + e);  
  48.                 }  
  49.             }  
  50.         } catch (OutOfMemoryError e) {  
  51.             LOG.warn("Out of Memory in server select", e);  
  52.             try {  
  53.                 Thread.sleep(60000);  
  54.             } catch (Exception ie) {  
  55.             }  
  56.         } catch (Exception e) {  
  57.             LOG.warn("Exception in Responder " + StringUtils.stringifyException(e));  
  58.         }  
  59.     }  
  60.     LOG.info("Stopping " + this.getName());  
  61. }  

通过线程执行可以看到,调用的相应数据的处理,是在服务器运行过程中处理的,而且分为两种情况:

1、一种情况是:如果某些调用超过了指定的时限而一直未被处理,这些调用被视为过期,服务器不会再为这些调用处理,而是直接清除掉;

2、另一种情况是:如果所选择的通道上,已经注册的调用是合法的,并且通道可写,会直接将调用的相应数据写入到通道,等待客户端读取。

 

上面实现的Server的内部类,基本上定义了一个Server应该实现的基本操作,下面再看Server类中就比较容易了。

启动服务器:

[java]  view plain copy
  1. public synchronized void start() throws IOException {  
  2.     responder.start(); // 启动调用的响应数据处理线程  
  3.     listener.start(); // 启动监听线程  
  4.     handlers = new Handler[handlerCount]; // 启动多个处理器线程  
  5.     for (int i = 0; i < handlerCount; i++) {  
  6.         handlers[i] = new Handler(i);  
  7.         handlers[i].start();  
  8.     }  
  9. }  
  

停止服务器:

[java]  view plain copy
  1. public synchronized void stop() {  
  2.     LOG.info("Stopping server on " + port);  
  3.     running = false;  
  4.     if (handlers != null) { // 先中断全部处理器线程  
  5.         for (int i = 0; i < handlerCount; i++) {  
  6.             if (handlers[i] != null) {  
  7.                 handlers[i].interrupt();  
  8.             }  
  9.         }  
  10.     }  
  11.     listener.interrupt(); // 终止监听器线程  
  12.     listener.doStop();   
  13.     responder.interrupt(); // 终止响应数据处理线程  
  14.     notifyAll();  
  15.     if (this.rpcMetrics != null) {  
  16.         this.rpcMetrics.shutdown();  
  17.     }  
  18. }  

通过对Server类的分析,应该能够了解IPC模型的Server端需要做哪些基本的事情。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值