栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JavaSE面试题(十五):网络IO流

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JavaSE面试题(十五):网络IO流

什么是bio

同步阻塞式IO,服务端创建一个ServerSocket,然后客户端用一个Socket去连接那个ServerSocket,然后ServerSocket接收到一个Socket的连接请求就创建一个Socket和一个线程去跟那个Socket进行通信。

public class BioServer {  
    public static void main(String[] args) {
 // 服务端开启一个端口进行监听
 int port = 8080;
 ServerSocket serverSocket = null;   //服务端
 Socket socket;  //客户端
 InputStream in = null;
 OutputStream out = null;
 try {

     serverSocket = new ServerSocket(port);  //通过构造函数创建ServerSocket,指定监听端口,如果端口合法且空闲,服务器就会监听成功
     // 通过无限循环监听客户端连接,如果没有客户端接入,则会阻塞在accept操作
     while (true) {
  System.out.println("Waiting for a new Socket to establish" + " ," + new Date().toString());
  socket = serverSocket.accept();//阻塞  三次握手
  in = socket.getInputStream();
  byte[] buffer = new byte[1024];
  int length = 0;
  while ((length = in.read(buffer)) > 0) {//阻塞
      System.out.println("input is:" + new String(buffer, 0, length) + " ," + new Date().toString());
      out = socket.getOutputStream();
      out.write("success".getBytes());
      System.out.println("Server end" + " ," + new Date().toString());
  }

     }
 } catch (Exception e) {
     e.printStackTrace();
 } finally {
     // 必要的清理活动
     if (serverSocket != null) {
  try {
      serverSocket.close();
  } catch (IOException e) {
      e.printStackTrace();
  }
     }
     if (in != null) {
  try {
      in.close();
  } catch (IOException e) {
      e.printStackTrace();
  }
     }
     if (out != null) {
  try {
      out.close();
  } catch (IOException e) {
      e.printStackTrace();
  }
     }
 }
    }
}
什么是nio

同步非阻塞
包括Selector,这是多路复用器,selector会不断轮询注册的channel,如果某个channel上发生了读写事件,selector就会将这些channel获取出来,我们通过SelectionKey获取有读写事件的channel,就可以进行IO操作。一个Selector就通过一个线程,就可以轮询成千上万的channel,这就意味着你的服务端可以接入成千上万的客户端。

public class NioDemo implements Runnable {
    public int id = 100001;
    public int bufferSize = 2048;

    @Override
    public void run() {
 init();
    }

    public void init() {
 try {
     // 创建通道和选择器
     ServerSocketChannel socketChannel = ServerSocketChannel.open();
     Selector selector = Selector.open();
     InetSocketAddress inetSocketAddress = new InetSocketAddress(
      InetAddress.getLocalHost(), 4700);
     socketChannel.socket().bind(inetSocketAddress);
     // 设置通道非阻塞 绑定选择器
     socketChannel.configureBlocking(false);
     socketChannel.register(selector, SelectionKey.OP_ACCEPT).attach(
      id++);
     System.out.println("Server started .... port:4700");
     listener(selector);

 } catch (Exception e) {

 }
    }

    public void listener(Selector in_selector) {
 try {
     while (true) {
  Thread.sleep(1 * 1000);
  in_selector.select(); // 阻塞 直到有就绪事件为止
  Set readySelectionKey = in_selector
   .selectedKeys();
  Iterator it = readySelectionKey.iterator();
  while (it.hasNext()) {
      SelectionKey selectionKey = it.next();
      // 判断是哪个事件
      if (selectionKey.isAcceptable()) {// 客户请求连接
   System.out.println(selectionKey.attachment()
    + " - 接受请求事件");
   // 获取通道 接受连接,
   // 设置非阻塞模式(必须),同时需要注册 读写数据的事件,这样有消息触发时才能捕获
   ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey
    .channel();
   serverSocketChannel
    .accept()
    .configureBlocking(false)
    .register(
     in_selector,
     SelectionKey.OP_READ
      | SelectionKey.OP_WRITE).attach(id++);
   System.out
    .println(selectionKey.attachment() + " - 已连接");

   // 下面这种写法是有问题的 不应该在serverSocketChannel上面注册
   
      }
      if (selectionKey.isReadable()) {// 读数据
   System.out.println(selectionKey.attachment()
    + " - 读数据事件");
   SocketChannel clientChannel = (SocketChannel) selectionKey.channel();
   ByteBuffer receiveBuf = ByteBuffer.allocate(bufferSize);
   clientChannel.read(receiveBuf);
   System.out.println(selectionKey.attachment()
    + " - 读取数据:" + getString(receiveBuf));
      }
      if (selectionKey.isWritable()) {// 写数据
   System.out.println(selectionKey.attachment()
    + " - 写数据事件");
   SocketChannel clientChannel = (SocketChannel) selectionKey.channel();
   ByteBuffer sendBuf = ByteBuffer.allocate(bufferSize);
   String sendText = "hellon";
   sendBuf.put(sendText.getBytes());
   sendBuf.flip(); //写完数据后调用此方法
   clientChannel.write(sendBuf);
      }
      if (selectionKey.isConnectable()) {
   System.out.println(selectionKey.attachment()
    + " - 连接事件");
      }
      // 必须removed 否则会继续存在,下一次循环还会进来,
      // 注意removed 的位置,针对一个.next() remove一次
      it.remove();
  }
     }
 } catch (Exception e) {
     System.out.println("Error - " + e.getMessage());
     e.printStackTrace();
 }

    }

    
    public static String getString(ByteBuffer buffer) {
 String string = "";
 try {
     for (int i = 0; i < buffer.position(); i++) {
  string += (char) buffer.get(i);
     }
     return string;
 } catch (Exception ex) {
     ex.printStackTrace();
     return "";
 }
    }
}
什么是aio

异步非阻塞
每个连接发送过来的请求,都会绑定一个buffer,然后通知操作系统去异步完成读,此时你的程序是会去干别的事儿的,等操作系统完成数据读取之后,就会回调你的接口,给你操作系统异步读完的数据。

public class AIOServer {

    public final static int PORT = 9888;
    private AsynchronousServerSocketChannel server;

    public AIOServer() throws IOException {
 server = AsynchronousServerSocketChannel.open().bind(
  new InetSocketAddress(PORT));
    }

    public void startWithFuture() throws InterruptedException,
     ExecutionException, TimeoutException {
 while (true) {// 循环接收客户端请求
     Future future = server.accept();
     AsynchronousSocketChannel socket = future.get();// get() 是为了确保 accept 到一个连接
     handleWithFuture(socket);
 }
    }

    public void handleWithFuture(AsynchronousSocketChannel channel) throws InterruptedException, ExecutionException, TimeoutException {
 ByteBuffer readBuf = ByteBuffer.allocate(2);
 readBuf.clear();

 while (true) {// 一次可能读不完
     //get 是为了确保 read 完成,超时时间可以有效避免DOS攻击,如果客户端一直不发送数据,则进行超时处理
     Integer integer = channel.read(readBuf).get(10, TimeUnit.SECONDS);
     System.out.println("read: " + integer);
     if (integer == -1) {
  break;
     }
     readBuf.flip();
     System.out.println("received: " + Charset.forName("UTF-8").decode(readBuf));
     readBuf.clear();
 }
    }

    public void startWithCompletionHandler() throws InterruptedException,
     ExecutionException, TimeoutException {
 server.accept(null,
  new CompletionHandler() {
      public void completed(AsynchronousSocketChannel result, Object attachment) {
   server.accept(null, this);// 再此接收客户端连接
   handleWithCompletionHandler(result);
      }

      @Override
      public void failed(Throwable exc, Object attachment) {
   exc.printStackTrace();
      }
  });
    }

    public void handleWithCompletionHandler(final AsynchronousSocketChannel channel) {
 try {
     final ByteBuffer buffer = ByteBuffer.allocate(4);
     final long timeout = 10L;
     channel.read(buffer, timeout, TimeUnit.SECONDS, null, new CompletionHandler() {
  @Override
  public void completed(Integer result, Object attachment) {
      System.out.println("read:" + result);
      if (result == -1) {
   try {
channel.close();
   } catch (IOException e) {
e.printStackTrace();
   }
   return;
      }
      buffer.flip();
      System.out.println("received message:" + Charset.forName("UTF-8").decode(buffer));
      buffer.clear();
      channel.read(buffer, timeout, TimeUnit.SECONDS, null, this);
  }

  @Override
  public void failed(Throwable exc, Object attachment) {
      exc.printStackTrace();
  }
     });
 } catch (Exception e) {
     e.printStackTrace();
 }
    }

    public static void main(String args[]) throws Exception {
// new AIOServer().startWithFuture();
 new AIOServer().startWithCompletionHandler();
 Thread.sleep(100000);
    }

}

什么是epoll

一种多路复用的技术,可以解决之前poll和select大量并发连接情况下cpu利用率过高,以及需要遍历整个被侦听的描述符集的问题。epoll只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

什么是mmap技术

把一个磁盘文件映射到内存里来,然后把映射到内存里来的数据通过socket发送出去
有一种mmap技术,也就是内存映射,直接将磁盘文件数据映射到内核缓冲区,这个映射的过程是基于DMA引擎拷贝的,同时用户缓 冲区是跟内核缓冲区共享一块映射数据的,建立共享映射之后,就不需要从内核缓冲区拷贝到用户缓冲区了
光是这一点,就可以避免一次拷贝了,但是这个过程中还是会用户态切换到内核态去进行映射拷贝,接着再次从内核态切换到用户态, 建立用户缓冲区和内核缓冲区的映射
接着把数据通过Socket发送出去,还是要再次切换到内核态
接着直接把内核缓冲区里的数据拷贝到Socket缓冲区里去,然后再拷贝到网络协议引擎里,发送出去就可以了,最后切换回用户态
减少一次拷贝,但是并不减少切换次数,一共是4次切换,3次拷贝

什么是零拷贝技术

linux提供了sendfile,也就是零拷贝技术
这个零拷贝技术,就是先从用户态切换到内核态,在内核态的状态下,把磁盘上的数据拷贝到内核缓冲区,同时从内核缓冲区拷贝一些 offset和length到Socket缓冲区;接着从内核态切换到用户态,从内核缓冲区直接把数据拷贝到网络协议引擎里去
同时从Socket缓冲区里拷贝一些offset和length到网络协议引擎里去,但是这个offset和length的量很少,几乎可以忽略
只要2次切换,2次拷贝,就可以了

说一下select,poll,epoll的区别?

select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。
epoll也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。

select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/238855.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号