zoukankan      html  css  js  c++  java
  • 网络编程Socket的阻塞和非阻塞IO

      网络应用程序一个很重要的工作是传输数据。传输数据的过程不一样取决于使用哪种“交通工具“,但是传输的方式都是一样的:都是以字节码传输。JAVA开发网络程序传输数据的过程和方式是被抽象了的,我们不需要关注底层接口,只需要使用Java API 或其他网络框架就能达到数据传输的目的。发送数据和接收数据都是字节码。

      Socket网络编程我就不多啰嗦了,这里我通过两个简单的示例比较下阻塞式IO(OIO)和非阻塞式IO(NIO)。

      OIO中,每个线程只能处理一个channel,该线程和该channel绑定。也就是同步的,客户端在发送请求后,必须得在服务端有回应后才发送下一个请求。所以这个时候的所有请求将会在服务端得到同步。

      NIO中,每个线程可以处理多个channel。也就是异步的,客户端在发送请求后,不必等待服务端的回应就可以发送下一个请求,这样对于所有的请求动作来说将会在服务端得到异步,这条请求的链路就象是一个请求队列,所有的动作在这里不会得到同步的。

      你可能使用过Java提供的网络接口工作过,遇到过想从阻塞传输切换到非阻塞传输的情况,这种情况是比较困难的,因为阻塞IO和非阻塞IO使用的API有很大的差异。当我们想切换传输方式时要花很大的精力和时间来重构代码。

      先看一个传统的阻塞IO传输实现的Socket服务端:

     1 /**
     2  *    传统阻塞IO(OIO),原始socket
     3  *  
     4  *   <p>Title: PlainOioServer</p>
     5  *    @author wyx
     6  *    @date 2016-6-15 下午1:36:04
     7  */
     8 public class PlainOioServer {
     9     public void server(int port) throws Exception{
    10         // bind server to port 
    11         final ServerSocket socket = new ServerSocket(port);
    12         while(true){
    13             // accept connection
    14             final Socket clientSocket = socket.accept();
    15             System.out.println("Accepted connection form " + clientSocket);
    16             // create new thread to handle connection
    17             new Thread(new Runnable() {
    18                 
    19                 @Override
    20                 public void run() {
    21                     OutputStream out;
    22                     try {
    23                         out = clientSocket.getOutputStream();
    24                         // write  message to connected client
    25                         out.write("Hi!
    ".getBytes(Charset.forName("UTF-8")));
    26                         out.flush();
    27                         // close connection once message written and flushed
    28                         clientSocket.close();
    29                     } catch (IOException e) {
    30                         e.printStackTrace();
    31                     }
    32                 }
    33             }).start(); // start thread to begin handling
    34         }
    35     }
    36 }

      上面的方式很简洁,但是这种阻塞模式在大连接的情况就会有严重的问题,如:客户端连接超时,服务器响应严重延迟等。为了解决这一问题,我们可以使用异步网络处理所有的并发连接,但问题在于NIO和OIO的API是完全不同的,所以一个用OIO开发的网络应用程序想要使用NIO重构代码几乎是重新开发。

      下面代码是使用Java NIO实现的例子:

     1 /**
     2  *    传统非阻塞式IO(NIO),原始socket
     3  *  
     4  *    <p>Title: PlainNioServer</p>
     5  *    @author wyx
     6  *    @date 2016-6-15 下午1:46:09
     7  */
     8 public class PlainNioServer {
     9     public void server(int port) throws Exception{
    10         System.out.println("Listening for connections on port " + port);
    11         // open selector that handles channels
    12         Selector selector = Selector.open();
    13         // open ServerSocketChannel
    14         ServerSocketChannel serverChannel = ServerSocketChannel.open();
    15         // get ServerSocket
    16         ServerSocket serverSocket = serverChannel.socket();
    17         // bind server to port     
    18         serverSocket.bind(new InetSocketAddress(port));
    19         // set to non-blocking
    20         serverChannel.configureBlocking(false);
    21         // register ServerSocket to selector and specify than it is interested in new accepted clients
    22         serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    23         final ByteBuffer msg = ByteBuffer.wrap("Hi!
    ".getBytes());
    24         while(true){
    25             // Wait for new events that are ready for process. this will block until something happens
    26             int n = selector.select();
    27             if(n > 0){
    28                 // Obtain all SelectionKey instances that received enents
    29                 Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
    30                 while(iter.hasNext()){
    31                     SelectionKey key = iter.next();
    32                     iter.remove();
    33                     //Check if event was because new client ready to get accepted
    34                     if(key.isAcceptable()){
    35                         ServerSocketChannel server = (ServerSocketChannel) key.channel();
    36                         SocketChannel client = server.accept();
    37                         System.out.println("Accepted connection from " + client);
    38                         client.configureBlocking(false);
    39                         // Accept client and register it to seletor 
    40                         client.register(selector, SelectionKey.OP_WRITE, msg.duplicate());
    41                     }
    42                     
    43                     // Check if event was because socket is ready to write data
    44                     if(key.isWritable()){
    45                         SocketChannel client = (SocketChannel) key.channel();
    46                         ByteBuffer buff = (ByteBuffer) key.attachment();
    47                         // Write date to connected client 
    48                         while(buff.hasRemaining()){
    49                             if(client.write(buff) == 0){
    50                                 break;
    51                             }
    52                         }
    53                         client.close();
    54                     }
    55                 }
    56             }
    57         }
    58     }
    59 }

      如你所见,即使它们实现的功能时候一样的,但是代码完全不同。根据不同需求选用不同的实现方式,当然,也可以直接选择流行的网络传输框架实现,如:Netty。以便于后期维护。

      

    The higher I got, the more amazed I was by the view.
  • 相关阅读:
    基于51单片机PWM调速数码管显示测速L298芯片控制直流电机正反运转的项目工程
    基于51单片机通过点击移位按键移位修改LCD1602字符型液晶显示器显示时分秒个位十位数值的计时项目工程
    基于51单片机DS18B20测温LCD1602显示可设时设温调时的项目工程
    基于51单片机定时器0计时外部中断0计数的霍尔传感器精确测速数码管显示测速的项目工程
    基于51单片机定时器0(或定时器1)工作方式2产生周期为1s方波的项目工程
    基于51单片机增加减少键控制PWM(脉冲宽度调制)来调整LED亮灭程度
    PID解释与离散化算法公式
    利用XPT2046芯片转换电位器模拟值为数码管显示数值的项目工程
    Glide生命周期原理
    一文了解 Consistent Hash
  • 原文地址:https://www.cnblogs.com/wyx312/p/5587451.html
Copyright © 2011-2022 走看看