Java NIO实例

概述

回显服务器是指接收到客户端的数据,原封不动的返回给客户端


小提示  
在阅读这里之前,希望先看一个视频和相关API的介绍,API的介绍不是很详细,当然也可以遇到不知道的API在百度/Google搜索
视频:https://www.bilibili.com/video/av57390893?t=5655
相关API: https://lyhcc.github.io/post/f2d80d11.html#more


源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**NIOServer.java**/
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.nio.channels.SocketChannel;
import java.util.Iterator;

public class NIOServer {
public static void main(String[] args) throws IOException {
//1. 打开一个选择器
Selector selector = Selector.open();

//2. 打开一个ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

//3. 配置异步模式
serverSocketChannel.configureBlocking(false);
//4. 绑定到TCP端口上,注意ServerSocketChannel不提供bind方法
//需要使用ServerSocketChannel 内部的socket对象对应的bind方法
serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",12122));

//5. 注册
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

//6. 服务器循环,调用Selector.select()方法等待IO事件,如果返回值为0,表明没有事件发生
while (true) {
//如果select()带参数,它将不会堵塞到等有感兴趣的数据过来
if (selector.select() == 0) {
System.out.println("Nothing to do!");
continue;
}

//获得连接已选键
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();

//如果是事件是”通道上有请求“
if (key.isAcceptable()) {
//相应的处理是通过accept()操作获得SocketChannel对象,并配置对象的异步工作方式
SocketChannel channel = serverSocketChannel.accept();

//设置异步工作模式、注册到选择器中,注册事件为通道可读 SelectionKeys.OP_READ
channel.configureBlocking(false);
SelectionKey connkey = channel.register(selector, SelectionKey.OP_READ);
//根据注册分到的SelectionKey 对象构造连接对象,并将对象作为SelectionKey对象附件
NIOConnection conn = new NIOConnection(connkey);
connkey.attach(conn);
}
//key有效,即通道未关闭,并且为可读的OP_READ
if (key.isValid() && key.isReadable()) {
NIOConnection conn = (NIOConnection) key.attachment();
conn.handleRead();
}
//key有效,即通道未关闭,并且为可写的 OP_WRITE
if (key.isValid() && key.isWritable()) {
NIOConnection conn = (NIOConnection) key.attachment();
conn.handleWrite();
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**NIOConnection.java**/
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;

public class NIOConnection {
private SelectionKey key;
private SocketChannel channel;
private ByteBuffer buffer;

public NIOConnection(SelectionKey key) {
this.buffer = ByteBuffer.allocate(1024);
this.key = key;
this.channel = (SocketChannel) key.channel();
}

/**
* 读操作
* @throws IOException
*/
public void handleRead() throws IOException {
int byteRead = channel.read(buffer);

if (byteRead == -1) {
//对方已关闭socket,服务器就将通道关闭
channel.close();
}else {
//有数据可读,此时设置感兴趣的I/O事件为读或写,
//读出了一部分数据,就说嘛有空间可以写了,当然还有可能有其他数据可读
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);

}
}

public void handleWrite() throws IOException {
//要开始读数据了,就得先把数据的开始索引,也就是当前索引position,改为数据的开始位置,
//在此之前,得先把limit的改为position,position所在位置是数据的下一个写入位置,
// 把限制limit设置为position
// 结合上面position ~ limit就是当前的全部数据的位置
buffer.flip();
//开始写出
System.out.println("收到的数据:" + Charset.forName("UTF-8").decode(buffer).toString());
channel.write(buffer);

//之后判断是否还有数据存在,如果没数据了,就可以将其设置为只可以读了
if (!buffer.hasRemaining()) {
key.interestOps(SelectionKey.OP_READ);
}
//写完后,有可能还有数据剩余,就将数据移到buffer的最前面
buffer.compact();
}

注意:NIO是没有专门的客户端的,你可以使用Socket进行连接,也可以使用telnet进行连接


Selector的使用步骤

  1. 创建一个Selector实例:
  2. 将该实例注册到各种通道,指定每个通道上感兴趣的I/O操作;
  3. 重复执行(选择器循环):
    1. 调用一种select()方法;
    2. 获取已选键集;
    3. 对于已选键集中的每-一个键:
      1. 将已选键从键集中移除;
      2. 获取信道,并从键中获取附件(如果需要);
      3. 确定准备就绪的操作并执行;对于accept操作获得的SocketChannel对象,需将信道设置为非阻塞模式,并将其注册到选择器中;
      4. 根据需要,修改键的兴趣操作集。

错误总结

这里的connkey用错会报错
Exception in thread "main" java.lang.ClassCastException: sun.nio.ch.ServerSocketChannelImpl cannot be cast to java.nio.channels.SocketChannel

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×