BIO编程
一般网络编程模型是Client/Server模型,也就是服务端(Server)提供IP地址和监听的端口Port,客户端(Client)则通过连接操作向服务器监听的地址和端口,通过三次握手来建立连接,如果连接建立成功,双方就会通过网络套字节(Socket)进行通信。 – 以上这段话摘自《Netty权威指南》
那么这里我要介绍下BIO模型:
我们可以看到Client的连接请求是先发送到Acceptor线程上,然后由Acceptor来为每个连接请求都创建一个线程来进行链路处理,处理完后把消息返回给客户端。
在这里要特别说一下Acceptor线程的作用,它的主要作用是监听客户端的连接,一有连接就会创建线程进行处理请求。
通过以上图解,不难看出BIO模型的瓶颈在那里,就是线程数量与客户端访问数成1:1的关系,如果我现在有100万个请求,那我就要创建100万个线程来进行处理,那服务器可能要挂了。
循例上一下实现代码:
public class TimeServer {
public static void main(String[] args) {
int port = 8080;
ServerSocket server = null;
try {
server = new ServerSocket(port);
System.out.println("The time server is start in port:" + port);
Socket socket = null;
// 通过无限循环来监听客户端连接,若有客户端连接则创建线程处理,没有则阻塞在accept()
while (true){
socket = server.accept();
new Thread(/* 这里传自己的类,前提要继承Runnable */).start();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(server != null){
System.out.println("The time server close");
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
server = null;
}
}
}
}
少量的客户端连接可能还可以接收,但是如果在并发量高的情况下,想想还是有点瑟瑟发抖!
NIO编程
虽然之前为了解决BIO的问题也出现过伪异步I/O(各位有兴趣可以去了解一下),但只是对BIO进行优化,并没有实质性的改变问题,这时候NIO(非阻塞I/O)就出现了,能够高速处理大量连接请求,下面我们简单点介绍下NIO的主要概念和功能。
- 缓冲区Buffer:NIO的所有数据处理都是用缓冲区处理,无论读写,读是从缓冲区读,写入数据是写入到缓冲区中。
- 通道Channel:通道就像一个水管,网络的数据都是通过Channel读取和写入。
- 多路复用器Selector:Selector提供选择已经就绪的任务的能力。当Channel已经就绪,就会被Selector轮询出来,然后获取就绪的Channel集合,再进行后续的I/O操作。
下面是NIO执行的流程:
由于NIO的代码实在太复杂,繁琐,这也是Netty产生的原因。我这里就不贴了。各位有兴趣可以去看看。
Netty的诞生
正是因为NIO的API复杂繁琐,同时使用也比较麻烦,需要投入的人力成本,因此就出现Netty,Netty是NIO的框架之一,有了它,程序员就能很容易的实现NIO的的功能。下面看看Netty官方实现的TimeServer:
TimeServer.java
public class TimeServer {
// 端口号
private int port;
public TimeServer (int port) {
this.port = port;
}
public void run() {
// NioEventLoopGroup是一个处理I/O操作的多线程循环事件
// 老板, 接收传入连接
EventLoopGroup boss = new NioEventLoopGroup();
// 工作者, 一旦老板接受了连接并向工作者注册了接受的连接,就处理接受连接的流量
EventLoopGroup worker = new NioEventLoopGroup();
try {
// ServerBootstrap是一个建立服务的辅助类
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss, worker)
// NioServerSocketChannel是用于实例化一个新的Channel来接收进来的连接
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new TimeServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128);
// 绑定端口
ChannelFuture channelFuture = bootstrap.bind(port).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
public static void main(String[] args) {
new TimeServer (8080).run();
}
}
TimeServerHandler.java
public class TimeServerHandler extends ChannelHandlerAdapter {
private int counter;
@Override
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
final ByteBuf time = ctx.alloc().buffer(4);
time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));
final ChannelFuture f = ctx.writeAndFlush(time);
f.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) {
assert f == future;
ctx.close();
}
});
}
public void exceptionCaught(ChannelHandlerContext chx, Throwable cause){
cause.printStackTrace();
chx.close();
}
}
TimeClient.java:
public class TimeClient {
public static void main(String[] args) {
String host = "localhost";
int port = Integer.parseInt("8080");
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeClientHandler());
}
});
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
}
TimeClientHandler.java
public class TimeClientHandler extends ChannelHandlerAdapter {
private int counter;
private byte[] req;
public TimeClientHandler(){
req = ("QUERY TIME ORDER").getBytes();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf m = (ByteBuf) msg;
try {
long currentTimeMillis = (m.readUnsignedInt() - 2208988800L) * 1000L;
System.out.println(new Date(currentTimeMillis));
ctx.close();
} finally {
m.release();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
Client显示结果:
总结
netty的诞生是因为nio的API操作繁琐,投入成本高,而nio的出现则是因为bio的低效率处理,其实,一个新的技术出现主要是为了弥补旧技术的不足,如果你认为新的不足,你也可以创建一个全新的框架。
下一篇会介绍netty的基础用法。