当我们想要做一个接受数据的IO操作的时候,有IO,BIO,和NIO方法。
BIO实现了同步阻塞IO,数据读取或写入必须阻塞在一个线程内完成。
NIO支持同步阻塞和非阻塞IO模式。
NIO无疑是先进的,效率高的。
我们来看下用JDK NIO的实现方式:
服务端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*
*/
public class NioServer {
private int port;
private Selector selector;
private ExecutorService service = Executors.newFixedThreadPool(5);
public static void main(String[] args){
new NioServer(8080).start();
}
public NioServer(int port) {
this.port = port;
}
public void init() {
ServerSocketChannel ssc = null;
try {
ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(port));
selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NioServer started ......");
} catch (IOException e) {
e.printStackTrace();
}finally {
}
}
public void accept(SelectionKey key) {
try {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
System.out.println("accept a client : " + sc.socket().getInetAddress().getHostName());
} catch (IOException e) {
e.printStackTrace();
}
}
public void start() {
this.init();
while (true) {
try {
int events = selector.select();
if (events > 0) {
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
while (selectionKeys.hasNext()) {
SelectionKey key = selectionKeys.next();
selectionKeys.remove();
if (key.isAcceptable()) {
accept(key);
} else {
service.submit(new NioServerHandler(key));
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static class NioServerHandler implements Runnable{
private SelectionKey selectionKey;
public NioServerHandler(SelectionKey selectionKey) {
this.selectionKey = selectionKey;
}
@Override
public void run() {
try {
if (selectionKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer);
buffer.flip();
System.out.println("收到客户端"+socketChannel.socket().getInetAddress().getHostName()+"的数据:"+new String(buffer.array()));
//将数据添加到key中
ByteBuffer outBuffer = ByteBuffer.wrap(buffer.array());
socketChannel.write(outBuffer);// 将消息回送给客户端
selectionKey.cancel();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
/**
*
*/
public class NioClient {
private static final String host = “127.0.0.1”;
private static final int port = 8080;
private Selector selector;
public static void main(String[] args){
for (int i=0;i<3;i++) {
new Thread(new Runnable() {
@Override
public void run() {
NioClient client = new NioClient();
client.connect(host, port);
client.listen();
}
}).start();
}
}
public void connect(String host, int port) {
try {
SocketChannel sc = SocketChannel.open();
sc.configureBlocking(false);
this.selector = Selector.open();
sc.register(selector, SelectionKey.OP_CONNECT);
sc.connect(new InetSocketAddress(host, port));
} catch (IOException e) {
e.printStackTrace();
}
}
public void listen() {
while (true) {
try {
int events = selector.select();
if (events > 0) {
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
while (selectionKeys.hasNext()) {
SelectionKey selectionKey = selectionKeys.next();
selectionKeys.remove();
//连接事件
if (selectionKey.isConnectable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
if (socketChannel.isConnectionPending()) {
socketChannel.finishConnect();
}
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
socketChannel.write(ByteBuffer.wrap(("Hello this is " + Thread.currentThread().getName()).getBytes()));
} else if (selectionKey.isReadable()) {
SocketChannel sc = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
sc.read(buffer);
buffer.flip();
System.out.println("收到服务端的数据:"+new String(buffer.array()));
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
我们可以看到,如果用JDK提供的NIO方法,实现方式是繁琐的。
netty顺势而出。我们来看下netty实现NIO的方式。
public class NettyTimeServer {
public void bind(int port) throws Exception {
// 配置服务端的 NIO 线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
// 绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
// 等待服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyTimeServerHandler());
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
new NettyTimeServer().bind(port);
}
}
public class NettyTimeServerHandler extends ChannelHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println("server recv body: " + body);
String now = "QUERY TIME ORDER".equals(body) ? new Date(System.currentTimeMillis()).toString()
: "BAD ORDER";
ByteBuf resp = Unpooled.copiedBuffer(now.getBytes());
ctx.write(resp);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
}
public class NettyTimeClient {
public void connect(int port, String host) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try{
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyTimeClientHandler());
}
});
// 发起异步连接操作
ChannelFuture f = b.connect(host, port).sync();
// 等待客户端链路关闭
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
new NettyTimeClient().connect(port, "127.0.0.1");
}
}
public class NettyTimeClientHandler extends ChannelHandlerAdapter {
private final ByteBuf firstMessage;
public NettyTimeClientHandler() {
byte[] req = "QUERY TIME ORDER".getBytes();
firstMessage = Unpooled.buffer(req.length);
firstMessage.writeBytes(req);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(firstMessage);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println("Now is : " + body);
}
}
从上边来看,netty实现就优雅清晰的多了。
netty总结下来和别的NIO实现方式相比有如下特点;
1.API使用简单,开发门槛低。
2.功能强大,预置了多种编码功能,支持多种主流协议。
3.性能高,和业界别的主流NIO框架对比,Netty的综合性能最优。
4.成熟,稳定,经历了大规模的应用和考验。