一、前言

  前面已经学习了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 全栈开发,Day1(python介绍,变量,if,while)

    python基础一 一,Python介绍 python的出生与应用 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆(中文名字:龟叔)为 ...

  2. oracle中主键自增

    oracle中主键自增 下面用一个例子来说明自增主键的创建: 1.建用户数据表 drop table dectuser; create table dectuser( userid integer p ...

  3. mydate97时间控件的使用

    mydate97官网: http://www.my97.net/dp/index.asp 1:用法如下所示,首先下载一个这个东西: 链接:http://pan.baidu.com/s/1kVmIckv ...

  4. [转] 详解webpack-dev-server的使用

    webpack-dev-server是一个小型的Node.js Express服务器,它使用webpack-dev-middleware来服务于webpack的包,除此自外,它还有一个通过Sock.j ...

  5. POJ 1631 Bridging signals (LIS:最长上升子序列)

    题意:给你一个长为n(n<=40000)的整数序列, 要你求出该序列的最长上升子序列LIS. 思路:要求(nlogn)解法 令g[i]==x表示当前遍历到的长度为i的所有最长上升子序列中的最小序 ...

  6. 【BZOJ4919】[Lydsy六月月赛]大根堆

    题解: 我觉得数据结构写成结构体还是有必要的 因为不然一道题里出现了两个相同的数据结构由于名字很像很容易出错 另外初始化用segmenttree(){ } 首先裸的dp很好想 f[i][j]表示在i点 ...

  7. 2.Django|简介与静态文件| URL控制器

    1.简介  MVC Web服务器开发领域里著名的MVC模式,所谓MVC就是把Web应用分为模型(M),控制器(C)和视图(V)三层,他们之间以一种插件式的.松耦合的方式连接在一起,模型负责业务对象与数 ...

  8. Scrapy项目结构分析和工作流程

    新建的空Scrapy项目: spiders目录: 负责存放继承自scrapy的爬虫类.里面主要是用于分析response并提取返回的item或者是下一个URL信息,每个Spider负责处理特定的网站或 ...

  9. Selenium3 + Python3自动化测试系列二——selenium元素定位

    一.selenium元素定位 Selenium对网页的控制是基于各种前端元素的,在使用过程中,对于元素的定位是基础,只有准去抓取到对应元素 才能进行后续的自动化控制,我在这里将对selenium8种元 ...

  10. Windows10下 tensorflow-gpu 配置

    引言 越来越多的的人入坑机器学习,深度学习,tensorflow 作为目前十分流行又强大的一个框架,自然会有越来越多的新人(我也刚入门)准备使用,一般装的都是 CPU 版的 tensorflow,然而 ...