本文是NIO作为服务端示例。同学们需要先熟悉NIO的三大组件,在这里小编也将对三大基本组件进行通俗易懂的解释。
首先是Channel:他和文件输入输出流一样是读写数据的双向通道。可以从channel将数据写入buffer也可以将buffeer的数据写入channel。这里需要重点关注的是他是双向的。而我们熟悉的FileInputStream这种Stream是单向的要么只能读要么只能写。
常见的channel有FileChannel,DatagramChannel,SocketChannel以及ServerSocketChannel。我们做为服务端一般用到ServerSocketChannel。而SocketChannel是用做客户端链接服务端用的。buffer则用来缓冲读取数据。常见的buffer有ByteBuffer,FloatBuffer,ShortBuffer等等
提示:以下是本篇文章正文内容,下面案例可供参考
position
当前读取的位置。读/写操作的当前下标。当使用buffer的相对位置进行读/写操作时,读/写会从这个下标进行,并在操作完成后,buffer会更新下标的值。
mark
为某一读过的位置做标记,便于某些时候回退到该位置。一个临时存放的位置下标。调用mark()会将mark设为当前的position的值,以后调用reset()会将position属性设置为mark的值。mark的值总是小于等于position的值,如果将position的值设的比mark小,当前的mark值会被抛弃掉。
capacity
初始化时候的容量。这个Buffer最多能放多少数据。capacity一般在buffer被创建的时候指定。
limit
在Buffer上进行的读写操作都不能越过这个下标。当写数据到buffer中时,limit一般和capacity相等,当读数据时,limit代表buffer中有效数据的长度。读写的上限,limit<=capacity。
## 2.下面是nio作为服务端具体代码
代码如下(示例):
```c
package cn.itcast.neety.c4;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.ConcurrentlinkedQueue;
@Slf4j
public class Boss {
public static void main(String[] args) throws IOException {
Thread.currentThread().setName("Boss");
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
//专用于链接事件
Selector boss = Selector.open();
ssc.register(boss, SelectionKey.OP_ACCEPT);
ssc.bind(new InetSocketAddress(9998));
//创建固定数量的worker
Worker worker = new Worker("worker=0");
while (true) {
boss.select();
Iterator iterator = boss.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
log.debug("===connected...{}", sc.getRemoteAddress());
//2关联selector
log.debug("=====beforeRegister{}", sc.getRemoteAddress());
worker.register(sc); //boss调用 初始化worker的selector 启动线程
log.debug("====afterRegister{}", sc.getRemoteAddress());
}
}
}
}
static class Worker implements Runnable {
private Thread thread;
private Selector selector;
private String name;
//线程是否启动
private volatile boolean start = false;
//线程安全队列放runnable对象
ConcurrentlinkedQueue queue = new ConcurrentlinkedQueue<>();
public Worker(String name) {
this.name = name;
}
public void register(SocketChannel sc) throws IOException {
if (!start) {
selector = Selector.open();
thread = new Thread(this, name);
thread.start();
start = true;
}
selector.wakeup();//唤醒selector
sc.register(selector, SelectionKey.OP_READ);
}
public void devBugAll(ByteBuffer source) {
source.flip();
StringBuilder s = new StringBuilder("");
for (int i = 0; i < source.limit(); i++) {
byte b = source.get(i);
s.append((char) b);
}
System.out.println("==========本次输出结果为:" + s);
source.clear();
}
@Override
public void run() {
try {
while (true) {
selector.select();
Iterator iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if ((key.isReadable())) {
try {
ByteBuffer buffer = ByteBuffer.allocate(16);
SocketChannel channel = (SocketChannel) key.channel();
int read = channel.read(buffer); //如果客户端是正常断开的话,read方法的返回值是-1
if(read == -1){
key.cancel();
}else {
devBugAll(buffer);
}
} catch (IOException e) {
e.printStackTrace();
//如果客户端被强制关闭那么把key从selectedKey集合中移除
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
---
# 总结
NIO使用selector实现了单个线程就可以监听多个channel的方式,使用Selector的好处在于:使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销。



