基于nio的socket编程

io编程中存在两个问题,io是阻塞的,并且保持多个链接的时候须要加入多线程来保持socket链接。这种方式比较浪费资源,由于每一个链接都须要一个线程来保持,这在链接比较多的时候是一个浪费的资源是至关严重的。而两个问题在nio编程中就很好的解决这个问题,可是相对来讲,对于编程的复杂度也相对有点提升。java

 

这里不去深刻的讨论nio编程的原理,只是基于实用的角度上作一些简单的介绍。在nio编程中,主要有三个组件比较重要:channel、buffer、selector。编程

 

channel:网络

通道,它和io中流有点类似,因此的源数据或者目标数据都是直接和channel打交道的。java nio中channel有如下几种实现,这些通道涵盖了TCP和UDP网络io:多线程

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

 

buffer:socket

缓冲区 , 他是直接与channel交互的,咱们若是要读取channle中的数据时,都是先把数据从channle中读入到buffer中;一样的,若是咱们要向channel中写数据的时候,也要先把数据写入到buffer中。java nio中有也有多个实现,他们不要为了针对不一样的数据类型:函数

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

 

注意:这里要介绍一个buffer中三个比较重要的方法,这三个方法在buffer读写的过程当中都颇有用。这三个方法都跟buffer中三个标记有关系:position、limit、capacity。position至关于指针,表示当前正在读写的位置;limit指的是界限,表示读写的时候界限;capacity表示容量,也就是buffer缓冲区的大小。为了弄明白这些方法,了解这三个标记是前提,说了这么多,这三个方法是:clear(); rewind(); flip()。这三个方法也就是调整这些标记位置,下面是这三个方法的源码,相信很好理解:post

  1. public final Buffer clear() {   
  2.          position = 0; //设置当前下标为0  
  3.          limit = capacity; //设置写越界位置与和Buffer容量相同  
  4.          mark = -1; //取消标记  
  5.          return this;   

 

  1. public final Buffer rewind() {   
  2.         position = 0;   
  3.         mark = -1;    //取消标记 
  4.         return this;   
  5. }  

 

  1. public final Buffer flip() {   
  2.         limit = position;   
  3.         position = 0;   
  4.         mark = -1;    //取消标记 
  5.         return this;   
  6.  } 

 

java nio缓冲区中标志mark标记,使缓冲区可以记住一个位置并在以后将其返回。缓冲区的标记在mark( )函数被调用以前是未定义的,调用时标记被设为当前位置的值。reset( )函数将位置设为当前的标记值。若是标记值未定义,调用reset( )将致使InvalidMarkException异常。一些缓冲区函数会抛弃已经设定的标记(rewind( ),clear( ),以及flip( )老是抛弃标记)。若是新设定的值比当前的标记小,调用limit( )或position( )带有索引参数的版本会抛弃标记。如调用mark( )来设定mark = postion。调用reset( )设定position = mark。标记在设定前是未定义的(undefined)。这四个属性之间老是遵循如下关系: 0 <= mark <= position <= limit <= capacitythis

 

 

 

selector:线程

选择器,这个就是nio为了解决io中多链接必需要多线程来处理的问题。selector容许单线程处理多个channel,你打开多个链接就会有多个channel,而selector能够管理多个channel。值得注意的是,selector必需要和非阻塞的通道配合使用,也就是说selector不能和fileChannel一块儿使用,由于fileChannel不能配置非阻塞的模式。指针

 

java io和java nio的主要区别:

一、io是面向流的,nio是面向缓冲区的。这就意味着io只能从流中读入一个或者或者多个字节。而nio由于是面向缓冲区的,数据都是先读入到缓冲区,也就是上面buffer中,并且在须要的时候,还能够在缓冲区中先后移动来获取不一样位置的数据。可是也有个麻烦的地方,就是要检查缓冲区中是否含有本身要处理的数据。

二、io是阻塞的,nio是非阻塞的。io在数据读入或者写入的时候是阻塞的,直到数据处理完成为止。而nio则是非阻塞的,咱们从buffer中读取数据的时候是当即返回的,可是这里有个问题就是咱们没法保证缓冲区中是有数据的。可是好处就是,咱们读取数据的时候不会阻塞,能够干别的事情。

 

下图能够说明这个问题(左边的是nio,右边的是io):

下面就是一个基于NIO的socket编程的例子:

public class NioSocketServer {
    public static void main(String[] args) {
        try {
            ServerSocketChannel server = ServerSocketChannel.open(); //新建NIO通道
            server.configureBlocking(false); //配置通道为非阻塞的
            ServerSocket socket = server.socket(); //建立socket服务
            socket.bind(new InetSocketAddress(9123)); //绑定端口号
            Selector selector = Selector.open(); //建立选择器
            server.register(selector, SelectionKey.OP_ACCEPT); //将服务注册到选择器上,而且配置关注的事件
            //循环获取通道内是否有关注的事件
            while(true) {
                //判断通道中事发后有关注的事件。若是 >1 则表示通道中有关注的事件
                int num = selector.select();
                if(num < 1) {
                    continue;
                }
                Set selectKeys = selector.selectedKeys();//得到事件的key值
                //遍历全部的事件
                Iterator iterator = selectKeys.iterator(); //得到迭代器
                while(iterator.hasNext()) {
                    SelectionKey key = (SelectionKey) iterator.next();
                    //移走这次事件
                    iterator.remove();
                    //判断该事件是不是请求链接的事件
                    if(key.isAcceptable()) {
                        //得到相应的socket channel
                        SocketChannel client = server.accept();
                        System.out.println("接受来自" + client + "的请求!");
                        client.configureBlocking(false); //将通道配置成非阻塞的
                        ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[1024]); //建立缓冲区
                        //在此通道上注册写事件
                        SelectionKey keyClient = client.register(selector, SelectionKey.OP_WRITE);
                        //通道执行事件
                        keyClient.attach(byteBuffer);
                    }else if(key.isWritable()) {
                        //得到此通道上的socket channel
                        SocketChannel client = (SocketChannel) key.channel(); //得到当前事件下的socketChannel
                        ByteBuffer outByteBuffer = (ByteBuffer)key.attachment(); //得到通道上绑定的byteBuffer
                        //若是缓冲区存在数据,清除掉,防止数据粘连,重置一下
                        if(outByteBuffer.hasRemaining()) {
                            outByteBuffer.clear();
                        }
                        //往此通道上写数据,先写入byteBuffer中
                        outByteBuffer.put("hello world".getBytes());
                        outByteBuffer.flip();
                        client.write(outByteBuffer);
                    }
                    key.channel().close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
相关文章
相关标签/搜索