关于java的io
简介
本篇文章是为了学习netty作为铺垫,同时也为了了解java的io模型。
本项目源码github地址:https://github.com/itwwj/netty-learn.git 中的 netty-day01-io项目
1. java的io分类
java的io分为bio、nio和aio。
BIO 同步阻塞I/O模式,全称Block-IO 是一种同步阻塞的io模型。并发处理能力低,通信耗时。
NIO同步非阻塞模式,全程 Non-Block IO ,是Java SE 1.4版以后,针对网络传输效能优化的新功能。是一种非阻塞同步的通信模式。NIO 与原来的 I/O 有同样的作用和目的, 他们之间最重要的区别是数据打包和传输的方式。原来的 I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。面向流的 I/O 系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。面向块的 I/O 系统以块的形式处理数据。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多。但是面向块的 I/O - 缺少一些面向流的 I/O 所具有的优雅性和简单性。
AIO异步非阻塞I/O模型,全程 Asynchronous IO,是异步非阻塞的IO。是一种非阻塞异步的通信模式。在NIO的基础上引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。
简单的来比较就是:
bio:
发起请求————>阻塞等待————>处理完成
nio:
发起请求——>单线程selector轮询channel——>处理请求——>处理完成
aio:
发起请求————————————>通知回调
其中aio是最理想的io模型,不过很遗憾linux暂不支持aio
2.tcp三次握手和四次挥手
三次握手:
第一次握手: 客户端将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认。
第二次握手: 服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。
第三次握手: 客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
四次挥手
第一次挥手: A数据传输完毕需要断开连接,A的应用进程向其TCP发出连接释放报文段(FIN = 1,序号seq = u),并停止再发送数据,主动关闭TCP连接,进入FIN-WAIT-1状态,等待B的确认。
第二次挥手: B收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),B进入CLOSE-WAIT关闭等待状态,此时的TCP处于半关闭状态,A到B的连接释放。而A收到B的确认后,进入FIN-WAIT-2状态,等待B发出的连接释放报文段。
第三次挥手: 当B数据传输完毕后,B发出连接释放报文段(FIN = 1,ACK = 1,序号seq = w,确认号ack=u+1),B进入LAST-ACK(最后确认)状态,等待A 的最后确认。
第四次挥手: A收到B的连接释放报文段后,对此发出确认报文段(ACK = 1,seq=u+1,ack=w+1),A进入TIME-WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,A才进入CLOSE状态。
3.使用io实现tcp服务端
3.1 bio实现
bio作为一种阻塞io效率低,但实现简单,经常和多线程配合使用
代码实现:
ServerSocket socket = new ServerSocket(9090);
while (true) {
Socket accept = socket.accept();
new Thread(() -> {
InputStream is = null;
try {
is = accept.getInputStream();
int size = is.read();
byte[] bytes = new byte[size];
is.read(bytes);
System.out.println(“收到消息:”+new String(bytes));
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
3.2 nio实现
public class NioServer {
public static void main(String[] args) throws IOException {
//创建serverSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//创建selector
Selector selector = Selector.open();
//绑定端口1100
serverSocketChannel.socket().bind(new InetSocketAddress(1100));
//设置成非阻塞
serverSocketChannel.configureBlocking(false);
//将serverSocketChannel注册到selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//循环等待客户端连接
while (true) {
//阻塞1s如果没有事件就跳出本次循环
if (selector.select(1000) == 0) {
continue;
}
//如果返回>0,表示以获取到关注的事件 获取selectionKeys集合
//selectedKeys获取有事件发生的SelectionKey!!!
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey key=iterator.next();
//判断当前的事件是否是key的注册事件
if (key.isAcceptable()) {
SocketChannel socketChannel = serverSocketChannel.accept();
//设置为非阻塞
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
System.out.println("收到连接。。。" + serverSocketChannel.hashCode());
//判断当前的事件是否是当前SelectionKey的读事件
} else if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
channel.read(byteBuffer);
System.out.println("服务端收到:" + new String(byteBuffer.array()));
}
iterator.remove();
}
}
}
}
3.3 aio实现
java aio实现:
启动类:
public class AioServer {
private static AioServerHandler serverHandler;
public static int clientCount = 0;
public static void start() {
if (serverHandler != null) {
return;
}
serverHandler = new AioServerHandler(1100);
new Thread(serverHandler, "Server").start();
}
public static void main(String[] args) {
AioServer.start();
}
}
AioServerHandler :
public class AioServerHandler implements Runnable {
public CountDownLatch latch;
//异步通信通道
public AsynchronousServerSocketChannel channel;
public AioServerHandler(int port){
try {
//建立服务端通道
channel=AsynchronousServerSocketChannel.open();
//绑定端口
channel.bind(new InetSocketAddress(port));
System.out.println("server is start,port:"+port);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
latch=new CountDownLatch(1);
//接收客户端连接
channel.accept(this,new AioAccepHandler());
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
AioReadHandler :
public class AioReadHandler implements CompletionHandler<Integer, ByteBuffer> {
private AsynchronousSocketChannel channel;
public AioReadHandler(AsynchronousSocketChannel channel) {
this.channel = channel;
}
/**
* 读取到消息后的处理
*
* @param result
* @param attachment
*/
@Override
public void completed(Integer result, ByteBuffer attachment) {
if (result == -1) {//如果条件成立,说明客户端主动终止了tcp套接字,这时服务端终止就可以了
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
attachment.flip();
byte[] bytes = new byte[attachment.remaining()];
attachment.get(bytes);
String msg;
try {
System.out.println(result);
msg = new String(bytes, "UTF-8");
System.out.println("server accept message:" + msg);
String response = Ch01Const.response(msg);
doWrite(response);
} catch (Exception e) {
e.printStackTrace();
}
}
private void doWrite(String result) {
byte[] bytes = result.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
if (attachment.hasRemaining()) {//如果没写完
channel.write(attachment, attachment, this);
} else { //如果写完 读取数据
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
//异步读取数据
channel.read(readBuffer,readBuffer,new AioReadHandler(channel));
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
this.channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
AioAccepHandler :
public class AioAccepHandler implements CompletionHandler<AsynchronousSocketChannel, AioServerHandler> {
@Override
public void completed(AsynchronousSocketChannel result, AioServerHandler attachment) {
AioServer.clientCount++;
System.out.println("链接的客户端数:" + AioServer.clientCount);
//重新注册监听,让别的客户端也可以连接
attachment.channel.accept(attachment, this);
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
//异步读取
//1)ByteBuffer dst:接收缓冲区,用于从异步Channel中读取数据包;
//2) A attachment:异步Channel携带的附件,通知回调的时候作为入参使用;
//3) CompletionHandler<Integer,? super A>:系统回调的业务handler,进行读操作
result.read(readBuffer,readBuffer,new AioReadHandler(result));
}
@Override
public void failed(Throwable exc, AioServerHandler attachment) {
exc.printStackTrace();
attachment.latch.countDown();
}
}
4.nio 流程解读
-
当客户端连接时,会通过ServerSocketChannel 得到 SocketChannel
-
Selector 进行监听 select 方法, 返回有事件发生的通道的个数.
-
将socketChannel注册到Selector上, register(Selector sel, int ops), 一个selector上可以注册多个SocketChannel
-
注册后返回一个 SelectionKey, 会和该Selector 关联(集合)
-
进一步得到各个 SelectionKey (有事件发生)
-
在通过 SelectionKey 反向获取 SocketChannel , 方法 channel()
-
可以通过 得到的 channel , 完成业务处理
本项目源码github地址:https://github.com/itwwj/netty-learn.git 中的 netty-day01-io项目
下一篇:
io模型及nio进阶