IO模型
IO客户端:
/**
* @program: learnnetty
* @description: IO客户端
* @create: 2020-04-29 10:47
**/
public class IOClient {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
try {
//目标ip以及端口号
Socket socket = new Socket("127.0.0.1", 8080);
while (true) {
//获取输出流并写入数据
socket.getOutputStream().write(
(new Date() + ": hello world").getBytes()
);
Thread.sleep(2000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
IO服务端:
/**
* @program: learnnetty
* @description: IO服务端
* @create: 2020-04-29 10:47
**/
public class IOServer {
public static void main(String[] args) throws IOException {
//监听端口
final ServerSocket serverSocket = new ServerSocket(8080);
//监听端口线程
new Thread(new Runnable() {
public void run() {
//不断轮询
while (true) {
try {
//执行Accept操作
final Socket socket = serverSocket.accept();
//读取数据线程
new Thread(new Runnable() {
public void run() {
try {
int len;
byte[] data = new byte[1024];
//获取socket的输入流
InputStream inputStream = socket.getInputStream();
//读取数据
while ((len = inputStream.read(data)) != -1){
System.out.println(new String(data, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
对于IO模型主要存在以下缺点:
- 线程资源受限:线程是操作系统中非常宝贵的资源,同一时刻有大量的线程处于阻塞状态是非常严重的资源浪费;
- 线程上下文切换效率低下:单机CPU的核心数固定,线程数过多会导致操作系统频繁的切换线程,导致应用效率下降;
- IO编程中,数据的读写是以字节流为单位的;
NIO模型
NIO解决了IO模型中线程资源受限的问题
IO模型中,一个连接接入时,会创建一个线程,线程对应一个while死循环,而死循环的目的是为了不断监听这个连接上是否有数据可读,但是使用死循环的副作用是在其它情况下(无数据可读时)这些while死循环会浪费资源。
在NIO模型中,将n条线程对应n个死循环改为了只有一个死循环,这个死循环则由一个线程控制。在NIO模型中添加了selector,在连接接入时,在NIO中不再建立新的while死循环去监听是否有新的数据可读,而是把这条新接入的连接注册到selector上,通过检查selector就可以批量的检查出是否数据可读的连接,然后再读取数据。
NIO解决了IO模型中线程上下文切换效率低下的问题
由于NIO模型中线程数量大大降低,线程切换的效率因此大大提高。
NIO中不再以字节流为单位读写数据
IO模型读写是面向流的,一次性能从流中读取一个或者多个字节,并且读取完后无法再读取,如需缓存数据需要自己实现。
NIO模型读写是面向缓存(buffer)的,可以读取buffer中任意一个字节数据,不许自己实现缓存。
NIO服务端示例代码:
/**
* @program: learnnetty
* @description: NIO服务端
* @create: 2020-04-29 15:17
**/
public class NIOServer {
public static void main(String[] args) throws IOException {
//服务端选择器
final Selector serverSelector = Selector.open();
//客户端选择器
final Selector clientSelector = Selector.open();
//处理连接线程
new Thread(new Runnable() {
public void run() {
try {
//获取Channel
ServerSocketChannel listenerChannel = ServerSocketChannel.open();
//绑定端口
listenerChannel.socket().bind(new InetSocketAddress(8080));
//是否阻塞
listenerChannel.configureBlocking(false);
//注册
listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);
while (true){
//检查是否有新的连接
if (serverSelector.select(1) > 0){
Set<SelectionKey> keys = serverSelector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()){
SelectionKey key = keyIterator.next();
//连接是否是可Accept的
if (key.isAcceptable()){
try {
//获取Channel
SocketChannel clientChannel = ((ServerSocketChannel)key.channel()).accept();
clientChannel.configureBlocking(false);
//注册
clientChannel.register(clientSelector, SelectionKey.OP_READ);
}finally {
keyIterator.remove();
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
//读取数据线程
new Thread(new Runnable() {
public void run() {
try {
while (true){
//是否连接又可读取的数据
if (clientSelector.select(1) > 0){
Set<SelectionKey> keys = clientSelector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()){
SelectionKey key = keyIterator.next();
//当前数据是否可读
if (key.isReadable()){
try {
//获取Channel
SocketChannel clientChannel = (SocketChannel)key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取数据
clientChannel.read(buffer);
buffer.flip();
System.out.println(Charset.defaultCharset().newDecoder().decode(buffer).toString());
}finally {
keyIterator.remove();
key.interestOps(SelectionKey.OP_READ);
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
NIO模型核心思路:
- NIO模型通常会有两个线程,每个线程绑定一个轮询器selector。
- 服务端检测到新的连接后,不再是创建一个新的线程,而是直接将其注册到某个selector上。
- selector通常在while死循环内,如果某一时刻有新的连接接入/连接内有可读数据,就可以通过Selector.select()方法发现,并进行后续处理。
- 在数据的读写阶段使用Buffer。
参考自掘金大佬