【Zookeeper】源码分析之网络通信(三)
一、前言
前面已经学习了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】源码分析之网络通信(三)的更多相关文章
- zookeeper源码分析之五服务端(集群leader)处理请求流程
		leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ... 
- zookeeper源码分析之四服务端(单机)处理请求流程
		上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ... 
- zookeeper源码分析之三客户端发送请求流程
		znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ... 
- Zookeeper 源码分析-启动
		Zookeeper 源码分析-启动 博客分类: Zookeeper 本文主要介绍了zookeeper启动的过程 运行zkServer.sh start命令可以启动zookeeper.入口的main ... 
- 手机自动化测试:appium源码分析之bootstrap三
		手机自动化测试:appium源码分析之bootstrap三 研究bootstrap源码,我们可以通过代码的结构,可以看出来appium的扩展思路和实现方式,从中可以添加我们自己要的功能,针对app ... 
- 【Zookeeper】源码分析之网络通信(三)之NettyServerCnxn
		一.前言 前面已经学习了NIOServerCnxn,接着继续学习NettyServerCnxn. 二.NettyServerCnxn源码分析 2.1 类的继承关系 public class Netty ... 
- 【Zookeeper】源码分析之网络通信(一)
		一.前言 前面已经分析了请求处理链中的多数类,接着继续分析Zookeeper中的网络通信模块. 二.总体框图 对于网络通信模块,其总体框图如下所示 说明: Stats,表示ServerCnxn上的统计 ... 
- 【Zookeeper】源码分析之网络通信(二)
		一.前言 前面介绍了ServerCnxn,下面开始学习NIOServerCnxn. 二.NIOServerCnxn源码分析 2.1 类的继承关系 public class NIOServerCnxn ... 
- 【Zookeeper】源码分析之网络通信(二)之NIOServerCnxn
		一.前言 前面介绍了ServerCnxn,下面开始学习NIOServerCnxn. 二.NIOServerCnxn源码分析 2.1 类的继承关系 public class NIOServerCnxn ... 
随机推荐
- zip-auto.sh
			#!/bin/sh #auto zip package #Define Path #####test######### mkdir -p /root/shell/test1 /root/shell/t ... 
- linux环境下Vim的配置
			原文链接:http://blog.chinaunix.net/uid-26826958-id-3272375.html (本文转自此链接中的部分内容,但做了适当修改) 安装vim命令:sudo ap ... 
- Java jsp 示例
			<!DOCTYPE html> <!-- [ published at 2015-11-13 12:30:50 ] --> <html> <head> ... 
- Cracking the code interview
			推荐一本书<Cracking the code interview> Now in the 5th edition, Cracking the Coding Interview gives ... 
- swift  定位 根据定位到的经纬度转换城市名
			好久没写随笔了 最近这段时间项目有点紧 天天在加班 国庆 一天假都没放 我滴娃娃 好啦 牢骚就不发了 毕竟没有什么毛用 待我那天闲了专门写一篇吐槽的随笔 
- MySQL 替换部分电话号码为000
			要做敏感信息剔除,要求又不能全换成同一个号码影响测试,想了几个方法,最终采用替换部分电话号码为000来做到敏感信息覆盖. mysql>update phone setb=replace(b,su ... 
- netcat工具的使用
			用途:网络管理工具. 可以读,写TCP或UDP 网络连接.简写为:nc 常见参数: -h 帮助信息 -l 坚挺模式 -n 指定IP地址 -p 指定端口号 -v 详细输出 1 客户端:很容易建立一个客 ... 
- enum 用法
			public enum WeekDay { SUN(, "Sunday", "SUN"), MON(, "Monday", "MO ... 
- 字典破解zip
			def pojie_zip(FilePath,PwdPath): zipFile = zipfile.ZipFile(FilePath , 'r' , zipfile.ZIP_DEFLATED) pa ... 
- .NET 通用高扩展性的细粒度权限管理架构(webApi/Mvc)
			一. 权限场景分析: 1. 系统具有角色概念, 部门概念, 且都具有相应不同的权限 2. 用户具有多个角色, 多个部门等关系, 并且能给单个用户指派独有的权限 3. 具有细粒度权限控制到资源的RBAC ... 
