一、前言

  前面已经学习了NIOServerCnxn,接着继续学习NettyServerCnxn。

二、NettyServerCnxn源码分析

  2.1 类的继承关系 

public class NettyServerCnxn extends ServerCnxn {}

  说明:NettyServerCnxn继承了ServerCnxn抽象类,使用Netty框架来高效处理与客户端之间的通信。

  2.2 类的内部类

  1. SendBufferWriter类 

    private class SendBufferWriter extends Writer {
private StringBuffer sb = new StringBuffer(); /**
* Check if we are ready to send another chunk.
* @param force force sending, even if not a full chunk
*/
// 是否准备好发送另一块
private void checkFlush(boolean force) {
if ((force && sb.length() > 0) || sb.length() > 2048) { // 当强制发送并且sb大小大于0,或者sb大小大于2048即发送缓存
sendBuffer(ByteBuffer.wrap(sb.toString().getBytes()));
// clear our internal buffer
sb.setLength(0);
}
} @Override
public void close() throws IOException {
if (sb == null) return;
// 关闭之前需要强制性发送缓存
checkFlush(true);
sb = null; // clear out the ref to ensure no reuse
} @Override
public void flush() throws IOException {
checkFlush(true);
} @Override
public void write(char[] cbuf, int off, int len) throws IOException {
sb.append(cbuf, off, len);
checkFlush(false);
}
}

SendBufferWriter

  说明:与NIOServerCnxn中相同,该类用来将给客户端的响应进行分块,不再累赘。

  2. ResumeMessageEvent类 

    static class ResumeMessageEvent implements MessageEvent {
// 通道
Channel channel; // 构造函数
ResumeMessageEvent(Channel channel) {
this.channel = channel;
}
@Override
public Object getMessage() {return null;}
@Override
public SocketAddress getRemoteAddress() {return null;}
@Override
public Channel getChannel() {return channel;}
@Override
public ChannelFuture getFuture() {return null;}
};

ResumeMessageEvent

  说明:ResumeMessageEvent继承MessageEvent,其表示消息的传输或接收。

  3. CommandThread类 

    private abstract class CommandThread /*extends Thread*/ {
PrintWriter pw; CommandThread(PrintWriter pw) {
this.pw = pw;
} public void start() {
run();
} public void run() {
try {
commandRun();
} catch (IOException ie) {
LOG.error("Error in running command ", ie);
} finally {
cleanupWriterSocket(pw);
}
} public abstract void commandRun() throws IOException;
}

CommandThread

  说明:其与NIOServerCnxn中类似,也是每个子类对应着一个命令,值得注意的是针对每个CMD命令,其仅仅使用一个线程来处理。

  2.3 类的属性 

public class NettyServerCnxn extends ServerCnxn {
// 日志
Logger LOG = LoggerFactory.getLogger(NettyServerCnxn.class); // 通道
Channel channel; // 通道缓存
ChannelBuffer queuedBuffer; // 节流与否
volatile boolean throttled; // Byte缓冲区
ByteBuffer bb; // 四个字节的缓冲区
ByteBuffer bbLen = ByteBuffer.allocate(4); // 会话ID
long sessionId; // 会话超时时间
int sessionTimeout; // 计数
AtomicLong outstandingCount = new AtomicLong(); /** The ZooKeeperServer for this connection. May be null if the server
* is not currently serving requests (for example if the server is not
* an active quorum participant.
*/
// Zookeeper服务器
private volatile ZooKeeperServer zkServer; // NettyServerCnxn工厂
NettyServerCnxnFactory factory; // 初始化与否
boolean initialized; // 四个字节
private static final byte[] fourBytes = new byte[4]; private static final String ZK_NOT_SERVING =
"This ZooKeeper instance is not currently serving requests";
}

类的属性

  说明:NettyServerCnxn维护了与客户端之间的通道缓冲、缓冲区及会话的相关属性。

  2.4 类的构造函数 

    NettyServerCnxn(Channel channel, ZooKeeperServer zks, NettyServerCnxnFactory factory) {
// 给属性赋值
this.channel = channel;
this.zkServer = zks;
this.factory = factory;
if (this.factory.login != null) { // 需要登录信息(用户名和密码登录)
this.zooKeeperSaslServer = new ZooKeeperSaslServer(factory.login);
}
}

构造函数

  说明:构造函数对NettyServerCnxn中的部分重要属性进行了赋值,其中还涉及到是否需要用户登录。

  2.5 核心函数分析

  1. receiveMessage函数 

    public void receiveMessage(ChannelBuffer message) {
try {
while(message.readable() && !throttled) { // 当writerIndex > readerIndex,并且不节流时,满足条件
if (bb != null) { // 不为null
if (LOG.isTraceEnabled()) {
LOG.trace("message readable " + message.readableBytes()
+ " bb len " + bb.remaining() + " " + bb);
ByteBuffer dat = bb.duplicate();
dat.flip();
LOG.trace(Long.toHexString(sessionId)
+ " bb 0x"
+ ChannelBuffers.hexDump(
ChannelBuffers.copiedBuffer(dat)));
} if (bb.remaining() > message.readableBytes()) { // bb剩余空间大于message中可读字节大小
// 确定新的limit
int newLimit = bb.position() + message.readableBytes();
bb.limit(newLimit);
}
// 将message写入bb中
message.readBytes(bb);
// 重置bb的limit
bb.limit(bb.capacity()); if (LOG.isTraceEnabled()) {
LOG.trace("after readBytes message readable "
+ message.readableBytes()
+ " bb len " + bb.remaining() + " " + bb);
ByteBuffer dat = bb.duplicate();
dat.flip();
LOG.trace("after readbytes "
+ Long.toHexString(sessionId)
+ " bb 0x"
+ ChannelBuffers.hexDump(
ChannelBuffers.copiedBuffer(dat)));
}
if (bb.remaining() == 0) { // 已经读完message,表示内容已经全部接收
// 统计接收信息
packetReceived();
// 翻转,可读
bb.flip(); ZooKeeperServer zks = this.zkServer;
if (zks == null) { // Zookeeper服务器为空
throw new IOException("ZK down");
}
if (initialized) { // 未被初始化
// 处理bb中包含的包信息
zks.processPacket(this, bb); if (zks.shouldThrottle(outstandingCount.incrementAndGet())) { // 是否已经节流
// 不接收数据
disableRecvNoWait();
}
} else { // 已经初始化
LOG.debug("got conn req request from "
+ getRemoteSocketAddress());
// 处理连接请求
zks.processConnectRequest(this, bb);
initialized = true;
}
bb = null;
}
} else { // bb为null
if (LOG.isTraceEnabled()) {
LOG.trace("message readable "
+ message.readableBytes()
+ " bblenrem " + bbLen.remaining());
// 复制bbLen缓冲
ByteBuffer dat = bbLen.duplicate();
// 翻转
dat.flip();
LOG.trace(Long.toHexString(sessionId)
+ " bbLen 0x"
+ ChannelBuffers.hexDump(
ChannelBuffers.copiedBuffer(dat)));
} if (message.readableBytes() < bbLen.remaining()) { // bb剩余空间大于message中可读字节大小
// 重设bbLen的limit
bbLen.limit(bbLen.position() + message.readableBytes());
}
// 将message内容写入bbLen中
message.readBytes(bbLen);
// 重置bbLen的limit
bbLen.limit(bbLen.capacity());
if (bbLen.remaining() == 0) { // 已经读完message,表示内容已经全部接收
// 翻转
bbLen.flip(); if (LOG.isTraceEnabled()) {
LOG.trace(Long.toHexString(sessionId)
+ " bbLen 0x"
+ ChannelBuffers.hexDump(
ChannelBuffers.copiedBuffer(bbLen)));
}
// 读取position后四个字节
int len = bbLen.getInt();
if (LOG.isTraceEnabled()) {
LOG.trace(Long.toHexString(sessionId)
+ " bbLen len is " + len);
} // 清除缓存
bbLen.clear();
if (!initialized) { // 未被初始化
if (checkFourLetterWord(channel, message, len)) { // 是否是四个字母的命令
return;
}
}
if (len < 0 || len > BinaryInputArchive.maxBuffer) {
throw new IOException("Len error " + len);
}
// 根据len重新分配缓冲,以便接收内容
bb = ByteBuffer.allocate(len);
}
}
}
} catch(IOException e) {
LOG.warn("Closing connection to " + getRemoteSocketAddress(), e);
close();
}
}

receiveMessage

  说明:该函数用于接收ChannelBuffer中的数据,函数在while循环体中,当writerIndex大于readerIndex(表示ChannelBuffer中还有可读内容)且throttled为false时执行while循环体,该函数大致可以分为两部分,首先是当bb不为空时,表示已经准备好读取ChannelBuffer中的内容,其流程如下

                if (bb != null) { // 不为null,表示已经准备好读取message
if (LOG.isTraceEnabled()) {
LOG.trace("message readable " + message.readableBytes()
+ " bb len " + bb.remaining() + " " + bb);
ByteBuffer dat = bb.duplicate();
dat.flip();
LOG.trace(Long.toHexString(sessionId)
+ " bb 0x"
+ ChannelBuffers.hexDump(
ChannelBuffers.copiedBuffer(dat)));
} if (bb.remaining() > message.readableBytes()) { // bb剩余空间大于message中可读字节大小
// 确定新的limit
int newLimit = bb.position() + message.readableBytes();
bb.limit(newLimit);
}
// 将message写入bb中
message.readBytes(bb);
// 重置bb的limit
bb.limit(bb.capacity()); if (LOG.isTraceEnabled()) {
LOG.trace("after readBytes message readable "
+ message.readableBytes()
+ " bb len " + bb.remaining() + " " + bb);
ByteBuffer dat = bb.duplicate();
dat.flip();
LOG.trace("after readbytes "
+ Long.toHexString(sessionId)
+ " bb 0x"
+ ChannelBuffers.hexDump(
ChannelBuffers.copiedBuffer(dat)));
}
if (bb.remaining() == 0) { // 已经读完message,表示内容已经全部接收
// 统计接收信息
packetReceived();
// 翻转,可读
bb.flip(); ZooKeeperServer zks = this.zkServer;
if (zks == null) { // Zookeeper服务器为空
throw new IOException("ZK down");
}
if (initialized) { // 未被初始化
// 处理bb中包含的包信息
zks.processPacket(this, bb); if (zks.shouldThrottle(outstandingCount.incrementAndGet())) { // 是否已经节流
// 不接收数据
disableRecvNoWait();
}
} else { // 已经初始化
LOG.debug("got conn req request from "
+ getRemoteSocketAddress());
// 处理连接请求
zks.processConnectRequest(this, bb);
initialized = true;
}
bb = null;
}
}

  其中主要的部分是判断bb的剩余空间是否大于message中的内容,简单而言,就是判断bb是否还有足够空间存储message内容,然后设置bb的limit,之后将message内容读入bb缓冲中,之后再次确定时候已经读完message内容,统计接收信息,再根据是否已经初始化来处理包或者是连接请求,其中的请求内容都存储在bb中。而当bb为空时,其流程如下 

                else { // bb为null
if (LOG.isTraceEnabled()) {
LOG.trace("message readable "
+ message.readableBytes()
+ " bblenrem " + bbLen.remaining());
// 复制bbLen缓冲
ByteBuffer dat = bbLen.duplicate();
// 翻转
dat.flip();
LOG.trace(Long.toHexString(sessionId)
+ " bbLen 0x"
+ ChannelBuffers.hexDump(
ChannelBuffers.copiedBuffer(dat)));
} if (message.readableBytes() < bbLen.remaining()) { // bb剩余空间大于message中可读字节大小
// 重设bbLen的limit
bbLen.limit(bbLen.position() + message.readableBytes());
}
// 将message内容写入bbLen中
message.readBytes(bbLen);
// 重置bbLen的limit
bbLen.limit(bbLen.capacity());
if (bbLen.remaining() == 0) { // 已经读完message,表示内容已经全部接收
// 翻转
bbLen.flip(); if (LOG.isTraceEnabled()) {
LOG.trace(Long.toHexString(sessionId)
+ " bbLen 0x"
+ ChannelBuffers.hexDump(
ChannelBuffers.copiedBuffer(bbLen)));
}
// 读取position后四个字节
int len = bbLen.getInt();
if (LOG.isTraceEnabled()) {
LOG.trace(Long.toHexString(sessionId)
+ " bbLen len is " + len);
} // 清除缓存
bbLen.clear();
if (!initialized) { // 未被初始化
if (checkFourLetterWord(channel, message, len)) { // 是否是四个字母的命令
return;
}
}
if (len < 0 || len > BinaryInputArchive.maxBuffer) {
throw new IOException("Len error " + len);
}
// 根据len重新分配缓冲,以便接收内容
bb = ByteBuffer.allocate(len);
}
}

  当bb为空时,表示还没有给bb分配足够的内存空间来读取message,首先还是将message内容(后续内容的长度)读入bbLen中,然后再确定读入的内容代表后续真正内容的长度len,然后再根据len来为bb分配存储空间,方便后续读取真正的内容。

  2. sendResponse函数 

    public void sendResponse(ReplyHeader h, Record r, String tag)
throws IOException {
if (!channel.isOpen()) {
return;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Make space for length
BinaryOutputArchive bos = BinaryOutputArchive.getArchive(baos);
try {
// 向baos中写入四个字节(空)
baos.write(fourBytes);
// 写入记录
bos.writeRecord(h, "header");
if (r != null) {
// 写入记录
bos.writeRecord(r, tag);
}
// 关闭
baos.close();
} catch (IOException e) {
LOG.error("Error serializing response");
} // 转化为Byte Array
byte b[] = baos.toByteArray();
// 将Byte Array封装成ByteBuffer
ByteBuffer bb = ByteBuffer.wrap(b);
bb.putInt(b.length - 4).rewind();
// 发送缓冲
sendBuffer(bb);
if (h.getXid() > 0) {
// zks cannot be null otherwise we would not have gotten here!
if (!zkServer.shouldThrottle(outstandingCount.decrementAndGet())) {
enableRecv();
}
}
}

  说明:其首先会将header和record都写入baos,之后再将baos转化为ByteBuffer,之后在调用sendBuffer来发送缓冲,而sendBuffer完成的操作是将ByteBuffer写入ChannelBuffer中。

  3. process函数 

    public void process(WatchedEvent event) {
// 创建响应头
ReplyHeader h = new ReplyHeader(-1, -1L, 0);
if (LOG.isTraceEnabled()) {
ZooTrace.logTraceMessage(LOG, ZooTrace.EVENT_DELIVERY_TRACE_MASK,
"Deliver event " + event + " to 0x"
+ Long.toHexString(this.sessionId)
+ " through " + this);
} // Convert WatchedEvent to a type that can be sent over the wire
WatcherEvent e = event.getWrapper(); try {
// 发送响应
sendResponse(h, e, "notification");
} catch (IOException e1) {
if (LOG.isDebugEnabled()) {
LOG.debug("Problem sending to " + getRemoteSocketAddress(), e1);
}
close();
}
}

  说明:首先创建ReplyHeader,然后再调用sendResponse来发送响应,最后调用close函数进行后续关闭处理。

三、总结

  本篇博文讲解了基于Netty完成服务端与客户端之间的通信,其效率相对较高,也谢谢各位园友的观看~ 

【Zookeeper】源码分析之网络通信(三)之NettyServerCnxn的更多相关文章

  1. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  2. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  3. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  4. Zookeeper 源码分析-启动

    Zookeeper 源码分析-启动 博客分类: Zookeeper   本文主要介绍了zookeeper启动的过程 运行zkServer.sh start命令可以启动zookeeper.入口的main ...

  5. 手机自动化测试:appium源码分析之bootstrap三

    手机自动化测试:appium源码分析之bootstrap三   研究bootstrap源码,我们可以通过代码的结构,可以看出来appium的扩展思路和实现方式,从中可以添加我们自己要的功能,针对app ...

  6. 【Zookeeper】源码分析之网络通信(三)

    一.前言 前面已经学习了NIOServerCnxn,接着继续学习NettyServerCnxn. 二.NettyServerCnxn源码分析 2.1 类的继承关系 public class Netty ...

  7. 【Zookeeper】源码分析之网络通信(一)

    一.前言 前面已经分析了请求处理链中的多数类,接着继续分析Zookeeper中的网络通信模块. 二.总体框图 对于网络通信模块,其总体框图如下所示 说明: Stats,表示ServerCnxn上的统计 ...

  8. 【Zookeeper】源码分析之网络通信(二)

    一.前言 前面介绍了ServerCnxn,下面开始学习NIOServerCnxn. 二.NIOServerCnxn源码分析 2.1 类的继承关系 public class NIOServerCnxn ...

  9. 【Zookeeper】源码分析之网络通信(二)之NIOServerCnxn

    一.前言 前面介绍了ServerCnxn,下面开始学习NIOServerCnxn. 二.NIOServerCnxn源码分析 2.1 类的继承关系 public class NIOServerCnxn ...

随机推荐

  1. python 全栈开发,Day141(flask之应用上下文,SQLAlchemy)

    一.flask之应用上下文 由于时间关系,详细过程略... 草稿图 参考链接: http://www.cnblogs.com/zhaopanpan/p/9457343.html 总结: 上下文管理(应 ...

  2. extern "C" 回顾

    引入:在测试"extern "C" 与gcc, g++无关"时,使用到了extern "C"的概念,网上找篇文章回顾一下. 试验如下: te ...

  3. NodeJS学习:搭建私有NPM

    工具 verdaccio nrm pm2 特点 verdaccio 的特点: 不同步拉取npm库,占据大量硬盘,没有硬盘被撑爆的问题: 安装配置极其简单,不需要数据库: 支持配置上游registry配 ...

  4. 你需要知道的 .NET

    1. 简述private.protected.public.internal 修饰符的访问权限. 答. private : 私有成员, 在类的内部才可以访问. protected : 保护成员,该类内 ...

  5. Django 2.0 Middleware的写法

    网上很多写法,都是传统的写法, process_request和process_response方法,还可以用,但process_view的执行流程已经不行了. 看了官方文档,推荐的写法,也是用__c ...

  6. [转]数据类型和Json格式

    作者: 阮一峰 日期: 2009年5月30日 1. 前几天,我才知道有一种简化的数据交换格式,叫做yaml. 我翻了一遍它的文档,看懂的地方不多,但是有一句话令我茅塞顿开. 它说,从结构上看,所有的数 ...

  7. 007 关于Spark下的第二种模式——standalone搭建

    一:介绍 1.介绍standalone Standalone模式是Spark自身管理资源的一个模式,类似Yarn Yarn的结构: ResourceManager: 负责集群资源的管理 NodeMan ...

  8. vimtutor学习笔记

    简介 vimtutor是vim这款知名的文本编辑器的学习工具/指南.语法如下. vimtutor [-g] [language] gvimtutor -g选项和gvimtutor是启动GUI版本的指南 ...

  9. 第01章 准备工作.md

    第1章 准备工作 1.1 本书的内容 本书讲的是利用Python进行数据控制.处理.整理.分析等方面的具体细节和基本要点.我的目标是介绍Python编程和用于数据处理的库和工具环境,掌握这些,可以让你 ...

  10. python有序字典OrderedDict()

    转python创建有序字典OrderedDict # -*- coding:utf-8 -*- """ python有序字典 需导入模块collections " ...