一、NIO的引入
对于BIO通信时,服务端ServerSocket会在accept方法,和read方法发生堵塞,导致别的请求无法进入,当然可以使用多线程处理,如果请求很多,就会有很多线程,消耗太大。NIO服务端ServerSocketChannel设置为非阻塞,把通道注册到选择器Selector上,多个客户端请求,都注册到选择器Selector上,由选择器轮询已经准备好的通道进行处理。
二、Java NIO的核心
Buffer、Channel、Selector。
Buffer缓冲区可进行读写,put写数据,flip方法转换为读数据,clear清空缓冲区。
Channel有ServerSocketChannel、SocketChannel,通道可进行读写操作。
Seletor会监听connect、accept、read、write事件。
三、细说Selector
1.Selector注册
通道调用register方法,把通道注册到Selector上,第一个参数是Selector,第二个参数是interest集合,意思是Selector监听channel时,对什么事件感兴趣,包含connect、accept、read、write事件。
ssChannel.register(selector,SelectionKey.OP_ACCEPT);
2.SelectionKey
当想Seletor注册channel是,会返回SelectionKey对象,这个对象也会保存在选择器Selector中,SelectionKey包含interest、channel属性,也就算说可以通过SelectionKey获得interest、channel。
3.selector.keys()与selector.selectKeys()
channel注册到selector,会把SelectionKey放到SelectorImp对象的Set
4.seletor.selector()
selector.selector方法返回的是publicSelectedKeys集合的大小,也可以说,触发事件通道的个数。如果返回的数量为0,则会处于阻塞状态。
四、服务端代码
public class NIOServer {
public static void main(String[] args) {
try {
openServer(8888);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void openServer(int port) throws IOException {
// 打开通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
// 设置为非阻塞
ssChannel.configureBlocking(false);
// 绑定端口
ssChannel.socket().bind(new InetSocketAddress(port));
// 选择器
Selector selector = Selector.open();
// 把通道接受事件注册到选择器
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
// 当被选中的通道个数大于0时,进行业务处理
while (selector.select() > 0){
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()){
SelectionKey sk = it.next();
if(sk.isAcceptable()){
// 得到发出请求连接的通道
ServerSocketChannel channel = (ServerSocketChannel) sk.channel();
SocketChannel sChannel = channel.accept();
// 通道设置非阻塞
sChannel.configureBlocking(false);
// 把通道读事件注册到选择器
sChannel.register(selector, SelectionKey.OP_READ);
}
else if(sk.isReadable()){
// 得到发出读请求的通道
SocketChannel sChannel = (SocketChannel) sk.channel();
// 定义一个缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
// len为0时,无数据可读;len为-1时,客户端关闭了socket
while ((len = sChannel.read(buffer)) > 0){
// 更改缓冲区为读模式
buffer.flip();
String str = new String(buffer.array(), 0, len);
System.out.println(str);
// 清空缓冲区
buffer.clear();
}
// 写响应数据
buffer.put("server response".getBytes());
buffer.flip();
sChannel.write(buffer);
buffer.clear();
if (len == -1){
sChannel.close();
}
}
// 取消
it.remove();
}
}
}
}
五、客户端代码
public class NIOClient {
public static void main(String[] args) {
try {
connect("127.0.0.1", 8888);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void connect(String host, int port) throws IOException {
// 打开通道
SocketChannel sChannel = SocketChannel.open();
// 通道设置为非阻塞
sChannel.configureBlocking(false);
// 连接
sChannel.connect(new InetSocketAddress(host, port));
// 选择器
Selector selector = Selector.open();
// 把通道注册到选择器,监听读事件
sChannel.register(selector, SelectionKey.OP_READ);
while (!sChannel.finishConnect());
// 开启一个线程读数据
new Thread(new Runnable() {
@Override
public void run() {
// 定义缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
// 轮询
while (selector.select() > 0){
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()){
SelectionKey sk = it.next();
// 读事件
if (sk.isReadable()){
SocketChannel channel = (SocketChannel) sk.channel();
int len = 0;
while ((len = sChannel.read(buffer))> 0){
sChannel.read(buffer);
buffer.flip();
String str = new String(buffer.array(), 0, len);
System.out.println(str);
buffer.clear();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
// 主线程写数据
Scanner sc = new Scanner(System.in);
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true){
String str =sc.next();
if("quit".equals(str)){
break;
}
buffer.put(str.getBytes());
buffer.flip();
sChannel.write(buffer);
buffer.clear();
}
// 关闭通道
sChannel.close();
}
}