OIO和NIO
OIO就是普通的阻塞IO,当一个方法被调用时,方法会阻塞等待有数据准备就绪,然后返回,在网络中,为了同时应对多个客户端连接,需要为每个连接创建一个线程来应对并发,在这种模型中其实CPU大部分时间都是在等待数据准备就绪,利用率不高,并发量低。NIO中线程不会阻塞等待数据返回,当缓冲取数据准备就绪时可以通过事件回调来通知方法返回(或者通过主动检查是否就绪),在linux中有select、poll、epoll可以实现NIO操作,NIO可以用一个线程处理大量并发。
netty简介
netty是一个java高性能轻量级服务器框架。现如今互联网技术非常发达,各自RPC(Romote Procedure Call)框架帮助我们能够迅速上手服务端业务开发而无需关注服务器本身实现,大部分人可能已经忘记了如何开发一个网络服务器。我们可以使用java原生的Socket编程开发一个OIO(Old IO,阻塞IO)的网络应用,或者使用Selector和Channel来实现一个NIO(Non Blocking IO, New IO, 非阻塞IO)网络应用程序,但是这样可能不是很好拓展。当你的应用程序的访问量越来越多,需要从OIO迁移到NIO的时候,除非你有良好的设计模式让网络服务器代码与业务代码解耦(甚至媲美Netty),否则你将会面临很大的迁移代码工作量。除此之外,如果你没有丰富的网络编程经验,对Java高并发理解不够深刻,那么你编写的网络服务器会非常不稳定,运行的时候会遇到不少奇怪的问题。
Netty 是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器 和客户端。自己编写一个高性能系统不仅要求超一流的编程技巧,还需要几 个复杂领域(网络编程、多线程处理和并发)的专业知识。Netty 优雅地处理了这些领域的知识,屏蔽了底层的复杂性,使得即使是网络编程新手也能使用。netty架构和设计非常精巧:
- 业务和网络逻辑解耦
- 模块化和可复用性
- 易测试
netty核心概念
- Channel :Java NIO 的一个基本构造。 它代表一个到实体的开放连接,如读操作和写操作 。可以把 Channel 看作是传入(入站)或者传出(出站)数据的载体。因此,它可以 被打开或者被关闭,连接或者断开连接。
- EventLoop:EventLoop 定义了 Netty 的核心抽象,将会为每个 Channel 分配一个 EventLoop,用于处理连接的生命周期中所发生的事件,包括监听事件,为事件注册Handler,一个EventLoop对应一个线程。
- ChannelHandler:Netty 使用不同的事件来通知我们状态的改变或者是操作的状态。这使得我们能够基于已经发生的事件来触发适当的动作。
- ChannelFuture:每个 Netty 的出站 I/O 操作都将返回一个 ChannelFuture;也就是说,它们都不会阻塞。 正如我们前面所提到过的一样,Netty 完全是异步和事件驱动的。
- BootStrap: 用来把以上关键组件串起来,并引导(启动)一个客户端程序或者服务端程序。
使用java原生库实现OIO和NIO
OIO代码:
public void start(int port) {
try (ServerSocket socket = new ServerSocket(port)) {
while (true) {
System.out.println("waite client to connect");
final Socket clientSocket = socket.accept();
System.out.println("Accepted connection from " + clientSocket);
new Thread(() -> {
InputStream in;
OutputStream out;
try {
in = clientSocket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
System.out.println("read from client: " + br.readLine());
out = clientSocket.getOutputStream();
out.write("Hi! Oio Client\r\n".getBytes(StandardCharsets.UTF_8));
out.flush();
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
}
catch (IOException ex) {
System.out.println("client close error!");
}
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
NIO代码:
public void serve(int port) throws IOException {
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
ServerSocket ssocket = serverChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
ssocket.bind(address);
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
final ByteBuffer msg = ByteBuffer.wrap("Hi! Nin Client\r\n".getBytes());
for (;;) {
try {
selector.select();
} catch (IOException ex) {
ex.printStackTrace();
// handle exception
break;
}
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_WRITE |
SelectionKey.OP_READ, msg.duplicate());
System.out.println("Accepted connection from " + client);
}
if (key.isReadable()) {
SocketChannel client = (SocketChannel)key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = client.read(buffer);
if (len > 0) {
buffer.flip();
String message = buffer.toString();
System.out.println("收到客户端消息:" + message);
}
}
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 ex) {
key.cancel();
try {
key.channel().close();
} catch (IOException cex) {
// ignore on close
}
}
}
}
}
这只是一个功能非常简单的例子,如果要应用到实际项目中还有非常多问题需要考虑。可以看到两种方式代码是完全不一样的,完全不能重用,如果需要考虑从OIO切换到NIO,这需要投入相当多的精力。
使用Netty实现NIO和OIO(deprecated)
NIO代码:
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
System.out.println("netty server started at port: " + port);
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.localAddress(new InetSocketAddress(8888))
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println("server receives: " + byteBuf.toString((CharsetUtil.UTF_8)));
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, netty client", CharsetUtil.UTF_8));
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel active!!");
}
});
}
});
ChannelFuture future = bootstrap.bind().sync();
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
OIO代码:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
.channel(OioServerSocketChannel.class)
与NIO代码仅有两三行差异!
netty运用了良好的设计模式,把网络、线程等各个组件抽象出来,屏蔽了底层了复杂性,让我们能够以一致的方式开发来开发我们的网络应用程序。
如果要使以上程序运行起来,访问github获取并运行。