I/O
I/O 模型的五种类型
- 阻塞I/O模型:最常用的模型,进程处理过程中阻塞一致到结果返回
- 非阻塞I/O模型:轮询检查是否缓冲区有数据,没有直接跳过
- I/O复用模型:select/poll 或者epoll,select 等待多个文件描述符就绪,即多个I/O阻塞在一个select,等待select分配
- 信号驱动I/O模型:非阻塞,当数据准备就绪生成信号通知进程处理
- 异步I/O:通知程序执行,等程序执行完主动通知
epoll(I/O复用模型)相对于select的改进
- 支持一个进程打开的socket文件描述符(fd)没有上限(仅限制操作系统的最大文件句柄数)
- I/O效率不会随着FD数目的增加而下降
- 使用mmap加速内核与用户空间的消息传递 转载:传送门mmap介绍
- API更加简单
NIO(非阻塞IO)
- 缓冲区buffer:包含写入/读出数据的对象。所有数据通过缓冲区处理,缓冲区本质为一个数组。常用byteBuffer,字节缓存区。
- 通道channel
- 多路复用器selector:提供选择已经就绪的任务的能力。轮询channel获取就绪的channel集合,进行后续的I/O处理。由于采用epoll没有上限,selector之需要一个线程执行轮询则可接入n多的客户端。
NIO服务端
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
public class ServerSocketChannelDemo implements Runnable{
private Selector selector;
private volatile boolean flag = false;
public ServerSocketChannelDemo() {
try {
ServerSocketChannel socketChannel = ServerSocketChannel.open();
socketChannel.socket().bind(new InetSocketAddress(InetAddress.getByName("localhost"),8080));
//设置非阻塞模式
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_ACCEPT);
//创建selector
selector = Selector.open();
}catch (IOException e){
e.printStackTrace();
}
}
public void stop(){
this.flag = true;
}
@SneakyThrows
@Override
public void run() {
while (!flag){
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
SelectionKey next = null;
while (iterator.hasNext()){
next = iterator.next();
iterator.remove();
try {
this.handleInput(next);
}catch (Exception e){
if(next != null){
next.cancel();
if(next.channel() != null){
next.channel().close();
}
}
}
}
}
if(selector != null){
selector.close();
}
}
@SneakyThrows
public void handleInput(SelectionKey key){
if(key.isValid()){
//处理新接入的请求信息
if(key.isAcceptable()){
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel channel = ssc.accept();
channel.configureBlocking(false);
channel.socket().setReuseAddress(true);
channel.register(selector,SelectionKey.OP_READ);
}
if(key.isReadable()){
SocketChannel channel = (SocketChannel)key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int read = channel.read(byteBuffer);
if(read > 0){
byteBuffer.flip();
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
String body = new String(bytes, StandardCharsets.UTF_8);
this.doWrite(channel,body);
}else if(read < 0){
key.channel();
channel.close();
}
}
}
}
@SneakyThrows
private void doWrite(SocketChannel channel,String response){
if(!StringUtils.isBlank(response)){
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer);
}
}
}
NIO客户端
import lombok.SneakyThrows;
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.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
public class ServerSocketChannelClientDemo implements Runnable{
private Selector selector;
private SocketChannel socketChannel;
private volatile boolean stop = false;
@SneakyThrows
public ServerSocketChannelClientDemo(){
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
}
@SneakyThrows
private void connect(){
if(socketChannel.connect(new InetSocketAddress("localhost",8080))){
socketChannel.register(selector, SelectionKey.OP_READ);
//
doWrite(socketChannel);
}else {
socketChannel.register(selector,SelectionKey.OP_CONNECT);
}
}
@SneakyThrows
private void doWrite(SocketChannel sc){
byte[] req = "hello nio".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
sc.write(writeBuffer);
if(!writeBuffer.hasRemaining()){
//已经全部发完
}
}
@Override
public void run() {
connect();
while (!stop){
try {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
SelectionKey key;
while (iterator.hasNext()){
key = iterator.next();
iterator.remove();
try {
handleInput(key);
}catch (Exception e){
if(key != null){
key.channel();
if(key.channel() != null){
key.channel().close();
}
}
}
}
}catch (Exception e){
e.printStackTrace();
}
}
if(selector != null){
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@SneakyThrows
private void handleInput(SelectionKey key) {
if(key.isValid()){
SocketChannel channel = (SocketChannel) key.channel();
if(key.isConnectable()){
if(channel.finishConnect()) {
channel.register(selector, SelectionKey.OP_READ);
doWrite(channel);
}
}
if(key.isReadable()){
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int length = channel.read(readBuffer);
if(length > 0){
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, StandardCharsets.UTF_8);
System.out.println(body);
this.stop = true;
}else if(length < 0){
key.cancel();
channel.close();
}
}
}
}
}