文章目录
同步与异步、阻塞与非阻塞
同步操作:一个任务完成之前,必须等待,不能做其他操作;
异步操作:一个任务完成之前,可以先做其他操作;
阻塞:对于CPU执行,挂起当前线程进行等待,不能做其他操作
非阻塞:对于CPU执行,不需要挂起当前线程,可以先执行其他操作;
BIO(Blocking I/O)
- 同步、阻塞的IO模型,客户端与服务器一个连接对应服务器的一个线程。每当客户端有连接请求到服务器时,服务器需要启动一个线程进行处理,没有处理完成时,线程不能做其他操作。
- 适用范围:JDK1.4之前的唯一选择,只适用于连接数比较小且固定的架构,对于服务器资源要求较高;
//服务端代码,传统IO
public class BioSocketServerDemo {
public static void main(String[] args) {
int port = 9696;
Thread serverThread = new Thread(new Runnable() {
@Override
public void run() {
try {
ServerSocket serverSocket = new ServerSocket(port);
while(true){
Socket socket = serverSocket.accept();
Thread serverHandler = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("服务端收到客户端请求!!");
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
br.lines().forEach(s-> System.out.println("收到客户端消息:" + s));
socket.shutdownInput();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("服务端正在运行!");
bw.write("Server Time = "+System.currentTimeMillis());
bw.flush();
socket.shutdownOutput();
socket.close();
System.out.println("服务端发送消息回应客户端!!");
} catch (IOException e) {
e.printStackTrace();
}
}
});
serverHandler.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
serverThread.start();
System.out.println("服务端已启动!!");
}
}
//客户端代码
public class BioSocketClientDemo {
public static void main(String[] args){
try {
Socket socket = new Socket(InetAddress.getLocalHost(),9696);
BufferedWriter cbw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
cbw.write("客户端请求服务器端!!");
cbw.write("client time: "+System.currentTimeMillis());
cbw.flush();
socket.shutdownOutput();
System.out.println("客户端发送消息成功!!");
BufferedReader cbr = new BufferedReader(new InputStreamReader(socket.getInputStream()));
cbr.lines().forEach(s-> System.out.println("客户端收到消息:" + s));
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
NIO(Nio-blocking I/O)
JDK1.4版本,java.nio.* 包中被引入的JAVA I/O类库,目的在于提高I/O的速度,包括文件I/O和网络I/O,部分旧的I/O包也使用nio重新实现。
旧的I/O类FileInputStream、FileOutputStream、RandomAccessFile这三个字节操纵流,可以产生FileChannel;与底层的noi性质一致,Reader以及Writer这种字符流不能用于产生FileChannel,但是java.nio.channels.Channels类中提供了方法可以产生Reader和Writer;
- 同步、非阻塞的IO模型,服务器启动一个线程,将所有客户端的连接请求注册到多路复用器(Selector)上,多路复用器轮询到有IO请求时才会启动一个线程处理;
- 适用范围:适用于连接数据多且连接比较短的架构。
NIO的基石
通道(Channel)
- 实现Channel接口的子类,可以通过它读取和写入数据,NIO中均是将数据先写入包含一个或多个字节的缓冲区,然后将缓冲区的数据写入通道中,或是将通道中的数据获取到缓冲区中,在从缓冲区中获取数据;
- 与流(Stream)的区别:①流的读写是单向的(一个流只能或读或写),通道的读写是双向的(一个通道可读可写);②流读写是阻塞的,通道可以异步读写;
选择器(Selector)
Selector是通道的集合,每次服务端接收Socket请求或者客户端进行Socket请求产生的通道,我们均会将它注册到Selector中并设置状态,并在死循环中,根据Selector中注册的Channel的状态对于Channel进行相应的操作,操作完成后,重新调整通道的状态注册到Selector中或者关闭通道或者退出死循环;
缓冲区(ByteBuffer)
ByteBuffer,唯一能直接与通道交互的缓冲器,内部用字节数组存储字节,包含从通道获取或者要写入通道的数据;
详细了解ByteBuffer,参考 https://www.jianshu.com/p/ebc52832dca0
NIO的Socket模型:
//客户端代码
public class NioSocketClientDemo {
public static void main(String[] args) {
try {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("localhost", 9797));
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
while (true) {
selector.select();
Set<SelectionKey> keySet = selector.selectedKeys();
Iterator<SelectionKey> iterator = keySet.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isConnectable()) {
SocketChannel connectChannel = (SocketChannel) key.channel();
connectChannel.finishConnect();
System.out.println("连接服务端成功!!");
connectChannel.register(selector, SelectionKey.OP_WRITE);
continue;
}
if (key.isWritable()) {
SocketChannel writeChannel = (SocketChannel) key.channel();
String message = new Scanner(System.in).nextLine();
ByteBuffer write = ByteBuffer.allocate(1024);
String sendMessage = message + System.currentTimeMillis();
write.put(sendMessage.getBytes());
write.flip();
writeChannel.write(write);
System.out.println("客户端发送消息成功!!");
writeChannel.register(selector, SelectionKey.OP_READ);
continue;
}
if (key.isReadable()) {
SocketChannel readChannel = (SocketChannel) key.channel();
ByteBuffer read = ByteBuffer.allocate(1024);
int num = readChannel.read(read);
System.out.println("收到服务端返回消息");
System.out.println("服务端返回消息:" + new String(read.array(), 0, num));
readChannel.register(selector,SelectionKey.OP_WRITE);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//服务端代码
public class NioSocketServerDemo {
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//设置非阻塞
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress("localhost", 9797));
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务端启动成功!!");
while (true) {
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
ServerSocketChannel socketChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socket = socketChannel.accept();
socket.configureBlocking(false);
socket.register(selector, SelectionKey.OP_READ);
System.out.println("客户端请求服务端连接成功,服务端时间:" + System.currentTimeMillis());
}
if (selectionKey.isReadable()) {
SocketChannel readChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int num = readChannel.read(byteBuffer);
String message = new String(byteBuffer.array(), 0, num);
System.out.println("服务端收到客户端消息:" + message);
readChannel.register(selector, SelectionKey.OP_WRITE);
}
if (selectionKey.isWritable()) {
SocketChannel writeChannel = (SocketChannel) selectionKey.channel();
ByteBuffer writer = ByteBuffer.allocate(1024);
String sendMessage = "服务端返回消息,服务端时间" + System.currentTimeMillis();
writer.put(sendMessage.getBytes());
writer.flip();
writeChannel.write(writer);
System.out.println("服务端发送消息成功!!");
writeChannel.register(selector,SelectionKey.OP_READ);
}
iterator.remove();
}
}
} catch (IOException e) {
System.out.println("e.getStackTrace() = " + e);
}
}
}
AIO(asynchronous I/O)
- 异步、非阻塞的IO模型,服务器对于一个有效请求启动一个线程,客户端的I/O请求都是由操作系统先完成I/O之后再回调通知服务器启动线程继续处理的;
- 适用范围:JDK1.7版本之后JAVA才开始支持,作为NIO的附属品,解决IO不能异步的实现,适用于服务器连接数目多且连接长的架构;
//AIO服务端代码
public class AioSocketServerDemo {
public static void main(String[] args) throws InterruptedException {
try {
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(9494));
serverSocketChannel.accept(serverSocketChannel, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel clientChannel, Object serverChannel) {
try {
//没有阻塞,继续监听端口接受其他socket连接请求
serverSocketChannel.accept(serverChannel, this);
ByteBuffer buffer = ByteBuffer.allocate(1024);
clientChannel.read(buffer, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result1, Object object) {
buffer.flip();
System.out.println("客户端:" + new String(buffer.array(), 0, buffer.limit()));
String message = "你好,客户端,服务端已收到请求!接收时间为:" + System.currentTimeMillis();
ByteBuffer write = ByteBuffer.allocate(message.getBytes().length);
write.put(message.getBytes());
write.flip();
clientChannel.write(write, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result1, Object attachment) {
if (write.hasRemaining()) {
clientChannel.write(write, null, this);
}
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println(" 服务端回应消息失败:" + exc.getMessage());
}
});
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println(" 服务端接收消息失败:" + exc.getMessage());
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, Object attachment) {
serverSocketChannel.accept(null, this);
System.out.println("服务端错误:" + exc.getMessage());
}
});
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("服务已启动");
Thread.sleep(Integer.MAX_VALUE);
}
}
//AIO客户端代码
public class AioSocketClientDemo {
public static void main(String[] args) throws InterruptedException {
try {
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
client.connect(new InetSocketAddress("localhost", 9494), null, new CompletionHandler<Void, Object>() {
@Override
String message = "Hi,服务端,客户端向你发送请求,客户端时间:" + System.currentTimeMillis();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
byteBuffer.put(message.getBytes());
byteBuffer.flip();
client.write(byteBuffer, byteBuffer, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
if (byteBuffer.hasRemaining()) {
client.write(byteBuffer, null, this);
} else {
ByteBuffer read = ByteBuffer.allocate(1024);
client.read(read, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
read.flip();
String message = new String(read.array());
System.out.println("服务端:" + message);
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("客户端从接收信息异常 = " + exc.getMessage());
}
});
}
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("客户端发送请求异常:" + exc.getMessage());
}
});
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("exc.getMessage() = " + exc.getMessage());
}
});
} catch (IOException e) {
e.printStackTrace();
}
//让当前主进程无限休眠,主线程执行完,程序退出
Thread.sleep(Integer.MAX_VALUE);
}
}
- AsynchronousSocketChannel的的用法与Socket类似,主要有三个方法connect()、read()、write()但是不同的是每个方法又分为Future版本与CompletionHandler版本;
- 使用AsynchronousSocketChannelChannel时,accept()、connect()、read()、write()等方法都不会阻塞,但是当使用返回Future类型的方法,并不是方法执行完的时候就成功IO,必须要使用Future.get方法,等get方法的阻塞结束后才能确保IO完成,继续执行下面的操作;