Java Network Programming (BIO and NIO)

Welcome Big Brother to Little Brother Blog 2022-09-23 07:40:33 阅读数:360

javanetworkprogrammingbionio

BIO、NIO

本文参考自《Netty权威指南》、《Netty实战》,主要对JDK的BIO、NIO和JDK1.7 最新提供的NIO 2.0的使用进行详细说明.

1、传统的同步阻塞式I/O编程

2、基于NIO的非阻塞编程

3、基于NIO2.0异步非阻塞(AIO)编程

4、为什么使用NIO编程

5、为什么选择Netty

​ 网络编程的基本模型是Client/Server模型(That is, two processes communicate with each other,其中服务端提供位置信息(绑定的Ip地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(Socket)进行通信.

一、传统的BIO编程

1.1BIO通信模型图

请添加图片描述

​ 采用BIOServer for communication model,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁.

请添加图片描述

​ The biggest problem with this model is the lack of elastic scaling,当客户端并发量增加后,The number of threads on the server side is the same as the number of concurrent accesses on the client side,线程对于JVMis a valuable system resource,当线程数膨胀之后,系统的性能将急剧下降,As the number of concurrent visits continues to increase,系统会发生线程堆栈溢出、创建新线程失败等问题.

 public final static int targetPort=9001;
public static void main(String[] args) throws IOException {

//实现服务器和客户端通信
//1、Create server-side communicationServerSocket对象,用以监听指定端口上的连接请求
ServerSocket serverSocket=new ServerSocket(targetPort);
//2、对accept()for listening and receiving thisServerSocket的连接,方法的调用将被阻塞,until a connection is established
for (; ; ) {

//Blocking receive client socket
final Socket socket = serverSocket.accept();
new Thread(new Runnable() {

@Override
public void run() {

OutputStream out = null;
try {

out = socket.getOutputStream();
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(out));
bw.write("hell0");
bw.newLine();
bw.flush();
socket.close();
} catch (IOException e) {

throw new RuntimeException(e);
} finally {

try {

out.close();
} catch (IOException e) {

throw new RuntimeException(e);
}
}
}
}).start();
}
}

​ We send a line on this,BIOThe main problem is whenever a new client requests access,服务端必须创建一个新的线程处理新接入的客户端链路,一个线程只能处理一个客户端连接.Obviously can not meet the high performance、高并发接入的场景.

To improve the one thread one connection model,Later evolved a thread pool or message queue to achieve one or more thread processingN个客户端的模型,But because the bottom layer still uses synchronous blockingI/O,所以被称为“伪异步”

1.2 伪异步I/O编程

为了解决同步阻塞I/O面临的一个链路需要一个线程处理的问题,The model has been optimized:Handle requests from multiple clients through a thread pool,The maximum number of threads in the thread pool can be much larger than the number of clients.Thread resources can be flexibly allocated through the thread pool.

Fake the asynchronous communication model

请添加图片描述

将客户端的Socket封装成为一个Task(实现Runnable接口)Submit to the thread pool for processing.By setting the thread poolmax Thread、Blocking queue to control

Asynchronous tasks execute logic

public class TimeServerHandler implements Runnable{

private Socket socket;
public TimeServerHandler(Socket socket) {

this.socket = socket;
}
@Override
public void run() {

BufferedReader br=null;
PrintWriter out=null;
try{

br=new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out=new PrintWriter(socket.getOutputStream(),true);
String currentTime=null;
String body=null;
for(;;){

body=br.readLine();
if(body==null){

break;
}
System.out.println("The time server receive order:"+body);
currentTime="QUERY TIME ORDER".equalsIgnoreCase(body)?new Date(System.currentTimeMillis())
.toString():"BAD ORDER";
out.println(currentTime);
}
} catch (Exception e) {

if(br!=null){

try {

br.close();
} catch (IOException ex) {

throw new RuntimeException(ex);
}
}
if(out!=null){

out.close();
out=null;
}
if(this.socket!=null){

try {

this.socket.close();
} catch (IOException ex) {

throw new RuntimeException(ex);
}
this.socket=null;
}
}
}
}

客户端

public class TimeClient {

public static void main(String[] args) throws IOException {

Socket socket=new Socket("127.0.0.1",8080);
// BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
// bw.write("QUERY TIME ORDER");
// bw.newLine();
// bw.flush();
PrintWriter pw=new PrintWriter(socket.getOutputStream(),true);
pw.println("QUERY TIME ORDER");
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
String s = br.readLine();
System.out.println("Now is :"+s);
}
}

服务端接收连接,Start the thread driver

public class TimeServer {

public static void main(String[] args) {

int port=8080;
ServerSocket server=null;
try{

server=new ServerSocket(port);
Socket socket=null;
//创建I/O任务线程池
TimeServerHandlerExecutePool singleExecutor=new TimeServerHandlerExecutePool(50,10000);
for(;;){

socket=server.accept();
singleExecutor.execute(new TimeServerHandler(socket));
}
}catch (IOException e){

e.printStackTrace();
}
}
}

线程池

public class TimeServerHandlerExecutePool {

private ExecutorService executor;
public TimeServerHandlerExecutePool(int maxPoolSize,int queueSize){

executor=new ThreadPoolExecutor(Runtime.getRuntime()
.availableProcessors(),maxPoolSize,120L, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(queueSize));
}
public void execute(Runnable task){

executor.execute(task);
}
}

请添加图片描述

请添加图片描述

伪异步I/O弊端分析
  • Socketthe input stream for a read operation,会一直阻塞,直到:
    • 有数据可读
    • 可用数据读取完毕
    • Controlling or happeningI/O异常
  • 调用OutputStream的writemethod when writing to the output stream,也会被阻塞,直到所有要发送的字节全部写入完毕,Or one way

二、NIO编程

​ 与Socket类和ServerSocket类相对应,NIO提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现.Both channels support blocking sums 非阻塞两种模式.对于阻塞模式,Performance and reliability are not good.But non-blocking mode is just the opposite.

一般,低负载、Low development application selectionBIO降低编程复杂度;对于高负载、高并发的网络应用,使用NIO的非阻塞模式进行开发

2.1 NIO类库

​ NIO是JDK1.4引入.made up for the originalBIO的不足.

1、缓冲区Buffer

请添加图片描述

​ Buffer继承关系

​ Buffer是一个对象,Contains some data to be written or read.NIO引入了Buffer对象,为了区别于BIO.在面向流的I/O中,Data can be directly written to or read fromStream对象中.

​ NIO库中,所有数据都是用缓冲区处理的,在读取数据时,Read directly into the buffer;写数据时,写入到缓冲区.任何时候访问NIO的数据,都是通过缓冲区进行操作

​ A buffer is essentially an array,通常是一个字节数组,Other kinds of arrays can also be used.但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问以及维护读写位置等信息

​ 如上图,JavaAll basic data types of , correspond to a buffer,每一个Buffer类都是Buffer接口的一个子实例.大多数标准I/O使用ByteBuffer,所以ByteBufferHas unique operations beyond normal buffer operations,方便网络读写.

2、Channel通道

​ 网络数据通过Channel读取和写入.通道和流不同之处在于通道是双向的,而流是单向的,Channels can be used for reading、写或者二者同时进行(Channel具有全双工).

​ ChannelAlso divided into two categories:用于网络读写的SelectableChanneland for file reading and writingFileChannel

请添加图片描述

3、多路复用器Selector

​ 多路复用器提供选择已经就绪的任务的能力.简单来说,Selectorwill keep falling registered on itChannel(一个多路复用器Selector可以同时轮询多个Channel),如果某个Channel上面发生读或写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey获取就绪Channel的集合,post-sequenceI/O操作.

NIO服务端序列图

请添加图片描述

public class PlainNioServer {

public void server(int port) throws IOException {

//1、开启ServerSocketChannel,用于监听客户端的连接,是所有客户端连接的父管道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
//2、设置连接为非阻塞,绑定监听端口
serverChannel.configureBlocking(false);
ServerSocket ssocket = serverChannel.socket();
//Bind the server to the appropriate port
InetSocketAddress inetSocketAddress = new InetSocketAddress(port);
ssocket.bind(inetSocketAddress);
//3、打开Selector处理Channel
Selector selector = Selector.open();
//4、将ServerSocket注册到Selector以接受连接
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
final ByteBuffer wrap = ByteBuffer.wrap("hello".getBytes());
for(;;){

try{

selector.select();
}catch (IOException e){

e.printStackTrace();
break;
}
// 5、获取所有接收事件的SelectionKey实例(readyChannel集合)
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
// 6、多路复用器 轮询 准备就绪的Key
while(iterator.hasNext()){

SelectionKey key=iterator.next();
iterator.remove();
try{

//检查事件是否是一个新的已经就绪可以被接受的连接
if(key.isAcceptable()){

ServerSocketChannel server = (ServerSocketChannel) key.channel();
//多路复用器监听到有新的客户端接入,处理新的接入请求,完成TCP三次握手,建立物理链路
SocketChannel client = server.accept();
//Set the client link to non-blocking
client.configureBlocking(false);
//Connect the newly connected client 注册到 多路复用器上,Monitor read and write operations
client.register(selector, SelectionKey.OP_WRITE |
SelectionKey.OP_READ,wrap.duplicate());
System.out.println("Accepted connection from"+client);
}
// 检查套接字是否已经准备好写数据
if(key.isWritable()){

SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
//将数据写到已连接的客户端
while (buffer.hasRemaining()) {

if (client.write(buffer) == 0) {

break;
} }
client.close();
}
}catch (IOException e){

key.cancel();
try{

key.channel().close();
}catch (Exception ce){

ce.printStackTrace();
}
}
}
}
}
}
copyright:author[Welcome Big Brother to Little Brother Blog],Please bring the original link to reprint, thank you. https://en.javamana.com/2022/266/202209230624511469.html