• Java Se : Java NIO(服务端)与BIO(客户端)通信


    Java目前有三种IO相关的API了,下面简单的说一下:

    BIO,阻塞IO,最常用的Java IO API,提供一般的流的读写功能。相信学习Java的人,都用过。

    NIO,非阻塞IO,在JDK1.4中开始出现,大量应用与服务器端编程,用于提高并发访问的性能,常用的NIO框架有Netty,Mina。

    AIO,异步IO,在JDK1.7开始出现。还没有了解过,等以后了解了再说。

    阻塞、非阻塞,同步、异步

    在写这篇文章前,在网上了解了一下,其中争议最的问题要数阻塞、非阻塞怎么理解,异步、同步怎么理解。

           由于每个人想法的不同,很难达到一个一致的答案,又没有真正的大牛出来给这一个准确的定义。这里也简单的说一下,我对这两组名词的理解。

    1)阻塞、非阻塞

           我认为,BIO,NIO没有大家想的那么复杂,就是底层实现中进行数据的读写(IO)采取的两种方案,只不过非阻塞读写要比阻塞IO读写更快一些。

    bio中的InputStream#read()是一个block方法。

     

    2)同步、异步

           同步与异步,我认为说的并不是IO本身,我认为说的是程序采用的编程模型,也就是说采用的是同步的编程模型还是异步的编程模型。

          

           BIO、NIO,他们的区别是操作系统读写数据采用的方式,他们是Java中的概念,在Java领域,他们的底层实现采用的是同步的编程模型。所以说BIO、NIO都是同步的。

           AIO的底层实现应当是异步的编程模型,所以说它是异步IO。

    这里我只是阐述了我对它们的理解,没有与大家争论到底怎么去理解他们。也许我没有大家想的那么深远,毕竟我只是学习了NIO不到一天时间而已。

    针对BIO、NIO,服务器编程如何提高性能

           一个程序运行的快慢,一般有会受到两个因素的影响:1)程序代码是否高效,2)IO读写是否高效。曾经看过这么一幅图,大致内容是:一帮不同角色的人(程序员、运维、项目经理等角色的人)在一起讨论一个应用程序效率地下的问题。

    程序员说的是:给我3个月时间,我能够让程序运行效率提高,当然了,我要调整代码的整体结构…

           运维说:…

           项目经理说:换用读写更快的硬件设备解决这个问题。

           故事我已经无法还原,但是这个故事说的内容就是程序优化带来的效率的提升远不及提高IO速度带来的提升。

    相比于BIO,NIO就是从读写来提升效率的。性能对于服务器来说尤为重要,服务器端编程并不是都采用了NIO编程。

          

           Tomcat服务器内部,就有BIO、NIO两种方式。

    1)BIO如何提高并发访问

    BIO,是一种阻塞IO,服务器端使用BIO进行数据读写时,一般都是采用了一个Socket请求对应一个Thread的方式来提高性能的。

           但是一台服务器上,可以跑的线程数量也是有限制的:线程不是越多越好,毕竟线程间的切换,也是有不小的开销。也不是越少越好,线程太少,极端情况下一个线程,如果用一个线程来解决用户的并发访问,服务器接收一个客户的请求时,其他人都要处于等待状态。你访问网页,多数情况下超过5秒,估计你就关掉它了吧。

      或者采用线程池方案。

    2)采用NIO编程时 如何提高并发访问

    采用选择器轮询可用通道,读写数据。具体的怎么做的就不说了,网上一大坨一大坨的,虽然网上大家写的大多是copy别人的。下面给会出一个例子,所以这里就不多说了,不知道的可以网上找相关的文章。

    一个Thread下开一个Selector,一个Selector处理多个Socket通道(也就是多个用于请求),这样就是一个Thread线程可以同时处理多个用户请求。

    孰优孰劣

           假若说,服务器设置同时处理1000个用户请求(也就是1000个处理用户请求的线程)。假若有10000个人来发请求。

    如果采用BIO API编程,那么就同时只能为1000个人服务,其他的9000人就处于等待状态。

    如果采用NIO API编程,也开启1000个线程,因为一个Thread可以同时处理多个用户请求,咱不说让它处理太多了,就处理10个吧,这样算下来,这个10000个用户请求,就都可以处理了。

    BIO(客户端)与NIO(服务端)通信

           今天学习了NIO,就用NIO来处理浏览器用户请求吧。浏览器发送的肯定不是采用NIO API发送Socket请求的,肯定是使用了阻塞式IO,也就是对应于Java中的BIO了。

    package com.fjn.other.nio.socket;
    
     
    
    import java.io.IOException;
    
    import java.io.InputStream;
    
    import java.io.OutputStream;
    
    import java.net.InetSocketAddress;
    
    import java.net.ServerSocket;
    
    import java.net.Socket;
    
    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.ArrayList;
    
    import java.util.Collection;
    
    import java.util.Iterator;
    
    import java.util.concurrent.Callable;
    
    import java.util.concurrent.ExecutorService;
    
    import java.util.concurrent.Executors;
    
     
    
    @SuppressWarnings({ "unchecked" })
    
    public class NioServer {
    
           ServerSocketChannel serverChannel;
    
           ServerSocket serverSocket;
    
           public final int port;
    
           private Selector selector;
    
           ByteBuffer buffer = ByteBuffer.allocate(1024);
    
     
    
           NioServer(final int port) {
    
                  this.port = port;
    
           }
    
     
    
           void init() throws Exception {
    
     
    
                  // 创建 ServerSocketChannel、ServerSocket
    
                  serverChannel = ServerSocketChannel.open();
    
                  serverSocket = serverChannel.socket();
    
                  serverSocket.bind(new InetSocketAddress(port));
    
     
    
                  // 设置通道为非阻塞模式
    
                  serverChannel.configureBlocking(false);
    
     
    
                  // 开启通道选择器,并注册 ServerSocketChannel
    
                  selector = Selector.open();
    
                  serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    
           }
    
     
    
           void go() throws Exception {
    
                  while (true) {
    
                         int num = selector.select();
    
                         if (num <= 0)
    
                                continue;
    
                         Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
    
     
    
                         while (keyIter.hasNext()) {
    
                                final SelectionKey key = keyIter.next();
    
     
    
                                // 接收一个Socket连接
    
                                // key.isAcceptable()如果为true,说明channnel支持accept(),也就是说明是一个ServerSocketChannel
    
                                if (key.isAcceptable()) {
    
                                       SocketChannel clientChannel = serverChannel.accept();
    
                                       if (clientChannel != null) {
    
                                              clientChannel.configureBlocking(false);
    
                                              clientChannel.register(selector, SelectionKey.OP_READ
    
                                                            | SelectionKey.OP_WRITE);
    
                                       }
    
     
    
                                }
    
                                // 如果isReadable()为true,说明是一个SocketChannel
    
                                if (key.isReadable()) {
    
                                       String requestContent = read(key);
    
                                       // 业务处理
    
                                       // responseContent=doSomthing(requestContent);
    
                                       write(key, "ok" /* responseContent */);
    
                                }
    
     
    
                                keyIter.remove();
    
                         }
    
                  }
    
           }
    
     
    
           // 从通道读取数据
    
           String read(SelectionKey key) throws Exception {
    
                  SocketChannel socketChannel = (SocketChannel) key.channel();
    
                  buffer.clear();// 这一步必须有
    
                  int len = 0;
    
                  StringBuffer str=new StringBuffer();
    
                  while ((len = socketChannel.read(buffer)) > 0) {
    
                         byte[] bs = buffer.array();
    
                         String block=new String(bs, 0, len);
    
                         System.out.println("Server read: " + block);
    
                         str.append(block);
    
                  }
    
                  buffer.clear();
    
                  return str.toString();
    
           }
    
     
    
           // 写数据到通道
    
           void write(SelectionKey key, String str) throws Exception {
    
                  SocketChannel socketChannel = (SocketChannel) key.channel();
    
                  buffer.clear();
    
                  buffer.put(str.getBytes());
    
                  buffer.flip();// 这一步必须有
    
                  socketChannel.write(buffer);
    
           }
    
     
    
     
    
           public static void main(String[] args) throws Exception {
    
                  final int port = 10000;
    
                  NioServer server = new NioServer(port);
    
                  server.init();
    
    ///========================================================
    
                  // 接下来模拟3个Client并发访问服务器
    
                  int poolsize = 3;
    
     
    
                  ExecutorService pool = Executors.newFixedThreadPool(poolsize);
    
                  Collection<Callable> tasks = new ArrayList<Callable>(10);
    
                  final String clientname="clientThread";
    
                  for (int i = 0; i < poolsize; i++) {
    
                         final int n = i;
    
     
    
                         // 若每一个Client都保持使用BIO方式发送数据到Server,并读取数据。
    
                         tasks.add(new Callable() {
    
     
    
                                @Override
    
                                public Object call() throws Exception {
    
     
    
                                       Socket socket = new Socket("127.0.0.1", port);
    
                                       final InputStream input = socket.getInputStream();
    
                                       final OutputStream out = socket.getOutputStream();
    
                                       final String clientname_n = clientname + "_" + n;
    
     
    
                                       // BIO读取数据线程
    
                                       new Thread(clientname_n + "_read") {
    
                                              @Override
    
                                              public void run() {
    
                                                     byte[] bs = new byte[1024];
    
                                                     while (true) {
    
                                                            try {
    
                                                                   Thread.sleep(1000);
    
                                                            } catch (InterruptedException e) {
    
                                                                   e.printStackTrace();
    
                                                            }
    
                                                            int len = 0;
    
                                                            try {
    
                                                                   while ((len = input.read(bs)) != -1) {
    
                                                                          System.out.println("Clinet thread "
    
                                                                                        + Thread.currentThread()
    
                                                                                                      .getName() + " read: "
    
                                                                                        + new String(bs, 0, len));
    
                                                                   }
    
                                                            } catch (IOException e) {
    
                                                                   e.printStackTrace();
    
                                                            }
    
                                                     }
    
                                              }
    
                                       }.start();
    
     
    
                                       // BIO写数据线程
    
                                       new Thread(clientname_n + "_write") {
    
                                              @Override
    
                                              public void run() {
    
                                                     int a = 0;
    
                                                     while (true) {
    
     
    
                                                            try {
    
                                                                   Thread.sleep(100);
    
                                                            } catch (InterruptedException e) {
    
                                                                   e.printStackTrace();
    
                                                            }
    
                                                            String str = Thread.currentThread().getName()
    
                                                                          + " hello, " + a;
    
                                                            try {
    
                                                                   out.write(str.getBytes());
    
                                                                   a++;
    
                                                            } catch (IOException e) {
    
                                                                   e.printStackTrace();
    
                                                            }
    
                                                     }
    
                                              }
    
                                       }.start();
    
     
    
                                       return null;
    
                                }
    
     
    
                         });
    
                  }
    
                  pool.invokeAll((Collection<? extends Callable<Object>>) tasks);
    
     
    
                  server.go();
    
     
    
           }
    
    }
    View Code

    上面的测试的是3个Client采用BIO API不断的并发的发送Socket 请求到Server端。Server采用NIO API处理Client的请求并作出响应,然后Client接收响应。

  • 相关阅读:
    台阶问题,100层台阶,1,2,3步组合走完。一种有几种组合?
    idea 导入gitlab项目
    查找学生信息
    谁是你潜在的朋友
    Sort
    统计同成绩学生人数
    打印日期
    今年的第几天?
    DayOfWeek
    日期差值
  • 原文地址:https://www.cnblogs.com/f1194361820/p/4019575.html
Copyright © 2020-2023  润新知