Netty笔记——技术点汇总
目录
· 文件描述符
· 阻塞IO模型
· 非阻塞IO模型
· IO复用模型
· 信号驱动IO模型
· 异步IO模型
· BIO编程
· 伪异步IO编程
· NIO编程
· 深入Buffer
· Selector
· AIO编程
· Netty入门
· 开发与部署
· 粘包/拆包问题
· 问题及其解决
· 问题描述及其解决
· HTTP协议开发
· 文件服务器
· 问题及其解决
· 原理(过程)
· 开发
· Netty架构
· 逻辑架构
· 高性能
· 可靠性
· 可定制性
· 可扩展性
· 私有协议栈开发
Linux网络IO模型
文件描述符
1. Linux内核将所有外部设备视为文件来操作。
2. 对一个文件的读写操作会调用内核提供的系统命令,返回一个file descripter(fd,文件描述符)。
3. 对一个socket的读写也会有相应的描述符,称为socketfd(socket描述符)。
阻塞IO模型
1. 最常用的IO模型。
2. 默认的IO模型。
3. 以socket接口为例说明阻塞IO模型。
非阻塞IO模型
1. 一般轮训检查内核数据是否就绪。
2. 如果内核数据未就绪,则直接返回一个EWOULDBLOCK错误。
IO复用模型
1. Linux提供select/poll,进程传递一个或多个fd给select或poll系统调用,阻塞在select操作上,这样select/poll可以帮助进程同时检测多个fd是否就绪。
2. select/poll存在支持fd数量有限、线性轮训等问题,应采用基于事件驱动方式的epoll代替(当有fd就绪时,立即回调函数)。
信号驱动IO模型
进程先系统调用sigaction执行一个非阻塞的信号处理函数,进程继续运行。当数据就绪时,为该进程生成一个SIGIO信号,通知进程调用recvfrom读取数据。
异步IO模型
1. 进程告知内核启动某个操作,并在内核完成整个操作后再通知进程。
2. 与信号驱动IO模型区别:信号驱动IO模型只通知数据就绪;异步IO模型通知操作已完成。
BIO编程
1. 有一个独立的Acceptor线程负责监听客户端连接,接收到连接后为每个客户端创建一个新的线程进行链路处理,处理完之后,通过输出流返回给客户端,线程销毁。
2. 问题:服务端线程个数与客户端并发访问数1:1关系。当客户端并发访问量越来越大时,系统会发生线程堆栈溢出、创建新线程失败等问题,最终导致进程宕机或僵死。
伪异步IO编程
1. 当新客户端接入时,将客户端Socket封装成一个Task(实现Runnable接口)投递到线程池中进行处理。
2. 好处:由于可以设置线程池队列的大小和最大线程数,所以资源占用是可控的,客户端并发数量增加不会导致资源耗尽、宕机。
3. 问题:底层通信依然采用同步阻塞模型,无法从根本上解决应答消息缓慢或网络传输较慢时,长时间阻塞线程的问题。
NIO编程
Buffer和Channel
1. BIO是面向流的,一次处理一个字节;NIO是面向块的,以块的形式处理数据。
2. BIO的java.io.*已经使用NIO重新实现过。
3. Buffer缓冲区存放着准备要写入或读出的数据。通常是一个字节数组,但也可以是其他类型的数组或不是数组。
4. Buffer类型:
a) ByteBuffer(常用)
b) CharBuffer
c) ShortBuffer
d) IntBuffer
e) LongBuffer
f) FloatBuffer
g) DoubleBuffer
5. Channel通道是双向的,可通过它读取或写入数据。所有的数据都要通过Buffer来处理,永远不会将数据直接写入Channel。
6. 写文件示例。
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.UnsupportedEncodingException;
- import java.nio.ByteBuffer;
- import java.nio.channels.FileChannel;
- import java.util.Random;
- import java.util.UUID;
- public class Test {
- private static byte[] getRandomData() {
- int randomLength = new Random().nextInt(100);
- StringBuilder data = new StringBuilder();
- for (int index = 0; index < randomLength; index++) {
- data.append(UUID.randomUUID().toString());
- }
- return data.toString().getBytes();
- }
- public static void main(String[] args) {
- FileOutputStream fileOutputStream = null;
- try {
- fileOutputStream = new FileOutputStream("D:/test.txt");
- FileChannel fileChannel = fileOutputStream.getChannel();
- ByteBuffer byteBuffer = null;
- for (int index = 0; index < 1000; index++) {
- byte[] data = getRandomData();
- if (byteBuffer == null) {
- byteBuffer = ByteBuffer.wrap(data);
- } else if (data.length > byteBuffer.capacity()) {
- if (byteBuffer.position() > 0) {
- byteBuffer.flip();
- fileChannel.write(byteBuffer);
- byteBuffer.clear();
- }
- byteBuffer = ByteBuffer.wrap(data);
- } else if (data.length > byteBuffer.remaining()) {
- byteBuffer.flip();
- fileChannel.write(byteBuffer);
- byteBuffer.clear();
- }
- byteBuffer.put(data);
- }
- byteBuffer.flip();
- fileChannel.write(byteBuffer);
- byteBuffer.clear();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (fileOutputStream != null) {
- try {
- fileOutputStream.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
7. 读文件示例。
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.nio.ByteBuffer;
- import java.nio.channels.FileChannel;
- public class Test {
- public static void main(String[] args) {
- FileInputStream fileInputStream = null;
- try {
- fileInputStream = new FileInputStream("D:/test.txt");
- FileChannel fileChannel = fileInputStream.getChannel();
- ByteBuffer byteBuffer = ByteBuffer.allocate(64);
- while (fileChannel.read(byteBuffer) > 0) {
- byteBuffer.flip();
- while (byteBuffer.hasRemaining()) {
- System.out.print((char) byteBuffer.get());
- }
- byteBuffer.clear();
- }
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (fileInputStream != null) {
- try {
- fileInputStream.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
8. 复制文件示例。
- import java.io.IOException;
- import java.io.RandomAccessFile;
- import java.nio.ByteBuffer;
- import java.nio.channels.FileChannel;
- public class Test {
- public static void main(String[] args) {
- RandomAccessFile sourceFile = null;
- RandomAccessFile targetFile = null;
- try {
- sourceFile = new RandomAccessFile("D:/test.txt", "r");
- targetFile = new RandomAccessFile("D:/test.txt.bak", "rw");
- FileChannel sourceFileChannel = sourceFile.getChannel();
- FileChannel targetFileChannel = targetFile.getChannel();
- ByteBuffer byteBuffer = ByteBuffer.allocate(64);
- while (sourceFileChannel.read(byteBuffer) > 0) {
- byteBuffer.flip();
- targetFileChannel.write(byteBuffer);
- byteBuffer.clear();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
深入Buffer
1. Buffer可以理解成数组,它通过以下3个值描述状态:
a) position:下一个元素的位置;
b) limit:可读取或写入的元素总数,position总是小于或者等于limit;
c) capacity:Buffer最大容量,limit总是小于或者等于capacity。
2. 以读、写举例说明Buffer。
a) 创建一个8字节的ByteBuffer。position=0,limit=8,capacity=8。
b) 读取3个字节。position=3,limit=8,capacity=8。
c) 读取2个字节。position=5,limit=8,capacity=8。
d) 执行flip()。position=0,limit=5,capacity=8。
e) 写入4个字节。position=4,limit=5,capacity=8。
f) 写入1个字节。position=5,limit=5,capacity=8。
g) 执行clear()。position=0,limit=8,capacity=8。
3. 创建ByteBuffer的两种方法:
a) 创建固定大小的Buffer。
- ByteBuffer.allocate(capacity)
b) 将数组及其内容包装成Buffer。
- byte array[] = new byte[1024];
- ByteBuffer buffer = ByteBuffer.wrap(array);
Selector
1. Selector即IO复用模型中的多路复用器。
2. JDK使用了epoll。
AIO编程
1. AIO也称NIO2.0,是异步IO模型。
2. JDK 7时在java.nio.channels包下新增了4个异步Channel。
a) AsynchronousSocketChannel
b) AsynchronousServerSocketChannel
c) AsynchronousFileChannel
d) AsynchronousDatagramChannel
3. 使用Future写文件:异步执行,阻塞Future.get(),直到取得结果。
- import java.io.IOException;
- import java.nio.ByteBuffer;
- import java.nio.channels.AsynchronousFileChannel;
- import java.nio.file.Path;
- import java.nio.file.Paths;
- import java.nio.file.StandardOpenOption;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Random;
- import java.util.UUID;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.Future;
- public class Test {
- private static byte[] getRandomData() {
- int randomLength = new Random().nextInt(100);
- StringBuilder data = new StringBuilder();
- for (int index = 0; index < randomLength; index++) {
- data.append(UUID.randomUUID().toString());
- }
- return data.append('\n').toString().getBytes();
- }
- public static void main (String [] args) {
- Path file = Paths.get("D:/test.txt");
- AsynchronousFileChannel asynchronousFileChannel = null;
- try {
- asynchronousFileChannel = AsynchronousFileChannel.open(file, StandardOpenOption.WRITE);
- List<Future<Integer>> futures = new ArrayList<>();
- for (int index = 0; index < 10; index++) {
- ByteBuffer byteBuffer = ByteBuffer.wrap(getRandomData());
- Future<Integer> future = asynchronousFileChannel.write(byteBuffer, 0);
- futures.add(future);
- }
- for (Future<Integer> future : futures) {
- Integer length = null;
- try {
- length = future.get();
- } catch (InterruptedException | ExecutionException e) {
- e.printStackTrace();
- }
- System.out.println("Bytes written: " + length);
- }
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (asynchronousFileChannel != null) {
- try {
- asynchronousFileChannel.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
4. 使用CompletionHandler写文件:异步执行,回调CompletionHandler。注意:示例中,由于不阻塞主线程,即异步任务是否结果主线程都会结束,有时会看不到结果,所以sleep 5秒。
- import java.io.IOException;
- import java.nio.ByteBuffer;
- import java.nio.channels.AsynchronousFileChannel;
- import java.nio.channels.CompletionHandler;
- import java.nio.file.Path;
- import java.nio.file.Paths;
- import java.nio.file.StandardOpenOption;
- import java.util.Random;
- import java.util.UUID;
- public class Test {
- private static byte[] getRandomData() {
- int randomLength = new Random().nextInt(100);
- StringBuilder data = new StringBuilder();
- for (int index = 0; index < randomLength; index++) {
- data.append(UUID.randomUUID().toString());
- }
- return data.append('\n').toString().getBytes();
- }
- public static void main (String [] args) {
- Path file = Paths.get("D:/test.txt");
- AsynchronousFileChannel asynchronousFileChannel = null;
- try {
- asynchronousFileChannel = AsynchronousFileChannel.open(file, StandardOpenOption.WRITE);
- CompletionHandler<Integer, Object> completionHandler = new CompletionHandler<Integer, Object>() {
- @Override
- public void completed(Integer result, Object attachment) {
- System.out.println("Bytes written: " + result);
- }
- @Override
- public void failed(Throwable exc, Object attachment) {
- }
- };
- for (int index = 0; index < 10; index ++) {
- ByteBuffer byteBuffer = ByteBuffer.wrap(getRandomData());
- asynchronousFileChannel.write(byteBuffer, 0, null, completionHandler);
- }
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (asynchronousFileChannel != null) {
- try {
- asynchronousFileChannel.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
5. 使用Future读文件:异步执行,阻塞Future.get(),直到取得结果。
- import java.io.IOException;
- import java.nio.ByteBuffer;
- import java.nio.channels.AsynchronousFileChannel;
- import java.nio.file.Path;
- import java.nio.file.Paths;
- import java.nio.file.StandardOpenOption;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.Future;
- public class Test {
- public static void main (String [] args) {
- Path file = Paths.get("D:/test.txt");
- AsynchronousFileChannel asynchronousFileChannel = null;
- try {
- asynchronousFileChannel = AsynchronousFileChannel.open(file, StandardOpenOption.READ);
- ByteBuffer byteBuffer = ByteBuffer.allocate(64);
- int position = 0;
- int length = 0;
- do {
- Future<Integer> future = asynchronousFileChannel.read(byteBuffer, position);
- length = future.get();
- if (length > 0) {
- byteBuffer.flip();
- System.out.print(new String(byteBuffer.array()));
- byteBuffer.clear();
- }
- position += length;
- } while (length > 0);
- } catch (IOException e) {
- e.printStackTrace();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
- e.printStackTrace();
- } finally {
- if (asynchronousFileChannel != null) {
- try {
- asynchronousFileChannel.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
6. 使用CompletionHandler读文件:异步执行,回调CompletionHandler。注意:示例中,由于不阻塞主线程,即异步任务是否结果主线程都会结束,有时会看不到结果,所以sleep 5秒。
- import java.io.IOException;
- import java.nio.ByteBuffer;
- import java.nio.channels.AsynchronousFileChannel;
- import java.nio.channels.CompletionHandler;
- import java.nio.file.Path;
- import java.nio.file.Paths;
- import java.nio.file.StandardOpenOption;
- public class Test {
- public static void main (String [] args) {
- Path file = Paths.get("D:/test.txt");
- AsynchronousFileChannel asynchronousFileChannel = null;
- try {
- asynchronousFileChannel = AsynchronousFileChannel.open(file, StandardOpenOption.READ);
- // 10个异步任务分别读取文件头64个字节,5秒后分别输出。
- CompletionHandler<Integer, ByteBuffer> completionHandler = new CompletionHandler<Integer, ByteBuffer>() {
- @Override
- public void completed(Integer result, ByteBuffer byteBuffer) {
- byteBuffer.flip();
- System.out.print(new String(byteBuffer.array()));
- byteBuffer.clear();
- }
- @Override
- public void failed(Throwable exc, ByteBuffer byteBuffer) {
- }
- };
- for (int index = 0; index < 10; index++) {
- ByteBuffer byteBuffer = ByteBuffer.allocate(64);
- asynchronousFileChannel.read(byteBuffer, byteBuffer.limit() * index, byteBuffer, completionHandler);
- }
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (asynchronousFileChannel != null) {
- try {
- asynchronousFileChannel.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
四种IO编程对比及选择Netty的原因
1. 对比。
2. 选择NIO框架Netty,而不选择JDK的NIO类库的理由。
a) NIO类库和API繁杂。
b) 需另具备Java多线程编程等技能。
c) 可靠性不高,工作量和难度非常大。
d) 臭名昭著的epoll Bug导致Selector空轮训。
Netty入门
开发与部署
1. 开发环境:CLASSPATH中导入“netty-all-x.y.z.jar”即可。
2. 打包部署:由于是非Web应用,构建成jar包部署即可。
Hello World
1. 配置Maven的pom.xml文件。
- <dependency>
- <groupId>io.netty</groupId>
- <artifactId>netty-all</artifactId>
- <version>5.0.0.Alpha1</version>
- </dependency>
2. 时间服务器TimeServer
- import io.netty.bootstrap.ServerBootstrap;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelInitializer;
- import io.netty.channel.ChannelOption;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.SocketChannel;
- import io.netty.channel.socket.nio.NioServerSocketChannel;
- public class TimeServer {
- public void bind(int port) throws Exception {
- // 服务器NIO线程组线
- EventLoopGroup bossGroup = new NioEventLoopGroup();
- EventLoopGroup workerGroup = new NioEventLoopGroup();
- try {
- ServerBootstrap serverBootstrap = new ServerBootstrap();
- serverBootstrap.group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class)
- .option(ChannelOption.SO_BACKLOG, 1024)
- .childHandler(new ChildChannelHandler());
- // 绑定端口,同步等待成功
- ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
- // 等待服务器监听端口关闭
- channelFuture.channel().closeFuture().sync();
- } finally {
- // 优雅退出,释放线程池资源
- workerGroup.shutdownGracefully();
- bossGroup.shutdownGracefully();
- }
- }
- private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
- @Override
- protected void initChannel(SocketChannel socketChannel) throws Exception {
- socketChannel.pipeline().addLast(new TimeServerHandler());
- }
- }
- public static void main(String[] args) throws Exception {
- new TimeServer().bind(8080);
- }
- }
3. 时间服务器TimeServerHandler
- import java.util.Date;
- import io.netty.buffer.ByteBuf;
- import io.netty.buffer.Unpooled;
- import io.netty.channel.ChannelHandlerAdapter;
- import io.netty.channel.ChannelHandlerContext;
- public class TimeServerHandler extends ChannelHandlerAdapter {
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- ByteBuf reqBuf = (ByteBuf) msg;
- byte[] req = new byte[reqBuf.readableBytes()];
- reqBuf.readBytes(req);
- String reqString = new String(req, "UTF-8");
- String respString = "QUERY TIME ORDER".equalsIgnoreCase(reqString) ? new Date().toString() : "BAD ORDER";
- ByteBuf respBuf = Unpooled.copiedBuffer(respString.getBytes());
- ctx.write(respBuf);
- }
- @Override
- public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
- ctx.flush();
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- ctx.close();
- }
- }
4. 时间客户端TimeClient
- import io.netty.bootstrap.Bootstrap;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelInitializer;
- import io.netty.channel.ChannelOption;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.SocketChannel;
- import io.netty.channel.socket.nio.NioSocketChannel;
- public class TimeClient {
- public void connect(String host, int port) throws Exception {
- EventLoopGroup group = new NioEventLoopGroup();
- try {
- // 客户端NIO线程组
- Bootstrap bootstrap = new Bootstrap();
- bootstrap.group(group).channel(NioSocketChannel.class)
- .option(ChannelOption.TCP_NODELAY, true)
- .handler(new ChildChannelHandler());
- // 发起异步连接操作
- ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
- // 等待客户端链路关闭
- channelFuture.channel().closeFuture().sync();
- } finally {
- // 优雅退出,释放NIO线程组
- group.shutdownGracefully();
- }
- }
- private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
- @Override
- protected void initChannel(SocketChannel socketChannel) throws Exception {
- socketChannel.pipeline().addLast(new TimeClientHandler());
- }
- }
- public static void main(String[] args) throws Exception {
- new TimeClient().connect("127.0.0.1", 8080);
- }
- }
5. 时间客户端TimeClientHandler
- import io.netty.buffer.ByteBuf;
- import io.netty.buffer.Unpooled;
- import io.netty.channel.ChannelHandlerAdapter;
- import io.netty.channel.ChannelHandlerContext;
- public class TimeClientHandler extends ChannelHandlerAdapter {
- private final ByteBuf reqBuf;
- public TimeClientHandler() {
- byte[] req = "QUERY TIME ORDER".getBytes();
- reqBuf = Unpooled.buffer(req.length);
- reqBuf.writeBytes(req);
- }
- @Override
- public void channelActive(ChannelHandlerContext ctx) throws Exception {
- ctx.writeAndFlush(reqBuf);
- }
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- ByteBuf respBuf = (ByteBuf) msg;
- byte[] resp = new byte[respBuf.readableBytes()];
- respBuf.readBytes(resp);
- String respString = new String(resp, "UTF-8");
- System.out.println(respString);
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- ctx.close();
- }
- }
粘包/拆包问题
问题及其解决
1. TCP是一个“流协议”,是没有界限的一串数据。
2. TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分。所以在业务上认为,一个完整的包可能会被TCP拆包发送,也可能封装多个
小包成大包发送。
3. 业界主流协议的解决方案归纳:
a) 消息定长。如每个报文的大小固定长度200字节,不足时空位补空格。
b) 在包尾增加回车换行符进行分割。如FTP协议。
c) 将消息分为消息头、消息体,消息头中包含消息总长度(或消息体长度)的字段。
d) 更复杂的应用层协议。
4. Netty提供了多种编码器用于解决粘包/拆包问题。
LineBasedFrameDecoder
1. 原理:遍历ByteBuf中的可读字节,发现“\n”或“\r\n”时就结束。
2. 支持携带结束符或不携带结束符两种编码方式;支持配置单行的最大长度(超过最大长度未发现换行符则抛出异常,同时忽略掉之前读到的异常码流)。
3. StringDecoder功能:将接受到的对象转成字符串,然后继续调用后面的Handler。
4. 使用LineBasedFrameDecoder优化后的时间服务器。
a) 时间服务器TimeServer
- import io.netty.bootstrap.ServerBootstrap;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelInitializer;
- import io.netty.channel.ChannelOption;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.SocketChannel;
- import io.netty.channel.socket.nio.NioServerSocketChannel;
- import io.netty.handler.codec.LineBasedFrameDecoder;
- import io.netty.handler.codec.string.StringDecoder;
- public class TimeServer {
- public void bind(int port) throws Exception {
- // 服务器NIO线程组线
- EventLoopGroup bossGroup = new NioEventLoopGroup();
- EventLoopGroup workerGroup = new NioEventLoopGroup();
- try {
- ServerBootstrap serverBootstrap = new ServerBootstrap();
- serverBootstrap.group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class)
- .option(ChannelOption.SO_BACKLOG, 1024)
- .childHandler(new ChildChannelHandler());
- // 绑定端口,同步等待成功
- ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
- // 等待服务器监听端口关闭
- channelFuture.channel().closeFuture().sync();
- } finally {
- // 优雅退出,释放线程池资源
- workerGroup.shutdownGracefully();
- bossGroup.shutdownGracefully();
- }
- }
- private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
- @Override
- protected void initChannel(SocketChannel socketChannel) throws Exception {
- socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
- socketChannel.pipeline().addLast(new StringDecoder());
- socketChannel.pipeline().addLast(new TimeServerHandler());
- }
- }
- public static void main(String[] args) throws Exception {
- new TimeServer().bind(8080);
- }
- }
b) 时间服务器TimeServerHandler
- import java.util.Date;
- import io.netty.buffer.ByteBuf;
- import io.netty.buffer.Unpooled;
- import io.netty.channel.ChannelHandlerAdapter;
- import io.netty.channel.ChannelHandlerContext;
- public class TimeServerHandler extends ChannelHandlerAdapter {
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- String reqString = (String) msg;
- String respString = "QUERY TIME ORDER".equalsIgnoreCase(reqString) ? new Date().toString() : "BAD ORDER";
- respString += "\n";
- ByteBuf respBuf = Unpooled.copiedBuffer(respString.getBytes());
- ctx.write(respBuf);
- }
- @Override
- public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
- ctx.flush();
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- ctx.close();
- }
- }
c) 时间客户端TimeClient
- import io.netty.bootstrap.Bootstrap;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelInitializer;
- import io.netty.channel.ChannelOption;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.SocketChannel;
- import io.netty.channel.socket.nio.NioSocketChannel;
- import io.netty.handler.codec.LineBasedFrameDecoder;
- import io.netty.handler.codec.string.StringDecoder;
- public class TimeClient {
- public void connect(String host, int port) throws Exception {
- EventLoopGroup group = new NioEventLoopGroup();
- try {
- // 客户端NIO线程组
- Bootstrap bootstrap = new Bootstrap();
- bootstrap.group(group).channel(NioSocketChannel.class)
- .option(ChannelOption.TCP_NODELAY, true)
- .handler(new ChildChannelHandler());
- // 发起异步连接操作
- ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
- // 等待客户端链路关闭
- channelFuture.channel().closeFuture().sync();
- } finally {
- // 优雅退出,释放NIO线程组
- group.shutdownGracefully();
- }
- }
- private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
- @Override
- protected void initChannel(SocketChannel socketChannel) throws Exception {
- socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
- socketChannel.pipeline().addLast(new StringDecoder());
- socketChannel.pipeline().addLast(new TimeClientHandler());
- }
- }
- public static void main(String[] args) throws Exception {
- new TimeClient().connect("127.0.0.1", 8080);
- }
- }
d) 时间客户端TimeClientHandler
- import io.netty.buffer.ByteBuf;
- import io.netty.buffer.Unpooled;
- import io.netty.channel.ChannelHandlerAdapter;
- import io.netty.channel.ChannelHandlerContext;
- public class TimeClientHandler extends ChannelHandlerAdapter {
- private final ByteBuf reqBuf;
- public TimeClientHandler() {
- byte[] req = "QUERY TIME ORDER\n".getBytes();
- reqBuf = Unpooled.buffer(req.length);
- reqBuf.writeBytes(req);
- }
- @Override
- public void channelActive(ChannelHandlerContext ctx) throws Exception {
- ctx.writeAndFlush(reqBuf);
- }
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- String respString = (String) msg;
- System.out.println(respString);
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- ctx.close();
- }
- }
DelimiterBasedFrameDecoder
1. 功能:以分隔符作为码流结束标识符的消息解码。
2. 时间服务器TimeServer
- import io.netty.bootstrap.ServerBootstrap;
- import io.netty.buffer.ByteBuf;
- import io.netty.buffer.Unpooled;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelInitializer;
- import io.netty.channel.ChannelOption;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.SocketChannel;
- import io.netty.channel.socket.nio.NioServerSocketChannel;
- import io.netty.handler.codec.DelimiterBasedFrameDecoder;
- import io.netty.handler.codec.string.StringDecoder;
- public class TimeServer {
- public void bind(int port) throws Exception {
- // 服务器NIO线程组线
- EventLoopGroup bossGroup = new NioEventLoopGroup();
- EventLoopGroup workerGroup = new NioEventLoopGroup();
- try {
- ServerBootstrap serverBootstrap = new ServerBootstrap();
- serverBootstrap.group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class)
- .option(ChannelOption.SO_BACKLOG, 1024)
- .childHandler(new ChildChannelHandler());
- // 绑定端口,同步等待成功
- ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
- // 等待服务器监听端口关闭
- channelFuture.channel().closeFuture().sync();
- } finally {
- // 优雅退出,释放线程池资源
- workerGroup.shutdownGracefully();
- bossGroup.shutdownGracefully();
- }
- }
- private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
- @Override
- protected void initChannel(SocketChannel socketChannel) throws Exception {
- ByteBuf delimiter = Unpooled.copiedBuffer("*&*".getBytes());
- socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
- socketChannel.pipeline().addLast(new StringDecoder());
- socketChannel.pipeline().addLast(new TimeServerHandler());
- }
- }
- public static void main(String[] args) throws Exception {
- new TimeServer().bind(8080);
- }
- }
3. 时间服务器TimeServerHandler
- import java.util.Date;
- import io.netty.buffer.ByteBuf;
- import io.netty.buffer.Unpooled;
- import io.netty.channel.ChannelHandlerAdapter;
- import io.netty.channel.ChannelHandlerContext;
- public class TimeServerHandler extends ChannelHandlerAdapter {
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- String reqString = (String) msg;
- String respString = "QUERY TIME ORDER".equalsIgnoreCase(reqString) ? new Date().toString() : "BAD ORDER";
- respString += "*&*";
- ByteBuf respBuf = Unpooled.copiedBuffer(respString.getBytes());
- ctx.write(respBuf);
- }
- @Override
- public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
- ctx.flush();
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- ctx.close();
- }
- }
4. 时间客户端TimeClient
- import io.netty.bootstrap.Bootstrap;
- import io.netty.buffer.ByteBuf;
- import io.netty.buffer.Unpooled;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelInitializer;
- import io.netty.channel.ChannelOption;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.SocketChannel;
- import io.netty.channel.socket.nio.NioSocketChannel;
- import io.netty.handler.codec.DelimiterBasedFrameDecoder;
- import io.netty.handler.codec.string.StringDecoder;
- public class TimeClient {
- public void connect(String host, int port) throws Exception {
- EventLoopGroup group = new NioEventLoopGroup();
- try {
- // 客户端NIO线程组
- Bootstrap bootstrap = new Bootstrap();
- bootstrap.group(group).channel(NioSocketChannel.class)
- .option(ChannelOption.TCP_NODELAY, true)
- .handler(new ChildChannelHandler());
- // 发起异步连接操作
- ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
- // 等待客户端链路关闭
- channelFuture.channel().closeFuture().sync();
- } finally {
- // 优雅退出,释放NIO线程组
- group.shutdownGracefully();
- }
- }
- private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
- @Override
- protected void initChannel(SocketChannel socketChannel) throws Exception {
- ByteBuf delimiter = Unpooled.copiedBuffer("*&*".getBytes());
- socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
- socketChannel.pipeline().addLast(new StringDecoder());
- socketChannel.pipeline().addLast(new TimeClientHandler());
- }
- }
- public static void main(String[] args) throws Exception {
- new TimeClient().connect("127.0.0.1", 8080);
- }
- }
5. 时间客户端TimeClientHandler
- import io.netty.buffer.ByteBuf;
- import io.netty.buffer.Unpooled;
- import io.netty.channel.ChannelHandlerAdapter;
- import io.netty.channel.ChannelHandlerContext;
- public class TimeClientHandler extends ChannelHandlerAdapter {
- private final ByteBuf reqBuf;
- public TimeClientHandler() {
- byte[] req = "QUERY TIME ORDER*&*".getBytes();
- reqBuf = Unpooled.buffer(req.length);
- reqBuf.writeBytes(req);
- }
- @Override
- public void channelActive(ChannelHandlerContext ctx) throws Exception {
- ctx.writeAndFlush(reqBuf);
- }
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- String respString = (String) msg;
- System.out.println(respString);
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- ctx.close();
- }
- }
FixedLengthFrameDecoder
1. 原理:无论一次接受到多少数据包,它都会按照设置的固定长度解码,如果是半包消息,则缓存半包消息并等待下个包到达后进行拼包,直到读取到一个完整的包。
2. 回显服务器EchoServer
- import io.netty.bootstrap.ServerBootstrap;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelInitializer;
- import io.netty.channel.ChannelOption;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.SocketChannel;
- import io.netty.channel.socket.nio.NioServerSocketChannel;
- import io.netty.handler.codec.FixedLengthFrameDecoder;
- import io.netty.handler.codec.string.StringDecoder;
- public class EchoServer {
- public void bind(int port) throws Exception {
- // 服务器NIO线程组线
- EventLoopGroup bossGroup = new NioEventLoopGroup();
- EventLoopGroup workerGroup = new NioEventLoopGroup();
- try {
- ServerBootstrap serverBootstrap = new ServerBootstrap();
- serverBootstrap.group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class)
- .option(ChannelOption.SO_BACKLOG, 1024)
- .childHandler(new ChildChannelHandler());
- // 绑定端口,同步等待成功
- ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
- // 等待服务器监听端口关闭
- channelFuture.channel().closeFuture().sync();
- } finally {
- // 优雅退出,释放线程池资源
- workerGroup.shutdownGracefully();
- bossGroup.shutdownGracefully();
- }
- }
- private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
- @Override
- protected void initChannel(SocketChannel socketChannel) throws Exception {
- socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(20));
- socketChannel.pipeline().addLast(new StringDecoder());
- socketChannel.pipeline().addLast(new EchoServerHandler());
- }
- }
- public static void main(String[] args) throws Exception {
- new EchoServer().bind(8080);
- }
- }
3. 回显服务器EchoServerHandler
- import io.netty.channel.ChannelHandlerAdapter;
- import io.netty.channel.ChannelHandlerContext;
- public class EchoServerHandler extends ChannelHandlerAdapter {
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- System.out.println(msg);
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- ctx.close();
- }
- }
4. 使用telnet命令测试,当长度达到20个字符时,服务器打印。
Java序列化问题
问题描述及其解决
1. 无法跨语言。Java序列化是Java语言内部的私有协议,其他语言并不支持。
2. 序列化后的码流太大。编码后的字节数组越大,存储的时候就越占空间,存储的硬件成本就越高,网络传输时更占带宽,导致系统的吞吐量降低。
3. 序列化性能太低。编解码耗时长。
4. 解决:编解码框架,如Google Protobuf、MessagePack。此处不深入展开。
HTTP协议开发
Netty HTTP
1. 由于HTTP协议的通用性,很多异构系统间的通信交互采用HTTP协议,如非常流行的HTTP + XML或者RESTful + JSON。
2. 与Web容器相比,Netty开发HTTP的优势:轻量级;安全。
3. 这里以文件服务器举例,至于HTTP + XML,此处不深入展开。
文件服务器
1. 文件服务器HttpFileServer
- import io.netty.bootstrap.ServerBootstrap;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelInitializer;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.SocketChannel;
- import io.netty.channel.socket.nio.NioServerSocketChannel;
- import io.netty.handler.codec.http.HttpObjectAggregator;
- import io.netty.handler.codec.http.HttpRequestDecoder;
- import io.netty.handler.codec.http.HttpResponseEncoder;
- import io.netty.handler.stream.ChunkedWriteHandler;
- public class HttpFileServer {
- public void run(int port, String folderPath) throws Exception {
- EventLoopGroup bossGroup = new NioEventLoopGroup();
- EventLoopGroup workerGroup = new NioEventLoopGroup();
- try {
- ServerBootstrap serverBootstrap = new ServerBootstrap();
- serverBootstrap.group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class)
- .childHandler(new ChannelInitializer<SocketChannel>() {
- @Override
- protected void initChannel(SocketChannel socketChannel) throws Exception {
- socketChannel.pipeline().addLast(new HttpRequestDecoder());
- socketChannel.pipeline().addLast(new HttpObjectAggregator(65536));
- socketChannel.pipeline().addLast(new HttpResponseEncoder());
- socketChannel.pipeline().addLast(new ChunkedWriteHandler());
- socketChannel.pipeline().addLast(new HttpFileServerHandler(folderPath));
- }
- });
- ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
- channelFuture.channel().closeFuture().sync();
- } finally {
- workerGroup.shutdownGracefully();
- bossGroup.shutdownGracefully();
- }
- }
- public static void main(String[] args) throws Exception {
- int port = 8080;
- String folderPath = "E:/workspace";
- new HttpFileServer().run(port, folderPath);
- }
- }
2. 文件服务器HttpFileServerHandler
- import io.netty.buffer.ByteBuf;
- import io.netty.buffer.Unpooled;
- import io.netty.channel.ChannelFutureListener;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.channel.SimpleChannelInboundHandler;
- import io.netty.handler.codec.http.DefaultFullHttpResponse;
- import io.netty.handler.codec.http.DefaultHttpResponse;
- import io.netty.handler.codec.http.FullHttpRequest;
- import io.netty.handler.codec.http.FullHttpResponse;
- import io.netty.handler.codec.http.HttpHeaders;
- import io.netty.handler.codec.http.HttpMethod;
- import io.netty.handler.codec.http.HttpResponse;
- import io.netty.handler.codec.http.HttpResponseStatus;
- import io.netty.handler.codec.http.HttpVersion;
- import io.netty.handler.stream.ChunkedFile;
- import io.netty.util.CharsetUtil;
- import java.io.File;
- import java.io.FileNotFoundException;
- import java.io.RandomAccessFile;
- import java.net.URLDecoder;
- public class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
- private String folderPath;
- public HttpFileServerHandler(String folderPath) {
- this.folderPath = folderPath;
- }
- @Override
- protected void messageReceived(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
- if (!req.getDecoderResult().isSuccess()) {
- sendStatus(ctx, HttpResponseStatus.BAD_REQUEST);
- return;
- }
- if (!HttpMethod.GET.equals(req.getMethod())) {
- sendStatus(ctx, HttpResponseStatus.METHOD_NOT_ALLOWED);
- return;
- }
- String uri = req.getUri();
- File file = getFile(uri);
- if (file == null || file.isHidden() || !file.exists()) {
- sendStatus(ctx, HttpResponseStatus.NOT_FOUND);
- return;
- }
- try {
- if (file.isDirectory()) {
- listFiles(ctx, file, uri);
- } else {
- returnFile(ctx, req, file);
- }
- } catch (Exception e) {
- sendStatus(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
- }
- }
- private File getFile(String uri) throws Exception {
- uri = URLDecoder.decode(uri, "UTF-8");
- return new File(folderPath + uri);
- }
- private void listFiles(ChannelHandlerContext ctx, File folder, String uri) throws Exception {
- uri = uri.endsWith("/") ? uri : uri + "/";
- StringBuilder html = new StringBuilder("<h1>Index of ").append(URLDecoder.decode(uri, "UTF-8")).append("</h1><hr/><pre><a href=\"").append(uri).append("../\">../</a>\n");
- File[] subfiles = folder.listFiles();
- if (subfiles != null && subfiles.length > 0) {
- for (File subfile : subfiles) {
- String name = subfile.getName();
- html.append("<a href=\"").append(uri).append(name).append("\">").append(name).append("</a>\n");
- }
- }
- html.append("</pre><hr/>");
- FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
- resp.headers().add(HttpHeaders.Names.CONTENT_TYPE, "text/html;charset=UTF-8");
- ByteBuf content = Unpooled.copiedBuffer(html, CharsetUtil.UTF_8);
- resp.content().writeBytes(content);
- ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);
- }
- private void returnFile(ChannelHandlerContext ctx, FullHttpRequest req, File file) throws Exception {
- RandomAccessFile randomAccessFile = null;
- try {
- randomAccessFile = new RandomAccessFile(file, "r");
- HttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
- resp.headers().set(HttpHeaders.Names.CONTENT_LENGTH, randomAccessFile.length())
- .set(HttpHeaders.Names.CONTENT_TYPE, "application/octet-stream");
- if (HttpHeaders.Values.KEEP_ALIVE.toString().equalsIgnoreCase(req.headers().get(HttpHeaders.Names.CONNECTION))) {
- resp.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
- }
- ctx.write(resp);
- ctx.writeAndFlush(new ChunkedFile(randomAccessFile, 0, randomAccessFile.length(), 8192)).addListener(ChannelFutureListener.CLOSE);
- } catch (FileNotFoundException e) {
- sendStatus(ctx, HttpResponseStatus.NOT_FOUND);
- } finally {
- if (randomAccessFile != null) {
- randomAccessFile.close();
- }
- }
- }
- private void sendStatus(ChannelHandlerContext ctx, HttpResponseStatus status) throws Exception {
- HttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
- ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);
- }
- }
WebSocket协议开发
问题及其解决
1. 轮训、Comet等服务器推送技术效率低下,大量消耗服务器带宽和资源。
2. WebSocket的特点:
a) 单一的TCP连接,全双工模式。
b) 对代理、防火墙和路由器透明。
c) 无头部信息、Cookie和身份验证。
d) 无安全开销。
e) 通过“ping/pong”帧保持链路激活。
f) 服务器可以主动传递消息给客户端,客户端不再轮训。
原理(过程)
1. 浏览器向服务器发起一个HTTP请求(特别的头信息,Sec-WebSocket-Key是随机的),准备建立WebSocket连接。
- GET /chat HTTP/1.1
- Host: server.example.com
- Upgrade: websocket
- Connection: Upgrade
- Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
- Sec-WebSocket-Protocol: chat, superchat
- Sec-WebSocket-Version: 13
- Origin: http://example.com
2. 服务器用Sec-WebSocket-Key加上魔幻字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,先SHA-1加密,再BASE-64编码,作为Sec-WebSocket-Accept返回浏览器。握手完成。
- HTTP/1.1 101 Switching Protocols
- Upgrade: websocket
- Connection: Upgrade
- Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
- Sec-WebSocket-Protocol: chat
3. 服务器和浏览器可通过message方式进行通信。
4. 关闭消息带有一个状态码和一个可选的关闭原因,按协议要求发送一个Close控制帧,当对端接受到关闭控制帧指令时,主动关闭WebSocket连接。
开发
1. 服务器WebSocketServer
- import io.netty.bootstrap.ServerBootstrap;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelInitializer;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.SocketChannel;
- import io.netty.channel.socket.nio.NioServerSocketChannel;
- import io.netty.handler.codec.http.HttpObjectAggregator;
- import io.netty.handler.codec.http.HttpRequestDecoder;
- import io.netty.handler.codec.http.HttpResponseEncoder;
- import io.netty.handler.stream.ChunkedWriteHandler;
- public class WebSocketServer {
- public void run(int port) throws Exception {
- EventLoopGroup bossGroup = new NioEventLoopGroup();
- EventLoopGroup workerGroup = new NioEventLoopGroup();
- try {
- ServerBootstrap serverBootstrap = new ServerBootstrap();
- serverBootstrap.group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class)
- .childHandler(new ChannelInitializer<SocketChannel>() {
- @Override
- protected void initChannel(SocketChannel socketChannel) throws Exception {
- socketChannel.pipeline().addLast(new HttpRequestDecoder());
- socketChannel.pipeline().addLast(new HttpObjectAggregator(65536));
- socketChannel.pipeline().addLast(new HttpResponseEncoder());
- socketChannel.pipeline().addLast(new ChunkedWriteHandler());
- socketChannel.pipeline().addLast(new WebSocketServerHandler());
- }
- });
- ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
- channelFuture.channel().closeFuture().sync();
- } finally {
- workerGroup.shutdownGracefully();
- bossGroup.shutdownGracefully();
- }
- }
- public static void main(String[] args) throws Exception {
- int port = 8080;
- new WebSocketServer().run(port);
- }
- }
2. 服务器WebSocketServerHandler
- import io.netty.channel.ChannelFutureListener;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.channel.SimpleChannelInboundHandler;
- import io.netty.handler.codec.http.DefaultHttpResponse;
- import io.netty.handler.codec.http.FullHttpRequest;
- import io.netty.handler.codec.http.HttpHeaders;
- import io.netty.handler.codec.http.HttpResponse;
- import io.netty.handler.codec.http.HttpResponseStatus;
- import io.netty.handler.codec.http.HttpVersion;
- import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
- import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
- import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
- import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
- import io.netty.handler.codec.http.websocketx.WebSocketFrame;
- import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
- import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
- import java.util.Date;
- public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
- private WebSocketServerHandshaker handshaker;
- @Override
- protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
- // 传统HTTP
- if (msg instanceof FullHttpRequest) {
- handleHttpRequest(ctx, (FullHttpRequest) msg);
- } else if (msg instanceof WebSocketFrame) {
- handleWebSocketFrame(ctx, (WebSocketFrame) msg);
- }
- }
- @Override
- public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
- ctx.flush();
- }
- private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
- if (!req.getDecoderResult().isSuccess()
- || !HttpHeaders.Values.WEBSOCKET.toString().equalsIgnoreCase(req.headers().get(HttpHeaders.Names.UPGRADE))) {
- sendStatus(ctx, HttpResponseStatus.BAD_REQUEST);
- return;
- }
- WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://localhost:8080/testws", null, false);
- handshaker = wsFactory.newHandshaker(req);
- if (handshaker == null) {
- WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel());
- } else {
- handshaker.handshake(ctx.channel(), req);
- }
- }
- private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
- if (frame instanceof CloseWebSocketFrame) {
- handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
- return;
- }
- if (frame instanceof PingWebSocketFrame) {
- ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
- return;
- }
- if (!(frame instanceof TextWebSocketFrame)) {
- throw new UnsupportedOperationException();
- }
- String req = ((TextWebSocketFrame) frame).text();
- ctx.channel().write(new TextWebSocketFrame("欢迎" + req + ",现在时刻" + new Date()));
- }
- private void sendStatus(ChannelHandlerContext ctx, HttpResponseStatus status) throws Exception {
- HttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
- ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);
- }
- }
3. 浏览器websocketclient.html
- <script type="text/javascript">
- var socket;
- function initSocket() {
- if (socket) return;
- if (!window.WebSocket) window.WebSocket = window.MozWebSocket;
- if (!window.WebSocket) {
- alert('浏览器不支持WebSocket');
- return;
- }
- socket = new WebSocket('ws://localhost:8080/testws');
- socket.onmessage = function(event) {
- alert(event.data);
- };
- socket.onopen = function(event) {
- alert('WebSocket连接建立成功');
- };
- socket.onclose = function(event) {
- alert('WebSocket连接已关闭');
- };
- }
- function sendMsg() {
- initSocket();
- if (socket && WebSocket && socket.readyState == WebSocket.OPEN) {
- var msg = document.getElementById('msg').value;
- socket.send(msg);
- }
- }
- </script>
- <input type="text" id="msg"/>
- <input type="button" value="Send" onclick="sendMsg()"/>
Netty架构
逻辑架构
1. Netty采用三层网络架构设计和开发。
2. Reactor通信调度层(第1层)。负责监听网络的读写和连接操作。将网络层的数据读取到内存缓存区,然后触发各种网络事件,如连接创建、连接激活、读事件、写事件等,将这些事件触发到Pipeline中,有Pipeline管理的责任链进行后续处理。
3. 责任链ChannelPipleline(第2层)。负责事件在责任链中的有序传播,同时动态地编排责任链。通常,由编解码Handler将外部协议消息转换成内部POJO对象,这样上层业务只需关心业务逻辑处理。
4. 业务逻辑编排层Service ChannelHandler(第3层)。通常有两类:存储的业务逻辑编排和其他应用层协议插件,用于特定协议相关的会话和链路管理。
5. 通常,开发者值需关系责任链和业务逻辑编排层。
高性能
Netty的高性能是如何实现的?
1. 采用异步非阻塞IO类库,基于Reactor模式实现,解决了传统同步阻塞IO模式下一个服务端无法平滑处理线性增长的客户端的问题。
2. TCP接收和发送缓冲区使用直接内存代替堆内存,避免内存复制,提升了IO读写性能。俗称“零拷贝”(Zero-Copy)。
3. 通过内存池方式循环利用ByteBuf,避免了频繁创建和销毁ByteBuf带来的性能损耗。
4. 可配置IO线程数、TCP参数等,为不同场景提供定制化的调优参数,满足不同的性能场景。
5. 采用环形数组缓冲区实现无锁化并发编程,代替传统的线程安全容器和锁。
6. 合理使用线程安全容器、原子类等,提升系统的并发处理能力。
7. 关键资源的处理使用单线程串行化方式,避免了多线程并发访问带来的锁竞争和额外的CPU资源消耗问题。
8. 通过引用计数器及时申请释放不再被引用的对象,细粒度的内存管理降低了GC频繁,减少了频繁GC带来的延时和CPU损耗。
可靠性
Netty的可靠性是如何实现的?
1. 链路有效性检测。
a) 长连接无需每次发送消息时创建链路,也无需在消息交互完成后关闭链路,因此相对短链接更高。
b) 为保证长连接有效性,需要周期性心跳检测。一旦发现问题,可以及时关闭链路,重建TCP链接。
2. 内存保护机制。
a) 通过对象引用计数器对ByteBuf等内置对象进行细粒度的内存申请和释放,对非法对象引用进行检测和保护。
b) 通过内存池方式循环利用ByteBuf,节省内存。
c) 可设置内存容量上限,包括ByteBuf、线程池线程数等。
3. 优雅停机。
a) 当系统退出时,JVM通过注册的Shutdown Hook拦截到退出信号量,然后执行退出操作,释放相关模块的资源,将缓冲区的消息处理完成或清空,将待刷新的数据持久化到磁盘或数据库,完成后再退出。
b) 需设置超时时间T,如果达到T后仍然没有退出,则通过“kill -9 pid”强杀进程。
可定制性
Netty的可定制性是如何实现的?
1. 责任链模式:ChannelPipeline基于责任链模式,便于业务逻辑的拦截、定制和扩展。
2. 基于接口开发:关键类库都提供了接口或抽象类。
3. 提供大量工厂类,重载工厂类可创建用户实现的对象。
4. 提供大量系统参数供用户设置。
可扩展性
可定义私有协议栈。
私有协议栈开发
1. 开发时编写的代码。
a) 数据结构NettyMessage;
b) 消息编解码器NettyMessageEncoder和NettyMessageDecoder;
c) 握手认证Handler LoginAuthReqHanlder和LoginAuthRespHanlder;
d) 心跳检测Handler HearBeatReqHanlder和HearBeatRespHanlder。
2. 私有协议栈细节待补充。
作者:netoxi
出处:http://www.cnblogs.com/netoxi
本文版权归作者和博客园共有,欢迎转载,未经同意须保留此段声明,且在文章页面明显位置给出原文连接。欢迎指正与交流。
Netty笔记——技术点汇总的更多相关文章
- Hadoop笔记——技术点汇总
目录 · 概况 · Hadoop · 云计算 · 大数据 · 数据挖掘 · 手工搭建集群 · 引言 · 配置机器名 · 调整时间 · 创建用户 · 安装JDK · 配置文件 · 启动与测试 · Clo ...
- Storm笔记——技术点汇总
目录 概况 手工搭建集群 引言 安装Python 配置文件 启动与测试 应用部署 参数配置 Storm命令 原理 Storm架构 Storm组件 Stream Grouping 守护进程容错性(Dae ...
- Spark SQL笔记——技术点汇总
目录 概述 原理 组成 执行流程 性能 API 应用程序模板 通用读写方法 RDD转为DataFrame Parquet文件数据源 JSON文件数据源 Hive数据源 数据库JDBC数据源 DataF ...
- JVM笔记——技术点汇总
目录 · 初步认识 · Java里程碑(关键部分) · 理解虚拟机 · Java虚拟机种类 · Java语言规范 · Java虚拟机规范 · 基本结构 · Java堆(Heap) · Java栈(St ...
- Java并发编程笔记——技术点汇总
目录 · 线程安全 · 线程安全的实现方法 · 互斥同步 · 非阻塞同步 · 无同步 · volatile关键字 · 线程间通信 · Object.wait()方法 · Object.notify() ...
- Kafka笔记——技术点汇总
Table of contents Table of contents Overview Introduction Use cases Manual setup Assumption Configur ...
- Spark Streaming笔记——技术点汇总
目录 目录 概况 原理 API DStream WordCount示例 Input DStream Transformation Operation Output Operation 缓存与持久化 C ...
- Spark笔记——技术点汇总
目录 概况 手工搭建集群 引言 安装Scala 配置文件 启动与测试 应用部署 部署架构 应用程序部署 核心原理 RDD概念 RDD核心组成 RDD依赖关系 DAG图 RDD故障恢复机制 Standa ...
- HDFS笔记——技术点汇总
目录 · 概况 · 原理 · HDFS 架构 · 块 · NameNode · SecondaryNameNode · fsimage与edits合并 · DataNode · 数据读写 · 容错机制 ...
随机推荐
- file里的路径
实例话file类的对象 File file=new File("d:/mydoc/hello.txt") 文件名:fileMethod.java 相对路径:fileMethod.j ...
- VPN断开后断网脚本
有时在实际中需要,不能暴露自己的真实IP,不得不使用VPN,但是VPN的稳定性及易受网络环境影响,在VPN的暂时掉线之后,会暴露自己的真实IP,此时通过脚本操作路由表让VPN断线之后,电脑失去网络访问 ...
- 基于Http协议订阅发布系统设计
基于Http协议订阅发布系统设计 --物联网系统架构设计 1,订阅发布(subscriber-publisher) 订阅发布模式最典型的应用场景就是消息系统的设计.在消息系统的架构中 ...
- Java ee el表达式
以前在开发的时候,偶尔会遇到jsp页面不支持el表达式的情况. 这个的原因是因为El功能被关闭了, 当时的解决办法是关闭忽略.isELIgnored 设设置 但是为什么有时候不用设置也可以了呢.发现原 ...
- [POJ2104/HDU2665]Kth Number-主席树-可持久化线段树
Problem Kth Number Solution 裸的主席树,模板题.但是求k大的时候需要非常注意,很多容易写错的地方.卡了好久.写到最后还给我来个卡空间. 具体做法参见主席树论文<可持久 ...
- CJOJ 2040 【一本通】分组背包(动态规划)
CJOJ 2040 [一本通]分组背包(动态规划) Description 一个旅行者有一个最多能用V公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2, ...
- oracle 小测
01)oracle10i,oracle11g,oracle12c,其它i,g,c什么意思? i(Internet)互联网 g(grid)网格 c(cloud) 云02)sqlplus是什么意思? 是o ...
- 小哈学Python第二课:Hello Word
Python入门 1.Hello World 2.Hello World
- log4go的精确定时程序(带自动延迟补偿)
程序设计目标是在程序启动10秒后执行某个任务,例如日志转储(rotate),以后每隔15秒执行一次. 初次的设计 package main import ( "time" &quo ...
- promise异步编程的原理
一.起源 JavaScript中的异步由来已久,不论是定时函数,事件处理函数还是ajax异步加载都是异步编程的一种形式,我们现在以nodejs中异步读取文件为例来编写一个传统意义的异步函数: var ...