Netty学习笔记

一、Java传统IO与NIO

1. Java 传统 socket IO

对于传统的Java socket编程,即使使用了线程池,一个客户端请求对应一个线程处理。
Java socket服务端代码如下:

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;

public class BIOServer {
	/**
	 * 传统socket服务端
	 */
	@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();
			}
		}
	}
}

当上面的程序启动时,有2个地方会阻塞:
1)final Socket socket = server.accept(); // 此处等待客户端建立连接。
2)int read = inputStream.read(bytes); // 与客户端建立连接完成后,等待接收客户端请求数据。

测试上述代码:
1)当服务端程序启动是,会阻塞在第一点accept上。
2)启动个控制台,并执行:

telnet localhost 10101

此时会建立连接,执行过accept, 并阻塞在第二点read上。
然后输入数据

111

此时会通过第二点read, 服务端收到客户端的请求数据111。

3)假设在上述控制台建立连接后,未输入111请求前,启动另一个控制台,然后执行telnet localhost 10101, 服务端仍可收到请求。不过,服务端需要另外启动个线程来处理这个新控制台的请求。

综上,上述Java传统socket的编程模型,一个线程只能处理一个请求,如果把服务端比喻成餐厅,处理请求的线程比喻成服务员,客户端的请求比喻成吃饭的客人,那么饭店中每一个客人,都需要一个服务员来接待,这样当客人很多时,需要很多服务员,这样饭店会赔钱。

所以,java传统的socket,是不能用来做长连接服务器的,但可以用来做短连接服务器,如旧版本的Tomcat,对应的场景是一问一答快速响应的场景。

二、Java NIO

1. Java NIO的demo:

package netty_test.bio;

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();
	}
}

当客户端建立连接前或者建立完连接发消息前,会阻塞在

selector.select();

当有连接请求时或建立完连接发消息时,会执行过这行,
如果是建立连接的请求,则把channel注册到selector:

channel.register(this.selector, SelectionKey.OP_READ);

如果是发消息请求,则读取消息内容:

int read = channel.read(buffer);

2. Java NIO的API

ServerSocketChannel :对应传统Socket编程中的Server Socket
SocketChannel :对应传统Socket编程中的Socket
Selector :Java NIO的核心,用于监听ServerSocketChannel和SocketChannel
SelectionKey : 每个客户端请求的唯一标识。

NIO可实现单线程为多个客户端服务。

3. 传统socket IO图与NIO图

传统socket IO:
在这里插入图片描述
对于传统socket IO, 大门是ServerSocket, 一个线程(服务员)对应一个请求(客人)。

NIO:
在这里插入图片描述
NIO模型的大门是ServerSocketChannel, 其服务员是线程+selector, 且不只服务于一个请求,且在服务于当前客人的同时,还在监听新来的客人。

注意:selector.select(); 这个方法底层是用C语言实现的,是基于操作系统的。
NIO是一个基于select的多路复用的IO(具体参见Linux操作系统)

二、Netty入门

netty 使用的版本:netty 3

1. netty的应用领域

领域1. 分布式进程间的通信
例如:Hadoop、Dubbo、akka等具有分布式功能的框架,底层RPC通信都是基于netty实现的,这些框架使用的netty版本通常还在3.x
领域2. 游戏服务器开发
最新的游戏服务器有部分公司可能会采用netty 4.x或netty 5.x

2. netty服务端hello world例子

1)Server.java

import 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!!!");
	}
}

2)HelloHandler.java

import org.jboss.netty.buffer.ChannelBuffer;
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);
	}
}

这样在启动Server.java后,
1)用telnet localhost 10101时,会触发HelloHandler.java的channelConnected方法;
2)当客户端发送消息后,会触发messageReceived方法;
3)当客户端断开连接时,会先后触发channelDisconnected方法和channelClosed方法。

boss和worker
boss和worker都分配一个线程,对应一个selector。
boss的selector负责监听端口;
worker的selector负责channel的读写任务。

3. netty客户端HelloWorld例子

Client.java:

package com.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();
		
		//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());
		}
	}

}

HiHandler.java:

package com.client;

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);
	}
}

总结:可以看到,客户端和服务端的代码非常类似:
1)主要处理消息的类是Handler
2)消息通过Channel传递
3)channelDisconnected与channelClosed的区别:
如果服务端没有启动,然后启动客户端,这时只有channelClosed,而没有channelDisconnected。

三、netty工作原理源码分析

1. 如何提高NIO的工作效率(即netty怎样优化的NIO)

传统NIO相当于一个餐厅只有一个服务员,当餐厅的规模越来越大,一个服务员不够,需要多招服务员(selector),这样就相当于从单线程(selector)改成多线程(多selector)。

1)思考问题:

1)一个NIO系统是不是只能有一个selector呢?
答:不是。一个NIO系统可以有多个selector。
2)selector是不是只能注册一个ServerSocketChannel?
答:不是。可以注册多个。

2)传统NIO的线程模型:

在这里插入图片描述
即一个餐厅只有一个服务员。

3)netty工作的线程模型:

在这里插入图片描述
每个服务员(selector)相当于一个线程。

2. netty核心源码解析

1)boss和worker的区别?

boss负责监听端口
worker为客户端服务,让客户端执行读写任务。

2)netty核心代码解析

  1. boss和worker都是由"线程+selector"的方式来处理用户连接或发消息,所以他们都可以继承自相同的父类AbstractNioSelector(继承自Runnable, 本身是一个线程)
    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 {
				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;

}

该AbstractNioSelector类包括的成员变量:

  • 线程池:Executor executor
  • boss和worker都有selector: Selector selector
  • 标记selector是否被激活:AtomicBoolean wakenUp
  • 任务队列:Queue taskQueue
  • 线程名:String threadName
  • 线程管理对象:NioSelectorRunnablePool selectorRunnablePool
    包含的方法有:
  • 获取selector并启动线程:openSelector()
  • 线程的run方法(分别有boss和worker自己实现)
  • 注册一个任务并激活selector:registerTask(Runnable task)
  • 执行队列里的任务:processTaskQueue()
  • 获取线程管理对象:NioSelectorRunnablePool getSelectorRunnablePool()
  • select抽象方法:select(Selector selector)
  • selector的业务处理:process(Selector selector)
  1. boss实现类(继承自AbstractNioSelector)
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();
	}

}

该类实现了其父类中的抽象方法:
其中,process方法获取到用户连接请求后,获取一个worker,并把该客户端连接的channel注册到worker。

  1. worker实现类(继承自AbstractNioSelector)
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);
	}
	
}

其业务处理process方法,主要代码都是经典的java NIO代码,用于与客户端读写。

  1. selector线程管理者类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)];
	}

}

该类用于管理boss和worker的线程池

  1. boss接口
import java.nio.channels.ServerSocketChannel;
/**
 * boss接口
 */
public interface Boss {
	
	/**
	 * 加入一个新的ServerSocket
	 * @param serverChannel
	 */
	public void registerAcceptChannelTask(ServerSocketChannel serverChannel);
}
  1. worker接口
import java.nio.channels.SocketChannel;
/**
 * worker接口
 */
public interface Worker {
	
	/**
	 * 加入一个新的客户端会话
	 * @param channel
	 */
	public void registerNewChannelTask(SocketChannel channel);

}
  1. 启动类
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");
	}

}

四、netty心跳

1. IdleStateHandler

IdleStateHandler用于检测Channel会话是否过期。
1)HelloHandler.java

package com.heart;

import org.jboss.netty.channel.ChannelEvent;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.handler.timeout.IdleState;
import org.jboss.netty.handler.timeout.IdleStateEvent;

public class HelloHandler extends SimpleChannelHandler {

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
		System.out.println(e.getMessage());
	}

	@Override
	public void handleUpstream(final ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
		if (e instanceof IdleStateEvent) {
			if(((IdleStateEvent)e).getState() == IdleState.ALL_IDLE){
				System.out.println("提玩家下线");
				//关闭会话,踢玩家下线
				ChannelFuture write = ctx.getChannel().write("time out, you will close");
				write.addListener(new ChannelFutureListener() {
					
					@Override
					public void operationComplete(ChannelFuture future) throws Exception {
						 ctx.getChannel().close();
					}
				});
			}
		} else {
			super.handleUpstream(ctx, e);
		}
	}
}

在做channel close前,先给客户端发送一条消息,告诉客户端,你超时了,服务端踢你下线了。

2)Server.java:用于加载IdleStateHandler, 启动HelloHandler.

package com.heart;

import 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;
import org.jboss.netty.handler.timeout.IdleStateHandler;
import org.jboss.netty.util.HashedWheelTimer;

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));
		
		final HashedWheelTimer hashedWheelTimer = new HashedWheelTimer();
		//设置管道的工厂
		bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
			
			@Override
			public ChannelPipeline getPipeline() throws Exception {

				ChannelPipeline pipeline = Channels.pipeline();
				pipeline.addLast("idle", new IdleStateHandler(hashedWheelTimer, 5, 5, 10));
				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!!!");
		
	}

}

对于代码:

IdleStateHandler(hashedWheelTimer, 5, 5, 10))

表示:
hashedWheelTimer:定时器
readerIdleTimeSeconds:读超时时间,单位秒
writerIdleTimeSeconds:写超时时间,单位秒
allIdleTimeSeconds:无论读还是写的超时时间,单位秒。

五、自定义序列化协议

如果要对int进行序列化与反序列化,有下面的方法。

1. 通过传统的java.io包

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;

public class Test1 {

	public static void main(String[] args) throws IOException {
		int id = 101;
		int age = 21;
		
		ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
		arrayOutputStream.write(int2bytes(id));
		arrayOutputStream.write(int2bytes(age));
		
		byte[] byteArray = arrayOutputStream.toByteArray();
		
		System.out.println(Arrays.toString(byteArray));
		
		//==============================================================
		ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(byteArray);
		byte[] idBytes = new byte[4];
		arrayInputStream.read(idBytes);
		System.out.println("id:" + bytes2int(idBytes));
		
		byte[] ageBytes = new byte[4];
		arrayInputStream.read(ageBytes);
		System.out.println("age:" + bytes2int(ageBytes));
		
	}
	
	
	/**
	 * 大端字节序列(先写高位,再写低位)
	 * 百度下 大小端字节序列
	 * @param i
	 * @return
	 */
	public static byte[] int2bytes(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;
	}
	
	
	/**
	 * 大端
	 * @param bytes
	 * @return
	 */
	public static int bytes2int(byte[] bytes){
		return (bytes[0] << 3*8) |
				(bytes[1] << 2*8) |
				(bytes[2] << 1*8) |
				(bytes[3] << 0*8);
	}
}

缺点:自己实现序列化需要用位运算,比较麻烦,如果对于long等其他类型,都需要自己手动写代码实现。

2. 通过Java NIO的API实现

import java.nio.ByteBuffer;
import java.util.Arrays;

public class Test2 {

	public static void main(String[] args) {
		int id = 101;
		int age = 21;
		
		ByteBuffer buffer = ByteBuffer.allocate(8);
		buffer.putInt(id);
		buffer.putInt(age);
		byte[] array = buffer.array();
		System.out.println(Arrays.toString(buffer.array()));
		
		//====================================================
		
		ByteBuffer buffer2 = ByteBuffer.wrap(array);
		System.out.println("id:"+buffer2.getInt());
		System.out.println("age:"+buffer2.getInt());

	}

}

优点:对于不同的类型,不用自己写序列化与反序列化代码
缺点:必须手动自动固定长度ByteBuffer.allocate(8),若超出长度则报错。

3. 通过netty的ChannelBuffer来实现

import java.util.Arrays;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;

public class Test3 {

	public static void main(String[] args) {

		ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();
		buffer.writeInt(101);
		buffer.writeDouble(80.1);

		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());	
	}
}

好处:可动态分配空间,且不用自己写序列化反序列化。
所以, ChannelBuffer结合了ByteArrayInputStream和ByteBuffer的优点。
缺点:不能序列化String对象

4. 自定义序列化与反序列化

1)序列化父类 Serializer.java:

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;
	}
}

2)自定义序列化的使用

要实现序列化接口:

import java.util.ArrayList;
import java.util.List;

import com.cn.core.Serializer;

public class Player extends Serializer{
	
	private long playerId;
	
	private int age;
	
	private List<Integer> skills = new ArrayList<>();
	
	private Resource resource = new Resource();
	
	public Resource getResource() {
		return resource;
	}

	public void setResource(Resource resource) {
		this.resource = resource;
	}

	public long getPlayerId() {
		return playerId;
	}

	public void setPlayerId(long playerId) {
		this.playerId = playerId;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public List<Integer> getSkills() {
		return skills;
	}

	public void setSkills(List<Integer> skills) {
		this.skills = skills;
	}

	@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(playerId);
		writeInt(age);
		writeList(skills);
		writeObject(resource);
	}
}

业务类:
Resource.java

package com.cn;

import com.cn.core.Serializer;

public class Resource extends Serializer {
	
	private int gold;
	

	public int getGold() {
		return gold;
	}

	public void setGold(int gold) {
		this.gold = gold;
	}

	@Override
	protected void read() {
		this.gold = readInt();
	}

	@Override
	protected void write() {
		writeInt(gold);
	}
}

5. Protobuf的序列化与反序列化

相比netty的序列化与反序列化,protocol buffer的序列化与反序列化做的更好。
对比:

1)对于Player的序列化:

package com.serial;
import java.io.IOException;
import java.util.Arrays;
/**
 * 自定义序列化协议
 */
public class Serial2Bytes {

	public static void main(String[] args) throws Exception {
		byte[] bytes = toBytes();
		toPlayer(bytes);
	}
	
	
	/**
	 * 序列化
	 * @throws IOException 
	 */
	public static byte[] toBytes() throws IOException{
		
		Player player = new Player(320, 20, "peter");
		player.getSkills().add(1001);
		
		//获取 字节数组
		byte[] byteArray = player.getBytes();
		System.out.println(Arrays.toString(byteArray));
		return byteArray;
	}
	
	
	/**
	 * 反序列化
	 * @param bs
	 * @throws Exception 
	 */
	public static void toPlayer(byte[] bs) throws Exception{
		
		Player player = new Player();
		player.readFromBytes(bs);
		
		//打印
		 System.out.println("playerId:" + player.getPlayerId());
		 System.out.println("age:" + player.getAge());
		 System.out.println("name:" + player.getName());
		 System.out.println("skills:" + (Arrays.toString(player.getSkills().toArray())));
	}

}

结果为:
[0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 20, 0, 5, 112, 101, 116, 101, 114, 0, 1, 0, 0, 3, -23]

对于protobuf, 对player类的序列化:

package com.proto;

import java.util.Arrays;
import com.proto.PlayerModule.PBPlayer;
import com.proto.PlayerModule.PBPlayer.Builder;
/**
 * protobuf学习
 */
public class PB2Bytes {

	public static void main(String[] args) throws Exception {
		byte[] bytes = toBytes();
		toPlayer(bytes);

	}

	/**
	 * 序列化
	 */
	public static byte[] toBytes(){
		//获取一个PBPlayer的构造器
		Builder builder = PlayerModule.PBPlayer.newBuilder();
		//设置数据
		builder.setPlayerId(101).setAge(20).setName("peter").addSkills(1001);
		//构造出对象
		PBPlayer player = builder.build();
		//序列化成字节数组
		byte[] byteArray = player.toByteArray();
		
		System.out.println(Arrays.toString(byteArray));
		
		return byteArray;
	}
	
	/**
	 * 反序列化
	 * @param bs
	 * @throws Exception 
	 */
	public static void toPlayer(byte[] bs) throws Exception{
		
		 PBPlayer player = PlayerModule.PBPlayer.parseFrom(bs);
		 
		 System.out.println("playerId:" + player.getPlayerId());
		 System.out.println("age:" + player.getAge());
		 System.out.println("name:" + player.getName());
		 System.out.println("skills:" + (Arrays.toString(player.getSkillsList().toArray())));
	}
}

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

总结:可以看出, protobuf的序列化后更小。

2)Protobuf的实现原理:位运算

看PB自动生成类PlayerModule.java中的writeTo方法:

public void writeTo(com.google.protobuf.CodedOutputStream output)
                        throws java.io.IOException {
      getSerializedSize();
      if (((bitField0_ & 0x00000001) == 0x00000001)) {
        output.writeInt64(1, playerId_);
      }
      if (((bitField0_ & 0x00000002) == 0x00000002)) {
        output.writeInt32(2, age_);
      }
      if (((bitField0_ & 0x00000004) == 0x00000004)) {
        output.writeBytes(3, getNameBytes());
      }
      for (int i = 0; i < skills_.size(); i++) {
        output.writeInt32(4, skills_.get(i));
      }
      getUnknownFields().writeTo(output);
    }

PB的配置文件:

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;
}

PB的源码:

public void writeRawVarint32(int value) throws IOException {
    while (true) {
      if ((value & ~0x7F) == 0) {
        writeRawByte(value);
        return;
      } else {
        writeRawByte((value & 0x7F) | 0x80);
        value >>>= 7;
      }
    }
  }

(value & ~0x7F) == 0解析:
其中,0x7F转成自己为 0111 1111, 对应7F, 取反后为:
1000 0000
如果int的值与1000 0000与运算的话,其1~7位全被置为0,之后判断其是否==0

假如 value > 0111 1111
value & 1000 0000

这句话实际在判断 value < 0x7F, 这样意味着他不需要占一个字节。
else表示 value > 0x7F, 则:
(value & 0x7F) | 0x80
其中,value & 0x7F表示获取到1~7位数据,再与0x80做或运算。
0x80表示1000 0000
做或运算后,得到
1XXX XXXX
所以,获取前7位数据,第8位(最左一位表示是否还有数据,1表示还有数据)。

所以,PB序列化后,不会像我们序列化那样正好占4个字节,而是会根据int数据的大小占用1~5个字节。

由于我们使用的int通常占用1~2个字节就够了,这样PB序列化就节省了空间。

同理,Long正常占8个字节,但在PB中占1~9个字节。但通常只需要占用2-3个字节。

所以,自己要序列化做优化节省空间时,不能使用用netty的writeToLong等方法,而是自己写位运算来做。

六、自定义数据包协议

1. 什么是粘包、分包

假设客户端向服务端发送了2条消息,分别为:
give me a coffee
give me a tea

粘包:
但实际上可能收到的消息为:
give me a coffeegive me a tea
即发生了粘包现象。

分包:
也有可能收到的消息为:
give me
a coffeegive me a tea
即收到了2部分消息,这叫做分包现象。

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

方法1. 采用分隔符方式:
give me a tea|give me a tea|
这样即便发生了粘包和分包的现象,仍然可以解析出原始的消息。
缺点:引入分隔符后,效率稍差。

方法2. 长度+数据
16give me a coffee
13give me a tea
这样即便发生了粘包和分包的现象,仍然可以解析出原始的消息。

2. 数据包

1)数据包格式定义

包头模块号命令号数据长度数据
4字节(int)2字节(short)2字节(short)4字节(int)序列化后的消息内容

2) 自定义数据包及Encoder & Decoder

  1. 数据包的POJO类:
package com.cn.model;
/**
 * 请求对象
 */
public class Request {
	
	/**
	 * 请求模块
	 */
	private short module;
	
	/**
	 * 命令号
	 */
	private short cmd;
	
	/**
	 * 数据部分
	 */
	private byte[] data;

	public short getModule() {
		return module;
	}

	public void setModule(short module) {
		this.module = module;
	}

	public short getCmd() {
		return cmd;
	}

	public void setCmd(short cmd) {
		this.cmd = cmd;
	}

	public byte[] getData() {
		return data;
	}

	public void setData(byte[] data) {
		this.data = data;
	}
	
	
	public int getDataLength(){
		if(data == null){
			return 0;
		}
		return data.length;
	}
}
  1. 数据包的RequestEncoder:把Request对象转成了ChannelBuffer对象,即把对象转成了二进制。
package com.cn.codc;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;

import com.cn.constant.ConstantValue;
import com.cn.model.Request;

/**
 * 请求编码器
 * <pre>
 * 数据包格式
 * +——----——+——-----——+——----——+——----——+——-----——+
 * | 包头          | 模块号        | 命令号      |  长度        |   数据       |
 * +——----——+——-----——+——----——+——----——+——-----——+
 * </pre>
 * 包头4字节
 * 模块号2字节short
 * 命令号2字节short
 * 长度4字节(描述数据部分字节长度)
 */
public class RequestEncoder extends OneToOneEncoder{

	@Override
	protected Object encode(ChannelHandlerContext context, Channel channel, Object rs) throws Exception {
		Request request = (Request)(rs);
		
		ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();
		//包头
		buffer.writeInt(ConstantValue.FLAG);
		//module
		buffer.writeShort(request.getModule());
		//cmd
		buffer.writeShort(request.getCmd());
		//长度
		buffer.writeInt(request.getDataLength());
		//data
		if(request.getData() != null){
			buffer.writeBytes(request.getData());
		}
		
		return buffer;
	}
}
  1. 数据包的RequestDecoder:
package com.cn.codc;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;

import com.cn.constant.ConstantValue;
import com.cn.model.Request;

/**
 * 请求解码器
 * <pre>
 * 数据包格式
 * +——----——+——-----——+——----——+——----——+——-----——+
 * | 包头          | 模块号        | 命令号      |  长度        |   数据       |
 * +——----——+——-----——+——----——+——----——+——-----——+
 * </pre>
 * 包头4字节
 * 模块号2字节short
 * 命令号2字节short
 * 长度4字节(描述数据部分字节长度)
 */
public class RequestDecoder extends FrameDecoder{
	
	/**
	 * 数据包基本长度
	 */
	public static int BASE_LENTH = 4 + 2 + 2 + 4;

	@Override
	protected Object decode(ChannelHandlerContext arg0, Channel arg1, ChannelBuffer buffer) throws Exception {
		
		//可读长度必须大于基本长度
		if(buffer.readableBytes() >= BASE_LENTH){
			//防止socket字节流攻击
			if(buffer.readableBytes() > 2048){
				buffer.skipBytes(buffer.readableBytes());
			}
			
			//记录包头开始的index
			int beginReader;
			
			while(true){
				beginReader = buffer.readerIndex();
				buffer.markReaderIndex();
				if(buffer.readInt() == ConstantValue.FLAG){
					break;
				}
				
				//未读到包头,略过一个字节
				buffer.resetReaderIndex();
				buffer.readByte();
				
				//长度又变得不满足
				if(buffer.readableBytes() < BASE_LENTH){
					return null;
				}
			}
			
			//模块号
			short module = buffer.readShort();
			//命令号
			short cmd = buffer.readShort();
			//长度
			int length = buffer.readInt();
			
			//判断请求数据包数据是否到齐
			if(buffer.readableBytes() < length){
				//还原读指针
				buffer.readerIndex(beginReader);
				return null;
			}
			
			//读取data数据
			byte[] data = new byte[length];
			buffer.readBytes(data);
			
			Request request = new Request();
			request.setModule(module);
			request.setCmd(cmd);
			request.setData(data);
			
			//继续往下传递 
			return request;
			
		}
		//数据包不完整,需要等待后面的包来
		return null;
	}
}

该类继承自FrameDecoder,FrameDecoder可帮我们解决粘包、分包问题。
decode方法return什么对象,下面的handler就会收到什么对象。

  1. 数据包的Response对象
package com.cn.model;
/**
 * 返回对象
 */
public class Response {
	/**
	 * 请求模块
	 */
	private short module;
	
	/**
	 * 命令号
	 */
	private short cmd;
	
	/**
	 * 状态码
	 */
	private int stateCode;
	
	/**
	 * 数据部分
	 */
	private byte[] data;

	public short getModule() {
		return module;
	}

	public void setModule(short module) {
		this.module = module;
	}

	public short getCmd() {
		return cmd;
	}

	public void setCmd(short cmd) {
		this.cmd = cmd;
	}

	public int getStateCode() {
		return stateCode;
	}

	public void setStateCode(int stateCode) {
		this.stateCode = stateCode;
	}

	public byte[] getData() {
		return data;
	}

	public void setData(byte[] data) {
		this.data = data;
	}
	
	public int getDataLength(){
		if(data == null){
			return 0;
		}
		return data.length;
	}
}

  1. ResponseEncoder
package com.cn.codc;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
import com.cn.constant.ConstantValue;
import com.cn.model.Response;

/**
 * 请求编码器
 * <pre>
 * 数据包格式
 * +——----——+——-----——+——----——+——----——+——-----——+——-----——+
 * | 包头          | 模块号        | 命令号       |  状态码    |  长度          |   数据       |
 * +——----——+——-----——+——----——+——----——+——-----——+——-----——+
 * </pre>
 * 包头4字节
 * 模块号2字节short
 * 命令号2字节short
 * 长度4字节(描述数据部分字节长度)
 */
public class ResponseEncoder extends OneToOneEncoder{

	@Override
	protected Object encode(ChannelHandlerContext context, Channel channel, Object rs) throws Exception {
		Response response = (Response)(rs);
		
		ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();
		//包头
		buffer.writeInt(ConstantValue.FLAG);
		//module
		buffer.writeShort(response.getModule());
		//cmd
		buffer.writeShort(response.getCmd());
		//状态码
		buffer.writeInt(response.getStateCode());
		//长度
		buffer.writeInt(response.getDataLength());
		//data
		if(response.getData() != null){
			buffer.writeBytes(response.getData());
		}
	
		return buffer;
	}
}
  1. ResponseDecoder
package com.cn.codc;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;
import com.cn.constant.ConstantValue;
import com.cn.model.Response;

/**
 * response解码器
 * <pre>
 * 数据包格式
 * +——----——+——-----——+——----——+——----——+——-----——+——-----——+
 * | 包头          | 模块号        | 命令号       |  状态码    |  长度          |   数据       |
 * +——----——+——-----——+——----——+——----——+——-----——+——-----——+
 * </pre>
 * 包头4字节
 * 模块号2字节short
 * 命令号2字节short
 * 长度4字节(描述数据部分字节长度)
 */
public class ResponseDecoder extends FrameDecoder{
	
	/**
	 * 数据包基本长度
	 */
	public static int BASE_LENTH = 4 + 2 + 2 + 4;

	@Override
	protected Object decode(ChannelHandlerContext arg0, Channel arg1, ChannelBuffer buffer) throws Exception {
		
		//可读长度必须大于基本长度
		if(buffer.readableBytes() >= BASE_LENTH){
			
			//记录包头开始的index
			int beginReader = buffer.readerIndex();
			
			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.readerIndex(beginReader);
				return null;
			}
			
			byte[] data = new byte[length];
			buffer.readBytes(data);
			
			Response response = new Response();
			response.setModule(module);
			response.setCmd(cmd);
			response.setStateCode(stateCode);
			response.setData(data);
			
			//继续往下传递 
			return response;
			
		}
		//数据包不完整,需要等待后面的包来
		return null;
	}
}

3. 使用上面写好的编码器Encoder与解码器Decoder

  1. 客户端代码
package com.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 com.cn.codc.RequestEncoder;
import com.cn.codc.ResponseDecoder;
import com.cn.model.Request;
import com.cn.module.fuben.request.FightRequest;
/**
 * netty客户端入门
 */
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);
		}
	}

}

HiHandler.java:

package com.client;

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;

import com.cn.model.Response;
import com.cn.model.StateCode;
import com.cn.module.fuben.request.FightRequest;
import com.cn.module.fuben.response.FightResponse;
/**
 * 消息接受处理类
 */
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);
	}
}

  1. 业务FightRequest对象, 继承Serializer对象。
package com.cn.module.fuben.request;

import com.cn.serial.Serializer;

public class FightRequest extends Serializer{
	
	/**
	 * 副本id
	 */
	private int fubenId;
	
	/**
	 * 次数
	 */
	private int count;

	public int getFubenId() {
		return fubenId;
	}

	public void setFubenId(int fubenId) {
		this.fubenId = fubenId;
	}

	public int getCount() {
		return count;
	}

	public void setCount(int count) {
		this.count = count;
	}

	@Override
	protected void read() {
		this.fubenId = readInt();
		this.count = readInt();
	}

	@Override
	protected void write() {
		writeInt(fubenId);
		writeInt(count);
	}
}
  1. 业务FightResponse对象
package com.cn.module.fuben.response;
import com.cn.serial.Serializer;
public class FightResponse extends Serializer{

	/**
	 * 获取金币
	 */
	private int gold;

	public int getGold() {
		return gold;
	}

	public void setGold(int gold) {
		this.gold = gold;
	}

	@Override
	protected void read() {
		this.gold = readInt();
	}

	@Override
	protected void write() {
		writeInt(gold);
	}
}
  1. 服务端代码
package com.server;

import 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;

import com.cn.codc.RequestDecoder;
import com.cn.codc.ResponseEncoder;
/**
 * 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 RequestDecoder());
				pipeline.addLast("encoder", new ResponseEncoder());
				pipeline.addLast("helloHandler", new HelloHandler());
				return pipeline;
			}
		});
		
		bootstrap.bind(new InetSocketAddress(10101));
		System.out.println("start!!!");
	}
}

HelloHandler.java:

package com.server;

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;

import com.cn.model.Request;
import com.cn.model.Response;
import com.cn.model.StateCode;
import com.cn.module.fuben.request.FightRequest;
import com.cn.module.fuben.response.FightResponse;
/**
 * 消息接受处理类
 */
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);
	}
}

七、粘包、分包分析

1. pipeline分析

1)问题:消息如何在管道中流转?即当前一个handler如何往下一个handler传递对象的?

每个Handler会加入到pipeline(管道)中,一个pipeline中会有多个handler。那么handler之间的对象是如何流转的呢?
当用户向netty发消息时,netty会把消息封装成channel event(事件对象),然后把事件对象传递到pipeline里。pipeline中的每个handler是双向链表的节点。
handler往下传递对象的方法是netty的sendUpstream(event)

2)pipeline例子

server.java:

package com.server;

import 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;

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("handler1", new MyHandler1());
				pipeline.addLast("handler2", new MyHandler2());
				return pipeline;
			}
		});
		
		bootstrap.bind(new InetSocketAddress(10101));
		
		System.out.println("start!!!");
	}

}

MyHandler1.java:

package com.server;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.UpstreamMessageEvent;

public class MyHandler1 extends SimpleChannelHandler {

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

		ChannelBuffer buffer = (ChannelBuffer)e.getMessage();
		
		byte[] array = buffer.array();
		String message = new String(array);
		System.out.println("handler1:" + message);
		
		//传递
		ctx.sendUpstream(new UpstreamMessageEvent(ctx.getChannel(), "abc", e.getRemoteAddress()));
		ctx.sendUpstream(new UpstreamMessageEvent(ctx.getChannel(), "efg", e.getRemoteAddress()));
	}
}

MyHandler2.java:

package com.server;

import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

public class MyHandler2 extends SimpleChannelHandler {

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

		String message = (String)e.getMessage();
		
		System.out.println("handler2:" + message);
	}
}

2. 粘包、分包现象的处理

要解决分包和粘包问题,要在客户端和服务端之间定义一个稳定的数据结构:length+data

1) 粘包与分包的问题复现:

ClientTest.java: 循环发送1000次hello字符串

package com.server;

import java.io.IOException;
import java.net.Socket;

public class ClientTest {

	public static void main(String[] args) throws IOException {
		Socket socket = new Socket("127.0.0.1", 10101);
		
		for (int i = 0; i < 1000; i++) {
			socket.getOutputStream().write("hello".getBytes());
		}
		
		socket.close();
	}
}

ServerTest.java:

package com.server;

import 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;

public class ServerTest {

	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("handler1", new HelloHandler());
				return pipeline;
			}
		});

		bootstrap.bind(new InetSocketAddress(10101));

		System.out.println("start!!!");
	}

}

HelloHandler.java:

package com.server;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

public class HelloHandler extends SimpleChannelHandler {
	
	private int count = 1;

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
//		System.out.println(e.getMessage() + "  " +count);
//
//		count++;
		ChannelBuffer message = (ChannelBuffer)e.getMessage();
		byte[] array = message.array();
		System.out.println(new String(array));
	}
}

服务端收到客户端发来的消息:
hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohell
ohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohe
llohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello
可以看到,发送了分包与粘包的现象。

2)解决分包与粘包

client.java:

package com.server;

import java.net.Socket;
import java.nio.ByteBuffer;

public class Client {

	public static void main(String[] args) throws Exception {
		Socket socket = new Socket("127.0.0.1", 10101);
		
		String message = "hello";
		
		byte[] bytes = message.getBytes();
		
		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.java:

package com.server;

import 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;

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 MyDecoder());
				pipeline.addLast("handler1", new HelloHandler());
				return pipeline;
			}
		});
		
		bootstrap.bind(new InetSocketAddress(10101));
		
		System.out.println("start!!!");
	}

}

MyDecoder.java:

package com.server;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;

public class MyDecoder extends FrameDecoder {

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

		if (buffer.readableBytes() > 4) {
//			System.out.println("readable:" + buffer.readableBytes());
			
			// 标记
			buffer.markReaderIndex();
			// 长度
//			System.out.println("readable2:" + buffer.readableBytes());
			int length = buffer.readInt();
//			System.out.println("length:"+length);

			if (buffer.readableBytes() < length) {
//				System.out.println("缓存当前剩余的buffer数据,等待剩下数据包到来,length:" + length + ", readable:" + buffer.readableBytes());
				buffer.resetReaderIndex();
//				System.out.println("buffer.readableBytes():" + buffer.readableBytes());
				// 缓存当前剩余的buffer数据,等待剩下数据包到来
				return null;
			}

			// 读数据
			byte[] bytes = new byte[length];
			buffer.readBytes(bytes);
			// 往下传递对象
			return new String(bytes);
		}

		// 缓存当前剩余的buffer数据,等待剩下数据包到来
		return null;
	}

}

HelloHandler.java:

package com.server;

import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

public class HelloHandler extends SimpleChannelHandler {
	
	private int count = 1;

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
		System.out.println(e.getMessage() + "  " +count);
		count++;
	}
}

问题:

  1. 为什么FrameDecoder decode方法return的对象就是往下传递的对象?
    答:因为调用了sendUpstream(event)方法
  2. buffer里面数据未读取完怎么办?
    答:由cumulation缓存
  3. 为什么return null就可以缓存buffer?
    答:由FrameDecoder的ReceiveMessage方法调用了updateCumulation(ctx, input);来缓存buffer中的数据,由cumulation缓存。

3. FrameDecoder源码分析

netty FrameDecoder方法:

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

        Object m = e.getMessage();
        if (!(m instanceof ChannelBuffer)) {
            ctx.sendUpstream(e);
            return;
        }

        ChannelBuffer input = (ChannelBuffer) m;
        if (!input.readable()) {
            return;
        }

        if (cumulation == null) {
            try {
                // the cumulation buffer is not created yet so just pass the input to callDecode(...) method
                callDecode(ctx, e.getChannel(), input, e.getRemoteAddress()); // 调用MyDecode的decode方法
            } finally {
                updateCumulation(ctx, input); //保存数据到缓存
            }
        } else {
            input = appendToCumulation(input); // 把之前剩余的数据拼到新数据里面
            try {
                callDecode(ctx, e.getChannel(), input, e.getRemoteAddress());
            } finally {
                updateCumulation(ctx, input);
            }
        }
    }

里面的cumulation实际就是换成的ChannelBuffer对象。如果return null, 则会callDecode调用decode(context, channel, cumulation),实际上会走到MyDecoder中的decode方法。

4. Socket字节流攻击

思考问题:
长度+数据的方式的弊端:如果客户端把长度设置为Integer.max时,会把服务端内存爆掉。

字节流攻击:把长度定义的很大,这种数据包,通常被称为socket攻击。

解决方案:如果长度>2048, 则清除buffer。
但一旦清除了,我们将不知道数据包的开始位置,所以我们要用:
包头+长度+数据
所以“包头+长度”是必须的字段。
这样我们即使清除了数据包长度,仍然能通过包头找到数据包的起始位置。

防Socket攻击又可安全解码的Decoder:

package com.cn.codc;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;

import com.cn.constant.ConstantValue;
import com.cn.model.Request;

/**
 * 请求解码器
 * <pre>
 * 数据包格式
 * +——----——+——-----——+——----——+——----——+——-----——+
 * | 包头          | 模块号        | 命令号      |  长度        |   数据       |
 * +——----——+——-----——+——----——+——----——+——-----——+
 * </pre>
 * 包头4字节
 * 模块号2字节short
 * 命令号2字节short
 * 长度4字节(描述数据部分字节长度)
 * 
 * @author -琴兽-
 *
 */
public class RequestDecoder extends FrameDecoder{
	
	/**
	 * 数据包基本长度
	 */
	public static int BASE_LENTH = 4 + 2 + 2 + 4;

	@Override
	protected Object decode(ChannelHandlerContext arg0, Channel arg1, ChannelBuffer buffer) throws Exception {
		
		//可读长度必须大于基本长度
		if(buffer.readableBytes() >= BASE_LENTH){
			//防止socket字节流攻击
			if(buffer.readableBytes() > 2048){
				buffer.skipBytes(buffer.readableBytes());
			}
			
			//记录包头开始的index
			int beginReader;
			
			while(true){
				beginReader = buffer.readerIndex();
				buffer.markReaderIndex();
				if(buffer.readInt() == ConstantValue.FLAG){
					break;
				}
				
				//未读到包头,略过一个字节
				buffer.resetReaderIndex();
				buffer.readByte();
				
				//长度又变得不满足
				if(buffer.readableBytes() < BASE_LENTH){
					return null;
				}
			}
			
			//模块号
			short module = buffer.readShort();
			//命令号
			short cmd = buffer.readShort();
			//长度
			int length = buffer.readInt();
			
			//判断请求数据包数据是否到齐
			if(buffer.readableBytes() < length){
				//还原读指针
				buffer.readerIndex(beginReader);
				return null;
			}
			
			//读取data数据
			byte[] data = new byte[length];
			buffer.readBytes(data);
			
			Request request = new Request();
			request.setModule(module);
			request.setCmd(cmd);
			request.setData(data);
			
			//继续往下传递 
			return request;
			
		}
		//数据包不完整,需要等待后面的包来
		return null;
	}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值