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; ...get和set方法,这里省略 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; ....省略get和set方法}
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线程分离成两个线程,提高性能
同一个角色的请求做有序处理,避免掉线程锁竞争
- rpc netty
- Netty-RPC
- Netty RPC框架
- netty rpc 框架
- RPC与netty
- Netty RPC demo 试跑
- Netty实现简单RPC
- RPC学习----------netty实现通讯
- RPC与Netty是什么鬼
- spark2.0 rpc.netty.Dispatcher
- 3. [netty-RPC]--服务端程序设计
- Spark RPC之Netty启动
- Netty 实现简单RPC调用
- 基于netty的rpc框架
- RPC框架与Netty框架
- netty rpc 方面demo 调研
- netty-protobuf-rpc与protobuf-socket-rpc互通
- Triple is an RPC framework(Netty,ZooKeeper)
- python logging模块使用教程
- 2017年6月反思、半年工作反思
- Javasrcipt 基础整理
- gulp-简介
- Linux系统更新
- Netty-RPC
- 深入理解Java虚拟机(6)--String.intern()方法
- 常用数据库的连接字符串
- Java实现邮件的自动发送
- FLV文件格式——图文并茂
- H桥驱动小功率直流电机正反转
- PHP禁用函数
- Kotlin/Native v0.3 发布,将 Kotlin 编译成机器码
- 面试——单例模式