这是我参与8月更文挑战的第21天,活动详情查看:8月更文挑战
一、不通过Netty使用OIO和NIO
下面我们使用JDK API的应用程序的阻塞(OIO)版本和异步(NIO)版本。以下代码展示了其阻塞版本的实现。
public class PlainOioServer {
public void server(int port) throws IOException {
// 将服务器绑定到指定端口
final ServerSocket socket = new ServerSocket(port);
try {
for (;;) {
// 接受连接
final Socket clientSocket = socket.accept();
System.out.println("Accepted connection from " + clientSocket);
// 创建一个新的线程来处理该连接
new Thread(()->{
OutputStream out;
try {
out = clientSocket.getOutputStream();
// 将消息写给已连接的客户端
out.write("Hi\r\n".getBytes(Charset.forName("UTF-8")));
out.flush();
// 关闭连接
clientSocket.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
以下代码完全可以处理中等数量的并发客户端。但是随着应用程序变得流行起来,你会发现它并不能很好地伸缩到支撑成千上万的并发。当你决定改用异步网络编程,你会发现异步API是完全不同的,以至于你不得不重写程序。
以下代码是未使用Netty的异步网络代码清单:
public class PlainNioServer {
public void server(int port) throws Exception {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
ServerSocket socket = serverSocketChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
// 将服务器绑定到选定的端口
socket.bind(address);
// 打开Selector来处理Channel
Selector selector = Selector.open();
// 将ServerSocketChannel注册到Selector以接受连接
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
for (;;) {
try {
// 等待需要处理的新事件;阻塞将一直持续到下一个传入事件
selector.select();
} catch (IOException e) {
e.printStackTrace();
break;
}
// 获取所有接收事件的SelectionKey实例
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
try {
// 检查事件是否是一个新的已经就绪可以被接受的连接
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
// 接受客户端并将它注册到选择器
client.register(selector,
SelectionKey.OP_READ|SelectionKey.OP_WRITE, msg.duplicate());
System.out.println("Accepted connection from " + client);
}
// 检查套接字是否已经准备好写数据
if (key.isWritable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
while (buffer.hasRemaining()) {
// 将数据写到已连接的客户端
if (client.write(buffer) == 0) {
break;
}
}
// 关闭连接
client.close();
}
} catch (IOException e) {
key.cancel();
key.channel().close();
}
}
}
}
}
虽然这段代码所做的事情与上一版本完全相同,但是代码却截然不同。虽然代码复杂性增加了,但效率提高了。
二、通过Netty使用OIO和NIO
以下代码清单,是使用Netty框架编写的阻塞网络编程。
public class NettyOioServer {
public void server(int port) throws Exception {
final ByteBuf buf = Unpooled.unreleasableBuffer(
Unpooled.copiedBuffer("Hi!\r\n", Charset.forName("UTF-8")));
// netty官方不建议使用,推荐使用NIO / EPOLL / KQUEUE transport
EventLoopGroup group = new OioEventLoopGroup();
try {
// 创建ServerBootstrap
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group)
// 使用OioServerSocketChannel以允许阻塞模式(旧的I/O)
.channel(OioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
// 指定ChannelInitializer,对于每个已接受的连接都调用它
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(
// 添加一个ChannelInboundHandlerAdapter以拦截和处理事件
new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(buf.duplicate())
// 将消息写到客户端,并添加ChannelFutureListener,以便消息一被写完就关闭连接
.addListener(ChannelFutureListener.CLOSE);
}
}
);
}
});
// 绑定服务器以接受连接
ChannelFuture future = bootstrap.bind().sync();
future.channel().closeFuture().sync();
} finally {
// 释放所有的资源
group.shutdownGracefully().sync();
}
}
}
非阻塞的Netty,只需要改动以下两处代码就能实现Netty的异步网络编程,第一处把EventLoopGroup group = new OioEventLoopGroup()改成EventLoopGroup group = new NioEventLoopGroup(),还一处是.channel(OioServerSocketChannel.class)改成.channel(NioServerSocketChannel.class),把这两处代码一改就成了Netty异步网络编程
因为Netty为每种传输的实现都暴露了相同的API,所以无论选用哪一种传输的实现,代码都仍然不受影响。在所有的情况下,传输的实现都依赖于interface Channel、ChannelPipeline和ChannelHandler。