分别使用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

http://www.javaworld.com/article/2078654/java-se/java-se-five-ways-to-maximize-java-nio-and-nio-2.html

http://netty.io/wiki/user-guide-for-5.x.html