firemail

标题: 网络编程基本概念 [打印本页]

作者: java    时间: 2017-12-18 16:43
标题: 网络编程基本概念
本帖最后由 java 于 2017-12-18 18:17 编辑

http://blog.csdn.net/haoyuyang/article/details/53231585

2. IO(BIO)与NIO的区别
其本质就是阻塞和非阻塞的区别。
阻塞概念:应用程序在获取网络数据的时候,如果网络传输数据很慢,那么程序就一直等着,直到传输完毕为止。
非阻塞概念:应用程序直接可以获取已经准备就绪的数据,无需等待。
IO为同步阻塞形式,NIO为同步非阻塞形式。NIO没有实现异步,在JDK1.7之后,升级了NIO库包,支持异步非阻塞通信模型,即NIO2.0(AIO)。
同步和异步:同步和异步一般是面向操作系统与应用程序对IO操作的层面上来区别的。①同步时,应用程序会直接参与IO读写操作,并且应用程序会直接阻塞到某一个方法上,直到数据准备就绪(BIO);或者采用轮询的策略实时检查数据的就绪状态,如果就绪则获取数据(NIO)。②异步时,则所有的IO读写操作都交给操作系统处理,与应用程序没有直接关系,应用程序并不关心IO读写,当操作系统完成IO读写操作时,会向应用程序发出通知,应用程序直接获取数据即可。
同步说的是Server服务端的执行方式,阻塞说的是具体的技术,接收数据的方式、状态(io、nio)。

3.NIO编程介绍
学习NIO编程,首先需要了解几个概念:
(1)Buffer(缓冲区)
Buffer是一个对象,它包含一些需要写入或者读取的数据。在NIO类库中加入Buffer对象,体现了新类库与原IO的一个重要区别。在面向流的IO中,可以直接将数据写入或读取到Stream对象中。在NIO类库中,所有的数据都是用缓冲区处理的(读写)。 缓冲区实质上是一个数组,通常它是一个字节数组(ByteBuffer),也可以使用其他类型的数组。这个数组为缓冲区提供了访问数据的读写等操作属性,如位置、容量、上限等概念,具体的可以参考API文档。
Buffer类型:最常使用的是ByteBuffer,实际上每一种java基本类型都对应了一种缓存区(除了Boolean类型)。
①ByteBuffer②CharBuffer③ShortBuffer④IntBuffer⑤LongBuffer⑥FloatBuffer⑦DoubleBuffer
(2)Channel(管道、通道)
Channel就像自来水管道一样,网络数据通过Channel读取和写入,通道与流的不同之处在于通道是双向的,而流只能在一个方向上移动(一个流必须是InputStream或者OutputStream的子类),而通道可以用于读、写或者二者同时进行,最关键的是可以和多路复用器集合起来,有多种的状态位,方便多路复用器去识别。通道分为两大类:一类是用于网络读写的SelectableChannel,另一类是用于文件操作的FileChannel,我们使用的SocketChannel和ServerSocketChannel都是SelectableChannel的子类。
(3)Selector(选择器、多路复用器)
是NIO编程的基础,非常重要。多路复用器提供选择已经就绪的任务的能力。简单说,就是Selector会不断的轮询注册在其上的通道(Channel),如果某个通道发生了读写操作,这个通道就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以取得就绪的Channel集合,从而进行后续的IO操作。一个多路复用器(Selector)可以负责成千上万的通道(Channel),没有上限。这也是JDK使用了epoll代替传统的select实现,获得连接句柄(客户端)没有限制。那也就意味着我们只要一个线程负责Selector的轮询,就可以接入成千上万个客户端,这是JDK NIO库的巨大进步。
Selector线程类似一个管理者(Master),管理了成千上万个管道,然后轮询哪个管道的数据已经准备好了,通知CPU执行IO的读取或写入操作。
Selector模式:当IO事件(管道)注册到选择器以后,Selector会分配给每个管道一个key值,相当于标签。Selector选择器是以轮询的方式进行查找注册的所有IO事件(管道),当IO事件(管道)准备就绪后,Selector就会识别,会通过key值来找到相应的管道,进行相关的数据处理操作(从管道中读取或写入数据,写到缓冲区中)。每个管道都会对选择器进行注册不同的事件状态,以便选择器查找。
事件状态:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
NIO通信模型图解:
(虚线表示不直接相关联)
下面用代码来演示一下Buffer、Channel、Selector的使用。
以IntBuffer为例,讲解一下Buffer的常用API:
  1. public class IntBufferTest {
  2.     public static void main(String[] args) {
  3.         //1、基本操作
  4.         //创建指定长度的缓冲区
  5.         /*IntBuffer buffer = IntBuffer.allocate(10);
  6.         buffer.put(11); //position位置:0->1
  7.         buffer.put(5); //position位置:1->2
  8.         buffer.put(32); //position位置:2->3
  9.         System.out.println("未调用flip复位方法前的buffer:" + buffer);
  10.         //把位置复位为0,也就是position位置由3->0
  11.         buffer.flip();
  12.         //比较未调用flip方法和调用之后buffer的limit可以发现,不进行复位操作的话,position的值为3,limit的值为10
  13.         // 因为缓冲区中已有11、5、32三个元素,也就意味着put()方法会使position向后递增1
  14.         System.out.println("调用flip复位方法后的buffer:" + buffer);
  15.         System.out.println("buffer容量为:" + buffer.capacity());
  16.         System.out.println("buffer限制为:" + buffer.limit());
  17.         System.out.println("获取下标为1的元素:" + buffer.get(1));
  18.         System.out.println("调用get(index)方法后的buffer:" + buffer); //调用get(index)方法,不会改变position的值
  19.         buffer.put(1, 4); //将buffer位置为1的值替换为4,调用put(index,value)不会改变position的值
  20.         System.out.println("调用put(index, value)方法后的buffer:" + buffer);

  21.         for(int i=0; i<buffer.limit(); i++) {
  22.             //调用get方法会使缓冲区的位置(position)向后递增一位
  23.             System.out.print(buffer.get() + "\t");
  24.         }
  25.         System.out.println("\nbuffer对象遍历之后buffer为:" + buffer);*/


  26.         //2、wrap方法的使用
  27.         /*int[] arr = new int[]{1, 2, 3};
  28.         IntBuffer buffer = IntBuffer.wrap(arr);
  29.         System.out.println("wrap(arr)方法:" + buffer);
  30.         //IntBuffer.wrap(array, postion, length)表示容量为array的长度,但是可操作的元素为位置postion到length的数组元素
  31.         buffer = IntBuffer.wrap(arr, 0, 2);
  32.         System.out.println("wrap(arr, 0, 2):" + buffer);*/

  33.         //3、其他方法
  34.         IntBuffer buffer = IntBuffer.allocate(10);
  35.         int[] arr = new int[]{1, 2, 3};
  36.         buffer.put(arr);
  37.         System.out.println("调用put(arr)方法后的buffer:" + buffer);
  38.         //一种复制方法,buffer1的pos、lim、cap与buffer的一样
  39.         IntBuffer buffer1 = buffer.duplicate();
  40.         System.out.println("buffer1:" + buffer1);

  41.         buffer.position(1); //将buffer的position设置为1,不建议使用。功能相当于flip()方法,但是从运行结果可以看出,lim依然等于10
  42.         System.out.println("调用position()方法后的buffer:" + buffer);
  43.         System.out.println("buffer的可读数据量:" + buffer.remaining()); //计算出从pos到lim的长度
  44.         int[] arr1 = new int[buffer.remaining()];
  45.         //将缓冲区的数据放入arr1中
  46.         buffer.get(arr1);
复制代码
public class IntBufferTest {
  1. <blockquote><font color="#555555" face="microsoft yahei"><span style="font-size: 15px;">public static void main(String[] args) {</span></font>
复制代码

接下来是Buffer、Channel、Selector的一个入门的小例子:
  1. public class Server implements Runnable {

  2.     private Selector selector;
  3.     private ByteBuffer buffer = ByteBuffer.allocate(1024);

  4.     public Server(int port) {
  5.         try {
  6.             //1 打开多复用器
  7.             selector = Selector.open();
  8.             //2 打开服务器通道
  9.             ServerSocketChannel ssc = ServerSocketChannel.open();
  10.             //3 设置服务器通道为非阻塞方式
  11.             ssc.configureBlocking(false);
  12.             //4 绑定地址
  13.             ssc.bind(new InetSocketAddress(port));
  14.             //5 把服务器通道注册到多路复用选择器上,并监听阻塞状态
  15.             ssc.register(selector, SelectionKey.OP_ACCEPT);
  16.             System.out.println("Server start, port:" + port);
  17.         } catch (IOException e) {
  18.             e.printStackTrace();
  19.         }
  20.     }

  21.     @Override
  22.     public void run() {
  23.         while (true) {
  24.             try {
  25.                 //1 必须让多路复用选择器开始监听
  26.                 selector.select();
  27.                 //2 返回所有已经注册到多路复用选择器上的通道的SelectionKey
  28.                 Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
  29.                 //3 遍历keys
  30.                 while (keys.hasNext()) {
  31.                     SelectionKey key = keys.next();
  32.                     keys.remove();
  33.                     if(key.isValid()) { //如果key的状态是有效的
  34.                         if(key.isAcceptable()) { //如果key是阻塞状态,则调用accept()方法
  35.                             accept(key);
  36.                         }
  37.                         if(key.isReadable()) { //如果key是可读状态,则调用read()方法
  38.                             read(key);
  39.                         }
  40.                     }
  41.                 }
  42.             } catch (IOException e) {
  43.                 e.printStackTrace();
  44.             }
  45.         }
  46.     }

  47.     private void accept(SelectionKey key) {
  48.         try {
  49.             //1 获取服务器通道
  50.             ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
  51.             //2 执行阻塞方法
  52.             SocketChannel sc = ssc.accept();
  53.             //3 设置阻塞模式为非阻塞
  54.             sc.configureBlocking(false);
  55.             //4 注册到多路复用选择器上,并设置读取标识
  56.             sc.register(selector, SelectionKey.OP_READ);
  57.         } catch (Exception e) {
  58.             e.printStackTrace();
  59.         }
  60.     }

  61.     private void read(SelectionKey key) {
  62.         try {
  63.             //1 清空缓冲区中的旧数据
  64.             buffer.clear();
  65.             //2 获取之前注册的SocketChannel通道
  66.             SocketChannel sc = (SocketChannel) key.channel();
  67.             //3 将sc中的数据放入buffer中
  68.             int count = sc.read(buffer);
  69.             if(count == -1) { // == -1表示通道中没有数据
  70.                 key.channel().close();
  71.                 key.cancel();
  72.                 return;
  73.             }
  74.             //读取到了数据,将buffer的position复位到0
  75.             buffer.flip();
  76.             byte[] bytes = new byte[buffer.remaining()];
  77.             //将buffer中的数据写入byte[]中
  78.             buffer.get(bytes);
  79.             String body = new String(bytes).trim();
复制代码
public class Server implements Runnable {
  1. <blockquote><font color="#555555" face="microsoft yahei"><span style="font-size: 15px;">
  2. </span></font>
复制代码

客户端:
  1. public class Client {
  2.     public static void main(String[] args) {
  3.         InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8379);
  4.         SocketChannel sc = null;
  5.         ByteBuffer buffer = ByteBuffer.allocate(1024);
  6.         try {
  7.             //打开通道
  8.             sc = SocketChannel.open();
  9.             //建立连接
  10.             sc.connect(address);
  11.             while (true) {
  12.                 byte[] bytes = new byte[1024];
  13.                 System.in.read(bytes);
  14.                 //把输入的数据放入buffer缓冲区
  15.                 buffer.put(bytes);
  16.                 //复位操作
  17.                 buffer.flip();
  18.                 //将buffer的数据写入通道
  19.                 sc.write(buffer);
  20.                 //清空缓冲区中的数据
  21.                 buffer.clear();
  22.             }
  23.         } catch (Exception e) {
  24.             e.printStackTrace();
  25.         } finally {
  26.             if(sc != null) {
  27.                 try {
  28.                     sc.close();
  29.                 } catch (IOException e) {
  30.                     e.printStackTrace();
  31.                 }
  32.             }
  33.         }
  34.     }
  35. }
复制代码
3、AIO
在NIO的基础上引入了异步通道的概念,并提供了异步文件和异步套接字通道的实现,从而在真正意义上实现了异步非阻塞,之前的NIO只是非阻塞而并非异步。AIO不需要通过对多路复用器对注册的通道进行轮询操作即可实现异步读写,从而简化NIO编程模型。
①AsynchronousServerSocketChannel
②AsynchronousSocketChannel
下面看代码:
  1. public class Server {
  2.     //线程池
  3.     private ExecutorService executorService;
  4.     //线程组
  5.     private AsynchronousChannelGroup channelGroup;
  6.     //服务器通道
  7.     public AsynchronousServerSocketChannel channel;

  8.     public Server(int port) {
  9.         try {
  10.             //创建线程池
  11.             executorService  = Executors.newCachedThreadPool();
  12.             //创建线程组
  13.             channelGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1);
  14.             //创建服务器通道
  15.             channel = AsynchronousServerSocketChannel.open(channelGroup);
  16.             //绑定地址
  17.             channel.bind(new InetSocketAddress(port));
  18.             System.out.println("server start, port:" + port);
  19.             channel.accept(this, new ServerCompletionHandler());
  20.             Thread.sleep(Integer.MAX_VALUE);
  21.         } catch (Exception e) {
  22.             e.printStackTrace();
  23.         }
  24.     }

  25.     public static void main(String[] args) {
  26.         Server server = new Server(8379);
  27.     }
  28. }
复制代码
ServerCompletionHandler类:
  1. public class ServerCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, Server> {
  2.     @Override
  3.     public void completed(AsynchronousSocketChannel channel, Server attachment) {
  4.         //当有下一个客户端接入的时候,直接调用Server的accept方法,这样反复执行下去,保证多个客户端都可以阻塞
  5.         attachment.channel.accept(attachment, this);
  6.         read(channel);
  7.     }

  8.     private void read(AsynchronousSocketChannel channel) {
  9.         //读取数据
  10.         ByteBuffer buffer = ByteBuffer.allocate(1024);
  11.         channel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
  12.             @Override
  13.             public void completed(Integer resultSize, ByteBuffer attachment) {
  14.                 attachment.flip();
  15.                 System.out.println("Server->" + "收到客户端发送的数据长度为:" + resultSize);
  16.                 String data = new String(buffer.array()).trim();
  17.                 System.out.println("Server->" + "收到客户端发送的数据为:" + data);
  18.                 String response = "服务器端响应了客户端。。。。。。";
  19.                 write(channel, response);
  20.             }

  21.             @Override
  22.             public void failed(Throwable exc, ByteBuffer attachment) {
  23.                 exc.printStackTrace();
  24.             }
  25.         });
  26.     }

  27.     private void write(AsynchronousSocketChannel channel, String response) {
  28.         try {
  29.             ByteBuffer buffer = ByteBuffer.allocate(1024);
  30.             buffer.put(response.getBytes());
  31.             buffer.flip();
  32.             channel.write(buffer).get();
  33.         } catch (Exception e) {
  34.             e.printStackTrace();
  35.         }
  36.     }

  37.     @Override
  38.     public void failed(Throwable exc, Server attachment) {
  39.         exc.printStackTrace();
  40.     }
  41. }
复制代码
客户端:
  1. public class Client implements Runnable {

  2.     private AsynchronousSocketChannel channel;

  3.     public Client() throws IOException {
  4.         channel = AsynchronousSocketChannel.open();
  5.     }

  6.     public void connect() {
  7.         channel.connect(new InetSocketAddress("127.0.0.1", 8379));
  8.     }

  9.     public void write(String data) {
  10.         try {
  11.             channel.write(ByteBuffer.wrap(data.getBytes())).get();
  12.             read();
  13.         } catch (Exception e) {
  14.             e.printStackTrace();
  15.         }
  16.     }

  17.     public void read() {
  18.         ByteBuffer buffer = ByteBuffer.allocate(1024);
  19.         try {
  20.             channel.read(buffer).get();
  21.             buffer.flip();
  22.             byte[] bytes = new byte[buffer.remaining()];
  23.             buffer.get(bytes);
  24.             String data = new String(bytes, "UTF-8").trim();
  25.             System.out.println(data);
  26.         } catch (Exception e) {
  27.             e.printStackTrace();
  28.         }
  29.     }

  30.     @Override
  31.     public void run() {
  32.         while (true) {

  33.         }
  34.     }

  35.     public static void main(String[] args) {
  36.         try {
  37.             Client c1 = new Client();
  38.             Client c2 = new Client();
  39.             Client c3 = new Client();

  40.             c1.connect();
  41.             c2.connect();
  42.             c3.connect();

  43.             new Thread(c1).start();
  44.             new Thread(c2).start();
  45.             new Thread(c3).start();

  46.             Thread.sleep(1000);

  47.             c1.write("c1 aaa");
  48.             c2.write("c2 bbbb");
  49.             c3.write("c3 ccccc");
  50.         } catch (Exception e) {
  51.             e.printStackTrace();
  52.         }
  53.     }
  54. }
复制代码





欢迎光临 firemail (http://firemail.wang:8088/) Powered by Discuz! X3