一、IO模型
1、5种IO模型
- 阻塞IO模型
- 非阻塞IO模型:一般会轮询检查有没有数据到来,没有就返回一个EWOULDBLOCK错误。
- IO复用模型
多个进程的IO可以注册到一个复用器上,然后用一个进程调用该select,select会监听所有注册进来的IO。
当没有IO在内核缓存区有数据时,select会阻塞,当任一IO有数据了,select就返回,并且自己或者通知另外的进程(注册进程)来再次发起读取IO,读取内核区准备好的数据
- 信号驱动IO模型:首先开启套接口信号驱动IO功能,并通过系统调用sigaction执行一个信号处理函数。当数据准备就绪时,就为该进程生成了一个SIGIO信号,通过信号回调通知应用程序来读取数据
- 异步IO:告知内核启动某个操作,并让内核在整个操作完成后通知我们。
2、IO多路复用模型
通过将多个IO的阻塞复用到同一个select的阻塞上,从而使系统在单线程的情况下可以同时处理多个客户端请求。
目前支持IO多路复用的系统调用有select,pselect,poll,epoll
特性:
- 支持一个进程打开的socket描述符不受限制
- IO效率不会随着FD数目的增加而线性下降,传统select、poll会因为socket集合大时,因为每次都要扫描整个集合,导致效率会下降。但是epoll不存在这个问题
- 使用mmap加速内核与用户空间的消息传递
- epoll的API更加简单
二、NIO
NIO到底是什么?
有人说NIO是New IO,因为他对比于以前的I/O库是新增的,但是NIO类库的目标是让Java支持非阻塞I/O,所以也有人称之为Non-block I/O。
2.1 缓冲区Buffer
在NIO库中,所有的数据都是在Buffer中处理的。读取数据时,是直接读取到缓冲区中;写数据时,又是写入缓冲区中。缓冲区实质上是一个数组,但不仅仅是一个数组,还维护了一些对数据的结构化访问以及维护读写位置(limit)等信息。
2.2 通道Channel
Channel是一个通道,是全双工的,同时支持读写操作。
2.3 多路复用器Selector
多路复用器是Java NIO编程的基础。简单来讲Selector会不断轮询注册在上面的Channel,如果某个Channel上面发生读或写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SeletionKey可以获取就绪的Channel的集合,进行后续的IO处理。
三、NIO服务器、客户端
3.1 NIO实现一个TimeServer
TimeServer:
package com.mmc.springbootstudy.nio;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
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.util.Date;
import java.util.Iterator;
import java.util.Set;
public class MultiplexerTimeServer implements Runnable {
private Selector selector;
private ServerSocketChannel serverSocketChannel;
private volatile boolean stop;
public MultiplexerTimeServer(int port) {
try{
selector=Selector.open();
serverSocketChannel=ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(port),1024);
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
stop=false;
System.out.println("The time server is start port:"+port);
}catch (IOException e){
e.printStackTrace();
System.exit(1);
}
}
public void stop(){
stop=true;
}
@Override
public void run() {
while (!stop){
try {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
SelectionKey selectionKey=null;
while (iterator.hasNext()){
selectionKey = iterator.next();
iterator.remove();
try {
handleInput(selectionKey);
} catch (Exception e) {
e.printStackTrace();
if(selectionKey!=null){
selectionKey.cancel();
if(selectionKey.channel()!=null){
selectionKey.channel().close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//多路复用关闭后,注册在上面的Channel和Pipe都会自动关闭
if(selector!=null){
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey key) throws IOException {
if(key.isValid()){
if(key.isAcceptable()){
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(selector,SelectionKey.OP_READ);
}
if(key.isReadable()){
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int read = sc.read(readBuffer);
if(read>0){
readBuffer.flip();
byte[] bytes=new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "utf-8");
System.out.println("the time server receive order:"+body);
if(body.equals("query")){
String date = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
doWrite(sc,date);
}
}else if(read<0){
key.cancel();
sc.close();
}else {
//读到0字节,忽略
}
}
}
}
private void doWrite(SocketChannel channel,String content) throws IOException {
if(StringUtils.isNotEmpty(content)){
byte[] bytes = content.getBytes();
ByteBuffer writeBuffer=ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer);
}
}
}
package com.mmc.springbootstudy.nio;
public class TimeServer {
public static void main(String[] args) {
new Thread(new MultiplexerTimeServer(8899)).start();
}
}
TimeClient:
package com.mmc.springbootstudy.nio;
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.util.Iterator;
import java.util.Set;
public class TimeClientHandle implements Runnable {
private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private volatile boolean stop;
public TimeClientHandle(String host,int port){
this.host=host;
this.port=port;
try {
selector=Selector.open();
socketChannel=SocketChannel.open();
socketChannel.configureBlocking(false);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
@Override
public void run() {
try {
doConnect();
} catch (IOException e) {
e.printStackTrace();
}
while (!stop){
try {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
SelectionKey key=null;
while (iterator.hasNext()){
key=iterator.next();
iterator.remove();
try {
handleInput(key);
} catch (Exception e) {
if(key!=null){
key.cancel();
if(key.channel()!=null){
key.channel().close();
}
}
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
if(selector!=null){
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey key) throws IOException {
if(key.isValid()){
SocketChannel socketChannel= (SocketChannel) key.channel();
if(key.isConnectable()){
if(socketChannel.finishConnect()){
socketChannel.register(selector,SelectionKey.OP_READ);
doWrite(socketChannel);
}else {
System.exit(1);
}
}
if(key.isReadable()){
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int read = socketChannel.read(readBuffer);
if(read>0){
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "utf-8");
System.out.println("now is :"+body);
this.stop=true;
}else if(read<0){
key.cancel();
socketChannel.close();
}
}
}
}
private void doConnect() throws IOException {
//如果直接连接成功,则注册到多路复用器上,发送请求消息,读应答
if(socketChannel.connect(new InetSocketAddress(host,port))){
socketChannel.register(selector,SelectionKey.OP_READ);
doWrite(socketChannel);
}else {
socketChannel.register(selector,SelectionKey.OP_CONNECT);
}
}
private void doWrite(SocketChannel sc) throws IOException {
byte[] req="query".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
socketChannel.write(writeBuffer);
if(!writeBuffer.hasRemaining()){
System.out.println("Send order 2 server succeed");
}
}
}
package com.mmc.springbootstudy.nio;
public class TimeClient {
public static void main(String[] args) {
new Thread(new TimeClientHandle("127.0.0.1",8899)).start();
}
}
3.2 代码示例2
服务端:
package com.mmc.concurrentcystudy.nio;
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.util.Iterator;
import java.util.Set;
public class Server {
private Selector selector;
private ByteBuffer readBuffer=ByteBuffer.allocate(1024);
private ByteBuffer sendBuffer=ByteBuffer.allocate(1024);
String str;
public void start() throws IOException {
//打开服务器套接字
ServerSocketChannel ssc=ServerSocketChannel.open();
//设置为非阻塞
ssc.configureBlocking(false);
//进行地址绑定
ssc.bind(new InetSocketAddress("localhost", 8080));
//通过open找到Selector
selector=Selector.open();
//注册到selector
ssc.register(selector, SelectionKey.OP_ACCEPT);
while(!Thread.currentThread().isInterrupted()) {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while(iterator.hasNext()) {
SelectionKey key = iterator.next();
if(!key.isValid()) {
continue;
}
if(key.isAcceptable()) {
accept(key);
}else if(key.isWritable()) {
write(key);
}
else if(key.isReadable()) {
read(key);
}
iterator.remove(); //该事件已处理,可以移除
}
}
}
private void write(SelectionKey key) throws IOException {
SocketChannel socketChannel=(SocketChannel)key.channel();
System.out.println("write:"+str);
sendBuffer.clear();
sendBuffer.put(str.getBytes());
sendBuffer.flip();
socketChannel.write(sendBuffer);
socketChannel.register(selector,SelectionKey.OP_READ);
}
private void read(SelectionKey key) throws IOException {
SocketChannel socketChannel=(SocketChannel)key.channel();
readBuffer.clear();
int numRead;
try {
numRead=socketChannel.read(readBuffer);
} catch (IOException e) {
key.cancel();
socketChannel.close();
return;
}
str=new String(readBuffer.array(),0,numRead);
System.out.println(str);
socketChannel.register(selector,SelectionKey.OP_WRITE);
}
private void accept(SelectionKey key) throws IOException {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = ssc.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println("a new client connected "+clientChannel.getRemoteAddress());
}
public static void main(String[] args) throws IOException {
System.out.println("server started...");
new Server().start();
}
}
客户端:
package com.mmc.concurrentcystudy.nio;
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.util.Iterator;
import java.util.Scanner;
import java.util.Set;
public class Client {
ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
public void start() throws IOException {
//打开socket通道
SocketChannel sc=SocketChannel.open();
//设置为非阻塞
sc.configureBlocking(false);
//连接服务器地址
sc.connect(new InetSocketAddress("localhost",8080));
Selector selector=Selector.open();
sc.register(selector, SelectionKey.OP_CONNECT);
// Scanner scanner=new Scanner(System.in);
while(true) {
//此方法执行处于阻塞模式
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while(iterator.hasNext()) {
SelectionKey key=iterator.next();
iterator.remove();
//判断此通道是否进行连接操作
if(key.isConnectable()) {
sc.finishConnect();
sc.register(selector, SelectionKey.OP_WRITE);
System.out.println("server conneted.........");
break;
}else if(key.isWritable()) { //写操作
// System.out.print("please input message");
// String message=scanner.nextLine();
String message="服务端你好,我是Balla_兔子";
writeBuffer.clear();
writeBuffer.put(message.getBytes());
writeBuffer.flip();
sc.write(writeBuffer);
sc.register(selector, SelectionKey.OP_READ);
sc.register(selector, SelectionKey.OP_WRITE);
sc.register(selector, SelectionKey.OP_READ);
}else if(key.isReadable()) {
System.out.print("receive message:");
SocketChannel client = (SocketChannel) key.channel();
//将缓冲区清空以备下次读取
readBuffer.clear();
int num = client.read(readBuffer);
System.out.println(new String(readBuffer.array(),0, num));
sc.register(selector, SelectionKey.OP_WRITE);
}
}
}
}
public static void main(String[] args) throws IOException {
new Client().start();
}
}
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情