前言
阻塞型IO在读取数据时,如果数据未到达,会一直阻塞到读取到数据为止,所以称为阻塞型IO,在高并发的环境下性能不佳。
NIO不是使用 “流” 来作为数据的传输方式,而是使用通道,通道的数据传输是双向的,且NIO的数据写入和读取都是异步的,不会阻塞线程,所以称为非阻塞型IO,在高并发的环境下性能很好
之前写了Socket编程的应用聊天室,用的是阻塞版io,现推出NIO式聊天室
一、NIO简介
NIO不是使用流来作为数据传输的方式,而是使用通道,通道的数据传输是双向的,且NIO的数据写入和读取都是异步的,不会阻塞线程,所以称为非阻塞式IO,在高并发的环境下性能很好
相比之下,阻塞性IO在读取数据的时候,如果数据未到达,会一直阻塞直到读取到数据为止,所以称为阻塞式IO,在高并发 的环境下性能不佳;
二、思路
创建一个List用来存储每一个连接进来的Client创建的管道,所以添加类型修饰<SocketChannel>,代码List<SocketChannel> allChannel=new ArrayList<>();
创建ServerSocketChannel,ServerSocketChannel和SocketChannel的关系和ServerSocket与Socket的关系是一样的,一个物欲服务端,一个位于客户端,一个属于总阀门一个属于下属。
由于Channel默认是阻塞通道,因此需要通过配置修改其为非阻塞式通道,再接着是绑定端口
创建多路选择器,多路选择器是能够使用一个线程,通过轮询多个接口是否有Client关注的事件发生一种机制,避免了BIO通过多线程处理每一个客户端接入耗费线程的弊端
还用到了一点就是Bytebuffer的flip功能,读的时候position逐步往后走,读完flip,使limit=Position。position归0,至于capacity始终表示的是容量
三、上代码
package what;
import java.io.IOException;
import java.net.InetSocketAddress;
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.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* 使用NIO完成聊天室服务端
*/
public class NIOServer {
public void start(){
try {
List<SocketChannel> allChannel=new ArrayList<>();
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(8088));
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while(true){
selector.select();
Set<SelectionKey> keySet=selector.selectedKeys();
for(SelectionKey key:keySet){
if(key.isAcceptable()){
ServerSocketChannel ssc=(ServerSocketChannel) key.channel();
SocketChannel socketChannel=ssc.accept();
if(socketChannel==null){
continue;
}
socketChannel.configureBlocking(false);
//将SocketChannel注册到多路选择器上,关心的事件为:有数据可读取(客户端发信息过来)
socketChannel.register(selector,SelectionKey.OP_READ);
allChannel.add(socketChannel);
System.out.println(
socketChannel.socket().getInetAddress().getHostAddress()+
"上线了,当前在线人数:"+allChannel.size());
//如果该事件是表示有消息可读
}else if(key.isReadable()){
//SocketChannel是响应有消息可读的,所以这里造型成SocketChannel
SocketChannel sc=(SocketChannel)key.channel();
ByteBuffer buffer=ByteBuffer.allocate(1024);
sc.read(buffer);//读取客户端发送过来的 所有 消息!!这里读到的消息中就含有CRLF
buffer.flip();
if(buffer.limit()==0){//如果本次一个字节都没有读取到
buffer.clear();
continue;
}
//获取缓冲区本次读取到的所有字节:0——limit
byte[] data=new byte[buffer.limit()];
buffer.get(data);
String message=new String(data, StandardCharsets.UTF_8);
message=sc.socket().getInetAddress().getHostAddress()+":"+message;//不能加.trim()
buffer.clear();
buffer.put(message.getBytes(StandardCharsets.UTF_8));//敢这么放主要是这个1024的buffer不是那么容易占满的
//广播给所有客户端
for(SocketChannel channel:allChannel){
buffer.flip();
System.out.println(buffer);
channel.write(buffer);
}
System.out.print(message);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
NIOServer server =new NIOServer();
server.start();
}
}
总结
NIO主要用来解决高并发下的线程数过大问题,另外由于Channel是读写合一的,read和write都是可以直接通过SocketChannel.read(buffer)和SocketChannel.write(message)将消息读和写的,这里需要注意没有输入输出流那些东西。