在前面两篇文章中分析了IPC中Server端的初始化与启动和 客户端IPC连接与方法调用下面来分析服务器当客户端进行方法调用时,将数据发送给服务器后,服务器的处理过程。服务器处理过程涉及的了多个线程与类,正式服务器端的各个部分的合作,才使得IPC机制简单且高效。
连接建立
在org.apache.hadoop.ipc.Client.Connection.setupConnection()方法中的NetUtils.connect(this.socket, server, 20000);这行代码执行之后,客户端就向服务器端发送了一个连接请求。客户端的连接请求统一在在服务器端的org.apache.hadoop.ipc.Server.Listener类中的run()方法中进行处理,即在Listener线程中接收客户端的连接请求。服务器端使用Java NIO技术来接收IPC客户端的连接请求和处理方法调用相关过程,关于Java NIO请参见Java NIO。Server.Listener类中的run()方法代码如下:
@Override
public void run() {
LOG.info(getName() + ": starting");
SERVER.set(Server.this);
while (running) {
SelectionKey key = null;
try {
selector.select();
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
key = iter.next();
iter.remove();
try {
if (key.isValid()) {
if (key.isAcceptable())
doAccept(key);
}
} 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 (Exception e) {
closeCurrentConnection(key, e);
}
cleanupConnections(false);
}
LOG.info("Stopping " + this.getName());
synchronized (this) {
try {
acceptChannel.close();
selector.close();
} catch (IOException e) { }
selector= null;
acceptChannel= null;
// clean up all connections
while (!connectionList.isEmpty()) {
closeConnection(connectionList.remove(0));
}
}
}
在run()方法中首先使用一个ThreadLocal遍历将当前的Listener线程与启动Listener线程的Server对象关联,然后进入到一个循环,循环结束的条件是启动Listener线程的服务器停止运行,即running变量为false值,否则Listener线程一直处于运行状态接收客户端的连接请求。循环内部是标准的Java NIO中SocketServer的代码,遍历连接请求,然后逐个处理,如果连接请求有效,就使用方法doAccept()方法处理。doAccept()方法接收一个SelectionKey对象作为参数,在Java NIO中SelectionKey保存了Socket读写相关的信息,如doAccept()方法中通过ServerSocketChannel server = (ServerSocketChannel) key.channel();获取到数据通道ServerSocketChannel对象server,然后通过server.accept()方法就接收了客户端的一个连接请求,与客户端建立了一个连接。连接建立成功之后就配置连接通道信息如设置为非阻塞,设置不使用Nagle算法等。然后就获取一个Reader线程,Reader对象是在Listener创建过程中创建好的,放在一个由数组构成的循环队列中,通过getReader方法获取到。对于Reader类的解释,很多博文上都说一个客户端对应一个Reader线程,但是我觉得不是这样的,因为一个Reader的个数是由ipc.server.read.threadpool.size这个属性指定的,其默认值是1,而客户端的在同一时间访问同一个IPC服务器的数量可能不是一定的,不可能事先就确定好IPC客户端的数量,而Reader中有一个Selector变量readSelector变量,Selector类是Java NIO中的一个选择器类,它可以检测多个通道(Channel),所以没有必要一个客户端对应一个Reader线程。在IPC机制中,Reader线程的数量是可以配置的,觉得这个变量可以配置的原因是通过配置Reader线程的数量而提高性能。不知道这样理解有没有错误,如果有错误欢迎同学们指出。谢谢。doAccept()方法的代码如下:
void doAccept(SelectionKey key) throws IOException, OutOfMemoryError {
Connection c = null;
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel;
while ((channel = server.accept()) != null) {
channel.configureBlocking(false);
channel.socket().setTcpNoDelay(tcpNoDelay);
Reader reader = getReader();
try {
reader.startAdd();
SelectionKey readKey = reader.registerChannel(channel);
c = new Connection(readKey, channel, System.currentTimeMillis());
readKey.attach(c);
synchronized (connectionList) {
connectionList.add(numConnections, c);
numConnections++;
}
if (LOG.isDebugEnabled())
LOG.debug("Server connection from " + c.toString() +
"; # active connections: " + numConnections +
"; # queued calls: " + callQueue.size());
} finally {
reader.finishAdd();
}
}
}
doAccept()方法中通过getReader()方法获取到一个Reader对象之后,就调用Reader.startAdd()方法,