
// 使用 Selector
// 这里需要区分两个集合:
// 一个是注册 channel的集合,
// 一个是事件发生后存储对应 channel管理 SelectionKey的集合 - selectedKeys
// 对于后者需要处理事件后删除对于的 SelectionKey
@Slf4j
public class Server {
public static void main(String[] args) throws IOException {
// 使用 NIO来理解阻塞模式, 单线程
// 1.创建 Selector
Selector selector = Selector.open();
// 创建服务端对象
ServerSocketChannel server = ServerSocketChannel.open();
// 将 server切换至非阻塞模式 - 默认是阻塞模式
// ServerSocketChannel将不会在 accept上进行阻塞
server.configureBlocking(false);
server.bind(new InetSocketAddress(8080));
// 2.register - 注册 channel到 selector中去, selector可以来管理多个 channel
// SelectionKey - 获取事件以及知晓事件发生的 channel
// 简单理解 - 来管理 服务端的
// 事件其实是有多种类型的:accept、connect、read、write
// accept: 新的连接请求时触发 1 << 4
// connect: 客户端的, 连接建立后触发 1 << 3
// read:可读事件 1 << 0
// write: 可写事件 1 << 2
// 对于 sscKey, 其感兴趣的事件便是 accept
SelectionKey sscKey = server.register(selector, 0, null);
// sscKey 只关注 accept事件
sscKey.interestOps(SelectionKey.OP_ACCEPT);
log.debug("register {}", sscKey);
while (true) {
// 3.select 方法 - 事件驱动, 监听有无事件发生
// 没有事件发生, 线程阻塞
// 事件发生, 线程恢复运行
// 如果存在未被处理的事件 或 没被 cancelled的事件, 线程在此不会被阻塞 - 即不能对发生的事件置之不理
selector.select();
// 4.处理事件
// selectedKeys 获取发生的事件集合
// 根据不同的事件类型做不同的事件处理
// 此集合区别于注册 channel的集合 - 不会主动去删除 channel的管理者 SelectionKey
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
log.debug("事件集合数目 {}", selector.selectedKeys().size());
SelectionKey key = iter.next();
// log.debug("ops {}", key);
// 5. 区分事件类型
if (key.isAcceptable()) {
// 获取事件发生对应的 channel
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
// 建立连接 - 处理事件
SocketChannel sc = channel.accept();
sc.configureBlocking(false);
// 注册到 selector中去
// 这里返回的 sk, 其实也是将来负责对应的 SocketChannel上发生的事件
SelectionKey sk = sc.register(selector, 0, null);
sk.interestOps(SelectionKey.OP_READ);
} else if (key.isReadable()) { // 客户端关闭后,会触发 read事件
try {
// 获取触发读事件的 channel
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer bf = ByteBuffer.allocate(32);
// 处理事件
// read 返回 -1, 说明客户端断开了连接(正常断开)
// 正常情况下返回的会是读取到的字节数
int read = sc.read(bf);
// 这里需要处理客户端正常断开连接的情况
// 正常断开不会抛异常, 只是读取不到数据而已
if (read == -1) {
key.cancel();
} else {
bf.flip();
debugRead(bf);
}
} catch (IOException e) {
// 这里任然需要打印出捕获的异常
// 这里并不会终止服务端的运行
e.printStackTrace();
// 设置为取消状态, 以免来影响服务端运行
// 如果只是捕获, 不进行处理, 在 select处就不会被阻塞
key.cancel();
}
}
// 需手动移除当前已经处理完事件的 channel
iter.remove();
// log.debug("{}", sc);
// // 由于某种原因,不想去处理事件了, 使用 cancel
// key.cancel();
}
}
}
}
public class Client {
public static void main(String[] args) throws IOException {
// 1.创建客户端对象
SocketChannel client = SocketChannel.open();
// 2.建立与服务端之间的连接
// 指定主机名以及端口号
client.connect(new InetSocketAddress("localhost", 8080));
System.out.println("waiting...");
}
}