這裏分析一下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端需要做哪些基本的事情。