Netty-RPC

来源:互联网 发布:makerbot切片软件下载 编辑:程序博客网 时间:2024/06/08 06:26

Netty初涉

传统IO与NIO

传统IO与NIO的比较:

传统IO特点:

​ 阻塞点server.accept();

​ inputStream.read(bytes);

​ 单线程情况下只能有一个客户端

​ 用线程池可以有多个客户端连接,但是非常消耗性能

​ 例图:
这里写图片描述

每一个服务员服务一个客人,非常消耗系统资源,这是基于线程的

​ 示例代码:

import java.io.IOException;import java.io.InputStream;import java.net.ServerSocket;import java.net.Socket;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 传统socket服务端 * */public class OioServer {    @SuppressWarnings("resource")    public static void main(String[] args) throws Exception {        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();        //创建socket服务,监听10101端口        ServerSocket server=new ServerSocket(10101);        System.out.println("服务器启动!");        while(true){            //获取一个套接字(阻塞)            final Socket socket = server.accept();            System.out.println("来个一个新客户端!");            newCachedThreadPool.execute(new Runnable() {                @Override                public void run() {                    //业务处理                    handler(socket);                }            });        }    }    /**     * 读取数据     * @param socket     * @throws Exception     */    public static void handler(Socket socket){            try {                byte[] bytes = new byte[1024];                InputStream inputStream = socket.getInputStream();                while(true){                    //读取数据(阻塞)                    int read = inputStream.read(bytes);                    if(read != -1){                        System.out.println(new String(bytes, 0, read));                    }else{                        break;                    }                }            } catch (Exception e) {                e.printStackTrace();            }finally{                try {                    System.out.println("socket关闭");                    socket.close();                } catch (IOException e) {                    e.printStackTrace();                }            }    }}

NIO的特点:

ServerSocketChannel     ServerSocketSocketChannel       SocketSelectorSelectionKey

​ 单线程可以连接多个客户端

图例:
这里写图片描述
此时服务员能为多个客人服务并且还能监听大门

大门会让一个服务生去监听,服务生不停的向大门瞅一眼,当人来的时候,会拿到来的人的key,然后问那个人是不是来吃饭的,是的话,创建一个socket管道,分离开来,加入了管理队列,客人说话了,问是不是要点菜,客人说是,然后就是交换数据了。

​ 示例代码:

import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.Iterator;/** * NIO服务端 *  */public class NIOServer {    // 通道管理器    private Selector selector;    /**     * 获得一个ServerSocket通道,并对该通道做一些初始化的工作     *      * @param port     *            绑定的端口号     * @throws IOException     */    public void initServer(int port) throws IOException {        // 获得一个ServerSocket通道        ServerSocketChannel serverChannel = ServerSocketChannel.open();        // 设置通道为非阻塞        serverChannel.configureBlocking(false);        // 将该通道对应的ServerSocket绑定到port端口        serverChannel.socket().bind(new InetSocketAddress(port));        // 获得一个通道管理器        this.selector = Selector.open();        // 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,        // 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。        serverChannel.register(selector, SelectionKey.OP_ACCEPT);    }    /**     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理     *      * @throws IOException     */    public void listen() throws IOException {        System.out.println("服务端启动成功!");        // 轮询访问selector        while (true) {            // 当注册的事件到达时,方法返回;否则,该方法会一直阻塞            selector.select();            // 获得selector中选中的项的迭代器,选中的项为注册的事件            Iterator<?> ite = this.selector.selectedKeys().iterator();            while (ite.hasNext()) {                SelectionKey key = (SelectionKey) ite.next();                // 删除已选的key,以防重复处理                ite.remove();                handler(key);            }        }    }    /**     * 处理请求     *      * @param key     * @throws IOException     */    public void handler(SelectionKey key) throws IOException {        // 客户端请求连接事件        if (key.isAcceptable()) {            handlerAccept(key);            // 获得了可读的事件        } else if (key.isReadable()) {            handelerRead(key);        }    }    /**     * 处理连接请求     *      * @param key     * @throws IOException     */    public void handlerAccept(SelectionKey key) throws IOException {        ServerSocketChannel server = (ServerSocketChannel) key.channel();        // 获得和客户端连接的通道        SocketChannel channel = server.accept();        // 设置成非阻塞        channel.configureBlocking(false);        // 在这里可以给客户端发送信息哦        System.out.println("新的客户端连接");        // 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。        channel.register(this.selector, SelectionKey.OP_READ);    }    /**     * 处理读的事件     *      * @param key     * @throws IOException     */    public void handelerRead(SelectionKey key) throws IOException {        // 服务器可读取消息:得到事件发生的Socket通道        SocketChannel channel = (SocketChannel) key.channel();        // 创建读取的缓冲区        ByteBuffer buffer = ByteBuffer.allocate(1024);        int read = channel.read(buffer);        if(read > 0){            byte[] data = buffer.array();            String msg = new String(data).trim();            System.out.println("服务端收到信息:" + msg);            //回写数据            ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes());            channel.write(outBuffer);// 将消息回送给客户端        }else{            System.out.println("客户端关闭");            key.cancel();        }    }    /**     * 启动服务端测试     *      * @throws IOException     */    public static void main(String[] args) throws IOException {        NIOServer server = new NIOServer();        server.initServer(8000);        server.listen();    }}

NIO的一些问题:

​ 1.客户端关闭的时候会抛出异常,死循环

​ 解决问题:

        当读入数据小于0时,将客户端key标记移除掉        int read = channel.read(buffer);        if(read > 0){            byte[] data = buffer.array();            String msg = new String(data).trim();            System.out.println("服务端收到信息:" + msg);            //回写数据            ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes());            channel.write(outBuffer);// 将消息回送给客户端        }else{            System.out.println("客户端关闭");            key.cancel();        }

​ 2.selector.select()是阻塞,而NIO不是阻塞的:

​ selector.select()也可以是不阻塞的,

​ selector.select(100);

​ selector.wakeup();同时还可以唤醒selector,后面会再次详解

​ 3.SelectionKey.OP_WRITE,注册write,指的是底层空间,buffer的大小是否可用,从selector中取出isWriteable,返回是ture,不是false,一般也不需要注册,如果需要的话 |SelectionKey.OP_WRITE即可

netty可以运用在哪些领域:

​ 1.分布式进程通信

​ 例如:hadoop、dubbo、kafka等具有分布式功能的框架,底层RPC通信都是基于netty实现的

​ 2.游戏服务器开发

​ 入门案例:

​ 环境:导入netty.jar

//client:import java.net.InetSocketAddress;import java.util.Scanner;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import org.jboss.netty.bootstrap.ClientBootstrap;import org.jboss.netty.channel.Channel;import org.jboss.netty.channel.ChannelFuture;import org.jboss.netty.channel.ChannelPipeline;import org.jboss.netty.channel.ChannelPipelineFactory;import org.jboss.netty.channel.Channels;import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;import org.jboss.netty.handler.codec.string.StringDecoder;import org.jboss.netty.handler.codec.string.StringEncoder;/** * netty客户端入门 * */public class Client {    public static void main(String[] args) {        //服务类        ClientBootstrap bootstrap = new  ClientBootstrap();        //线程池        ExecutorService boss = Executors.newCachedThreadPool(); //监听        ExecutorService worker = Executors.newCachedThreadPool();//channel-wr具体执行        //socket工厂        bootstrap.setFactory(new NioClientSocketChannelFactory(boss, worker));        //管道工厂        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {            @Override            public ChannelPipeline getPipeline() throws Exception {                ChannelPipeline pipeline = Channels.pipeline();                pipeline.addLast("decoder", new StringDecoder());//解码                pipeline.addLast("encoder", new StringEncoder());//转码                pipeline.addLast("hiHandler", new HiHandler());                return pipeline;            }        });        //连接服务端        ChannelFuture connect = bootstrap.connect(new InetSocketAddress("127.0.0.1", 10101));        Channel channel = connect.getChannel();        System.out.println("client start");        Scanner scanner = new Scanner(System.in);        while(true){            System.out.println("请输入");            channel.write(scanner.next());        }    }}import org.jboss.netty.channel.ChannelHandlerContext;import org.jboss.netty.channel.ChannelStateEvent;import org.jboss.netty.channel.ExceptionEvent;import org.jboss.netty.channel.MessageEvent;import org.jboss.netty.channel.SimpleChannelHandler;/** * 消息接受处理类 * */public class HiHandler extends SimpleChannelHandler {    /**     * 接收消息     */    @Override    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {        String s = (String) e.getMessage();//消息事件中拿到粗略消息内容        System.out.println(s);        super.messageReceived(ctx, e);    }    /**     * 捕获异常     */    @Override    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {        System.out.println("exceptionCaught");        super.exceptionCaught(ctx, e);    }    /**     * 新连接     */    @Override    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {        //这里可以做业务上的减少恶意资源攻击        System.out.println("channelConnected");        super.channelConnected(ctx, e);    }    /**     * 必须是链接已经建立,关闭通道的时候才会触发     */    @Override    public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {        //这里可以做业务上的清除连接者的缓存---例如玩家下线操作        System.out.println("channelDisconnected");        super.channelDisconnected(ctx, e);    }    /**     * channel关闭的时候触发     */    @Override    public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {        System.out.println("channelClosed");        super.channelClosed(ctx, e);    }}

//serverimport java.net.InetSocketAddress;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import org.jboss.netty.bootstrap.ServerBootstrap;import org.jboss.netty.channel.ChannelPipeline;import org.jboss.netty.channel.ChannelPipelineFactory;import org.jboss.netty.channel.Channels;import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;import org.jboss.netty.handler.codec.string.StringDecoder;import org.jboss.netty.handler.codec.string.StringEncoder;/** * netty服务端入门 * */public class Server {    public static void main(String[] args) {        //服务类        ServerBootstrap bootstrap = new ServerBootstrap();        //boss线程监听端口,worker线程负责数据读写        ExecutorService boss = Executors.newCachedThreadPool();        ExecutorService worker = Executors.newCachedThreadPool();        //设置niosocket工厂        bootstrap.setFactory(new NioServerSocketChannelFactory(boss, worker));        //设置管道的工厂        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {            @Override            public ChannelPipeline getPipeline() throws Exception {                ChannelPipeline pipeline = Channels.pipeline();                pipeline.addLast("decoder", new StringDecoder());                pipeline.addLast("encoder", new StringEncoder());                pipeline.addLast("helloHandler", new HelloHandler());                return pipeline;            }        });        bootstrap.bind(new InetSocketAddress(10101));        System.out.println("start!!!");     }}import org.jboss.netty.channel.ChannelHandlerContext;import org.jboss.netty.channel.ChannelStateEvent;import org.jboss.netty.channel.ExceptionEvent;import org.jboss.netty.channel.MessageEvent;import org.jboss.netty.channel.SimpleChannelHandler;/** * 消息接受处理类 * */public class HelloHandler extends SimpleChannelHandler {    /**     * 接收消息     */    @Override    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {        String s = (String) e.getMessage();        System.out.println(s);        //回写数据        ctx.getChannel().write("hi");        super.messageReceived(ctx, e);    }    /**     * 捕获异常     */    @Override    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {        System.out.println("exceptionCaught");        super.exceptionCaught(ctx, e);    }    /**     * 新连接     */    @Override    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {        System.out.println("channelConnected");        super.channelConnected(ctx, e);    }    /**     * 必须是链接已经建立,关闭通道的时候才会触发     */    @Override    public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {        System.out.println("channelDisconnected");        super.channelDisconnected(ctx, e);    }    /**     * channel关闭的时候触发     */    @Override    public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {        System.out.println("channelClosed");        super.channelClosed(ctx, e);    }}

当客户端已经连接时,服务端会调用disconnected(),没有连接则会调用close()

netty工作原理源码分析:

​ 这就需要看源码了,而开源框架一般封装比较多的,所以一下子是不那么容易看懂的,要是看晕了就划不来了。下面抛出一些问题来逐步引导到源码:

1、netty是基于NIO的,我们也能实现一个基本的服务和客户端,但是我们如何提高NIO的工作效率呢?

​ 前面写的是单线程的,而多线程是可以提高效率的,而NIO多线程实现也不是那么容易的,首次改进时我用线程池创建线程来运行handler(key),这个方法就是真正用来处理请求业务的,然后发现抛出了空指针异常,看了一下程序,发现当不停循环时,两次处理出现了不一致,前面的标记处理在处理完之后移除掉了当前key,但是另一个处理中却是运行在nio线程的,特点就是立即返回,实际上还没有处理,下次进入循环时,会再次selector.select()时不会阻塞再继续向下执行,然后再遍历key,再进入handlerAccept(SelectionKey key),其中会执行server.accept(),前面客户端已经accept()一次了,很显然再次accept()时就会出现空指针异常了。上面的nio用的一个selector

2、一个NIO是不是只能有一个selector呢?

​ 肯定不是,一个系统可以有多个selector

3、selector是不是只能注册一个ServerSocketChannel呢?

​ 不是,可以注册多个

从selector和channel上,我们来进行优化,

引出一张图和一个精简的nettyDemo工程后再分析:

模拟的netty线程工作模型:
这里写图片描述

​ 如图优化为:分区块管理服务

工程代码:

Start类:

package com.cn;import java.net.InetSocketAddress;import java.util.concurrent.Executors;import com.cn.pool.NioSelectorRunnablePool;/** * 启动函数 * */public class Start {    public static void main(String[] args) {        //初始化线程        NioSelectorRunnablePool nioSelectorRunnablePool = new NioSelectorRunnablePool(Executors.newCachedThreadPool(), Executors.newCachedThreadPool());        //获取服务类        ServerBootstrap bootstrap = new ServerBootstrap(nioSelectorRunnablePool);        //绑定端口        bootstrap.bind(new InetSocketAddress(10101));        System.out.println("start");    }}

ServerBootstrap类:

package com.cn;import java.net.SocketAddress;import java.nio.channels.ServerSocketChannel;import com.cn.pool.Boss;import com.cn.pool.NioSelectorRunnablePool;/** * 服务类 * */public class ServerBootstrap {private NioSelectorRunnablePool selectorRunnablePool;    public ServerBootstrap(NioSelectorRunnablePool selectorRunnablePool) {        this.selectorRunnablePool = selectorRunnablePool;    }    /**     * 绑定端口     * @param localAddress     */    public void bind(final SocketAddress localAddress){        try {            // 获得一个ServerSocket通道            ServerSocketChannel serverChannel = ServerSocketChannel.open();            // 设置通道为非阻塞            serverChannel.configureBlocking(false);            // 将该通道对应的ServerSocket绑定到port端口            serverChannel.socket().bind(localAddress);            //获取一个boss线程            Boss nextBoss = selectorRunnablePool.nextBoss();            //向boss注册一个ServerSocket通道            nextBoss.registerAcceptChannelTask(serverChannel);        } catch (Exception e) {            e.printStackTrace();        }    }}

NioServerWorker类:

package com.cn;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.ClosedChannelException;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.SocketChannel;import java.util.Iterator;import java.util.Set;import java.util.concurrent.Executor;import com.cn.pool.NioSelectorRunnablePool;import com.cn.pool.Worker;/** * worker实现类 * */public class NioServerWorker extends AbstractNioSelector implements Worker{    public NioServerWorker(Executor executor, String threadName, NioSelectorRunnablePool selectorRunnablePool) {        super(executor, threadName, selectorRunnablePool);    }    @Override    protected void process(Selector selector) throws IOException {        Set<SelectionKey> selectedKeys = selector.selectedKeys();        if (selectedKeys.isEmpty()) {            return;        }        Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator();        while (ite.hasNext()) {            SelectionKey key = (SelectionKey) ite.next();            // 移除,防止重复处理            ite.remove();            // 得到事件发生的Socket通道            SocketChannel channel = (SocketChannel) key.channel();            // 数据总长度            int ret = 0;            boolean failure = true;            ByteBuffer buffer = ByteBuffer.allocate(1024);            //读取数据            try {                ret = channel.read(buffer);                failure = false;            } catch (Exception e) {                // ignore            }            //判断是否连接已断开            if (ret <= 0 || failure) {                key.cancel();                System.out.println("客户端断开连接");            }else{                 System.out.println("收到数据:" + new String(buffer.array()));                //回写数据                ByteBuffer outBuffer = ByteBuffer.wrap("收到\n".getBytes());                channel.write(outBuffer);// 将消息回送给客户端            }        }    }    /**     * 加入一个新的socket客户端     */    public void registerNewChannelTask(final SocketChannel channel){         final Selector selector = this.selector;         registerTask(new Runnable() {            @Override            public void run() {                try {                    //将客户端注册到selector中                    channel.register(selector, SelectionKey.OP_READ);                } catch (ClosedChannelException e) {                    e.printStackTrace();                }            }        });    }    @Override    protected int select(Selector selector) throws IOException {        return selector.select(500);    }}

NioServerBoss

package com.cn;import java.io.IOException;import java.nio.channels.ClosedChannelException;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.Iterator;import java.util.Set;import java.util.concurrent.Executor;import com.cn.pool.Boss;import com.cn.pool.NioSelectorRunnablePool;import com.cn.pool.Worker;/** * boss实现类 * */public class NioServerBoss extends AbstractNioSelector implements Boss{    public NioServerBoss(Executor executor, String threadName, NioSelectorRunnablePool selectorRunnablePool) {        super(executor, threadName, selectorRunnablePool);    }    @Override    protected void process(Selector selector) throws IOException {        Set<SelectionKey> selectedKeys = selector.selectedKeys();        if (selectedKeys.isEmpty()) {            return;        }        for (Iterator<SelectionKey> i = selectedKeys.iterator(); i.hasNext();) {            SelectionKey key = i.next();            i.remove();            ServerSocketChannel server = (ServerSocketChannel) key.channel();            // 新客户端            SocketChannel channel = server.accept();            // 设置为非阻塞            channel.configureBlocking(false);            // 获取一个worker            Worker nextworker = getSelectorRunnablePool().nextWorker();            // 注册新客户端接入任务            nextworker.registerNewChannelTask(channel);            System.out.println("新客户端链接");        }    }    public void registerAcceptChannelTask(final ServerSocketChannel serverChannel){         final Selector selector = this.selector;         registerTask(new Runnable() {            @Override            public void run() {                try {                    //注册serverChannel到selector                    serverChannel.register(selector, SelectionKey.OP_ACCEPT);                } catch (ClosedChannelException e) {                    e.printStackTrace();                }            }        });    }    @Override    protected int select(Selector selector) throws IOException {        return selector.select();    }}

AbstractNioSelector

package com.cn;import java.io.IOException;import java.nio.channels.Selector;import java.util.Queue;import java.util.concurrent.ConcurrentLinkedQueue;import java.util.concurrent.Executor;import java.util.concurrent.atomic.AtomicBoolean;import com.cn.pool.NioSelectorRunnablePool;/** * 抽象selector线程类 *  *  */public abstract class AbstractNioSelector implements Runnable {    /**     * 线程池     */    private final Executor executor;    /**     * 选择器     */    protected Selector selector;    /**     * 选择器wakenUp状态标记     */    protected final AtomicBoolean wakenUp = new AtomicBoolean();    /**     * 任务队列     */    private final Queue<Runnable> taskQueue = new ConcurrentLinkedQueue<Runnable>();    /**     * 线程名称     */    private String threadName;    /**     * 线程管理对象     */    protected NioSelectorRunnablePool selectorRunnablePool;    AbstractNioSelector(Executor executor, String threadName, NioSelectorRunnablePool selectorRunnablePool) {        this.executor = executor;        this.threadName = threadName;        this.selectorRunnablePool = selectorRunnablePool;        openSelector();    }    /**     * 获取selector并启动线程     */    private void openSelector() {        try {            this.selector = Selector.open();        } catch (IOException e) {            throw new RuntimeException("Failed to create a selector.");        }        executor.execute(this);    }    @Override    public void run() {        Thread.currentThread().setName(this.threadName);        while (true) {            try {                //标记一个wakeUp状态                wakenUp.set(false);                select(selector);                //执行任务队列里面的任务处理                processTaskQueue();                //各自的业务处理                process(selector);            } catch (Exception e) {                // ignore            }        }    }    /**     * 注册一个任务并激活selector     *      * @param task     */    protected final void registerTask(Runnable task) {        taskQueue.add(task);        Selector selector = this.selector;        if (selector != null) {            if (wakenUp.compareAndSet(false, true)) {                selector.wakeup();            }        } else {            taskQueue.remove(task);        }    }    /**     * 执行队列里的任务     */    private void processTaskQueue() {        for (;;) {            final Runnable task = taskQueue.poll();            if (task == null) {                break;            }            task.run();        }    }    /**     * 获取线程管理对象     * @return     */    public NioSelectorRunnablePool getSelectorRunnablePool() {        return selectorRunnablePool;    }    /**     * select抽象方法     *      * @param selector     * @return     * @throws IOException     */    protected abstract int select(Selector selector) throws IOException;    /**     * selector的业务处理     *      * @param selector     * @throws IOException     */    protected abstract void process(Selector selector) throws IOException;}

划分到新的包结构下:pool

Boss

package com.cn.pool;import java.nio.channels.ServerSocketChannel;/** * boss接口 * */public interface Boss {    /**     * 加入一个新的ServerSocket     * @param serverChannel     */    public void registerAcceptChannelTask(ServerSocketChannel serverChannel);}

NioSelectorRunnablePool

package com.cn.pool;import java.util.concurrent.Executor;import java.util.concurrent.atomic.AtomicInteger;import com.cn.NioServerBoss;import com.cn.NioServerWorker;/** * selector线程管理者 * */public class NioSelectorRunnablePool {    /**     * boss线程数组     */    private final AtomicInteger bossIndex = new AtomicInteger();    private Boss[] bosses;    /**     * worker线程数组     */    private final AtomicInteger workerIndex = new AtomicInteger();    private Worker[] workeres;    public NioSelectorRunnablePool(Executor boss, Executor worker) {        initBoss(boss, 1);        initWorker(worker, Runtime.getRuntime().availableProcessors() * 2);    }    /**     * 初始化boss线程     * @param boss     * @param count     */    private void initBoss(Executor boss, int count) {        this.bosses = new NioServerBoss[count];        for (int i = 0; i < bosses.length; i++) {            bosses[i] = new NioServerBoss(boss, "boss thread " + (i+1), this);        }    }    /**     * 初始化worker线程     * @param worker     * @param count     */    private void initWorker(Executor worker, int count) {        this.workeres = new NioServerWorker[count];        for (int i = 0; i < workeres.length; i++) {            workeres[i] = new NioServerWorker(worker, "worker thread " + (i+1), this);        }    }    /**     * 获取一个worker     * @return     */    public Worker nextWorker() {         return workeres[Math.abs(workerIndex.getAndIncrement() % workeres.length)];    }    /**     * 获取一个boss     * @return     */    public Boss nextBoss() {         return bosses[Math.abs(bossIndex.getAndIncrement() % bosses.length)];    }}

Worker

package com.cn.pool;import java.nio.channels.SocketChannel;/** * worker接口 * */public interface Worker {    /**     * 加入一个新的客户端会话     * @param channel     */    public void registerNewChannelTask(SocketChannel channel);}

​ 以上是将线程相关的从netty中抽了出来然后也并没有处理的那么细致,比如说服务器关闭。上面的工程的运行流程:

​ Start.main(String[] args):

​ 创建了NioSelectorRunnablePool 来管理整个线程池的

​ NioSelectorRunnablePool(Executor boss,Executor worker)中:

​ initBoss(boss,1);默认为1

​ NioSelectorRunnalbePool.initBoss(Executor boss,int count)中:

​ 注意:{NioSelectorRunnablePool中维护了 private Boss[] bosses;}

​ this.bosses = new NioServerBoss[count];

​ 注意:{public class NioServerBoss extends AbstractNioSelector implements Boss

​ pubic abstract class AbstractNioSelector implements Runnable

​ – 这里为什么是抽象类呢?因为boss和worker职能不同而又有很大的共同功能,便于管理维护,所以有顶层抽象AbstractNioSelector

​ }

​ 然后再根据bosses的大小.length()给bosses中每一格值设置值(某人注:该类里面维护的是数组,数组使用不是那么方便,先声明了数据,然后逐个设置值,也就是创建NioServerBoss(boss,”boss thread “+(i+1),this) )

​ (注:public class NioServerBoss extends AbstractNioSelector impements Boss)

​ NioServerBoss(Executor executor,String threadName,NioSelectorRunnablePool selectorRunnablePool){

​ super(executor,threadName,selectorRunnablePool);

​ }

​ {注:public abstract class AbstractNioSelector implements Runnable }

​ AbstarctNioSelector(Executor executor,String threadName,NioSelectorRunnablePool selectorRunnablePool){

​ this.executor = executor;//线程池

​ this.threadName = threadName; //线程名

​ this.selectorRunnablePool = selectorRunnablePool;//线程管理者

​ oepnSelector();

​ }

​ 父类里面维护了传入值

​ private void openSelector() //对多个线程服务的能力

​ executor.execute(this) 执行任务

​ @Override public void run(){}

​ 再看NioSelectorRunnablePool

​ initBoss(Executor boss,int count)

​ for(){ 创建NioServerBoss为每一格值赋值}

​ public NiodSelectorRunnablePool(Executor boss,Executor worker)

​ initWorder()//参数不做解释,前面的博客讲过,就是这么任性

​ for(){ 创建NioServerWorker为每一格值赋值}

​ public class NioServerWorker extends AbstractNioSelector implements Worker

​ NioServerWorker( super())

​ AbstractNioSelector

​ …同上

​ @public void run(){

​ Thread.currentThread().setName(this.threadName);

​ while(true){

​ 1.wakeUp.set(false); //状态位

​ 注:{final AtomicBoolean wakeUp = new AutomicBoolean()}

​ 2.select(selector);

​ 注:{protected abstract int select(Selector selector)可以做不同的选择,NioServerBoss中是做阻塞的,worker是阻塞500ms然后返回}

​ 3.processTaskQueue(); //处理任务队列

​ private void processTaskQueue(){

​ taskQueue.poll()

​ 注:{private final Queue taskQueue = new ConcurrentLinkedQueue();}

​ 返回final Runnable task

​ task.run()

​ }

​ 4.process(selector) —–boss和worker做不同的处理

​ NioServerBoss.process

​ selector.selectedKeys()—–>Set selectedKeys

​ 当selectedKeys为空直接返回

​ 不为空遍历 set

​ key.channel–>ServerSocketChannel server.accept()再设置为非阻塞

​ 通过线程管理者得到worker,通知worker

​ nextworker.registerNewChannelTask(channel);

​ NioServerWorker.registerNewChannelTask(final Socket channel){

​ ..channel.register(selector,SelectionKey.OP_READ); – 将客户端注册到selector中

注:{向别的线程任务队列中加入任务,可以很好的避免并发问题,不直接操作别的线程,而是向线程任务队列中加入任务,,典型例子,安卓系统中子线程不能改变主线程界面,需要拿到主线程的任务队列然后添加任务}

​ }

​ 上面的如何获取worker呢?

​ workers[Math.abs(workerIndex.getAndIncrement()%workers.length)];

​ }}

worker的业务处理:

​ 公共点省略:

​ ….

​ 移除,放置重复 ite.remove();

​ 得到事件发生的socket通道 (SocketChannel)key.channel();

​ 读取数据–判断是否断开<是:断开 <否:打印收到数据

​ 加入任务的实现:

​ taskQueue.add(task);

​ wakenUp.compareAndSet(false,true)

​ selector.wakeup();

​ 反之:taskQueue.remove(task)

如何去看一个开源的系统框架

多断点、多打印、多看调用栈、多搜索

netty源码工程百度云链接:http://pan.baidu.com/s/1dFAGWEX

netty5源码学习demo工程百度云连接:http://pan.baidu.com/s/1o8OBN66

一个thread + 队列 == 一个单线程线程池 ===>线程安全的,任务是线性串行执行的

线程安全,不会产生阻塞效应,使用对象组

实现不安全,会产生阻塞效应,使用对象池

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

心跳

idelStateHandler

​ netty提高的维持心跳的工具类

netty3.* 实现:extends IdelStateAwareChannelHandler implemetns ChannelHandler

重写:void channelIdel(ChannelHandleContext ctx,IdelStateEvent e) throws Exception

也可以直接继承 extends SimpleChannelHandler

​ 重写void messageReceived(ChannnelHandlerContext ctx,MessageEvent e) throws Exception – e.getMessage()

​ void handleupstream(ChannelHandlerContext ctx,ChannelEvent e) throws Exception{

​ if(e instanceof IdleStateEvent){

​ IdelStateEvent event = (IdleStateEvent) e;

​ event.getState()

​ 应用场景:{

​ if(((IdleStateEvent)e).getState() == IdleState.ALL_IDLE){

​ System.out.println(“踢玩家下线”);

​ ctx.getChannel().close(); – 简单,粗暴,有效‘

​ ctx.getChannel().write(“sorry to timeout,you will close()”);

​ write.addListenner(new ChannelFutureListenner(){

​ @Override

​ public void operationComplete(ChannelFuture future) throws Exception{

​ ctx.getChannel().close();

​ }

​ })

​ }

​ }

​ }else{

​ super.handlerUpstream(ctx,e);

​ }

​ }

常用来检测回话状态

心跳其实就是一个普通的请求,特点:数据简单,后台对业务处理也简单,比如说返回成功/系统时间或者失败

心跳对于服务端来说,最大作用是定时清除闲置会话inactive(netty5) channelclose(netty3)同一台电脑断点后会存在一个老的会话,相当于僵尸进程,

心跳对于客户端来说,当网络断开时自动去尝试重新连接,用来检测回话是否断开,是否重连;还可以用来检测网络延时,比如说游戏客户端的ping

​ 心跳也是根据系统业务进行

protocal buff

1.protocol buff 是一种协议,是谷歌推出的一种序列化协议

2.java序列化协议也是一种协议

​ 将对象转成字符串的规则

3.两者的目的是,将对象序列化成字节数组,或者说是二进制数据

使用protoc.exe生成java文件

需要书写一个配置文件

例如:

player.proto

option java_package = “com.proto”;

option java_outer_classname = “PlayerModule”;

message PBPlayer{

​ required int64 playerId = 1;

​ required int32 age = 2;

​ required String name = 3;

​ repeated int32 skills = 4;

}

message PBResource{

​ required int64 gold = 1;

​ required int32 energy = 2;

}

required 设置字段为必须的,没有的话就直接报错

int64 为long

int32 为int

需要设置key值且不能重复 = 数值

{“playId”:101}

需要创建一个批处理文件

build.bat

protoc ./*.proto –java_out=./

pasue

代码实现序列化

public byte[] toBytes(){  //获取一个PBPlayer的构造器  Builder builder = PlayerModule.PBPlayer.newBuilder();  //设置数据  builder.setPlayerId(101).setName("peter").addSkills(1001);  //构造出对象    PBPlayer player = builder.build();    //序列化成字节数组    byte[] byteArray  = palyer.toByteArray();    System.out.println(Arrays.toString(byteArray));    return byteArray;}

反序列话:

public static void toPlayer(byte[] bs) throws Exception{  PlayerModule.PBPlayer.parseFrom(bs);  System.out.println("playerId:"+player.getPlayerId());  System.out.println("skills:"+(Arrays.toString(player.getSkillsList().toArray())))}

java序列化原始实现:

public class Player implements Serialize{  private long palyerId;  private int  age;  private String name;  private List<Integer> skills = new ArrayList<Integer>();  public Palyer(long playerId,int age,String name){    this.playerId = playerId;    this.age = age;    this.name = name;  }   ...get和set方法}//序列化public static byte[] toBytes() throws IOException{  Player player = new Player(101,20,"pp")  player.getSkills().add(101);  ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();  ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);  //写入对象  objectOutputStream.write(player);  //获取字节数组  byte[] byteArray = byteArrayOutputStream.toByteArray();  System.out.println(Arrays.toString(byteArray));  return null;}//反序列化public void toPlayer(byte[] bs) throws Exception{  ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(bs));  Player player = (Player) inputStream.readObject();}

[-84, -19, 0, 5, 115, 114, 0, 15, 99, 111, 109, 46, 106, 97, 118, 97, 46, 80, 108, 97, 121, 101, 114, -73, 43, 28, 39, -119, -86, -125, -3, 2, 0, 4, 73, 0, 3, 97, 103, 101, 74, 0, 8, 112, 108, 97, 121, 101, 114, 73, 100, 76, 0, 4, 110, 97, 109, 101, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 76, 0, 6, 115, 107, 105, 108, 108, 115, 116, 0, 16, 76, 106, 97, 118, 97, 47, 117, 116, 105, 108, 47, 76, 105, 115, 116, 59, 120, 112, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 101, 116, 0, 5, 112, 101, 116, 101, 114, 115, 114, 0, 19, 106, 97, 118, 97, 46, 117, 116, 105, 108, 46, 65, 114, 114, 97, 121, 76, 105, 115, 116, 120, -127, -46, 29, -103, -57, 97, -99, 3, 0, 1, 73, 0, 4, 115, 105, 122, 101, 120, 112, 0, 0, 0, 1, 119, 4, 0, 0, 0, 10, 115, 114, 0, 17, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 73, 110, 116, 101, 103, 101, 114, 18, -30, -96, -92, -9, -127, -121, 56, 2, 0, 1, 73, 0, 5, 118, 97, 108, 117, 101, 120, 114, 0, 16, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 78, 117, 109, 98, 101, 114, -122, -84, -107, 29, 11, -108, -32, -117, 2, 0, 0, 120, 112, 0, 0, 3, -23, 120]

[8, 101, 16, 20, 26, 5, 112, 101, 116, 101, 114, 32, -23, 7]

差别:proto buff 可以把对象序列化成更加短的字节数组,因此更加省带宽,可以传输更多的数据

为什么可以proto buff变成如此短的数据呢?

java生成的字节中包含了类的信息,类的字段,协议头,类的名称,类的类型,数值,java序列化不需要配置文件,proto buff要求两边使用同一个配置信息,配置文件配置了上面的一些信息。proto buff是由可伸缩性的,比如 int 4字节,long 8字节,而proto buff会根据int 的实际大小分配实际字节,在极端情况下1-5是差不多的,但是实际上很多情况下都是用不到那么多字节的,这里引出一个问题:如何实现动态伸缩呢?以及proto buff如何实现的呢?

自定义协议

自定义序列化协议

自定义数据包(报文)协议

        public static void main(String[] args){int id = 101;int age = 21;ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();arrayOutputStream.write(int2bytes(id));arrayOutputStream.write(int2bytes(age));byte[] byteArray = arrayOutStream.toByteArray();System.out.println(Arrays.toString(byteArray));ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(byteArray);byte[] idBytes = new Byte[4];arrayInputStream.read(idBytes);System.out.println("id:"+byte2int(idBytes));byte[] ageBytes = new byte[4];arrayInputStream.read(ageBytes);System.out.println("age:"+byte2int(ageBytes));}        public static byte[] int2byte(int i){//大端字节序列(先写高位,再写地位),小端序列(先写地位,再写高位)byte[] bytes = new byte[4];bytes[0] = (byte)(i>>3*8);bytes[1] = (byte)(i>>2*8);bytes[2] = (byte)(i>>1*8);bytes[3] = (byte)(i>>0*8);return bytes;}         //大端        public static int byte2int(byte[] bytes){ return (bytes[0]<<3*8)| (bytes[1]<<2*8)| (bytes[2]<<1*8)| (bytes[3]<<0*8);}

版本2

        public static void main(String[] args) throws Exception{    int id = 101;    int age = 21;    ByteBuffer buffer = ByteBuffer.allocate(8);    buffer.putInt(id);    buffer.putInt(age);    byte[] array = buffer.array();    System.out.println(Arrats.toString(array));    //---------------------------     ByteBuffer buffer2 = ByteBuffer.wrap(array);    System.out.println("id:"+buffer2.getInt());   System.out.println("age"+buffer2.getInt);   //这样有一个问题,必须先申固定长度,如果长度不足则会报出java.nio.BufferOverflowException,它不会根据写的数据进行扩容}

版本3 ,使用netty中的buffer,基于ByteBuffer进行自动扩容

        public staitc void main(String[] args){  ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();  bufer.writeInt(101);  //写数据  buffer.writeDouble(8.1);  //String数据呢 根据自定义数据包(报文)协议:  byte:1字节  short:2字节  int:4字节  long:8字节  char:4字节  String:short长度+字节数组  List:short:长度+..  Map:short:长度+..  //序列化  byte[] bytes = new byte[buffer.writerIndex()];  buffer.readBytes(bytes);  System.out.println(Arrays.toString(bytes));  //反序列化  ChannelBUffer wrappedBuffer = ChannelBuffers.wrappedBuffer(bytes);  System.out.println(wrappedBuffer.readInt());  System.out.println(wrappedBuffer.readDouble());}

将序列化规则抽象拆成一个基类

BufferFactory.java

package com.cn.core;import java.nio.ByteOrder;import org.jboss.netty.buffer.ChannelBuffer;import org.jboss.netty.buffer.ChannelBuffers;/** * buff工厂 * @author -琴兽- * */public class BufferFactory {    public static ByteOrder BYTE_ORDER = ByteOrder.BIG_ENDIAN;    /**     * 获取一个buffer     *      * @return     */    public static ChannelBuffer getBuffer() {        ChannelBuffer dynamicBuffer = ChannelBuffers.dynamicBuffer();        return dynamicBuffer;    }    /**     * 将数据写入buffer     * @param bytes     * @return     */    public static ChannelBuffer getBuffer(byte[] bytes) {        ChannelBuffer copiedBuffer = ChannelBuffers.copiedBuffer(bytes);        return copiedBuffer;    }} 

Serializer.java

package com.cn.core;import java.nio.charset.Charset;import java.util.ArrayList;import java.util.Collection;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Map.Entry;import org.jboss.netty.buffer.ChannelBuffer;/** * 自定义序列化接口 * */public abstract class Serializer {    public static final Charset CHARSET = Charset.forName("UTF-8");    protected ChannelBuffer writeBuffer;    protected ChannelBuffer readBuffer;    /**     * 反序列化具体实现     */    protected abstract void read();    /**     * 序列化具体实现     */    protected abstract void write();    /**     * 从byte数组获取数据     * @param bytes 读取的数组     */    public Serializer readFromBytes(byte[] bytes) {        readBuffer = BufferFactory.getBuffer(bytes);        read();        readBuffer.clear();        return this;    }    /**     * 从buff获取数据     * @param readBuffer     */    public void readFromBuffer(ChannelBuffer readBuffer) {        this.readBuffer = readBuffer;        read();    }    /**     * 写入本地buff     * @return     */    public ChannelBuffer writeToLocalBuff(){        writeBuffer = BufferFactory.getBuffer();        write();        return writeBuffer;    }    /**     * 写入目标buff     * @param buffer     * @return     */    public ChannelBuffer writeToTargetBuff(ChannelBuffer buffer){        writeBuffer = buffer;        write();        return writeBuffer;    }    /**     * 返回buffer数组     *      * @return     */    public byte[] getBytes() {        writeToLocalBuff();        byte[] bytes = null;        if (writeBuffer.writerIndex() == 0) {            bytes = new byte[0];        } else {            bytes = new byte[writeBuffer.writerIndex()];            writeBuffer.readBytes(bytes);        }        writeBuffer.clear();        return bytes;    }    public byte readByte() {        return readBuffer.readByte();    }    public short readShort() {        return readBuffer.readShort();    }    public int readInt() {        return readBuffer.readInt();    }    public long readLong() {        return readBuffer.readLong();    }    public float readFloat() {        return readBuffer.readFloat();    }    public double readDouble() {        return readBuffer.readDouble();    }    public String readString() {        int size = readBuffer.readShort();        if (size <= 0) {            return "";        }        byte[] bytes = new byte[size];        readBuffer.readBytes(bytes);        return new String(bytes, CHARSET);    }    public <T> List<T> readList(Class<T> clz) {        List<T> list = new ArrayList<>();        int size = readBuffer.readShort();        for (int i = 0; i < size; i++) {            list.add(read(clz));        }        return list;    }    public <K,V> Map<K,V> readMap(Class<K> keyClz, Class<V> valueClz) {        Map<K,V> map = new HashMap<>();        int size = readBuffer.readShort();        for (int i = 0; i < size; i++) {            K key = read(keyClz);            V value = read(valueClz);            map.put(key, value);            }        return map;    }    @SuppressWarnings("unchecked")    public <I> I read(Class<I> clz) {        Object t = null;        if ( clz == int.class || clz == Integer.class) {            t = this.readInt();        } else if (clz == byte.class || clz == Byte.class){            t = this.readByte();        } else if (clz == short.class || clz == Short.class){            t = this.readShort();        } else if (clz == long.class || clz == Long.class){            t = this.readLong();        } else if (clz == float.class || clz == Float.class){            t = readFloat();        } else if (clz == double.class || clz == Double.class){            t = readDouble();        } else if (clz == String.class ){            t = readString();        } else if (Serializer.class.isAssignableFrom(clz)){            try {                byte hasObject = this.readBuffer.readByte();                if(hasObject == 1){                    Serializer temp = (Serializer)clz.newInstance();                    temp.readFromBuffer(this.readBuffer);                    t = temp;                }else{                    t = null;                }            } catch (Exception e) {                e.printStackTrace();            }         } else {            throw new RuntimeException(String.format("不支持类型:[%s]", clz));        }        return (I) t;    }    public Serializer writeByte(Byte value) {        writeBuffer.writeByte(value);        return this;    }    public Serializer writeShort(Short value) {        writeBuffer.writeShort(value);        return this;    }    public Serializer writeInt(Integer value) {        writeBuffer.writeInt(value);        return this;    }    public Serializer writeLong(Long value) {        writeBuffer.writeLong(value);        return this;    }    public Serializer writeFloat(Float value) {        writeBuffer.writeFloat(value);        return this;    }    public Serializer writeDouble(Double value) {        writeBuffer.writeDouble(value);        return this;    }    public <T> Serializer writeList(List<T> list) {        if (isEmpty(list)) {            writeBuffer.writeShort((short) 0);            return this;        }        writeBuffer.writeShort((short) list.size());        for (T item : list) {            writeObject(item);        }        return this;    }    public <K,V> Serializer writeMap(Map<K, V> map) {        if (isEmpty(map)) {            writeBuffer.writeShort((short) 0);            return this;        }        writeBuffer.writeShort((short) map.size());        for (Entry<K, V> entry : map.entrySet()) {            writeObject(entry.getKey());            writeObject(entry.getValue());        }        return this;    }    public Serializer writeString(String value) {        if (value == null || value.isEmpty()) {            writeShort((short) 0);            return this;        }        byte data[] = value.getBytes(CHARSET);        short len = (short) data.length;        writeBuffer.writeShort(len);        writeBuffer.writeBytes(data);        return this;    }    public Serializer writeObject(Object object) {        if(object == null){            writeByte((byte)0);        }else{            if (object instanceof Integer) {                writeInt((int) object);                return this;            }            if (object instanceof Long) {                writeLong((long) object);                return this;            }            if (object instanceof Short) {                writeShort((short) object);                return this;            }            if (object instanceof Byte) {                writeByte((byte) object);                return this;            }            if (object instanceof String) {                String value = (String) object;                writeString(value);                return this;            }            if (object instanceof Serializer) {                writeByte((byte)1);                Serializer value = (Serializer) object;                value.writeToTargetBuff(writeBuffer);                return this;            }            throw new RuntimeException("不可序列化的类型:" + object.getClass());        }        return this;    }    private <T> boolean isEmpty(Collection<T> c) {        return c == null || c.size() == 0;    }    public <K,V> boolean isEmpty(Map<K,V> c) {        return c == null || c.size() == 0;    }}

如何使用?

public class Palyer extends Serializer{    private long playerId;    private int  age;    private List<Integer> skills = new ArrayList<Integer>();    private Resource resource = new Resource();    ....get和set方法  @Override  protected  void read(){ //序列化过程    //按顺序读取数据    this.playerId = readLong();    this.age = readInt();    this.skills = readList(Integer.class);    this.resource = read(Resource.class);  }  @Override  protected void write(){ //反序列化过程    writeLong(palyerID);    writeInt(age);    writeList(skills);    writeObject(resource);  }}

嵌套的一个object:

public class Resource{  private long gold;  ...................同上}

测试:

        public static void main(String[] args){  Player  player = new Player();  player.setPlayerId(1001);  player.setAge(18);  player.setSkills().add(101);  byte[] bytes = player.getBytes();  System.out.println(Arrays.toString(bytes));  //反序列化  Player player2  = new Player();  player2.readFromBytes(bytes);  System.out.println(player2.getPlayerId+"  "+player2.getAge()+"  "+Arrays.toString(player2.getSkills()));}

小结:上面的自定义协议的实现的原理是类的数据直接按顺序往buffer里面写,读的时候向buffer读

尽管如此,上面实现结果还是比proto buff的结果长一些

分析一下proto buff的实现:

->PB2Bytes.java        public static byte[] toBytes(){...byte[] byteArray = player.toByteArray();}->AbstractMessageLite.class        public byte[] toByteArray(){...writTo(CodeOutputStream.newInstance(new byte[getSerializedSize()])); -->}->MessageList.class        void writeTo(odeOutputStream output) throws IOException;ctral+t 搜索PBPlayer->PlayerModule.java        publicc void writeTo(com.google.protobuf.CodeOutputStream output) throws java.io.IOException{...if((bitField0_ & 0x0000001) == 0x0000001){  output.writeInt64(1,playerId_);}}->CodeOutputStream.class        public void writeInt32(final int fieldNumber,final int value) throws IOException{ writeTag(fieldNumber,WireFormat.WIRETYPE_VARINT);writeInt32NoTag(value); -->}        public void writeInt32NoTag(final int value) throws IOException{  if(value>=0){    writeRawVarint32(value); -->  }else{    writeRawVarint64(value);  }}        public void writeRawVarint32(int value) throws IOException{while(true){ //死循环  if((value & ~0x7F) == 0){ // &  再与0x7F取反 字节表示为0111 1111取反为1000 0000 & ,结果 1~7 全被置成0,假如 value > 0111 1111 ,value & 1000 0000 ,结果 int 4位 4*8=32 ,value < 0x7F,意味着不需占1个字节    writeRawByte(value);    return;  } else{ //反之 大于    writeRawByte((value & 0x7F)|0X80);//value& 0x7F获取到1~7位数据,再|0x80 ,1000 0000,| 意味着合并,1XXX XXXX,原来1~8表示为数据位,proto buff 为:1~7 位数据位,第8位表示是否还有数据,1:后面有数据,0:后面没有数据了    value >>>= 7; //去掉7位数据  }}      }// 我们的实现 : bytes[0] = (byte)(i >> 3*8即24) 得到第一位数据// 它用7表示位数据,第8位表示有没有数据,4个字节中,能表示数据位只有4*(8-1)=>28位,后面可能还有加一个字节补充第8位还有没有数据,字节不够用的情况,它不会像 内存中 int 占 4字节,它会根据int 大小不一样,它会占到1~5个字节,其他long...也是这个原理,可能一般1~2个就够了,这里大大缩小int 占用的数组的大小,这样的话就很省空间,long 在内存中占8 个字节,在proto buff中占9个字节

小结:

自定义了序列化协议为了将对象转换为二进制数据

自定义数据包协议

message1 message2

message1message2 粘包现象

messa

ge1message2 分包现象:

粘包和分包出现的原因是:没有一个稳定的数据结构

采取的措施:

​ 分隔符 message1|message2|

​ 长度+数据 长度message1长度message2

数据包格式

包头+模块号+命令号+长度+数据

包头4字节

模块号2字节short

命令号2字节short

长度4字节(描述数据部分字节长度)

命令号

Player 1

1.获取玩家数据

2.注册用户

3.购买金币

长度

长度表示数据的长度,不同业务定义可能不同

demo:

ConstantValue 存储固定量

public interface ConstantValue{  //包头  int FLAG = -5122315; }

Request

//请求对象public class Request{  //请求模块  private short module;  //命令号  private short cmd;  //数据部分 序列化出来的数据,可以自定义协议也可以使用proto buff  private byte[] data;  ...getset方法,这里省略  public int getDataLength(){    if(data == null)    return 0;    return data.length();  }}

RequestEncoder请求编码器

//请求编码对象public class RequestEncoder extends OneToOneEncoder{//数据包格式:包头+模块号+命令号+长度+数据        @Override         protected Object encode(ChannelHandlerContext context,Channel channel,Object req) throws Exception{Request request = (Request)req;channelBuffer buffer = chanelBuffers.dynamicBuffer();//获取一个动态的buffer//包头 使得数据结构更加稳定buffer.writeInt(ConstantVlaue.FLAG);//modulebuffer.writeShort(request.getModule());//cmdbuffer.writeShort(request.getCmd());//长度buffer.writeInt(request.getData());//dataif(data != null)buffer.writeBytes(request.getData());return buffer;}}

RequesetDecoder 请求解码器,可以协助处理粘包和分包问题

public class RequestDecoder extends FrameDecoder{    public static int BASE_LENGTH = 4 +2 + 2 +4;//一个数据包最短的长度为12个字节        @Override        protected Object decode(ChannelHandlerContext context,Channel channel,ChannelBuffer buffer) throws Exception{//可短长度必须大于基本长度if(buffer.readableBytes()>BASE_LENGTH) //buffer的长度{    //记录包头开始的Index    int beginReader = buffer.readerIndex();//buffer中有一个读指针readIndex和写指针writeIndex,读指针大于等于0,写指针会小于等于读指针,超过的话就会读超位    //读到包头    while(true){        if(buffer.readInt() == ConstantValue,FLAG){         break;         }    }    //模块号    short module = buffer.readShort();//读取完后,读指针会移动    //命令号    short cmd    = buffer.readShort();    //长度    int length = buffer.readInt();    //数据不完整时继续等待    if(buffer.readableBytes()<length){     //还原读指针,让下次完整后重新可以去读到结构中前的数据     buffer.readIndex(beginReader);      return nulll;    }    //读取数据    byte[] data = new byte[length];    buffer.readBytes(data);//用buffer读取数据到data中    //封装数据到request中    Request request = new Request();    request.setModule(module);    request.setCmd(cmd);    request.setData(data);    //继续往下传递   ->sendUpStreamEvent()     return request;}//数据包不完整,需要等待后面的包来,如果返回一个对象则是解码出一个对象,向后面的handler传递  return null;  }}

Response 返回对象

public class Response{  //响应模块  private short module;  //命令号  private short cmd;  //状态吗  private int stateCode;  //数据部分  private byte[] data;  ....省略getset方法}

StateCode

public interface StateCode{    //成功    int  SUCCESS = 0;    //失败    int  FAIL = 1;}

ResponseEncoder**请求编码器

//请求编码对象public class ResponseEncoder extends OneToOneEncoder{//数据包格式:包头+模块号+命令号+状态码+长度+数据        @Override         protected Object encode(ChannelHandlerContext context,Channel channel,Object res) throws Exception{Response response = (Response)res;channelBuffer buffer = chanelBuffers.dynamicBuffer();//获取一个动态的buffer//包头 使得数据结构更加稳定buffer.writeInt(ConstantVlaue.FLAG);//modulebuffer.writeShort(response.getModule());//cmdbuffer.writeShort(response.getCmd());//状态码buffer.wrietInt(response.getStateCode())//长度buffer.writeInt(response.getData());//dataif(data != null)buffer.writeBytes(response.getData());return buffer;}}

ResponsetDecoder 响应解码器,可以协助处理粘包和分包问题

public class ResponseDecoder extends FrameDecoder{    public static int BASE_LENGTH = 4 +2 + 2 +4;//一个数据包最短的长度为12个字节        @Override        protected Object decode(ChannelHandlerContext context,Channel channel,ChannelBuffer buffer) throws Exception{//可短长度必须大于基本长度if(buffer.readableBytes()>BASE_LENGTH) //buffer的长度{    //记录包头开始的Index    int beginReader = buffer.readerIndex();//buffer中有一个读指针readIndex和写指针writeIndex,读指针大于等于0,写指针会小于等于读指针,超过的话就会读超位    //读到包头    while(true){        if(buffer.readInt() == ConstantValue,FLAG){         break;         }    }    //模块号    short module = buffer.readShort();//读取完后,读指针会移动    //命令号    short cmd    = buffer.readShort();    //状态码    int stateCode = buffer.readInt();    //长度    int length = buffer.readInt();    //数据不完整时继续等待,判断请求数据包数据是否到齐    if(buffer.readableBytes()<length){     //还原读指针,让下次完整后重新可以去读到结构中前的数据     buffer.readIndex(beginReader);      return nulll;    }    //读取数据    byte[] data = new byte[length];    buffer.readBytes(data);//用buffer读取数据到data中    //封装数据到response中    Response response = new Response();    response.setModule(module);    response.setCmd(cmd);    response.setData(data);    //继续往下传递   ->sendUpStreamEvent()     return response;}//数据包不完整,需要等待后面的包来,如果返回一个对象则是解码出一个对象,向后面的handler传递  return null;  }}

FightRequest

public class FIghtRequest extends Serializer{    //副本id    private int fubenId;    //次数    priate int count;    ...set和get方法    @Override    protected void read(){      this.fubenId = readInt();      this.count   = readInt();    }    @Override    protected void write(){      writeInt(fubenId);      writeInt(count);    }}

FightResponse

public class FightResponse extends Serializer {  //攻打获取了的金币数量  private int gold;  @Override  protected void read(){    this.gold =readInt();  } @Override protected void write(){   writeInt(gold); }}

测试**

客户端

Client

public class Client {    public static void main(String[] args) throws InterruptedException {        //服务类        ClientBootstrap bootstrap = new  ClientBootstrap();        //线程池        ExecutorService boss = Executors.newCachedThreadPool();        ExecutorService worker = Executors.newCachedThreadPool();        //socket工厂        bootstrap.setFactory(new NioClientSocketChannelFactory(boss, worker));        //管道工厂        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {            @Override            public ChannelPipeline getPipeline() throws Exception {                ChannelPipeline pipeline = Channels.pipeline();                pipeline.addLast("decoder", new ResponseDecoder());                pipeline.addLast("encoder", new RequestEncoder());                pipeline.addLast("hiHandler", new HiHandler());                return pipeline;            }        });        //连接服务端        ChannelFuture connect = bootstrap.connect(new InetSocketAddress("127.0.0.1", 10101));        Channel channel = connect.sync().getChannel();        System.out.println("client start");        Scanner scanner = new Scanner(System.in);        while(true){            System.out.println("请输入");            int fubenId = Integer.parseInt(scanner.nextLine());            int count = Integer.parseInt(scanner.nextLine());            FightRequest fightRequest = new FightRequest();            fightRequest.setFubenId(fubenId);            fightRequest.setCount(count);            Request request = new Request();            request.setModule((short) 1);            request.setCmd((short) 1);            request.setData(fightRequest.getBytes());            //发送请求            channel.write(request);        }    }}

Handler

public class HiHandler extends SimpleChannelHandler {    /**     * 接收消息     */    @Override    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {            Response message = (Response)e.getMessage();            if(message.getModule() == 1){                if(message.getCmd() == 1){                    FightResponse fightResponse = new FightResponse();                    fightResponse.readFromBytes(message.getData());                    System.out.println("gold:" + fightResponse.getGold());                }else if(message.getCmd() == 2){                }            }else if (message.getModule() == 1){            }    }    /**     * 捕获异常     */    @Override    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {        System.out.println("exceptionCaught");        super.exceptionCaught(ctx, e);    }    /**     * 新连接     */    @Override    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {        System.out.println("channelConnected");        super.channelConnected(ctx, e);    }    /**     * 必须是链接已经建立,关闭通道的时候才会触发     */    @Override    public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {        System.out.println("channelDisconnected");        super.channelDisconnected(ctx, e);    }    /**     * channel关闭的时候触发     */    @Override    public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {        System.out.println("channelClosed");        super.channelClosed(ctx, e);    }}

Server 服务端

public class Server {    public static void main(String[] args) {        //服务类        ServerBootstrap bootstrap = new ServerBootstrap();        //boss线程监听端口,worker线程负责数据读写        ExecutorService boss = Executors.newCachedThreadPool();        ExecutorService worker = Executors.newCachedThreadPool();        //设置niosocket工厂        bootstrap.setFactory(new NioServerSocketChannelFactory(boss, worker));        //设置管道的工厂        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {            @Override            public ChannelPipeline getPipeline() throws Exception {                ChannelPipeline pipeline = Channels.pipeline();                pipeline.addLast("decoder", new RequestDecoder());                pipeline.addLast("encoder", new ResponseEncoder());                pipeline.addLast("helloHandler", new HelloHandler());                return pipeline;            }        });        bootstrap.bind(new InetSocketAddress(10101));        System.out.println("start!!!");    }}

Handler 处理器

public class HelloHandler extends SimpleChannelHandler {    /**     * 接收消息     */    @Override    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {        Request message = (Request)e.getMessage();        if(message.getModule() == 1){            if(message.getCmd() == 1){                FightRequest fightRequest = new FightRequest();                fightRequest.readFromBytes(message.getData());                System.out.println("fubenId:" +fightRequest.getFubenId() + "   " + "count:" + fightRequest.getCount());                //回写数据                FightResponse fightResponse = new FightResponse();                fightResponse.setGold(9999);                Response response = new Response();                response.setModule((short) 1);                response.setCmd((short) 1);                response.setStateCode(StateCode.SUCCESS);                response.setData(fightResponse.getBytes());                ctx.getChannel().write(response);            }else if(message.getCmd() == 2){            }        }else if (message.getModule() == 1){        }    }    /**     * 捕获异常     */    @Override    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {        System.out.println("exceptionCaught");        super.exceptionCaught(ctx, e);    }    /**     * 新连接     */    @Override    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {        System.out.println("channelConnected");        super.channelConnected(ctx, e);    }    /**     * 必须是链接已经建立,关闭通道的时候才会触发     */    @Override    public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {        System.out.println("channelDisconnected");        super.channelDisconnected(ctx, e);    }    /**     * channel关闭的时候触发     */    @Override    public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {        System.out.println("channelClosed");        super.channelClosed(ctx, e);    }}

粘包分包分析

引入:

消息在管道中是如何流转?

当前的handler如何往下面的handler传递对象?

–>pubic abstract class OneToOneDecoder implements ChannelUpstreamHandler

–>public interface ChannelUpstreamHandler extends ChannelHadler

一个管道中会有多个handler

netty内部处理:

​ 处理器 事件

​ channel evnet->Handler1->channel event1|->

​ Handler2->chanel event2_1|->

​ ->channel event2_2|->

​ Handler3->channel event3->

事件源头在抽象selector中,AbstractNioSelector

–>public abstract class AbstractNioSelector implements NioSelector

–>public interface NioSelector extends Runnable

@Override

public void run(){

int selected = select(selector);

processTaskQueue();

process(selector);

}

–>AbstractNioWorker.class

@Orverride

protected void process(Selector selector) throws IOException{

Set selectedKeys = selector.selectedKeys();

..

for(Iterator i = selectedKeys.iterator();i.hasNext();){

SelectionKey k = i.next();

if(!read(k)){ //读数据

}

}

}

–>protected abstract boolean read(SelectionKey k);

–>NioWorker

@Override

protected boolean read(SelectionKey k){

final SocketChannel ch = (SocketChannel) k.channel();

ByteBuffer bb = recvBufferPool.get(predictedRecvBufSize).order(bufferFactory.getDefaultOrder())

try{while((ret == ch.read(bb) > 0){

​ readytes += ret;

​ if(!bb.hasRemaining()){break;}

}}

final ChannelBuffer buffer = bufferFactory.getBuffer(readBytes);

buffer.setBytes(0,bb);

fireMessageReceived(channel,buffer);

}

–>Channels

public static void fireMessageReceived(Channel channel,Object message){

​ fireMessageReceived(channel,message,null); //从这里开始进入管道部分

}

public static void fireMessageReceived(Channel channel,Object message,SocketAddress remoteAddress){

​ channel.getPipline().sendUpstream(new UpstreamMessageEvent(channel,message,remoteAddress));

}

–>DefaultChannelPipelLine

public void sendUpstream(Channel Event e){

​ DefaultChannelHandlerContext head = getActuralUpstreamContext(this.head);

注:{private final class DefaultChannelHandlerContext implements ChannelHandlerContext {

private final ChannelHandler handler;

}}

}

–>Server.java

pipelline.addLast(“decoder”,new StringDecoder());

–>DefaultChannelPipelLine.java

private final class DDefaultChannelHandlerContext implements ChannelHandlerContext{

​ volatile DefaultChannelHandlerContext next;

​ volatile DefaultChannelHandlerContext prev;

​ //咳咳,链表,责任链模式有木有?

}

–>DefaultChannelPipleLine.java

public void sendUpstream(ChannelEvent e){

DefaultChannelHandlerContext head = getActualUpstream(this.head);

}

->private DefaultChannelHandlerContext getActualUpstreamContext(DefaultChannelHanddlerContext ctx){

​ DefaultChannelHandlerContext realCTX = ctx;

​ while(!realCtx.canHandleUpstream()){

​ readlCtx = realCtx.next;

​ if(realCtx == null){

​ return null;

​ }

​ }

​ return realCtx;

}

->public void sendUpstream(ChannelEvent e){

​ DefaultChannelHandlerContext head = getActualUpstreamContext(this.head);

}

–>Server.java

pipeline.addLast(“encoder”,new StringEncoder());

–>public class StringEncoder extends OneToOneEncoder

–>public abstract class OneToOneEncoder implements ChannelDownstreamHandler //下行事件的handler

–>DefaultChannelPipeline.java

public void sendUpstream(ChannelEvent e){

​ DefaultChannelHandlerContext head = getActualUpstreamContext(this.head);

sendUpstream(head,e);

}

->void sendUpstream(DefaultChannelHandlerContext ctx,ChannelEvent e){

​ try{

​ ((ChannelUpstreamHandler)ctx.getHandler()).handleUpstream(ctx,e);

​ }

}

–>public class HelloHandler extends SimpleChannelHandler

–>SimpleChannelUpstreamHandler.java

public void handleUpstream(ChannelHandlerContext ctx,ChannelEvent e) throws Exception{

​ if(e instanceof MessageEvent){

​ messageReceived(ctx,(MessageEvent)e);

​ }

}

->SimpleChannelUpstreamHandler.java

public void messageReceived(ChannelHandlerContext ctx,MessageEvent e) throws Exception{ctx.sendUpstream(e);}

–>public class HelloHandler extends SimpleChannelHandler

@Override

public void messageReceived(ChannelHandlerContext ctx,MessageEvent e) throws Exception

–>SimpleChannelUpstreamHandler.java

public void messageReceived(ChannelHandlerContext ctx,MessagEvent e) throws Exception{

ctx.sendUpstream(e);

}

–>DefaultChannelPipeline.java

public void sendUpstream(ChannelEvent e){ // 真正向下传递消息的方法

DefaultChannelHandlerContextv net = geAtualUpstream(this.next);

if(next != null){

​ DefaultChannelPipelline.this.sendUpstream(next,e);

}

}

handler向下传递对象的方法时sendUpstream(Handler);

粘包和分包是怎么样一个情况?

服务端和客户端没有一个稳定的传输的数据结构,服务端不确定数据的完整性

定义一个稳定的结构

​ length + 数据

例子:

Client

public class Client{        public static void maint(String[] args){  Socket socket = new Socket("127.0.0.1",10010);  String message = "hello";  ByteBuffer buffer = ByteBUffer.allocate(4+bytes.length);  buffer.putInt(bytes.length);  buffer.put(bytes);  byte[] array = buffer.array();  for(int i = 0 ; i< 1000;i++){    socket.getOutputStream().write(array);  }    socket.close();} }

Server

public class MyDecoder extends FrameDecoder{ // 服务端借助FramDecoder类  @Override   protected Objet decode(ChannelHandlerContext context,Channel channel,ChannelBuffer buffer) throws Exception{   if(buffer.readableBytes()>4){     //标记     buffer.markReaderIndex();     //长度     int length = buffer.readInt();     //数据    if(buffer.readableBytes() < length){        buffer.resetReaderIndex();        //缓存当前剩余的buffer数据,等待剩下数据包到来        return null;    }     //读数据     byte[] bytes = new byte[length];     buffer.readBytes(bytes);     //往下传递对象     return new String(bytes);   }   //缓存当前剩余的buffer数据,等待剩下的数据包到来   return null;  }}

1.为什么FramDecoder return对象就是往下传递的对象?(还是调用了sendUpstream())

2.buffer里面数据未被读取完怎么办?(cumulation缓存)

3.为什么return null就可以缓存?(cumulation缓存)

来吧,看源码

–>public abstract class FrameDeder extends SImpleChannelUpstreamHandler implements LisfeCycleAwareChannel

–>public class SimpleChannelUpstreamHandler implements ChannelUpstreamHandler

–>FrameDecoder.class

​ @Override

​ public void messageReceived(ChannelHandlerContext ctx,MessageEvent e) throws Exception{

​ Object m = e.getMessage();

​ if(! (m instantceof ChannelBuffer)){

​ ctx.sendUpstream(e);

​ return;

​ }

ChannleBuffer input = (ChannelBuffer)m;

if(!input.readble()){

return ;

}

if(cumulation == null){ //其实就是一个缓存的buffer对象

注:{protected ChannelBuffer cumulation;}

​ try{

​ callDecode(ctx,e.getChannel(),input,e.getRemoteAddress());

->private void callDecode(ChannelHandlerContext contet ,Channel channel ,ChannelBuffer cumulation ,SocketAdddress remoteAddress) throws Exception{

while(cumulation.readable()){

​ int oldReaderIndex = cumulation.readerIndex();

​ Object frame = decode(context,channel,cumulation);

​ if(frame == null){

​ if(oldReaderIndex == cumulation.readerIndex()){

​ break;

​ }else{

​ continue;

​ }

​ }

}

}

<-

​ }finally{

​ updateCumulation(ctx,input);

->protected ChannelBUffer updateCumulation(ChannelHandlerContext ctx,ChannelBufer input){

​ …

​ cumulation = newCumulation = newCumulationBuffer(ctx,input,readableBytes());

​ cumulation.writeBytes(input);

​ 将input缓存到cumulation中

}

<-

​ }

}else{

​ input = appendToCumulation(input);//将缓存的旧数据拼接到新数据后,合并数据,– 合包

_>protected ChannnelBuffer appendToCumulation(ChannelBuffer input){

​ …

​ this.cumulation = input = ChannelBuffers.wrappedBuffer(cumulation,input);

return input;

}

<-

try{

​ callDecode(ctx,e.getChannel(),input,e.getRemoteAddress());

-> private void callDecode(ChannelHandlerContext context,Channel channel,ChannelBuffer cumulation,SocketAddress remoteAddress) throws Exception{

while(cumulation.readable()){

​ int oldReaderIndex = cumulation.readerIndex();

​ Object frame = decode(context,channel,cumulation);
->public class MyDecoder extends FrmeDecoder{

​ @Override

​ protected Object decode(ChannelHandlerContext ctx,Channel channel ,ChannelBuffer buffer) throws Exception{

​ ….

​ if(buffer.readableBytes()

socket字节流攻击

其实 长度+数据 的结构有问题,比如说长度和数据的长度不一致会导致后面的数据异乱,如果别人知道了你的传输结构,故意发送大长度的假数据的包给我们的服务器,会导致缓存撑爆,这种称为socket攻击,字节流式攻击。

if(buffer.readableBytes()>2048){//防止socket字节流攻击

​ buffer.skipBytes(buffer.readableBytes()); //清除缓存

​ //缓存数据中会对请求数据分包截断的,下次来的数据不是开头,所以这样也是不行的

}

所以我们在传输结构上再加上一个包头,

结构=包头+长度+数据

int beginReader ;//记录包头开始的index

while(true){

​ beginReader = buffer.readerIndex();

​ //标记

​ buffer.markReaderIndex;

​ if(buffer.readInt() == ConstantValeu.FLAG){break;}

​ //包头4个字节不好,可能请求数据在第4个字节开始

​ //所以每次为1个字节

​ //未读到包头略过一个字节

​ buffer.resetReaderIndex();

​ buffer.readByte();

​ //略过一个字节可能导致基本数据长度变小 ,长度又变得不满足

​ if(buffer.readableBytes()

聊天室demo

结合Spring,使业务分离,提高维护性和可拓展性

百度云链接:http://pan.baidu.com/s/1dFowzKl

三个版本,netty3实现,netty4,另加proto buff版

补充:

netty3 netty4或5

ChannelBuffer ByteBuf

ChannelBuffers PooledByteBufAllocator(要注意使用完后释放buffer)或UnpooledByteBufAllocator或Unpooled

FrameDecoder ByteToMessageDecoder

OneToOneEncoder MessageToByteEncoder

messageReceive channelRead0(netty5里面是messageReceive)

业务线程池和消息串行化

将worker线程和service线程分离成两个线程,提高性能

同一个角色的请求做有序处理,避免掉线程锁竞争

原创粉丝点击