验证码: 看不清楚,换一张 查询 注册会员,免验证
  • {{ basic.site_slogan }}
  • 打开微信扫一扫,
    您还可以在这里找到我们哟

    关注我们

Java IO网络模型如何实现

阅读:1014 来源:乙速云 作者:代码code

Java IO网络模型如何实现

      在开始本篇文章内容之前,有一个简单的关于Socket的知识需要说明:在进行网络通信的时候,需要一对Socket,一个运行于客户端,一个运行于服务端,同时服务端还会有一个服务端Socket,用于监听客户端的连接。下图进行一个简单示意。

      Java IO网络模型如何实现

      那么整个通信流程如下所示。

      • 服务端运行后,会在服务端创建listen-socketlisten-socket会绑定服务端的ipport,然后服务端进入监听状态;

      • 客户端请求服务端时,客户端创建connect-socketconnect-socket描述了其要连接的服务端的listen-socket,然后connect-socketlisten-socket发起连接请求;

      • connect-socketlisten-socket成功连接后(TCP三次握手成功),服务端会为已连接的客户端创建一个代表该客户端的client-socket,用于后续和客户端进行通信;

      • 客户端与服务端通过socket进行网络IO操作,此时就实现了客户端和服务端中的不同进程的通信。

      需要知道的就是,在客户端与服务端通信的过程中,出现了三种socket,分别是。

      • listen-socket。是服务端用于监听客户端建立连接的socket

      • connect-socket。是客户端用于连接服务端的socket

      • client-socket。是服务端监听到客户端连接请求后,在服务端生成的与客户端连接的socket

      (注:上述中的socket,可以被称为套接字,也可以被称为文件描述符。)

      正文

      一. BIO

      BIO,即同步阻塞IO模型。用户进程调用read时发起IO操作,此时用户进程由用户态转换到内核态,只有在内核态中将IO操作执行完后,才会从内核态切换回用户态,这期间用户进程会一直阻塞。

      BIO示意图如下。

      Java IO网络模型如何实现

      简单的BIOJava编程实现如下。

      服务端实现

      public class BioServer {
      
          public static void main(String[] args) throws IOException {
              // 创建listen-socket
              ServerSocket listenSocket = new ServerSocket(8080);
              // 进入监听状态,是一个阻塞状态
              // 有客户端连接时从监听状态返回
              // 并创建代表这个客户端的client-socket
              Socket clientSocket = listenSocket.accept();
              // 获取client-socket输入流
              BufferedReader bufferedReader = new BufferedReader(
                      new InputStreamReader(clientSocket.getInputStream()));
              // 读取客户端发送的数据
              // 如果数据没准备好,会进入阻塞状态
              String data = bufferedReader.readLine();
              System.out.println(data);
      
              // 获取client-socket输出流
              BufferedWriter bufferedWriter = new BufferedWriter(
                      new OutputStreamWriter(clientSocket.getOutputStream()));
              // 服务端向客户端发送数据
              bufferedWriter.write("来自服务端的返回数据n");
              // 刷新流
              bufferedWriter.flush();
          }
      
      }

      客户端实现

      public class BioClient {
      
          public static final String SERVER_IP = "127.0.0.1";
          public static final int SERVER_PORT = 8080;
      
          public static void main(String[] args) throws IOException {
              // 客户端创建connect-socket
              Socket connectSocket = new Socket(SERVER_IP, SERVER_PORT);
              // 获取connect-socket输出流
              BufferedWriter bufferedWriter = new BufferedWriter(
                      new OutputStreamWriter(connectSocket.getOutputStream()));
              // 客户端向服务端发送数据
              bufferedWriter.write("来自客户端的请求数据n");
              // 刷新流
              bufferedWriter.flush();
      
              // 获取connect-socket输入流
              BufferedReader bufferedReader = new BufferedReader(
                      new InputStreamReader(connectSocket.getInputStream()));
              // 读取服务端发送的数据
              String returnData = bufferedReader.readLine();
              System.out.println(returnData);
          }
      
      }

      BIO的问题就在于服务端在accept时是阻塞的,并且在主线程中,一次只能accept一个SocketacceptSocket后,读取客户端数据时又是阻塞的。

      二. Non Blocking IO

      Non Blocking IO,即同步非阻塞IO。是用户进程调用read时,用户进程由用户态转换到内核态后,此时如果没有系统资源数据能够被读取到内核缓冲区中,返回read失败,并从内核态切换回用户态。也就是用户进程发起IO操作后会立即得到一个操作结果。

      Non Blocking IO示意图如下所示。

      Java IO网络模型如何实现

      简单的Non Blocking IOJava编程实现如下。

      public class NonbioServer {
      
          public static final List<SocketChannel> clientSocketChannels = new ArrayList<>();
      
          public static void main(String[] args) throws Exception {
              // 客户端创建listen-socket管道
              // 管道支持非阻塞模式和同时读写
              ServerSocketChannel listenSocketChannel = ServerSocketChannel.open();
              // 设置为非阻塞模式
              listenSocketChannel.configureBlocking(false);
              // 绑定监听的端口号
              listenSocketChannel.socket().bind(new InetSocketAddress(8080));
              // 在子线程中遍历clientSocketChannels并读取客户端数据
              handleSocketChannels();
      
              while (true) {
                  // 非阻塞方式监听客户端连接
                  // 如果无客户端连接则返回空
                  // 有客户端连接则创建代表这个客户端的client-socket管道
                  SocketChannel clientSocketChannel = listenSocketChannel.accept();
                  if (clientSocketChannel != null) {
                      // 设置为非阻塞模式
                      clientSocketChannel.configureBlocking(false);
                      // 添加到clientSocketChannels中
                      // 用于子线程遍历并读取客户端数据
                      clientSocketChannels.add(clientSocketChannel);
                  } else {
                      LockSupport.parkNanos(1000 * 1000 * 1000);
                  }
              }
          }
      
          public static void handleSocketChannels() {
              new Thread(() -> {
                  while (true) {
                      // 遍历每一个client-socket管道
                      Iterator<SocketChannel> iterator = clientSocketChannels.iterator();
                      while (iterator.hasNext()) {
                          SocketChannel clientSocketChannel = iterator.next();
                          ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                          int read = 0;
                          try {
                              // 将客户端发送的数据读取到ByteBuffer中
                              // 这一步的操作也是非阻塞的
                              read = clientSocketChannel.read(byteBuffer);
                          } catch (IOException e) {
                              // 移除发生异常的client-socket管道
                              iterator.remove();
                              e.printStackTrace();
                          }
                          if (read == 0) {
                              System.out.println("客户端数据未就绪");
                          } else {
                              System.out.println("客户端数据为:" + new String(byteBuffer.array()));
                          }
                      }
                      LockSupport.parkNanos(1000 * 1000 * 1000);
                  }
              }).start();
          }
      
      }

      上述是Non Blocking IO的一个简单服务端的实现,相较于BIO,服务端在accept时是非阻塞的,在读取客户端数据时也是非阻塞的,但是还是存在如下问题。

      • 一次只能accept一个Socket

      • 需要在用户进程中遍历所有的SocketChannel并调用read() 方法获取客户端数据,此时如果客户端数据未准备就绪,那么这一次的read() 操作的开销就是浪费的。

      三. IO多路复用

      在上述的BIONon Blocking IO中,一次系统调用,只会获取一个IO的状态,而如果采取IO多路复用机制,则可以一次系统调用获取多个IO的状态。

      也就是获取多个IO的状态可以复用一次系统调用。

      最简单的IO多路复用方式是基于select模型实现,步骤如下。

      • 在用户进程中将需要监控的IO文件描述符(Socket)注册到IO多路复用器中;

      • 执行select操作,此时用户进程由用户态转换到内核态(一次系统调用),然后在内核态中会轮询注册到IO多路复用器中的IO是否准备就绪,并得到所有准备就绪的IO的文件描述符列表,最后返回这些文件描述符列表;

      • 用户进程在select操作返回前会一直阻塞,直至select操作返回,此时用户进程就获得了所有就绪的IO的文件描述符列表;

      • 用户进程获得了就绪的IO的文件描述符列表后,就可以对这些IO进行相应的操作了。

      换言之,IO多路复用中,只需要一次系统调用,IO多路复用器就可以告诉用户进程,哪些IO已经准备就绪可以进行操作了,而如果不采用IO多路复用,则需要用户进程自己遍历每个IO并调用accept() 或者read() 方法去判断,且一次accept() 或者read() 方法调用只能判断一个IO

      四. NIO

      NIO,即New IO。关于NIO,有如下三大组件。

      • channel(管道)。介于buffer(字节缓冲区)和Socket(套接字)之间,用于数据的读写操作;

      • buffer(字节缓冲区)。是用户程序和channel(管道)之间进行读写数据的中间区域;

      • selectorIO多路复用器)。服务端的listen-socketclient-socket,客户端的connect-socket,都可以注册在selector上,注册的时候还需要指定监听的事件,比如为listen-socket指定监听的事件为ACCEPT事件,该事件发生则表示客户端建立了连接,还比如为client-socket指定监听的事件为READ事件,该事件发生则表示客户端发送的数据已经可读。

      NIO的代码实现如下所示。

      服务端实现

      public class NioServer {
      
          private static Selector selector;
      
          public static void main(String[] args) {
      
              try {
                  // 开启并得到多路复用器
                  selector = Selector.open();
                  // 服务端创建listen-socket管道
                  ServerSocketChannel listenSocketChannel = ServerSocketChannel.open();
                  // 设置为非阻塞模式
                  listenSocketChannel.configureBlocking(false);
                  // 为管道绑定端口
                  listenSocketChannel.socket().bind(new InetSocketAddress(8080));
                  // 将listen-socket管道注册到多路复用器上,并指定监听ACCEPT事件
                  listenSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
      
                  while (true) {
                      // 获取发生的事件,这个操作是阻塞的
                      selector.select();
                      // 拿到有事件发生的SelectionKey集合
                      // SelectionKey表示管道与多路复用器的绑定关系
                      Set<SelectionKey> selectionKeys = selector.selectedKeys();
                      // 遍历每个发生的事件,然后判断事件类型
                      // 根据事件类型,进行不同的处理
                      Iterator<SelectionKey> iterator = selectionKeys.iterator();
                      while (iterator.hasNext()) {
                          SelectionKey selectionKey = iterator.next();
                          iterator.remove();
                          if (selectionKey.isAcceptable()) {
                              // 处理客户端连接事件
                              handlerAccept(selectionKey);
                          } else if (selectionKey.isReadable()) {
                              // 处理客户端数据可读事件
                              handlerRead(selectionKey);
                          }
                      }
                      LockSupport.parkNanos(1000 * 1000 * 1000);
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      
          private static void handlerAccept(SelectionKey selectionKey) {
              // 从事件中获取到listen-socket管道
              ServerSocketChannel listenSocketChannel = (ServerSocketChannel) selectionKey.channel();
              try {
                  // 为连接的客户端创建client-socket管道
                  SocketChannel clientSocketChannel = listenSocketChannel.accept();
                  // 设置为非阻塞模式
                  clientSocketChannel.configureBlocking(false);
                  // 将client-socket管道注册到多路复用器上,并指定监听READ事件
                  clientSocketChannel.register(selector, SelectionKey.OP_READ);
                  // 给客户端发送数据
                  clientSocketChannel.write(ByteBuffer.wrap("连接已建立n".getBytes()));
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      
          private static void handlerRead(SelectionKey selectionKey) {
              // 从事件中获取到client-socket管道
              SocketChannel clientSocketChannel = (SocketChannel) selectionKey.channel();
              ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
              try {
                  // 读取客户端数据
                  int read = clientSocketChannel.read(byteBuffer);
                  if (read <= 0) {
                      // 关闭管道
                      clientSocketChannel.close();
                      // 从多路复用器移除绑定关系
                      selectionKey.cancel();
                  } else {
                      System.out.println(new String(byteBuffer.array()));
                  }
              } catch (IOException e1) {
                  try {
                      // 关闭管道
                      clientSocketChannel.close();
                  } catch (IOException e2) {
                      e2.printStackTrace();
                  }
                  // 从多路复用器移除绑定关系
                  selectionKey.cancel();
                  e1.printStackTrace();
              }
          }
      
      }

      客户端实现

      public class NioClient {
      
          private static Selector selector;
      
          public static final String SERVER_IP = "127.0.0.1";
          public static final int SERVER_PORT = 8080;
      
          public static void main(String[] args) {
              try {
                  // 开启并得到多路复用器
                  selector = Selector.open();
                  // 创建connect-socket管道
                  SocketChannel connectSocketChannel = SocketChannel.open();
                  // 设置为非阻塞模式
                  connectSocketChannel.configureBlocking(false);
                  // 设置服务端IP和端口
                  connectSocketChannel.connect(new InetSocketAddress(SERVER_IP, SERVER_PORT));
                  // 将connect-socket管道注册到多路复用器上,并指定监听CONNECT事件
                  connectSocketChannel.register(selector, SelectionKey.OP_CONNECT);
      
                  while (true) {
                      // 获取发生的事件,这个操作是阻塞的
                      selector.select();
                      // 拿到有事件发生的SelectionKey集合
                      // SelectionKey表示管道与多路复用器的绑定关系
                      Set selectionKeys = selector.selectedKeys();
                      // 遍历每个发生的事件,然后判断事件类型
                      // 根据事件类型,进行不同的处理
                      Iterator iterator = selectionKeys.iterator();
                      while (iterator.hasNext()) {
                          SelectionKey selectionKey = iterator.next();
                          iterator.remove();
                          if (selectionKey.isConnectable()) {
                              // 处理连接建立事件
                              handlerConnect(selectionKey);
                          } else if (selectionKey.isReadable()) {
                              // 处理服务端数据可读事件
                              handlerRead(selectionKey);
                          }
                      }
                      LockSupport.parkNanos(1000 * 1000 * 1000);
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      
          private static void handlerConnect(SelectionKey selectionKey) throws IOException {
              // 拿到connect-socket管道
              SocketChannel connectSocketChannel = (SocketChannel) selectionKey.channel();
              if (connectSocketChannel.isConnectionPending()) {
                  connectSocketChannel.finishConnect();
              }
              // 设置为非阻塞模式
              connectSocketChannel.configureBlocking(false);
              // 将connect-socket管道注册到多路复用器上,并指定监听READ事件
              connectSocketChannel.register(selector, SelectionKey.OP_READ);
              // 向服务端发送数据
              connectSocketChannel.write(ByteBuffer.wrap("客户端发送的数据n".getBytes()));
          }
      
          private static void handlerRead(SelectionKey selectionKey) {
              // 拿到connect-socket管道
              SocketChannel connectSocketChannel = (SocketChannel) selectionKey.channel();
              ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
              try {
                  // 读取服务端数据
                  int read = connectSocketChannel.read(byteBuffer);
                  if (read <= 0) {
                      // 关闭管道
                      connectSocketChannel.close();
                      // 从多路复用器移除绑定关系
                      selectionKey.cancel();
                  } else {
                      System.out.println(new String(byteBuffer.array()));
                  }
              } catch (IOException e1) {
                  try {
                      // 关闭管道
                      connectSocketChannel.close();
                  } catch (IOException e2) {
                      e2.printStackTrace();
                  }
                  // 从多路复用器移除绑定关系
                  selectionKey.cancel();
                  e1.printStackTrace();
              }
          }
      
      }
    分享到:
    *特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: hlamps#outlook.com (#换成@)。
    相关文章
    {{ v.title }}
    {{ v.description||(cleanHtml(v.content)).substr(0,100)+'···' }}
    你可能感兴趣
    推荐阅读 更多>