目录

· Linux网络IO模型

· 文件描述符

· 阻塞IO模型

· 非阻塞IO模型

· IO复用模型

· 信号驱动IO模型

· 异步IO模型

· BIO编程

· 伪异步IO编程

· NIO编程

· Buffer和Channel

· 深入Buffer

· Selector

· AIO编程

· 四种IO编程对比及选择Netty的原因

· Netty入门

· 开发与部署

· Hello World

· 粘包/拆包问题

· 问题及其解决

· LineBasedFrameDecoder

· DelimiterBasedFrameDecoder

· FixedLengthFrameDecoder

· Java序列化问题

· 问题描述及其解决

· HTTP协议开发

· Netty HTTP

· 文件服务器

· WebSocket协议开发

· 问题及其解决

· 原理(过程)

· 开发

· 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笔记——技术点汇总的更多相关文章

  1. Hadoop笔记——技术点汇总

    目录 · 概况 · Hadoop · 云计算 · 大数据 · 数据挖掘 · 手工搭建集群 · 引言 · 配置机器名 · 调整时间 · 创建用户 · 安装JDK · 配置文件 · 启动与测试 · Clo ...

  2. Storm笔记——技术点汇总

    目录 概况 手工搭建集群 引言 安装Python 配置文件 启动与测试 应用部署 参数配置 Storm命令 原理 Storm架构 Storm组件 Stream Grouping 守护进程容错性(Dae ...

  3. Spark SQL笔记——技术点汇总

    目录 概述 原理 组成 执行流程 性能 API 应用程序模板 通用读写方法 RDD转为DataFrame Parquet文件数据源 JSON文件数据源 Hive数据源 数据库JDBC数据源 DataF ...

  4. JVM笔记——技术点汇总

    目录 · 初步认识 · Java里程碑(关键部分) · 理解虚拟机 · Java虚拟机种类 · Java语言规范 · Java虚拟机规范 · 基本结构 · Java堆(Heap) · Java栈(St ...

  5. Java并发编程笔记——技术点汇总

    目录 · 线程安全 · 线程安全的实现方法 · 互斥同步 · 非阻塞同步 · 无同步 · volatile关键字 · 线程间通信 · Object.wait()方法 · Object.notify() ...

  6. Kafka笔记——技术点汇总

    Table of contents Table of contents Overview Introduction Use cases Manual setup Assumption Configur ...

  7. Spark Streaming笔记——技术点汇总

    目录 目录 概况 原理 API DStream WordCount示例 Input DStream Transformation Operation Output Operation 缓存与持久化 C ...

  8. Spark笔记——技术点汇总

    目录 概况 手工搭建集群 引言 安装Scala 配置文件 启动与测试 应用部署 部署架构 应用程序部署 核心原理 RDD概念 RDD核心组成 RDD依赖关系 DAG图 RDD故障恢复机制 Standa ...

  9. HDFS笔记——技术点汇总

    目录 · 概况 · 原理 · HDFS 架构 · 块 · NameNode · SecondaryNameNode · fsimage与edits合并 · DataNode · 数据读写 · 容错机制 ...

随机推荐

  1. FileInputStreamTest

    package JBJADV003;import java.io.FileNotFoundException;import java.io.IOException;import java.io.Inp ...

  2. WPF WebBrowser Memory Leak 问题及临时解决方法

    首先介绍一下内存泄漏(Memory Leak)的概念,内存泄露是指程序中已动态分配的堆内存由于某种原因未释放或者无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果. 最近在使用W ...

  3. mysql表生成JavaBean

    MySQLToBean.java package org.just.util; import java.io.File; import java.io.FileInputStream; import ...

  4. MySql单表最大8000W+ 之数据库遇瓶颈记

    前言 昨晚救火到两三点,早上七点多醒来,朦胧中醒来发现电脑还开着,赶紧爬起来看昨晚执行的SQL命令结果.由于昨晚升级了阿里云的RDS,等了将近两个小时 还在 升降级中,早上阿里云那边回复升级过程中出现 ...

  5. python cookbook第三版学习笔记十:类和对象(一)

    类和对象: 我们经常会对打印一个对象来得到对象的某些信息. class pair:     def __init__(self,x,y):         self.x=x         self. ...

  6. OCP 11G 实验环境安装文档 ( RedHat5.5 + Oracle11g )

    RedHat5.5 linux下Oracle11g软件安装 一.配置虚拟机 为了创建和配置虚拟机,你需要添加硬件设备如磁盘和cpu,在你开始安装之前,创建一个windows目录作为存放虚拟机的目录 目 ...

  7. poj_3258:River Hopscotch(二分)

    题目链接 L为N+2块石子中最右边石子位置,0最左,M为可移除块数,求移除后相邻石子可达到的最大距离. #include<iostream> #include<cstdio> ...

  8. nopCommerce 3.9 大波浪系列 之 路由扩展 [多语言Seo的实现]

    一.nop种的路由注册 在Global.asax,Application_Start()方法中会进行路由注册,代码如下. public static void RegisterRoutes(Route ...

  9. [bzoj 2243]: [SDOI2011]染色 [树链剖分][线段树]

    Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“ ...

  10. DL4NLP —— seq2seq+attention机制的应用:文档自动摘要(Automatic Text Summarization)

    两周以前读了些文档自动摘要的论文,并针对其中两篇( [2] 和 [3] )做了presentation.下面把相关内容简单整理一下. 文本自动摘要(Automatic Text Summarizati ...