NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。
NIO | IO |
---|---|
面向缓冲区Buffer | 面向流Stream |
双向(基于通道Channel) | 单向(分别建立输入流、输出流) |
同步非阻塞(non-blocking) | 同步阻塞 |
选择器(Selector,多路复用) | 无 |
支持字符集编码解码解决方案,支持锁,支持内存映射文件的文件访问接口 | 无 |
1.服务端阻塞点 server.accept();获取套接字的时候 inputStream.read(bytes);输入流读取数据的时候
2.传统socket是短连接,可以做短连接服务器,他无法做长连接,属于一问一答的模式,比如老的tomcat底层用的就是socket,用完就会关掉线程,因此不会出现线程一直被占用的情况,支持处理多个客户端连接
(1)单线程情况下只能有一个客户端(一个线程维护一个连接,也就是一个socket客户连接)线程一直被占用。
(2)用线程池可以有多个客户端连接,但是非常消耗性能(用此案城池,就是老tomcat原理,只不过是用完后就释放)
主要API介绍:
ServerSocketChannel对应传统IO中的ServerSocket。
SocketChannel对应传统IO中的Socket。
Selector 是NIO核心 ,负载监听 ServerSocketChannel与SocketChannel ,支持单线程连多个客户端;类似通道管理器而且底层是c实现的;线程拥有一个selector就可以支持多个客户端。
SelectionKey 相当于map中的key 相当于记录根据不同动作做不同事情,一个key一个事件。
Java IO和NIO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
非直接缓冲区
通过:static ByteBuffe allocate(int capacity)创建指定大小的缓冲区,在JVM内存中创建,在每次调用基础操作系统的一个本机IO之前或者之后,虚拟机都会将缓冲区的内容复制到中间缓冲区(或者从中间缓冲区复制内容),缓冲区的内容驻留在JVM内,因此销毁容易,但是占用JJVM内存开销,处理过程中有复杂的操作。
直接缓冲区
通过:static ByteBuffer allocateDirect(int capacity)字节Buffer创建指定大小的缓冲区,其他类型的Buffer通过wrap()方法创建缓冲区;在JVM内存外开辟空间,在每次调用基础操作系统的一个本机IO之前或者之后,虚拟机都会避免将缓冲区的内容复制到中间缓冲区(或者从缓冲区中复制内容),缓冲区的内容驻留在屋里内存中,少一次复制过程,如果需要循环使用缓冲区,用直接缓冲区可以很大地提高性能;虽然直接缓冲区可以使JVM进行高效的I/O操作,但它使用的内存使操作系统分配的,绕过了JVM堆栈,建立和销毁比堆栈上的缓存区要更大的开销。
直接缓冲区和非直接缓冲区的区别
字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则 Java 虚拟机会尽最大努力直接在此缓冲区上执行本机 I/O 操作。也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。
直接字节缓冲区可以通过调用此类的 allocateDirect() 工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
直接字节缓冲区还可以通过 FileChannel 的 map() 方法 将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer 。 Java 平台的实现有助于通过 JNI 从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。
字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其 Buffer.isDirect() 方法来确定。提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理
使用IDEA创建服务端和客户端程序(新建java工程)
服务器端代码:
import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class server { public static void main(String[] args) throws IOException { //创建客户端的Socket对象(SevereSocket) //ServerSocket (int port)创建绑定到指定端口的服务器套接字 ServerSocket ss=new ServerSocket(50000); //Socket accept()侦听要连接到此套接字并接受他 Socket s=ss.accept(); //获取输入流,读数据,并把数据显示在控制台 InputStream is=s.getInputStream(); byte[] bys=new byte[1024]; int len=is.read(bys); String data=new String(bys,0,len); System.out.println("数据是:"+data); //释放资源 s.close(); ss.close(); } }
客户端代码:
import java.io.IOException; import java.io.OutputStream; import java.net.Socket; public class click { public static void main(String[] args) throws IOException{ //创建客户端的Socket对象 Socket s=new Socket("192.168.0.179", 50000); //获取输出流,写数据 OutputStream os=s.getOutputStream(); os.write("hello,物联网19级".getBytes()); //释放资源 s.close(); } }
运行效果:
IO编程模型在客户端较少的情况下运行良好,但是对于客户端比较多的业务来说,单机服务端可能需要支撑成千上万的连接,IO模型可能就不太合适了。IO编程中,我们看到数据读写是以字节流为单位,效率不高。
使用IDEA创建服务端和客户端程序(新建java工程)
服务器端代码:
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.Set; public class Server { //网络通信IO操作,TCP协议,针对面向流的监听套接字的可选择通道(一般用于服务端) private ServerSocketChannel serverSocketChannel; private Selector selector; /* *开启服务端 */ public void start(Integer port) throws Exception { serverSocketChannel = ServerSocketChannel.open(); selector = Selector.open(); //绑定监听端口 serverSocketChannel.socket().bind(new InetSocketAddress(port)); //设置为非阻塞模式 serverSocketChannel.configureBlocking(false); //注册到Selector上 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); startListener(); } private void startListener() throws Exception { while (true) { // 如果客户端有请求select的方法返回值将不为零 if (selector.select(1000) == 0) { System.out.println("当前没有任务!!!"); continue; } // 如果有事件集合中就存在对应通道的key Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); // 遍历所有的key找到其中事件类型为Accept的key while (iterator.hasNext()) { SelectionKey key = iterator.next(); if (key.isAcceptable()) handleConnection(); if (key.isReadable()) handleMsg(key); iterator.remove(); } } } /** * 处理建立连接 */ private void handleConnection() throws Exception { SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024)); } /* * 接收信息 */ private void handleMsg(SelectionKey key) throws Exception { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer attachment = (ByteBuffer) key.attachment(); channel.read(attachment); System.out.println("当前信息: " + new String(attachment.array())); } public static void main(String[] args) throws Exception { Server myServer = new Server(); myServer.start(8887); } }
客户端代码:
import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class Click { public static void main(String[] args) throws Exception { SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); // 连接服务器 if (!socketChannel.connect(new InetSocketAddress("192.168.0.179", 8887))) { while (!socketChannel.finishConnect()) { System.out.println("connecting..."); } } //发送数据 String str = "hello,物联网19级"; ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes()); socketChannel.write(byteBuffer); System.in.read(); } }
运行效果:
使用IDEA创建服务端和客户端程序(新建java工程)
Project Structure…–>Modules–>Dependencies
搜索
勾选Download to
服务器端代码:
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import java.net.InetSocketAddress; /** * */ public class Server { private int port; public static void main(String[] args){ new Server(12345).start(); } public Server(int port) { this.port = port; } public void start() { /** * 创建两个EventLoopGroup,即两个线程池,boss线程池用于接收客户端的连接, * 一个线程监听一个端口,一般只会监听一个端口所以只需一个线程 * work池用于处理网络连接数据读写或者后续的业务处理(可指定另外的线程处理业务, * work完成数据读写) */ EventLoopGroup boss = new NioEventLoopGroup(1); EventLoopGroup work = new NioEventLoopGroup(); try { /** * 实例化一个服务端启动类, * group()指定线程组 * channel()指定用于接收客户端连接的类,对应java.nio.ServerSocketChannel * childHandler()设置编码解码及处理连接的类 */ ServerBootstrap server = new ServerBootstrap() .group(boss, work).channel(NioServerSocketChannel.class) .localAddress(new InetSocketAddress(port)) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline() .addLast("decoder", new StringDecoder()) .addLast("encoder", new StringEncoder()) .addLast(new HelloWorldServerHandler()); } }); //绑定端口 ChannelFuture future = server.bind().sync(); System.out.println("server started and listen " + port); future.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); }finally { boss.shutdownGracefully(); work.shutdownGracefully(); } } public static class HelloWorldServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("HelloWorldServerHandler active"); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("server channelRead.."); System.out.println(ctx.channel().remoteAddress()+"->Server :"+ msg.toString()); ctx.write("server write"+msg); ctx.flush(); } } }
客户端代码:
import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; /** * */ public class Click { private static final String HOST = "localhost"; private static final int PORT= 12345; public static void main(String[] args){ new Click().start(HOST, PORT); } public void start(String host, int port) { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap client = new Bootstrap().group(group).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline() .addLast("decoder", new StringDecoder()) .addLast("encoder", new StringEncoder()) .addLast(new HelloWorldClientHandler()); } }); ChannelFuture future = client.connect(host, port).sync(); future.channel().writeAndFlush("Hello Netty Server ,I am a netty client"); future.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } } public static class HelloWorldClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("HelloWorldClientHandler Active"); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("HelloWorldClientHandler read Message:"+msg); } } }
运行效果:
分别基于IO、NIO、Netty的Java网络程序
Java NIO详解