美文网首页
JNIO与网络编程

JNIO与网络编程

作者: zheng7 | 来源:发表于2017-05-10 05:58 被阅读0次

    The NIO library was introduced with JDK 1.4. NIO was created to allow Java programmers to implement high-speed I/O without having to write custom native code. NIO moves the most time-consuming I/O activites ( namely, filling and draining buffers) back into the operating system, thus allowing for a great increase in speed. The most important distinction between the original I/O library and NIO is how data is packaged and transmitted. Original I/O deals with data in stream, whereas NIO deals with data in blocks

    摘自:IBM:Getting started with new I/O (NIO)

    Part1 Buffer 和 Channel

    参考:IBM:Getting started with new I/O (NIO)

    Channel 和 Buffer 的基本使用

    BufferChannel 是NIO中的重要对象,几乎所有的I/O操作都要用到这两个对象。Channel意为通道,他的作用类似于流对象,所有发送和接受的数据都要通过ChannelBuffer实质上是一个容器对象。

    所有从Channel中读取的数据都读到了Buffer里,所有写入Channel的数据必须首先存放在Buffer里。

    例如:

    /*从文件中读取
     *1)从 FileInputStream得到 channel
     *2)生成 Buffer 的对象
     *3)从Channel中读入Buffer
    */
    FileInputStream fin = new FileInputStream("read.txt");
    FileChannel fc = fin.getChannel();
    ByteBuffer buffer = ByteBuffer.allocate( 1024 );
    fc.read(buffer)     //所有从Channel中读取的(比如:文件中的)数据都读到了Buffer里
    
    /*向文件中读入
     *1)从 FileOutputStream得到 channel
     *2)生成 Buffer 的对象,填充Buffer对象
     *3)从Buffer中读入Channel
    */
    FileOutputStream fout = new FileOutputStream("read.txt");
    FileChannel fc = fout.getChannel();
    ByteBuffer buffer = ByteBuffer.allocate( 1024 );
    
    for(int i = 0; i < message.length; i++){
      buffer.put(message[i]);  
    }
    buffer.flip();      //flip() 方法,用于在将数据填入buffer和数据取出buffer之间的切换
    fc.write(buffer)     //所有从Channel中写入(比如:文件中)的数据都写到了Buffer里
    

    Buffer中的状态量

    在上一段代码中,我们看到在Buffer切换状态时用到了filp()方法,事实上Buffer还有其他用于切换状态的函数,其中最常用的是filp()clear()

    这两个方法通常的用法如下:

    buffer.clear(); //在buffer填入数据之前调用
    int r = fcin.read(buffer);
    
    if(r == -1){
      break;
    }
    
    buffer.flip();//在buffer填入数据之后,取出数据之前调用
    fcout.write(buffer);
    

    为什么会出现吧这样的情况呢?
    这是因为Buffer中有几个状态量,positionlimitcapacity。这在对Buffer进行操作时,这三个状态量不停变化,从而决定Buffer的可操作范围。

    //TODO 关于buffer的三个状态量的解释
    //TODO advanced JNIO
    

    Part2 网络编程中用到的Channel

    译自:Java NIO指南

    DatagramChannel

    操作1:打开DatagramChannelopen()

    /*
    *在这个例子中,我们打开了一个 DatagramChannel,并且可
    *以从 9999 端口接收UDP数据报。
    */
    DatagramChannel channel = DatagramChannel.open();
    channel.socket().bind( new InetSocketAddress(9999));
    

    操作2:接收数据 receive()

    /*
    *receive()方法会将收到的packet中的数据拷贝到Buffer中
    *如果Buffer不足以容纳接收到的数据,超出容纳空间的数据
    *将被静默地丢弃
    */
    ByteBuffer buf = ByteBuffer.allocate( 1024 );
    buf.clear();
    
    channel.recrive(buf);
    

    操作3:发送数据send()

    /*
    *这个例子中我们向“jenkov.com”的80端口发送了一个字符串
    *但是,我们得不到发送的数据被接受或者没有被接收的反馈
    *因为UDP不对数据送达作出任何保证。
    */
    String newData = "New String to write to file..."
                                 +System.currentTimeMillis();
    ByteBuffer buf = ByteBuffer.allocate( 1024 );
    buf.clear();
    buf.put(newData.getBytes());
    buf.flip();
    
    int bytesSent =  channel.send(buf, 
                     new InetSocketAddress("jenkov.com", 80));
    

    操作4:链接到特定地址connect()

    /*
    *这个例子中的connect()不同于TCP中的链接,这个例子中的
    *connect()并不建立真的链接,而是将channel有特定的地址
    *绑定,但仍然不保证数据一定被送达。
    */
    channel.connect(new InetSocketAddress("jenkov.com", 80));
    //链接到特定地址后可以直接使用channel的read()和write()方法
    int bytesRead = channel.read(buf);
    int bytesWrriten = channel.write(buf);
    

    SocketChannel

    操作1:打开SocketChnnel

    SocketChannel socketChannel = socketChannel.open();
    socketChannel.connect(new InetSocketAddress("http://jenvok.com", 80));
    

    操作2:关闭SocketChannel

    socketChannel.close();
    

    操作3:从SocketChannel中读取数据

    ByteBuffer buf = ByteBuffer.allocate( 1024 );
    //返回值表示buf接受了多少字节的数据,-1表示链接断开
    int bytesRead = socketChannel.read(buf);
    

    操作4:向SocketChannel中写入数据

    String newData = "New String to write to file..."
                                 +System.currentTimeMillis();
    ByteBuffer buf = ByteBuffer.allocate( 1024 );
    buf.clear();
    buf.put(newData.getBytes());
    buf.flip();
    
    //注意:SocketChannel.write()在while循环内调用,因为不能
    //保证write()方法会向channel中写入多少比特,因此我们反复
    //调用write()方法,知道buf的内容全部被写入
    while(buf.hasRemaining()){
      channel.write(buf);
    }
    

    操作5:非阻塞模式下的connect()write()read()方法

    connect()方法:

    如果 SocketChannel是非阻塞的,当我们调用connect()并返回时,链接可能还没有建立。因此,我们需要调用finishConnect()方法进行检测。

    socketChannel.configureBlocking(false);
    socketChannel.connect(
                              new InetSocketAddress("http://jenlov.com",80));
    while(! socketChannel.finishConnectt()){
      //wait, or do something else...
    }
    

    write()方法:

    不需要特殊变化,因为write()方法已经在循环中调用了。

    read()方法:

    需要注意这个方法的返回值,因为返回值表示实际实际读到多少数据。

    ServerSocketChannel

    操作1:打开SeverSocketChannel

    ServerScoketChannel serverSocketChannel = ServerSocketChannel.open();
    

    操作2:关闭ServerSocketChannel

    serverSocketChannel.close();
    

    操作3:监听到来的连接

    /*
    *由于要监听多个链接,所以将accept()放在while循环之中当然在实际
    *编程中会用其他条件替换while循环中的true
    */
    while(true){
      SocketChannel socketChannel = serverSocketChannel.accept();
      //do something with socketChannel
    }
    

    操作4:非阻塞模式

    /*
    *当没有连接到来时,accept()返回null
    */
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.socket.bind(new InetSocketAddress(9999));
    serverSocketChannel.configureBlocking(false);
    
    while(true){
      SocektChannel socketChannel = serverSocketChannel.accept();
      if(socketChannel != null){
        //do something with socketChannel...
      }
    }
    

    Part3 对象的序列化

    译自:Java2Blog:Java中的序列化

    为了硬盘存储和网络传输的需要,我们需要对Java对象序列化(Object->bytes);相反,在需要使用Java对象时,我们需要进行反序列化(bytes->Object)。

    serialVersionUID的概念
    serialVersionUID是在对象反序列化是用来确保版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现异常。
    serialVersionUID的类型必须是如下形式:

    ANY-ACCESS-MODIFIER static final long 
    

    序列化过程

    Serialization机制.jpg
    需要进行序列化的对象必须实现Serializable接口。这是一个 maker interface,也就是一个空的接口。它的源码如下:
    public interface Serializable{
    }
    

    反序列化机制

    Deserialization机制.jpg

    其他情况

    1. 如果需要序列化的对象中有其他对象的引用,其他对象也必须实现Serializable接口。
    2. TODO
    3. TODO
    4. TODO
    5. TODO
    6. TODO
    7. TODO

    Part4 网络和异步I/O

    译自:IBM:Getting started with new I/O (NIO)

    异步I/O是一种非阻塞的I/O。在调用异步I/O之后,通过对关心的事件进行注册,当这些事件(如 :有可以读取的数据到达、一个新的socket连接,等)发生时会得到系统通知。异步I/O/的优势在于,可以监听任意数量的Channels的I/O事件,而不用使用轮询或者创建额外的线程。

    Selectors

    Sector是异步I/O的核心组件。我们在Selector上注册感兴趣的I/O事件,在这些事件发生的时候 Selector就会通知我们。

    操作1:打开Selector

    Selector selector = Selector.open();
    

    操作2:用非阻塞方式打开ServerSocketChannel
    为了接收连接,我们需要ServerSockeChanne。为了保证异步I/O操作,ServerSocketChannel要以非阻塞方式打开。

    SeverSocketChannel ssc = SeverSocketChannel.open();
    ssc.configureBlocking( false );
    
    ServerSocket ss = ssc.socket();
    InetSocketAddress address = new InetSocketAddress( port[i] );
    ss.bind( address );
    

    Selection Keys

    操作3:注册ServerSocketChannelSelector
    使用ServerSocketChannel.register()方法。这个方法的第一个参数是Selector,第二个参数是要监测的事件(如:accept)。当有监测的事件发生时,Selector将它添加到SelectionKey中。

    SelectionKey key = ssc.register( selector, SelectionKey.PO_ACCEPT);
    

    操作4:监听事件

    //阻塞直到至少有一个事件发生
    int num = selector.select();
    
    Set selectedKeys = selector.seectedKeys();
    for(SelectionKye key: selectedKeys){
      //deal with I/O event
    }
    

    操作5:检查发生的事件的类型

    //确认事件是accept
    if( ( key.readOps() ) & SelectionKey.OP_ACCEPT == SelectionKey.OP_ACCEPT){
      //Accept the new connection
      //...
    }
    

    操作6:接受一个新连接
    上一步,我们已经确认youyige连接在等待,ServerSocket接受,所以我们可以安全的接受这个连接而不用担心accept()方法阻塞。

    ServerSocketChannel ssc = key.channel();
    SocketChannel sc = ssc.accept();
    

    操作7:注册新接收的ScoketChannel

    sc.configureBloking( false );
    SelectionKey newkey = sc.register( selector, Selection.OP_READ);
    

    操作8:删除处理过的key

    selectedKeys.remove(key);
    

    操作9:接受到来的数据

    if( (key.readyOps() &  Selectionkey.OP_READ) == SelectionKey.OP_READ){
      SocketChannel sc = (SocketChannel)key.channel();
    }
    

    Part5 并发

    译自:ORACLE: Java并发

    在并发编程中有两个基本的执行单元:进程和线程。在Java中,并发编程主要指多线程并发。

    Thread对象

    **两种定义方法

    1. 实现Runnable接口
    2. 继承Thread类
    public class HelloRunnable implements Runnable{
      public void run(){
        System.out.println("Hello from a thread!");
      }
      public static void main(String args[]){
        (new Thread(new HelloRunnable())).start();
      }
    
    }
    
    public class HelloThread exends Thread{
      public void run(){
        System.out.println("Hello from a thread!");
      }
      public static void main(String args[]){
        (new HelloThread()).start;
      }
    }
    

    //待续

    相关文章

      网友评论

          本文标题:JNIO与网络编程

          本文链接:https://www.haomeiwen.com/subject/xgrwtxtx.html