服务端
package com.suosuo.myprojects.netty.channel;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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.Set;
/**
* 1. 监听
* 1.1 服务端通过 SelectionKey 监听客户端连接事件
* 1.2 SelectionKey 监听客户端 SocketChannel 数据读取事件
* 2. 关系 Selector selectionkey = channel = buffer 1:n:n:n
* <p>
* 总结:
* 创建服务端ServerSocketChannel,并监听端口
* 创建Selector
* 服务端ServerSocketChannel 注册到Selector,监听客户端连接事件
* Selector 监听客户端连接事件,存在连接,获取该Selector 对应所有Selectionkey
* 遍历Selctionkey,监听连接事件,针对每个客户端判断是否有连接请求
* 创建客户端socketChannel,并注册到Selector,监听客户端读事件
* 读取客户端数据,从channel中读取到ByteBuffer
*/
@Slf4j
public class NIOServer {
public static void main(String[] args) {
ServerSocketChannel serverSocketChannel = null;
try {
// 创建ServerSocketChannel
serverSocketChannel = ServerSocketChannel.open();
// 服务端创建serverSocket 监听 端口
ServerSocket socket = serverSocketChannel.socket();
socket.bind(new InetSocketAddress(6666));
// 设置为非阻塞
serverSocketChannel.configureBlocking(false);
// 创建selector
Selector selector = Selector.open();
// 把serverSocketChannel注册到selector 关心 事件为客户端连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 缓存等待客户端连接
while (true) {
// selector 进行监听 select方法,
// 等待1s,没有连接事件发生,返回,这里需要注意Selector监听很多个客户端,这里可能连接多个,对应多个通道,下面获取SelectionKey才有意义
if (selector.select(1000) == 0) {
log.info("服务器等待了1s,无连接");
continue;
}
// 如果返回>0,获取selectionkey集合,返回有事件发生的通道个数
Set<SelectionKey> selectionKeys = selector.selectedKeys();
// 遍历selectionkey
Iterator<SelectionKey> iterator = selectionKeys.iterator();
if (iterator.hasNext()) {
// 获取key,对应一个通道
SelectionKey key = iterator.next();
// 判断key 对应通道发生的事件,如果是op_accept,有新的客户的连接
if (key.isAcceptable()) {
// 为该客户端生成一个socketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
log.info("服务端:客户端连接成功,生成了一个socketChannel:{}", socketChannel.hashCode());
// 设置为非阻塞
socketChannel.configureBlocking(false);
// 将 socketChannel 注册到Selector上,一个Selector上可以注册多个SocketChannel
// 关注客户端读事件,并关联一个buffer,读取通道的中数据到buffer中, 由此SocketChannel 与 Selector建立关联
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
// 判断客户端是否有读事件发生,根据selectkey判断
if (key.isReadable()) {
log.info("服务端:监听到客户端请求,开始读取客户端通道数据...");
// 通过selectionkey 反向获取SocketChanel
// SelectableChannel selectableChannel = key.channel();
SocketChannel socketChannel = (SocketChannel) key.channel();
// 获取channel关联buffer
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
// 读取客户端通道中数据写入buffer
socketChannel.read(byteBuffer);
log.info("服务端: 读取客户端数据:{}", new String(byteBuffer.array()));
}
// SelectionKey 处理完删除,避免重复操作
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
serverSocketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端
package com.suosuo.myprojects.netty.channel;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* 1.创建客户端channel
* 2.监听服务端,绑定端口
* 连接服务器
* 3.创建buffer,写入发送内容
* 4.写入channel
*/
@Slf4j
public class NIOClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
// 客户端连接服务端
if (!socketChannel.connect(inetSocketAddress)) {
// 没连接成功,可能是没连接完成
if (!socketChannel.finishConnect()) {
log.info("连接需要时间,客户端不会阻塞,可以做其他工作");
}
}
String content = "hello world!";
ByteBuffer byteBuffer = ByteBuffer.wrap(content.getBytes());
socketChannel.write(byteBuffer);
log.info("客户端:数据已写入通道,已发送完成");
}
}