一、上一节说到了RPC中的通信问题,RPC中的通信分为服务端Server和客户端Client。服务端监听接受客户端传递的信息并处理返回。那么在RPC中服务类Server是怎么实现的呢。接下来为你解析。首先来了解一下Server类中的一些内部类。
从上节可以看到Server类中有许多内部类,这其中有几个是比较重要的,我把它们列举出来,接下来举个例子说明一下它们的作用。
- Call、RpcCall
- Listener
- Connection
- Handler
- Reader
- ConnectionManager
- Responder
这里用一个场景来解释一下这些类的作用,我们用客服电话来举例,我们这里假设客服电话所有的操作都是由人来完成。首先客服电话对外肯定是同一个号码,比如我们打移动客服都是打10086,打电信客服都是打10000号一样,所以在Server类中也是对外提供一个固定服务地址。当有人打客服电话时,会专门有台电话去接入来电,这就是Listener类的作用,它专门用于监听客户端的连接请求。当客户电话接入之后,会随机分发给一个客服人员进行电话接听,这里的业务员就是Reader。注意,客服公司不可能为每个来电都去聘请一个新的客服。所以在RPC中也不可能为每一个连接创建一个Reader。所以每个Reader需要对应多个客服端。这就需要为每个连接信息进行保存,这里使用Connection类来保存连接信息,每个连接都实例化一个Connection,并使用ConnectManager进行管理。在客服电话场景中,当客服员接到一个客户的需求时,她会将这个需求保证成一个邮件或者工单发给业务处理人员,业务处理人员读取这些邮件或者工单然后去处理相关业务。这里的包装客户需求的邮件或者工单对应的是Call、RPCCall类,它们的用途就是根据RPC的请求信息构建Call对象。这里的业务处理员对应Handler类,Handler类用于处理具体的RPC请求。在客服电话场景中,业务处理员处理完之后可能会自己直接回复客户,也有可能因为各种原因不能自己回复,所以需要一个专门针对这种情况的回复人员,这就是Responder。用于回复RPC请求结果。
二、Listener
Listener是一个线程类,整个Server类中只有一个Listener线程,用于监听来着客户端的Socket连接请求。
private ServerSocketChannel acceptChannel = null; //the accept channel
private Selector selector = null; //the selector that we use for the server
private Reader[] readers = null;
private int currentReader = 0;
private InetSocketAddress address; //the address we bind at
上述是Listener类的主要属性,对于每个新的连接请求,Listener都会从readers中挑选一个Reader线程来处理。Listener中有个selector,它注册了OP_ACCEPT事件,用于监听连接事件。
public Listener() throws IOException {
address = new InetSocketAddress(bindAddress, port);
// Create a new server socket and set to non blocking mode
acceptChannel = ServerSocketChannel.open();
acceptChannel.configureBlocking(false);
// Bind the server socket to the local host and port
bind(acceptChannel.socket(), address, backlogLength, conf, portRangeConfig);
port = acceptChannel.socket().getLocalPort(); //Could be an ephemeral port
// create a selector;
selector= Selector.open();
readers = new Reader[readThreads];
for (int i = 0; i < readThreads; i++) {
Reader reader = new Reader(
"Socket Reader #" + (i + 1) + " for port " + port);
readers[i] = reader;
reader.start();
}
// Register accepts on the server socket with the selector.
acceptChannel.register(selector, SelectionKey.OP_ACCEPT);
this.setName("IPC Server listener on " + port);
this.setDaemon(true);
}
Listener类中的run方法
public void run() {
LOG.info(Thread.currentThread().getName() + ": starting");
SERVER.set(Server.this);
connectionManager.startIdleScan();
while (running) {
SelectionKey key = null;
try {
getSelector().select();//循环判断是否有新的连接建立请求
Iterator<SelectionKey> iter = getSelector().selectedKeys().iterator();
while (iter.hasNext()) {
key = iter.next();
iter.remove();
try {
if (key.isValid()) {
if (key.isAcceptable())
doAccept(key);//调用doAccept方法接受连接请求
}
} 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);
connectionManager.closeIdle(true);
try { Thread.sleep(60000); } catch (Exception ie) {}
} catch (Exception e) {
closeCurrentConnection(key, e);
}
}
LOG.info("Stopping " + Thread.currentThread().getName());
synchronized (this) {
try {
acceptChannel.close();
selector.close();
} catch (IOException e) { }
selector= null;
acceptChannel= null;
// 关闭所有的连接
connectionManager.stopIdleScan();
connectionManager.closeAll();
}
}
doAccept()方法:该方法会接受来自客户端的socket请求并初始化socket连接,然后从readers线程池中选出一个Reader线程来读取客户端的RPC请求。但是每个Reader上会有多个客户端的socket连接,那怎么区分某次请求是那个socket的?这里就用到了Connection类,用它来封装Server与Client之间的Socket连接。
void doAccept(SelectionKey key) throws InterruptedException, IOException, OutOfMemoryError {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel;//客户端的socket连接
while ((channel = server.accept()) != null) {
channel.configureBlocking(false);
channel.socket().setTcpNoDelay(tcpNoDelay);
channel.socket().setKeepAlive(true);
Reader reader = getReader();//从Reader线程池中获取一个Reader
Connection c = connectionManager.register(channel);//注册该连接,并返回一个Connection,这部分在后面讲解
// If the connectionManager can't take it, close the connection.
if (c == null) {
if (channel.isOpen()) {
IOUtils.cleanup(null, channel);
}
connectionManager.droppedConnections.getAndIncrement();
continue;
}
key.attach(c); // 将这个Connection连接信息附在key上
reader.addConnection(c);//将包装之后的Connection连接信息添加到reader中。
}
}
三、Reader类
Reader也是一个线程类
final private BlockingQueue<Connection> pendingConnections;
private final Selector readSelector;
Reader中有两个属性,一个阻塞队列,用于存放新的Connection,一个Selector。在Reader的构造方法中,阻塞队列选择 LinkedBlockingQueue。可以在配置文件中配置阻塞数。
Reader(String name) throws IOException {
super(name);
this.pendingConnections =
new LinkedBlockingQueue<Connection>(readerPendingConnectionQueue);
this.readSelector = Selector.open();
}
Reader线程的主循环则是在doRunLoop()方法中实现,doRunLoop()方法会监听当前Reader对象负责的所有客户端连接中是否有新的RPC请求到达。如果有则读取并将其封装成一个Call对象,放入callQueue中等待Handler线程去处理。
private synchronized void doRunLoop() {
while (running) {
SelectionKey key = null;
try {
// consume as many connections as currently queued to avoid
// unbridled acceptance of connections that starves the select
int size = pendingConnections.size();
for (int i=size; i>0; i--) {
//对每个新的连接进行注册OP_READ事件
Connection conn = pendingConnections.take();
conn.