分别使用Java IO、NIO、Netty实现的一个Echo Server示例
jopen
9年前
分别使用Java IO、Java NIO、Netty来实现一个简单的EchoServer(即原样返回客户端的输入信息)。
Java IO
int port = 9000; ServerSocket ss = new ServerSocket(port); while (true) { final Socket socket = ss.accept(); new Thread(new Runnable() { public void run() { while (true) { try { BufferedInputStream in = new BufferedInputStream( socket.getInputStream()); byte[] buf = new byte[1024]; int len = in.read(buf); // read message from client String message = new String(buf, 0, len); BufferedOutputStream out = new BufferedOutputStream( socket.getOutputStream()); out.write(message.getBytes()); // echo to client out.flush(); } catch (IOException e) { e.printStackTrace(); } } } }).start(); }
实际效果用telnet来演示,如下所示:
$ telnet 127.0.0.1 9000 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. hi hi 你好 你好
java io缺点:
需要为每个客户端连接创建一个线程。
Java NIO
ServerSocketChannel ssChannel = ServerSocketChannel.open(); int port = 9001; ssChannel.bind(new InetSocketAddress(port)); Selector selector = Selector.open(); ssChannel.configureBlocking(false); ssChannel.register(selector, SelectionKey.OP_ACCEPT); //注册监听连接请求 while (true) { selector.select();//阻塞 直到某个channel注册的事件被触发 Set<SelectionKey> keys = selector.selectedKeys(); for (SelectionKey key : keys) { if (key.isAcceptable()) { //客户端连接请求 ServerSocketChannel ssc = (ServerSocketChannel) key .channel(); SocketChannel sc = ssc.accept(); sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); //注册监听客户端输入 } if(key.isReadable()){ //客户端输入 SocketChannel sc = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); sc.read(buffer); buffer.flip(); sc.write(buffer); } } keys.clear(); }
优点:
事件驱动 可通过一个线程来管理多个连接(channel)
但要注意 并不是任何场景都是NIO优于IO的。
Netty
public class NettyEchoServer { public class EchoServerHandler extends ChannelHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { //ehco to client ctx.write(msg); ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // Close the connection when an exception is raised. cause.printStackTrace(); ctx.close(); } } private int port; public NettyEchoServer(int port) { this.port = port; } public void run() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast( new EchoServerHandler()); } }).option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); // Bind and start to accept incoming connections. ChannelFuture f = b.bind(port).sync(); // Wait until the server socket is closed. // In this example, this does not happen, but you can do that to gracefully shut down your server. f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { int port = 9002; new NettyEchoServer(port).run(); } }
上例摘自官方文档。
优点:
事件驱动的概念比NIO更直观 似乎仅需要继承ChannelHandlerAdapter, 然后覆盖相应方法即可。
参考文档:
http://en.wikipedia.org/wiki/Non-blocking_I/O_(Java)
https://today.java.net/pub/a/today/2007/02/13/architecture-of-highly-scalable-nio-server.html