慕课网课程链接:https://www.imooc.com/video/19331
Java 知识目录:https://blog.csdn.net/qq_38009970/article/details/103266870
FileChannel
DatagramChannel
ServerSocketChannel/SocketChannel
//服务端通过服务器socket创建channel
ServerSocketChannel serverSocketChannel = new ServerSocketChannel.open();
//服务器端绑定端口
serverSocketChannel.bind(new InetSocketAddress(8000));
//服务器监听客户端连接,建立 socketChannel 连接
SocketChannel socketChannel = serverSocketChanel.accept();
//客户端连接远程主机及端口
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8000));
Channel
中的数据Buffer
类byte
数组可以容纳的字节数buffer
后,position
会向后移动到可写的位置。最大值为容量 - 1
position
会被重置为0。当从中读取数据时,positon
会向后移动到可读的位置buffer
中写入多少数据。此时Limit = Capacity
buffer
中读取多少数据。此时Limit = 写模式下的position值
position
位置。可以调用 Buffered.reset
方法获取这个位置Buffer API
Buffer flip() 翻转这个缓冲区。
Buffer mark() 将此缓冲区的标记设置在其位置。
Buffer clear() 清除此缓冲区。
ByteBuffer API:
static ByteBuffer allocate(int capacity) 分配一个新的字节缓冲区。
ByteBuffer put(byte[] src, int offset, int length) 相对大容量 put方法 (可选操作) 。
//创建Selector
Selector selector = new Selector.open();
//将 channel 注册到 selector 上,监听读就绪事件
SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_READ);
//阻塞等待 channel 有就绪事件发生
int selectNum = selector.select();
//获取就绪事件的 channel 集合
Set<SelectionKey> selectionkeys = selector.selectedKeys();
Selector
ServerSocketChannel
,并绑定监听端口Channel
设置为非阻塞模式Channel
注册到 Selector
上,监听链接事件Selector
的select
方法,检测就绪情况selectedKey
方法获取就绪 channel
集合代码示例:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
public class NioServer {
public void start() throws Exception {
//1. 创建 `Selector`
Selector selector = Selector.open();
//2. 创建 `ServerSocketChannel`,并绑定监听端口
ServerSocketChannel channel = ServerSocketChannel.open();
channel.bind(new InetSocketAddress(8000));
//3. 将`Channel`设置为非阻塞模式
channel.configureBlocking(false);
//4. 将`Channel` 注册到 `Selector` 上,监听链接事件
channel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器启动成功");
//5. 循环调用`Selector`的`select`方法,检测就绪情况
for(;;){
//TODO 获取可用连接的数量
int readChannel = selector.select();
//TODO 为什么加这一句
if(readChannel == 0) continue;
//6. 调用 `selectedKey` 方法获取就绪 `channel` 集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
//获取 SelectionKey 实例
SelectionKey selectionKey = iterator.next();
//移除当前的 selectionKey
iterator.remove();
//7. 判断就绪事件总类,调用业务处理方法
//如果是接入事件
if(selectionKey.isAcceptable()){
acceptHandler(channel,selector);
}
//如果是可读事件
if(selectionKey.isReadable()){
readHandler(selectionKey,selector);
}
}
}
}
/**
* 接入事件处理器
*/
public void acceptHandler(ServerSocketChannel serverSocketChannel,Selector selector) throws Exception{
//如果是接入事件,创建socketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
//将socketChannel设置为非阻塞的工作模式
socketChannel.configureBlocking(false);
//将ServerSocketChannel注册到selector上,监听可读事件
socketChannel.register(selector,SelectionKey.OP_READ);
//回复给客户端信息
socketChannel.write(Charset.forName("UTF-8").encode("你与聊天室里其他人都不是朋友关系,请注意隐私安全"));
}
/**
* 可读事件处理器
*/
public void readHandler(SelectionKey selectionKey,Selector selector)throws Exception{
//从 socketChannel 中获取已就绪的channel
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
//创建buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//循环读取客户端的请求信息
String request = "";
while (socketChannel.read(byteBuffer)>0){
//切换buffer模式
byteBuffer.flip();
//读取buffer中的内容
request += Charset.forName("UTF-8").decode(byteBuffer);
}
//将socketChannel再次注册到selector上,监听它的可读事件
socketChannel.register(selector,SelectionKey.OP_READ);
//将客户端发送的请求消息发送给其他客户端
if(request.length()>0){
broadCast(selector,socketChannel,request);
}
}
public void broadCast(Selector selector,
SocketChannel sourceChannel,
String request){
//获取已接入的客户端Channel
Set<SelectionKey> selectionKeys = selector.keys();
selectionKeys.forEach(selectionKey -> {
Channel channel = selectionKey.channel();
if(channel instanceof SocketChannel && channel != sourceChannel){
try {
((SocketChannel)channel).write(Charset.forName("UTF-8").encode(request));
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
public static void main(String[] args) throws Exception {
NioServer nioServer = new NioServer();
nioServer.start();
}
}
代码实现
NioClient
public class NioClient {
public void start(String nickname) throws Exception{
//连接服务器
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8000));
System.out.println("客户端启动成功!");
//获取服务端响应数据
Selector selector = Selector.open();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
new Thread(new NioClientHandler(selector)).start();
//向服务器端发送数据
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String request = scanner.nextLine();
if(request != null && request.length()>0){
socketChannel.write(Charset.forName("UTF-8").encode(nickname + " : " + request));
}
}
}
public static void main(String[] args) throws Exception {
NioClient nioClient = new NioClient();
nioClient.start("A");
}
}
ClientHandler
作用:获取服务端响应数据。和 NioClient for循环的代码一致
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
public class NioClientHandler implements Runnable{
private Selector selector;
public NioClientHandler(Selector selector) {
this.selector = selector;
}
@Override
public void run() {
//循环调用selector接口,检测就绪状态
try {
for(;;){
int select = selector.select();
if(select == 0) continue;
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
iterator.remove();
if(selectionKey.isReadable()){
readHandler(selectionKey,selector);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void readHandler(SelectionKey selectionKey,Selector selector) throws IOException {
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
StringBuffer response = new StringBuffer(new String(""));
while (channel.read(byteBuffer) > 0){
byteBuffer.flip();
response.append(Charset.forName("UTF-8").decode(byteBuffer));
}
channel.register(selector,SelectionKey.OP_READ);
if(response.length()>0){
System.out.println(response);
}
}
}