一、前言

  前面已经学习了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. 学习Struts2经验总结

    一.struts 访问路径问题 1) Struts2的思想:主要围着“action”转,只要找到“action”它就知道自己该干嘛了. 首先配置struts.xml ,我们可以明白的看到,action ...

  2. windows 系统常用操作

    1.所有端口使用情况 netstat -ano 2.查询xxxx端口pid netstat -aon|findstr "xxxx" 3.根据端口Pid查详情 tasklist|fi ...

  3. Spring整合Quartz实现动态定时器,相关api,定时器添加,删除,修改

    一.版本说明 spring3.1以下的版本必须使用quartz1.x系列,3.1以上的版本才支持quartz 2.x,不然会出错. 原因:spring对于quartz的支持实现,org.springf ...

  4. 【BZOJ】3123: [Sdoi2013]森林

    题解 ------------------ 我莫不是一个智障吧 我把testdata的编号 当成数据组数读进来 我简直有毒 以为哪里写错了自闭了好久 实际上这题很简单,只要愉悦地开个启发式合并,然后每 ...

  5. JdbcType类型和Java类型的对应关系

    在Oracle中有些字段不是必填时在用户使用的时候会出现数据null的情况.这个时候在Oracle中是无法进行插入的. JDBC Type Java Type CHAR String VARCHAR ...

  6. P1197 [JSOI2008]星球大战 并查集 反向

    题目描述 很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治着整个星系. 某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球.这些星球通过特殊的以太隧 ...

  7. 聊聊Java 8 Lambda 表达式

      早在2014年oracle发布了jdk 8,在里面增加了lambda模块.于是java程序员们又多了一种新的编程方式:函数式编程,也就是lambda表达式.我自己用lambda表达式也差不多快4年 ...

  8. springboot springcloud 父项目pom工程创建pom文件

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...

  9. Linux开源监控平台 -- Zabbix 小白安装以及使用

    安装准备: 1.安装前需要先关闭selinux和firewall. 关闭Linux: [root@zabbix ~]# vi /etc/selinux/config 将SELINUX=enforcin ...

  10. Java的split()用法

    特殊情况有 * ^ : | . \ 一.单个符号作为分隔符  String address="上海\上海市|闵行区\吴中路"; String[] splitAddress=addr ...