Netty是一个供应异步事宜驱动的收集运用框架,用以疾速开辟高性能、高牢靠的收集效劳器和客户端顺序。Netty简化了收集顺序的开辟,是许多框架和公司都在运用的手艺。更是口试的加分项。Netty并不是横空出世,它是在BIO,NIO,AIO演化中的产品,是一种NIO框架。而BIO,NIO,AIO更是笔试中要考,口试中要问的手艺。也是一个很好的加分项,加分就是加工资,你还在等什么?本章带你细细品味三者的差别!
流程图:
BIO NIO AIO 流程图
手艺:BIO,NIO,AIO
申明:github上有更全的源码。
源码:https://github.com/ITDragonBl…
BIO
BIO 全称Block-IO 是一种壅塞同步的通信形式。我们常说的Stock IO 平常指的是BIO。是一个比较传统的通信体式格局,形式简朴,运用轻易。但并发处置惩罚才低,通信耗时,依靠网速。
BIO 设想道理:
效劳器经由历程一个Acceptor线程担任监听客户端请乞降为每一个客户端竖立一个新的线程举行链路处置惩罚。典范的一要求一应对形式。若客户端数目增加,频仍地竖立和烧毁线程会给效劳器翻开很大的压力。后改进为用线程池的体式格局替代新增线程,被称为伪异步IO。
效劳器供应IP地点和监听的端口,客户端经由历程TCP的三次握手与效劳器衔接,衔接胜利后,双放才经由历程套接字(Stock)通信。
小结:BIO模子中经由历程Socket和ServerSocket完成套接字通道的完成。壅塞,同步,竖立衔接耗时。
BIO效劳器代码,担任启动效劳,壅塞效劳,监听客户端要求,新建线程处置惩罚使命。
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
- IO 也称为 BIO,Block IO 壅塞同步的通信体式格局
- 比较传统的手艺,现实开辟中基本上用Netty或许是AIO。熟习BIO,NIO,体味个中变化的历程。作为一个web开辟人员,stock通信口试常常题目。
- BIO最大的题目是:壅塞,同步。
- BIO通信体式格局很依靠于收集,若网速不好,壅塞时候会很长。每次要求都由顺序实行并返回,这是同步的缺点。
- BIO事变流程:
- 第一步:server端效劳器启动
- 第二步:server端效劳器壅塞监听client要求
- 第三步:server端效劳器吸收要求,竖立线程完成使命
*/
public class ITDragonBIOServer {
private static final Integer PORT = 8888; // 效劳器对外的端口号
public static void main(String[] args) {
ServerSocket server = null;
Socket socket = null;
ThreadPoolExecutor executor = null;
try {
server = new ServerSocket(PORT); // ServerSocket 启动监听端口
System.out.println("BIO Server 效劳器启动.........");
/*--------------传统的新增线程处置惩罚----------------*/
/*while (true) {
// 效劳器监听:壅塞,守候Client要求
socket = server.accept();
System.out.println("server 效劳器确认要求 : " + socket);
// 效劳器衔接确认:确认Client要求后,竖立线程实行使命 。很明显的题目,若每吸收一次要求就要竖立一个线程,显然是不合理的。
new Thread(new ITDragonBIOServerHandler(socket)).start();
} */
/*--------------经由历程线程池处置惩罚减缓高并发给顺序带来的压力(伪异步IO编程)----------------*/
executor = new ThreadPoolExecutor(10, 100, 1000, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50));
while (true) {
socket = server.accept(); // 效劳器监听:壅塞,守候Client要求
ITDragonBIOServerHandler serverHandler = new ITDragonBIOServerHandler(socket);
executor.execute(serverHandler);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != socket) {
socket.close();
socket = null;
}
if (null != server) {
server.close();
server = null;
System.out.println("BIO Server 效劳器封闭了!!!!");
}
executor.shutdown();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BIO效劳端处置惩罚使命代码,担任处置惩罚Stock套接字,返回套接字给客户端,解耦。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import com.itdragon.util.CalculatorUtil;
public class ITDragonBIOServerHandler implements Runnable{
private Socket socket;
public ITDragonBIOServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader reader = null;
PrintWriter writer = null;
try {
reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
writer = new PrintWriter(this.socket.getOutputStream(), true);
String body = null;
while (true) {
body = reader.readLine(); // 若客户端用的是 writer.print() 传值,那readerLine() 是不能猎取值,细节
if (null == body) {
break;
}
System.out.println("server效劳端吸收参数 : " + body);
writer.println(body + " = " + CalculatorUtil.cal(body).toString());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != writer) {
writer.close();
}
try {
if (null != reader) {
reader.close();
}
if (null != this.socket) {
this.socket.close();
this.socket = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BIO客户端代码,担任启动客户端,向效劳器发送要求,吸收效劳器返回的Stock套接字。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Random;
/**
- BIO 客户端
- Socket : 向效劳端发送衔接
- PrintWriter : 向效劳端通报参数
- BufferedReader : 从效劳端吸收参数
*/
public class ITDragonBIOClient {
private static Integer PORT = 8888;
private static String IP_ADDRESS = "127.0.0.1";
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
clientReq(i);
}
}
private static void clientReq(int i) {
Socket socket = null;
BufferedReader reader = null;
PrintWriter writer = null;
try {
socket = new Socket(IP_ADDRESS, PORT); // Socket 提议衔接操纵。衔接胜利后,两边经由历程输入和输出流举行同步壅塞式通信
reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 猎取返回内容
writer = new PrintWriter(socket.getOutputStream(), true);
String []operators = {"+","-","*","/"};
Random random = new Random(System.currentTimeMillis());
String expression = random.nextInt(10)+operators[random.nextInt(4)]+(random.nextInt(10)+1);
writer.println(expression); // 向效劳器端发送数据
System.out.println(i + " 客户端打印返回数据 : " + reader.readLine());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != reader) {
reader.close();
}
if (null != socket) {
socket.close();
socket = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
NIO
NIO 全称New IO,也叫Non-Block IO 是一种非壅塞同步的通信形式。
NIO 设想道理:
NIO 相对于BIO来讲一大进步。客户端和效劳器之间经由历程Channel通信。NIO能够在Channel举行读写操纵。这些Channel都会被注册在Selector多路复用器上。Selector经由历程一个线程不断的轮询这些Channel。找出已准备停当的Channel实行IO操纵。
NIO 经由历程一个线程轮询,完成万万个客户端的要求,这就黑白壅塞NIO的特性。
1)缓冲区Buffer:它是NIO与BIO的一个主要区分。BIO是将数据直接写入或读取到Stream对象中。而NIO的数据操纵都是在缓冲区中举行的。缓冲区现实上是一个数组。Buffer最罕见的范例是ByteBuffer,别的另有CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer。
2)通道Channel:和流差别,通道是双向的。NIO能够经由历程Channel举行数据的读,写和同时读写操纵。通道分为两大类:一类是收集读写(SelectableChannel),一类是用于文件操纵(FileChannel),我们运用的SocketChannel和ServerSocketChannel都是SelectableChannel的子类。
3)多路复用器Selector:NIO编程的基本。多路复用器供应挑选已停当的使命的才。就是Selector会不断地轮询注册在其上的通道(Channel),假如某个通道处于停当状况,会被Selector轮询出来,然后经由历程SelectionKey能够获得停当的Channel鸠合,从而举行后续的IO操纵。效劳器端只需供应一个线程担任Selector的轮询,就能够接入不计其数个客户端,这就是JDK NIO库的巨大进步。
申明:这里的代码只完成了客户端发送要求,效劳端吸收数据的功用。其目标是简化代码,轻易明白。github源码中有完全代码。
小结:NIO模子中经由历程SocketChannel和ServerSocketChannel完成套接字通道的完成。非壅塞/壅塞,同步,防止TCP竖立衔接运用三次握手带来的开支。
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;
/**
- NIO 也称 New IO, Non-Block IO,非壅塞同步通信体式格局
- 从BIO的壅塞到NIO的非壅塞,这是一大进步。功归于Buffer,Channel,Selector三个设想完成。
- Buffer : 缓冲区。NIO的数据操纵都是在缓冲区中举行。缓冲区现实上是一个数组。而BIO是将数据直接写入或读取到Stream对象。
- Channel : 通道。NIO能够经由历程Channel举行数据的读,写和同时读写操纵。
- Selector : 多路复用器。NIO编程的基本。多路复用器供应挑选已停当状况使命的才。
- 客户端和效劳器经由历程Channel衔接,而这些Channel都要注册在Selector。Selector经由历程一个线程不断的轮询这些Channel。找出已准备停当的Channel实行IO操纵。
- NIO经由历程一个线程轮询,完成万万个客户端的要求,这就黑白壅塞NIO的特性。
*/
public class ITDragonNIOServer implements Runnable{
private final int BUFFER_SIZE = 1024; // 缓冲区大小
private final int PORT = 8888; // 监听的端口
private Selector selector; // 多路复用器,NIO编程的基本,担任治理通道Channel
private ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER_SIZE); // 缓冲区Buffer
public ITDragonNIOServer() {
startServer();
}
private void startServer() {
try {
// 1.开启多路复用器
selector = Selector.open();
// 2.翻开效劳器通道(收集读写通道)
ServerSocketChannel channel = ServerSocketChannel.open();
// 3.设置效劳器通道为非壅塞形式,true为壅塞,false为非壅塞
channel.configureBlocking(false);
// 4.绑定端口
channel.socket().bind(new InetSocketAddress(PORT));
// 5.把通道注册到多路复用器上,并监听壅塞事宜
/**
* SelectionKey.OP_READ : 示意关注读数据停当事宜
* SelectionKey.OP_WRITE : 示意关注写数据停当事宜
* SelectionKey.OP_CONNECT: 示意关注socket channel的衔接完成事宜
* SelectionKey.OP_ACCEPT : 示意关注server-socket channel的accept事宜
*/
channel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server start >>>>>>>>> port :" + PORT);
} catch (IOException e) {
e.printStackTrace();
}
}
// 须要一个线程担任Selector的轮询
@Override
public void run() {
while (true) {
try {
/**
* a.select() 壅塞到至少有一个通道在你注册的事宜上停当
* b.select(long timeOut) 壅塞到至少有一个通道在你注册的事宜上停当或许超时timeOut
* c.selectNow() 马上返回。假如没有停当的通道则返回0
* select要领的返回值示意停当通道的个数。
*/
// 1.多路复用器监听壅塞
selector.select();
// 2.多路复用器已挑选的效果集
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
// 3.不断的轮询
while (selectionKeys.hasNext()) {
// 4.猎取一个选中的key
SelectionKey key = selectionKeys.next();
// 5.猎取后便将其从容器中移除
selectionKeys.remove();
// 6.只猎取有用的key
if (!key.isValid()){
continue;
}
// 壅塞状况处置惩罚
if (key.isAcceptable()){
accept(key);
}
// 可读状况处置惩罚
if (key.isReadable()){
read(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 设置壅塞,守候Client要求。在传统IO编程中,用的是ServerSocket和Socket。在NIO中采纳的ServerSocketChannel和SocketChannel
private void accept(SelectionKey selectionKey) {
try {
// 1.猎取通道效劳
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
// 2.实行壅塞要领
SocketChannel socketChannel = serverSocketChannel.accept();
// 3.设置效劳器通道为非壅塞形式,true为壅塞,false为非壅塞
socketChannel.configureBlocking(false);
// 4.把通道注册到多路复用器上,并设置读取标识
socketChannel.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
private void read(SelectionKey selectionKey) {
try {
// 1.清空缓冲区数据
readBuffer.clear();
// 2.猎取在多路复用器上注册的通道
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
// 3.读取数据,返回
int count = socketChannel.read(readBuffer);
// 4.返回内容为-1 示意没有数据
if (-1 == count) {
selectionKey.channel().close();
selectionKey.cancel();
return ;
}
// 5.有数据则在读取数据前举行复位操纵
readBuffer.flip();
// 6.依据缓冲区大小竖立一个响应大小的bytes数组,用来猎取值
byte[] bytes = new byte[readBuffer.remaining()];
// 7.吸收缓冲区数据
readBuffer.get(bytes);
// 8.打印猎取到的数据
System.out.println("NIO Server : " + new String(bytes)); // 不能用bytes.toString()
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Thread(new ITDragonNIOServer()).start();
}
}
NIO客户端代码,担任衔接效劳器,声明通道,衔接通道
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class ITDragonNIOClient {
private final static int PORT = 8888;
private final static int BUFFER_SIZE = 1024;
private final static String IP_ADDRESS = "127.0.0.1";
public static void main(String[] args) {
clientReq();
}
private static void clientReq() {
// 1.竖立衔接地点
InetSocketAddress inetSocketAddress = new InetSocketAddress(IP_ADDRESS, PORT);
// 2.声明一个衔接通道
SocketChannel socketChannel = null;
// 3.竖立一个缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
try {
// 4.翻开通道
socketChannel = SocketChannel.open();
// 5.衔接效劳器
socketChannel.connect(inetSocketAddress);
while(true){
// 6.定义一个字节数组,然后运用体系录入功用:
byte[] bytes = new byte[BUFFER_SIZE];
// 7.键盘输入数据
System.in.read(bytes);
// 8.把数据放到缓冲区中
byteBuffer.put(bytes);
// 9.对缓冲区举行复位
byteBuffer.flip();
// 10.写出数据
socketChannel.write(byteBuffer);
// 11.清空缓冲区数据
byteBuffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != socketChannel) {
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
AIO
AIO 也叫NIO2.0 是一种非壅塞异步的通信形式。在NIO的基本上引入了新的异步通道的观点,并供应了异步文件通道和异步套接字通道的完成。
AIO 并没有采纳NIO的多路复用器,而是运用异步通道的观点。其read,write要领的返回范例都是Future对象。而Future模子是异步的,其中心头脑是:去主函数守候时候。
小结:AIO模子中经由历程AsynchronousSocketChannel和AsynchronousServerSocketChannel完成套接字通道的完成。非壅塞,异步。
AIO效劳端代码,担任竖立效劳器通道,绑定端口,守候要求。
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
- AIO, 也叫 NIO2.0 是一种异步非壅塞的通信体式格局
- AIO 引入了异步通道的观点 AsynchronousServerSocketChannel和AsynchronousSocketChannel 其read和write要领返回值范例是Future对象。
*/
public class ITDragonAIOServer {
private ExecutorService executorService; // 线程池
private AsynchronousChannelGroup threadGroup; // 通道组
public AsynchronousServerSocketChannel asynServerSocketChannel; // 效劳器通道
public void start(Integer port){
try {
// 1.竖立一个缓存池
executorService = Executors.newCachedThreadPool();
// 2.竖立通道组
threadGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1);
// 3.竖立效劳器通道
asynServerSocketChannel = AsynchronousServerSocketChannel.open(threadGroup);
// 4.举行绑定
asynServerSocketChannel.bind(new InetSocketAddress(port));
System.out.println("server start , port : " + port);
// 5.守候客户端要求
asynServerSocketChannel.accept(this, new ITDragonAIOServerHandler());
// 一向壅塞 不让效劳器住手,实在环境是在tomcat下运转,所以不须要这行代码
Thread.sleep(Integer.MAX_VALUE);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ITDragonAIOServer server = new ITDragonAIOServer();
server.start(8888);
}
}
AIO效劳器使命处置惩罚代码,担任,读取数据,写入数据
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import com.itdragon.util.CalculatorUtil;
public class ITDragonAIOServerHandler implements CompletionHandler<AsynchronousSocketChannel, ITDragonAIOServer> {
private final Integer BUFFER_SIZE = 1024;
@Override
public void completed(AsynchronousSocketChannel asynSocketChannel, ITDragonAIOServer attachment) {
// 保证多个客户端都能够壅塞
attachment.asynServerSocketChannel.accept(attachment, this);
read(asynSocketChannel);
}
//读取数据
private void read(final AsynchronousSocketChannel asynSocketChannel) {
ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
asynSocketChannel.read(byteBuffer, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer resultSize, ByteBuffer attachment) {
//举行读取今后,重置标识位
attachment.flip();
//猎取读取的数据
String resultData = new String(attachment.array()).trim();
System.out.println("Server -> " + "收到客户端的数据信息为:" + resultData);
String response = resultData + " = " + CalculatorUtil.cal(resultData);
write(asynSocketChannel, response);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}
// 写入数据
private void write(AsynchronousSocketChannel asynSocketChannel, String response) {
try {
// 把数据写入到缓冲区中
ByteBuffer buf = ByteBuffer.allocate(BUFFER_SIZE);
buf.put(response.getBytes());
buf.flip();
// 在从缓冲区写入到通道中
asynSocketChannel.write(buf).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, ITDragonAIOServer attachment) {
exc.printStackTrace();
}
}
AIO客户端代码,担任衔接效劳器,声明通道,衔接通道
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.Random;
public class ITDragonAIOClient implements Runnable{
private static Integer PORT = 8888;
private static String IP_ADDRESS = "127.0.0.1";
private AsynchronousSocketChannel asynSocketChannel ;
public ITDragonAIOClient() throws Exception {
asynSocketChannel = AsynchronousSocketChannel.open(); // 翻开通道
}
public void connect(){
asynSocketChannel.connect(new InetSocketAddress(IP_ADDRESS, PORT)); // 竖立衔接 和NIO一样
}
public void write(String request){
try {
asynSocketChannel.write(ByteBuffer.wrap(request.getBytes())).get();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
asynSocketChannel.read(byteBuffer).get();
byteBuffer.flip();
byte[] respByte = new byte[byteBuffer.remaining()];
byteBuffer.get(respByte); // 将缓冲区的数据放入到 byte数组中
System.out.println(new String(respByte,"utf-8").trim());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true){
}
}
public static void main(String[] args) throws Exception {
for (int i = 0; i < 10; i++) {
ITDragonAIOClient myClient = new ITDragonAIOClient();
myClient.connect();
new Thread(myClient, "myClient").start();
String []operators = {"+","-","*","/"};
Random random = new Random(System.currentTimeMillis());
String expression = random.nextInt(10)+operators[random.nextInt(4)]+(random.nextInt(10)+1);
myClient.write(expression);
}
}
}
罕见口试题
1 IO,NIO,AIO区分
IO 壅塞同步通信形式,客户端和效劳器衔接须要三次握手,运用简朴,但吞吐量小
NIO 非壅塞同步通信形式,客户端与效劳器经由历程Channel衔接,采纳多路复用器轮询注册的Channel。进步吞吐量和牢靠性。
AIO 非壅塞异步通信形式,NIO的升级版,采纳异步通道完成异步通信,其read和write要领均是异步要领。
2 Stock通信的伪代码完成流程
效劳器绑定端口:server = new ServerSocket(PORT)
效劳器壅塞监听:socket = server.accept()
效劳器开启线程:new Thread(Handle handle)
效劳器读写数据:BufferedReader PrintWriter
客户端绑定IP和PORT:new Socket(IP_ADDRESS, PORT)
客户端传输吸收数据:BufferedReader PrintWriter
3 TCP协定与UDP协定有什么区分
TCP : 传输掌握协定是基于衔接的协定,在正式收发数据前,必需和对方竖立牢靠的衔接。速度慢,适宜传输大批数据。
UDP : 用户数据报协定是与TCP相对应的协定。面向非衔接的协定,不与对方竖立衔接,而是直接就把数据包发送过去,速度快,合适传输少许数据。
4 什么是同步壅塞BIO,同步非壅塞NIO,异步非壅塞AIO
同步壅塞IO : 用户历程提议一个IO操纵今后,必需守候IO操纵的真正完成后,才继承运转。
同步非壅塞IO: 用户历程提议一个IO操纵今后,可做别的事变,但用户历程须要常常讯问IO操纵是不是完成,如许形成不必要的CPU资本糟蹋。
异步非壅塞IO: 用户历程提议一个IO操纵然后,马上返回,等IO操纵真正的完成今后,运用顺序会获得IO操纵完成的关照。类比Future形式。
总结
1 BIO模子中经由历程Socket和ServerSocket完成套接字通道完成。壅塞,同步,衔接耗时。
2 NIO模子中经由历程SocketChannel和ServerSocketChannel完成套接字通道完成。非壅塞/壅塞,同步,防止TCP竖立衔接运用三次握手带来的开支。
3 AIO模子中经由历程AsynchronousSocketChannel和AsynchronousServerSocketChannel完成套接字通道完成。非壅塞,异步。
BIO NIO AIO 对照