Netty入门一:何为Netty
先了解java的网络编程
Netty为何支持高并发
netty是基于java的nio非阻塞通信,而原始的阻塞通信无法满足高并发。下面我们通过两幅图来简要说明
BIO:
这种模式下一个线程处理一个连接,当某个连接阻塞处于等待状态时该线程也处于阻塞等待状态,而我们的机器一共支持的线程数是有限的,如:你的机器只支持30000个线程,那么它最大的并发量也就只有30000。
NIO:
在NIO模式下,我们看到一个线程并不是单一的去处理某一个连接。事实是这样的:先去建立连接,不管建立多少个,建立好之后都交给一个叫selector的处理器,这个处理器的作用就是循环遍历你建立的所有的连接,当遍历到某个连接时,它就判断这个连接是否处于阻塞等待状态,如果处于阻塞等待状态则什么都不做让它继续去等待,继续判断下一个;如果判断到某个连接已经建立通信,则将该连接交给thread去执行通信,这样我们的一个thread就不用专门去为一个连接服务,当某个连接阻塞时我们的线程仍然可以处理其他的连接;即,此时我们的一个线程可以处理多个连接,这就是为什么Netty支持高并发的原因
netty为何通讯快
netty传输速度快的主要原因是NIO的另一个特点:零拷贝。什么是0拷贝呢?我们先说说JAVA的内存机制,JAVA的内存分为5大块:栈、堆、方法区、本地方法区和寄存器。而这中间最重要的一块就是堆,也就是java存放对象的地方。当我们进行通信的时候,数据从客户端传输过来本来是先要将数据拷贝到Socket的缓冲区,再将这些数据拷贝到堆内存,再供程序去使用,当数据量较大的时候平凡的大量的数据拷贝就会占用大量的资源。而NIO的零拷贝是在java的内存里面又分配出一块新的内存专门用来存储客户端传过来的数据,也就是数据不经过socket的缓冲区和堆内存,直接到新开辟的这块内存供程序使用,使用的时候Netty为其提供了专门的Api:ByteBuf。由于数据直接从客户端到ByteBuf,应用程序直接从ByteBuf那里取数据,中间做到了零拷贝,所以netty的传输速度比较快。
netty为何封装好
阻塞I/O
public class BioServer { public void serve(int port) throws IOException { ServerSocket socket = new ServerSocket(8088); //1创建服务端 try { for (;;) {//死循环用来不断监听客户端 Socket clientSocket = socket.accept(); //2监听客户端,如果客户端未建立则程序阻塞挂起,处于等待状态 System.out.println("Accepted connection from " + clientSocket); new Thread(new Runnable() { //3创建线程处理已经建立的线程 @Override public void run() { OutputStream out; try { out = clientSocket.getOutputStream();//从客户端通道中获取输出流对象 out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8"))); //4写数据到客户端 out.flush(); clientSocket.close(); //5 } catch (IOException e) { e.printStackTrace(); try { clientSocket.close(); } catch (IOException ex) { // ignore on close } } } }).start(); //6 } } catch (IOException e) { e.printStackTrace(); } } }
NIO
public class NioServer { public void serve(int port) throws IOException { ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false);//默认就是非阻塞 ServerSocket ss = serverChannel.socket(); InetSocketAddress address = new InetSocketAddress(port); ss.bind(address); //1 Selector selector = Selector.open(); //2 serverChannel.register(selector, SelectionKey.OP_ACCEPT); //3注册Selector监听客户端连接 还有OP_CONNECT、OP_READ、OP_WRITE等事件 final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());//将数据格式化成ByteBuf格式 for (;;) { try { selector.select(); //4 } catch (IOException ex) { ex.printStackTrace(); // handle exception break; } Set<SelectionKey> readyKeys = selector.selectedKeys(); //5获取要监听的事件 Iterator<SelectionKey> iterator = readyKeys.iterator(); //创建迭代器 while (iterator.hasNext()) { SelectionKey key = iterator.next();//遍历到某个事件 iterator.remove(); try { if (key.isAcceptable()) { //6是否是客户端访问事件 ServerSocketChannel server = (ServerSocketChannel)key.channel(); SocketChannel client = server.accept(); client.configureBlocking(false); client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, msg.duplicate()); //7 System.out.println( "Accepted connection from " + client); } if (key.isWritable()) { //8 是否是写事件 SocketChannel client = (SocketChannel)key.channel(); ByteBuffer buffer = (ByteBuffer)key.attachment(); while (buffer.hasRemaining()) { if (client.write(buffer) == 0) { //9 break; } } client.close(); //10 } } catch (IOException ex) { key.cancel(); try { key.channel().close(); } catch (IOException cex) { // 在关闭时忽略 } } } } } }
Netty
public class NettyServer { public void server(int port) throws Exception { final ByteBuf buf = Unpooled.unreleasableBuffer( Unpooled.copiedBuffer("Hi!\r\n", Charset.forName("UTF-8"))); EventLoopGroup group = new OioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); //1 b.group(group) //2 .channel(OioServerSocketChannel.class) .localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer<SocketChannel>() {//3 @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { //4 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE);//5 } }); } }); ChannelFuture f = b.bind().sync(); //6 f.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); //7 } } }
从代码量上来看,Netty就已经秒杀传统Socket编程了,但是有些概念可能不是很明确,在这里给大家介绍一下Netty的一些重要概念,让大家更理解Netty。
- Channel,表示客户端和服务端建立的一次通信连接
- ChannelHandler,处理业务逻辑的处理器。
- ChannelHandlerContext,传输处理过程中的业务数据。
- ChannelPipeline,用于保存处理过程需要用到的ChannelHandler和ChannelHandlerContext。
ByteBuf:
ByteBuf是一个存储字节的容器,最大特点就是使用方便,它有自己的读索引和写索引,方便你对整段字节缓存进行读写,也支持get/set,方便你对其中每一个字节进行读写,他的数据结构如下图所示:
![](http://upload-images.jianshu.io/upload_images/1089449-b1ec677f253b692a.png?imageMogr2/auto-orient/strip|imageView2/2/w/558/format/webp)