分别使用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 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() {
@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, 然后覆盖相应方法即可。
参考文档: