开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第24天,点击查看活动详情
2.2.JavaIO网络应用
2.2.1.IO网络技术
- 直接 IO:磁盘--->内核空间的内核缓冲区--->应用程序内存--->内核空间的Socket缓冲区--->网络。
- 内存文件映射:内核空间的内核缓冲区与应用程序内存直接映射,磁盘--->内核空间的内核缓冲区--->内核空间的Socket缓冲区--->网络。
- 0拷贝:内核空间的内核缓冲区与Socket缓冲区直接映射,磁盘读入,直接输出给网络。
2.2.2.BIO
-
同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
场景:用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK 1.4 以前的唯一选择,但程序直观简单易理解。
问题:当并发数较大时,需要创建大量线程来处理连接,系统资源占用较大;连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在 Read 操作上,造成线程资源浪费
-
下面是一个使用BIO设计方式的服务器案例,也是最为简单的网络通信案例。(在基础的时候已经介绍过)
public class BIOServer{ public static void main(String[] args) throws IOException { ServerSocket server = new ServerSocket(8888); System.out.println("BIO server is listening to " + server.getLocalSocketAddress()); while(true){ Socket client = server.accept(); System.out.println("connect from " + client.getRemoteSocketAddress()); Scanner input = new Scanner(client.getInputStream()); while(true){ String reqStr = input.nextLine(); if("quit".equals(reqStr)) break; System.out.println(String.format("from %s, %s",client.getRemoteSocketAddress(), reqStr)); String res = "from server hello " + reqStr + "/n"; client.getOutputStream().write(res.getBytes()); } } } }这个非常简陋的服务器,便可以支持客户端的连接。当然有着非常大的优化空间。
2.2.3.NIO
-
同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求时才启动一个线程进行处理。
场景:适用于连接数目多且连接比较短 (轻操作) 的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK 1.4 开始支持
-
下面是一个使用NIO设计方式的服务器案例,该服务器最大的改进是支持多个连接,无需创建多个线程。
public class NIOServer{ public static void main(String[] args){ ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false); serverChannel.bind(new InetSocketAddress(8888)); System.out.println("NIO server is listening to " + serverChannel.getLocalAddress()); while(true){ SocketChannel clientChannel = serverChannel.accept(); if(clientChannel != null) System.out.println("connect from " clientChannel.getRemoteAddress()); } } }虽然该方式允许多个连接,但是,速度依然很慢,因为每次进行读写数据的时候都要进行内核调用,而内核调用是无法避免的阻塞状态的。
-
下面的例子使用一个选择器,选择一批符合要求的通道,统一调用内核,即将多次调用内核的方式合并为一次调用,类似于批处理,程序设计中很多运用了这种思想 (如数据库的批处理) 。
public class NIOServerNew{ public static void main(String[] args) throws IOException { //获取选择器 Selector selector = Selector.open(); //获取通道 ServerSocketChannel serverChannel = ServerSocketChannel.open(); //通道设为非阻塞 serverChannel.configureBlocking(false); //绑定链接端口 serverChannel.bind(new InetSocketAddress(8888)); System.out.println("NIO server is listening to " + serverChannel.getLocalAddress()); //将通道注册到选择器上,并注册IO事件为等待连接 serverChannel.register(selector, SelectionKey.OP_ACCEPT); //轮询IO事件 while(selector.select() > 0){ //获取选择键集合 Iterator<SelectionKey> selectKeys = selector.selectedKeys().iterator(); while(selectKeys.hasNext()){ //获取单个选择键,后续根据不同的选择键处理 SelectionKey selectionKey = selectKeys.next(); //判断事件类型 if(selectionKey.isAcceptable()){ SocketChannel clientChannel = serverChannel.accept(); clientChannel.configureBlocking(false); clientChannel.register(selector, SelectionKey.OP_READ); }else if(selectionKey.isReadable()){ SocketChannel clientChannel = (SocketChannel) selectionKey.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int length = 0 ; while((length = clientChannel.read(buffer)) > 0) { buffer.filp(); System.out.println(new String(buffer.array(),0,length)); buffer.clear(); } clientChannel.close(); } selectKeys.remove(); } } serverChannel.close(); } }使用 NIO 的设计思想编写客户端测试,当然可以编写多个客户端,只要将代码复制,启动多个即可:
public class NIOClient{ public static void main(String[] args){ InetSocketAddress address = new InetSocketAddress(127.0.0.1, 8888); //获取通道(channel) SocketChannel clientChannel = SocketChannel.open(address); //切换成非阻塞模式 clientChannel.configureBlocking(false); //不断的自旋、等待连接完成,或者做一些其他的事情 while (!clientChannel.finishConnect()) { //. . . } System.out.println("客户端连接成功"); //分配指定大小的缓冲区 ByteBuffer buffer = buffer.allocate(1024); buffer.put("Msg to the world.".getBytes()); buffer.flip(); clientChannel.write(byteBuffer); while(true){ try { Thread.sleep(1000); }catch (Exception e){ e.printStackTrace(); } } //socketChannel.shutdownOutput(); //socketChannel.close(); } }
2.2.4.AIO
-
异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的 I/O 请求都是由 OS 先完成再通知服务器应用去启动线程进行处理。
场景:用于连接数目多且连接比较长 (重操作) 的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK 7 开始支持。目前并未得到广泛应用,Linux 对 AIO 的支持也并不完善。