通常IO操作包括对磁盘的读写,socket网络的读写和外部连接设备的读写,在讲解java的io之前需要了解,同步、异步、阻塞和非阻塞的概念
网络IO请求完整的过程分为两个阶段,第一个发送io请求指令,第二阶段响应结果的处理。同步异步主要发生在第二阶段故同步异步的差异就发生在第二阶段,阻塞非阻塞主要发生在第一阶段。
同步:在第二阶段服务器响应结果,需要客户端先线程主动去询问是否完成和响应数据
异步:在第二阶段服务器响应结果,系统内核通过selector或epoll基于事件驱动,通过回调把处理结果推给客户端线程
阻塞:在第一阶段发送io请求指令后,用户线程只能阻塞状态等待io请求的响应
非阻塞:在第一阶段发送io请求指令后,用户线程可以继续去做其他事情
BIO
BIO传统的io,同步阻塞io,客户线程提交IO请求后,线程阻塞,只能等待服务断io数据的返回,InputStream和OutputStream直流读取,网络BIO使用传统的socket套接字完成网络io通信。
实例代码:
Server服务端:
package io.bio;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author shengtao
* @Description:
* @date 2019/03/29 15:49
*/
public class Server {
public static final int port = 8081;//监听的端口号
public static void main(String[] args) {
System.out.println("Server start...\n");
Server server = new Server();
server.init();
}
public void init() {
try {
//创建一个ServerSocket,这里可以指定连接请求的队列长度
//new ServerSocket(port,3);意味着当队列中有3个连接请求是,如果Client再请求连接,就会被Server拒绝
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
//从请求队列中取出一个连接
Socket client = serverSocket.accept();
// 处理这次连接
new HandlerThread(client);
}
} catch (Exception e) {
System.out.println("服务器异常: " + e.getMessage());
}
}
private class HandlerThread implements Runnable {
private Socket socket;
public HandlerThread(Socket client) {
socket = client;
new Thread(this).start();
}
public void run() {
try {
// 读取客户端数据
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String clientInputStr = input.readLine();//这里要注意和客户端输出流的写方法对应,否则会抛 EOFException
// 处理客户端数据
System.out.println("客户端发过来的内容:" + clientInputStr);
// 向客户端回复信息
PrintStream out = new PrintStream(socket.getOutputStream());
System.out.print("请输入:\t");
// 发送键盘输入的一行
String s = new BufferedReader(new InputStreamReader(System.in)).readLine();
out.println(s);
out.close();
input.close();
} catch (Exception e) {
System.out.println("服务器 run 异常: " + e.getMessage());
} finally {
if (socket != null) {
try {
socket.close();
} catch (Exception e) {
socket = null;
System.out.println("服务端 finally 异常:" + e.getMessage());
}
}
}
}
}
}
Client客户端:
package io.bio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
/**
* @author shengtao
* @Description:
* @date 2019/03/29 15:58
*/
public class Client {
public static final int port = 8081;
public static final String host = "localhost";
public static void main(String[] args) {
System.out.println("Client Start...");
while (true) {
Socket socket = null;
try {
//创建一个流套接字并将其连接到指定主机上的指定端口号
socket = new Socket(host,port);
//读取服务器端数据
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//向服务器端发送数据
PrintStream out = new PrintStream(socket.getOutputStream());
System.out.print("请输入: \t");
String str = new BufferedReader(new InputStreamReader(System.in)).readLine();
out.println(str);
String ret = input.readLine();
System.out.println("服务器端返回过来的是: " + ret);
// 如接收到 "OK" 则断开连接
if ("OK".equals(ret)) {
System.out.println("客户端将关闭连接");
Thread.sleep(500);
break;
}
out.close();
input.close();
} catch (Exception e) {
System.out.println("客户端异常:" + e.getMessage());
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
socket = null;
System.out.println("客户端 finally 异常:" + e.getMessage());
}
}
}
}
}
}
运行结果:
Server端:
Server start...
客户端发过来的内容:你好
请输入: 你好
客户端发过来的内容:认识你很高兴
请输入: OK
Client端:
Client Start...
请输入: 你好
服务器端返回过来的是: 你好
请输入: 认识你很高兴
服务器端返回过来的是: OK
客户端将关闭连接
Process finished with exit code 0
NIO
同步非阻塞IO,(1)BIO中引入了Buffer缓冲区,网络IO请求读写都是在作用在Buffer缓冲区,然后Buffe缓冲区与内核指令处理是异步完成的。(2)BIO引入了通道的概念,与传统的IO流不一样,传统的IO流是单向的,输入输出是两个分开的流,而NIO引入的通道则是双向的,客户端与服务端(认证后)建立通道后,客户端可以和服务端可以通过此通道进行数据通信。(3)BIO引入了IO复用Selector组件,轮询查看注册在监听器上的客户端连接,如果客户端有读、写、连接和断开事件发生,IO复用器监听到会申请线程去处理。(4)BIO是基于事件驱动的IO。
NIOServer端:
package io.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;
/**
* @author shengtao
* @Description:
* @date 2019/03/29 19:24
*/
public class NIOServer
{
/**
* 选择器
*/
private Selector selector;
/**
* 通道
*/
ServerSocketChannel serverSocketChannel;
public void initServer(int port) throws IOException
{
//打开一个通道
serverSocketChannel = ServerSocketChannel.open();
//通道设置非阻塞
serverSocketChannel.configureBlocking(false);
//绑定端口号
serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", port));
//注册
this.selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
public void listen() throws IOException
{
System.out.println("server started succeed!");
while (true)
{
selector.select();
Iterator<SelectionKey> ite = selector.selectedKeys().iterator();
while (ite.hasNext())
{
SelectionKey key = ite.next();
if (key.isAcceptable())
{
SocketChannel channel = serverSocketChannel.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
}
else if (key.isReadable())
{
recvAndReply(key);
}
ite.remove();
}
}
}
public void recvAndReply(SelectionKey key) throws IOException
{
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(256);
int i = channel.read(buffer);
if (i != -1)
{
String msg = new String(buffer.array()).trim();
System.out.println("NIO server received message = " + msg);
System.out.println("NIO server reply = " + msg);
channel.write(ByteBuffer.wrap( msg.getBytes()));
}
else
{
channel.close();
}
}
public static void main(String[] args) throws IOException
{
NIOServer server = new NIOServer();
server.initServer(8081);
server.listen();
}
}
NIOClient客户端:
package io.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* @author shengtao
* @Description:
* @date 2019/03/29 19:27
*/
public class NIOClient
{
/**
* 通道
*/
SocketChannel channel;
public void initClient(String host, int port) throws IOException
{
//构造socket连接
InetSocketAddress servAddr = new InetSocketAddress(host, port);
//打开连接
this.channel = SocketChannel.open(servAddr);
}
public void sendAndRecv(String words) throws IOException
{
byte[] msg = new String(words).getBytes();
ByteBuffer buffer = ByteBuffer.wrap(msg);
System.out.println("Client sending: " + words);
channel.write(buffer);
buffer.clear();
channel.read(buffer);
System.out.println("Client received: " + new String(buffer.array()).trim());
channel.close();
}
public static void main(String[] args) throws IOException
{
NIOClient client = new NIOClient();
client.initClient("localhost", 8081);
client.sendAndRecv("I am a client");
}
}
执行结果:
NIOServer端:
server started succeed!
NIO server received message = I am a client
NIO server reply = I am a client
Process finished with exit code 1
NIOClient端:
Client sending: I am a client
Client received: I am a client
Process finished with exit code 0
AIO
AIO异步非阻塞IO,是JDK1.7引入的NIO2模型,在java的java.nio包下,select多路复用IO底层调用系统的epoll事件驱动模型实现的,使用了NIO1中的buffer缓冲区,双向通道,事件驱动,IO复用器,外层包裹的线程组,一个线程轮询事件,接受事件用线程组中的线程处理客户端读写事件,客户端发起IO请求,客户端线程不会阻塞可以处理其他任务,服务端内核接受IO事件请求,处理IO事件后服务断回调客户端的ahndler对象的complated函数,故被称为异步非阻塞IO。
AIOServer端:
package io.aio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
/**
* @author shengtao
* @Description:
* @date 2019/03/29 20:03
*/
public class AIOServer implements Runnable{
private int port = 8081;
private int threadSize = 10;
protected AsynchronousChannelGroup asynchronousChannelGroup;
protected AsynchronousServerSocketChannel serverChannel;
public AIOServer(){
init();
}
private void init(){
try {
asynchronousChannelGroup = AsynchronousChannelGroup.withCachedThreadPool(Executors.newCachedThreadPool(),threadSize);
serverChannel = AsynchronousServerSocketChannel.open(asynchronousChannelGroup);
serverChannel.bind(new InetSocketAddress(port));
System.out.println("aio socket channel open on port:"+port);
} catch (IOException e) {
e.printStackTrace();
System.out.println("asynchronousChannelGroup init error !");
}
}
@Override
public void run() {
try {
if(serverChannel == null){
return;
}else {
serverChannel.accept(this, new CompletionHandler<AsynchronousSocketChannel, AIOServer>() {
final ByteBuffer echoBuffer = ByteBuffer.allocateDirect(1024);
@Override
public void completed(AsynchronousSocketChannel result, AIOServer attachment) {
System.out.println("==============================================================");
try {
System.out.println("client host: " + result.getRemoteAddress());
echoBuffer.clear();
result.read(echoBuffer).get();
echoBuffer.flip();
System.out.println("received : " + Charset.defaultCharset().decode(echoBuffer));
result.write(ByteBuffer.wrap("hello client".getBytes()));
} catch (Exception e) {
e.printStackTrace();
} finally {
attachment.serverChannel.accept(attachment, this);// 监听新的请求,递归调用。
}
}
@Override
public void failed(Throwable exc, AIOServer attachment) {
System.out.println("received failed");
exc.printStackTrace();
attachment.serverChannel.accept(attachment, this);
}
});
}
System.in.read();//线程阻塞
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args){
AIOServer aioServer = new AIOServer();
new Thread(aioServer).start();
}
}
AIOClient客户端:
package io.aio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
/**
* @author shengtao
* @Description:
* @date 2019/03/29 20:21
*/
public class AIOClient implements Runnable {
private AsynchronousSocketChannel client;
private String host;
private int port;
public AIOClient(String host, int port) throws IOException {
this.client = AsynchronousSocketChannel.open();
this.host = host;
this.port = port;
}
@Override
public void run() {
client.connect(new InetSocketAddress("127.0.0.1", 8081),null, new CompletionHandler<Void,Object>() {
@Override
public void completed(Void result, Object attachment) {
String msg = "hello server";
client.write(ByteBuffer.wrap(msg.getBytes()));
System.out.println("client send data:" + msg);
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("client send faield");
}
});
final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
client.read(byteBuffer, this, new CompletionHandler<Integer, AIOClient>() {
@Override
public void completed(Integer result, AIOClient attachment) {
System.out.println("content length:"+result);
System.out.println("client read data: " + new String(byteBuffer.array()));
}
@Override
public void failed(Throwable exc, AIOClient attachment) {
System.out.println("read faield");
}
});
}
public static void main(String[] args){
try {
new Thread(new AIOClient("127.0.0.1", 8081)).start();
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
执行结果:
服务端:
aio socket channel open on port:8081
==============================================================
client host: /127.0.0.1:61335
received : hello server
客户端:
client send data:hello server
content length:12
client read data: hello client
总结:虽然我们工作中不经常会直接使用BIO和AIO网络IO模型,但是对于这些只是深入了解会对我们学习netty和mina这样的基于事件驱动的高性能网络架构很有帮助。