socketNio

0≤mark≤position≤limit ≤capacity

Buffer的clear(),flip(),rewind()

MethodPrepares Buffer forPositionLimitMark
ByteBuffer.clear()read()/put() into buffer0capacityundefined
ByteBuffer.flip()write()/get() from buffer0positionundefined
ByteBuffer.rewind()rewrite()/get() from buffer0unchangedundefined

ByteBuffer.compact():为了后续的put()/read()调用让出空间,所以将position与limit之间的元素复制到缓冲区的开始位置,position设为要复制的数据的长度,limit设为capacity,mark设为未定义

while (channel.read(buffer) != -1) {
    buffer.flip();
    channel.write(buffer);
    buffer.compact();
}
while (buffer.hasRemaining())
    channel.write(buffer);

ByteBuffer.duplicate():创建一个与原始缓冲区共享内容的新缓冲区.新缓冲区的position,limit,capacity,mark都初始化为原始缓冲区的索引值.新旧缓冲区的索引都是独立的.

MethodCapacityPositionLimitMark
ByteBuffer duplicate()capacitypositionlimitmark
ByteBuffer slice()remaining()0remaining()undefined
ByteBuffer asReadOnlyBuffer()capacitypositionlimitmark
CharBuffer asCharBuffer()remaining()/20remaining()/2undefined
DoubleBuffer asDoubleBuffer()remaining()/80remaining()/8undefined
FloatBuffer asFloatBuffer()remaining()/40remaining()/4undefined
IntBuffer asIntBuffer()remaining()/40remaining()/4undefined
LongBuffer asLongBuffer()remaining()/80remaining()/8undefined
ShortBuffer asShortBuffer()remaining()/20remaining()/2undefined

总的来说.使用Selector的步骤如下:

一. 创建一个Selector实例.
二. 将其注册到各种信道,指定每个信道上感兴趣的I/O操作.
三. 重复执行:
  1. 调用一种select方法
  2. 获取选取的键列表
  3. 对于已选键集中的每个键.
    a. 获取信道,并从键中获取附件(如果合适的话)
    b.确定准备就绪的操作并执行.如果是accept操作,将接受的信道设置为非阻塞模式,并将其与选择器注册
    c.如果需要,修改键的兴趣操作集
    d.从已选键集中移除键

TCP例子:
服务端:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;

public class TCPServerSelector {
    private static final int BUFSIZE = 256;  // Buffer size (bytes)
    private static final int TIMEOUT = 3000; // Wait timeout (milliseconds)
    public static void main(String[] args) throws IOException {
        if (args.length < 1) { // Test for correct # of args
            throw new IllegalArgumentException("Parameter(s): <Port> ...");
        }
        //创建一个selector来多路复用监听与连接
        Selector selector = Selector.open();
        //为每个端口创建监听socketChannel,并注册selector
        for (String arg : args) {
            ServerSocketChannel listnChannel = ServerSocketChannel.open();
            listnChannel.socket().bind(new InetSocketAddress(Integer.parseInt(arg)));
            listnChannel.configureBlocking(false); //必须配置为非阻塞才能注册selector
            listnChannel.register(selector, SelectionKey.OP_ACCEPT);//注册selector
        }
        // Create a handler that will implement the protocol
        TCPProtocol protocol = new EchoSelectorProtocol(BUFSIZE);
        while (true) { //循环处理可用的I/O操作
            // Wait for some channel to be ready (or timeout)
            if (selector.select(TIMEOUT) == 0) { //方法是阻塞的,返回准备好的信道数
                System.out.print(".");
                continue;
            }
            //获取所选择的键集进行处理
            Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
            while (keyIter.hasNext()) {
                SelectionKey key = keyIter.next(); // Key is bit mask
                // Server socket channel有挂起的连接请求?
                if (key.isAcceptable()) {
                    protocol.handleAccept(key);
                }
                // Client socket channel有挂起的数据?
                if (key.isReadable()) {
                    protocol.handleRead(key);
                }
                // Client socket channel可用写操作,并且key是有效的(例如,信道没有关闭)?
                if (key.isValid() && key.isWritable()) {
                    protocol.handleWrite(key);
                }
                keyIter.remove(); //从选择键集中移除
            }
        }
    }
}
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class EchoSelectorProtocol implements TCPProtocol {
    private int bufSize; // Size of I/O buffer
    public EchoSelectorProtocol(int bufSize) {
        this.bufSize = bufSize;
    }
    public void handleAccept(SelectionKey key) throws IOException {
        SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept();//从键中取出信道,这个就是Client socket channel
        clntChan.configureBlocking(false); //必须配置为非阻塞
        clntChan.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufSize));//使用新信道注册选择器,用于读,并附加字节缓冲
    }
    public void handleRead(SelectionKey key) throws IOException {
        // Client socket channel已挂起数据
        SocketChannel clntChan = (SocketChannel) key.channel();
        ByteBuffer buf = (ByteBuffer) key.attachment();
        long bytesRead = clntChan.read(buf);
        if (bytesRead == -1) { // Did the other end close?
            clntChan.close();
        } else if (bytesRead > 0) {
            key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);//通过读/写都是感兴趣的key来表明
        }
    }
    public void handleWrite(SelectionKey key) throws IOException {
        //Channel is available for writing, and key is valid (i.e., client channel not closed).
        ByteBuffer buf = (ByteBuffer) key.attachment();//返回之前读到的数据
        buf.flip(); //为写操作准备缓冲
        SocketChannel clntChan = (SocketChannel) key.channel();
        clntChan.write(buf);
        if (!buf.hasRemaining()) { //缓冲完全写出去了?
            key.interestOps(SelectionKey.OP_READ);//没有什么可做的,所以不再对写感兴趣
        }
        buf.compact(); //压缩缓冲:为下次读入更多的数据腾出空间
    }
}

客户端:

import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class TCPEchoClientNonblocking {

    public static void main(String args[]) throws Exception {
        //创建信道并设为非阻塞
        SocketChannel clntChan = SocketChannel.open();
        clntChan.configureBlocking(false);
        //发起连接到服务端并重复轮询直至完成
        if (!clntChan.connect(new InetSocketAddress("127.0.0.1", 9000))) {
            while (!clntChan.finishConnect()) {//轮询连接状态,如果返回前建立了连接,返回true,否则返回false
                System.out.print(".");  //做些其它事情
            }
        }
        byte[] argument = "sendData".getBytes();
        ByteBuffer writeBuf = ByteBuffer.wrap(argument);//使用已有字节数组创建写缓冲
        ByteBuffer readBuf = ByteBuffer.allocate(argument.length);//分配固定长度读缓冲
        int totalBytesRcvd = 0; //已接收的总字节数
        int bytesRcvd; //最后一次读到的字节数
        while (totalBytesRcvd < argument.length) {
            if (writeBuf.hasRemaining()) {//只要缓冲区还有数据
                clntChan.write(writeBuf);//就调用write()
            }
            if ((bytesRcvd = clntChan.read(readBuf)) == -1) {//read方法不是阻塞的,但没有数据可读时,返回0
                throw new SocketException("Connection closed prematurely");
            }
            totalBytesRcvd += bytesRcvd;
            System.out.print(".");   //做些其它事情
        }
        System.out.println("Received: " +  new String(readBuf.array(), 0, totalBytesRcvd));//打印接收到的数据
        clntChan.close();//关闭信道
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值