Java网络编程
同步异步、阻塞非阻塞
同步异步
同步指的是用户进程/线程触发IO操作并等待或者轮询的去查看IO操作是否就绪,而异步是指用户进程触发IO操作后便去做自己的事情,之后等待OS通知IO操作已完成。
同步异步是指的是线程是否主动去查看IO操作是否完成,主动的是同步,非主动的是异步。
阻塞非阻塞
阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取不同的方式,阻塞方式下读取或者写入方法将一直等待,而非阻塞方式读取或者写入方法则会立即返回一个状态值。
阻塞和非阻塞是进程在等待IO处理结果的时候是否被挂起,等待的时候被挂起是阻塞,没有被挂起是非阻塞。
所以异步必定是非阻塞的,异步阻塞没有意义,同步才分阻塞和非阻塞。
同步阻塞就是一直等待IO操作完成。
同步非阻塞就是轮询查看IO操作是否完成(每一次轮询都能得到状态值)没有堵塞,可以去干别的事。
异步非阻塞就是不等待IO操作的结果,线程可以去干别的事,等待OS通知IO操作是否完成。
BIO、NIO、AIO
BIO(JDK1.4版本之前用的)
同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务端就需要开启一个线程来进行处理,如果这个连接不做任何事情就会造成不必要的线程开销,可通过线程池机制进行优化。
BIO适用于连接数小且比较固定的架构,这种方式对服务器资源要求比较高。
线程池优化:把客户端连接通过submit/execute 到threadpool,让线程池和客户端连接。
BIO编程过程
1、服务器端启动一个serversocket
2、客户端创建socket与服务端连接,服务器创建一个线程与客户端对应(一般用线程池)
3、客户端发出请求后,先咨询服务器是否有响应,如果没有则会等待,或者被拒绝
4、如果有响应,客户端线程会等待请求结束后,在继续执行。
//1、创建线程池
ExecutorService service = Executors.newCachedThreadPool();
//2、创建serversocket
ServerSocket serverSocket = new ServerSocket(6666);
//3、循环监听端口
while (true){//循环等待连接
Socket socket = serverSocket.accept();//有人来连接
//有客户端连接就创建线程与之通信
//线程池
service.execute(new Handler(socket, num++));
}
NIO
同步非阻塞模型,NIO本身是基于事件驱动来完成的,主要是为了解决BIO的大并发问题。
当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器(selector)上,当这个多路复用器进行轮询的时候,发现连接上面有请求的话,就会开启一个线程进行处理,也就是一个请求一个线程。
每一个channel 和客户端之间都有一个buffer,channel和客户端是通过buffer来交换数据的。
NIO是面向缓冲区编程的,数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加处理过程的灵活性,,使用它可以提供非阻塞式的高伸缩性网络。
缺点:在NIO的处理方式中,当开启一个线程来处理请求,可能还会等待后续资源的连接,这个开启的线程实际上是被阻塞了,当并发上来了,还是会存在和BIO一样的问题。
过程:
1、当客户端连接时,会通过serversocketChannel得到SocketChannel。
2、将socketChannel注册到Selector上,register(Selector sel, int ops), 一个selector可以注册多个channel
3、注册后返回一个SeletionKey,会和该Selector进行关联(集合)。
4、Selector进行监听(调用select方法), 返回有事件发生的通道的个数.
5、进一步得到各个SelectionKey(有事件发生的通道的对应selectionkey)
6、通过selectionkey反向得到Socketchannel,调用方法channel();
7、可以通过得到的channel完成对业务的处理
serverchannel.register()函数有两个参数,第一个是注册的selector,第二个是状态编码,
状态编码有四个:
OP_ACCept,连接成功的标志
OP_READ:可以读的标记
OP_WRITER:可以写的标记
OP_CONNET:连接建立后的标志
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pulkHX8Y-1606650391345)(F:\blog\hexo\source\assets\image-20201003173526351.png)]
AIO模型
异步非阻塞(Asynchronous:IO)的编程方式,与NIO不同,当进行读写操作时,只需直接调用API的read或write方法即可,这两种方法皆是异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法缓冲区,并通知应用程序,对于写操作而言,当操作系统将write方法传递的流写入完毕时,会主动通知应用程序。即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。
异步非阻塞,服务器实现为一个有效的请求对应一个线程,客户端I/O请求都是由 OS先完成了再通知服务器应用去启动线程进行处理。
AIO方式用于连接数目多且比较长的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7之后支持。
过程:当客户端发起请求时,会建立一个AsynchronousSocketChannel(通道)
如图:(handle充当操作系统的角色)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2RTBh1cU-1606650391347)(F:\blog\hexo\source\assets\image-20201003184553630.png)]
TCPServer
public class Server {
public static void main(String[] args) throws IOException {
//创建serversocket,指定监听的端口
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(9999);
} catch (IOException e) {
e.printStackTrace();
}
//accept监听端口
Socket accept = serverSocket.accept();
InputStream is;
//字节输入流获取输入的信息
is = accept.getInputStream();
byte[] buffer = new byte[1024];
//管道流存储输入流,防止乱码
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int read = is.read(buffer);
//将读取到的流写到管道流,方便转换
byteArrayOutputStream.write(buffer,0,read);
System.out.println(byteArrayOutputStream.toString());
//关闭流和连接
byteArrayOutputStream.close();
is.close();
accept.close();
serverSocket.close();
}
}
TCPclient
public class Client {
public static void main(String[] args) throws Exception {
//指定服务器的IP地址
InetAddress serverip = InetAddress.getByName("127.0.0.1");
//指定端口号,建立连接
Socket socket =new Socket(serverip, 9999);
//使用字节流传输数据,输出流对象连接到socket的输出流
OutputStream os = socket.getOutputStream();
os.write("Hi, server".getBytes());
os.close();
socket.close();
}
}
UDPReceive
/**
* UDP接收端,跟发送端差不多,差别是要指定接受的端口(阻塞监听接受)
*/
public class UDPReceive {
public static void main(String[] args) throws IOException {
//创建socket
DatagramSocket socket = new DatagramSocket(9999);//指定监听的端口
byte[] buffer = new byte[1024];//创建缓存区存储接受到的包
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);//指定接受包存储的信息
//接受包
socket.receive(packet);
//输出结果看看
System.out.println(packet.getAddress().getHostAddress());
System.out.println(packet.getPort());
System.out.println(packet.getData().length);
System.out.println(new String(packet.getData(),0,packet.getLength()));
socket.close();
}
}
UDPSent
/**
* UDP 就是一个创建包、发送包、拆解包的过程(多路复用/分解),只有发送端和接受端
*/
public class UDPSent {
public static void main(String[] args) throws Exception{
//创建一个socket
DatagramSocket socket = new DatagramSocket();
//建立包
String msg = "你好啊接受端";
InetAddress localhost = InetAddress.getByName("localhost");//指定接收端的IP
int port = 9999; //指定接受端的接口
DatagramPacket packet = new DatagramPacket(msg.getBytes(),0, msg.length(), localhost, port);
//发送包
socket.send(packet);
socket.close();
}
}
UDP双人聊天
聊天发送线程
//talksent
public class TalkSent implements Runnable{
private int to_port;
private String to_ip;
private DatagramSocket socket;
public TalkSent(int to_port, String to_ip) {
this.to_port = to_port;
this.to_ip = to_ip;
try {
socket = new DatagramSocket();
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while (true){
String data = null;
try {
data = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
byte[] datas = data.getBytes();
InetAddress localhost = null;
try {
localhost = InetAddress.getByName(to_ip);//指定接收端的IP
} catch (UnknownHostException e) {
e.printStackTrace();
}
DatagramPacket packet = new DatagramPacket(datas,0,datas.length, localhost, to_port);
try {
socket.send(packet);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
聊天接收线程
package com.hua.socket;
import javax.sound.midi.Receiver;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class TalkReceive implements Runnable{
private int receive_port;
private DatagramSocket socket;
private String name;
public TalkReceive(int receive_port,String name) {
this.name = name;
this.receive_port = receive_port;
try {
socket = new DatagramSocket(this.receive_port);
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
while (true) {
byte[] buffer = new byte[1024];
//接受数据包
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
String data = new String(buffer,0,buffer.length);
System.out.println(name + ":" + data);
if(data.equals(("bye"))){
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
socket.close();
}
}
}
两个聊天对象(聊天端)
public class StudentTalk {
public static void main(String[] args) {
new Thread(new TalkSent(9999, "localhost")).start();
new Thread(new TalkReceive(8888,"老师")).start();
}
}
public class TeacherTalk {
public static void main(String[] args) {
new Thread(new TalkSent(8888, "localhost")).start();
new Thread(new TalkReceive(9999,"学生")).start();
}
}
netty框架
netty介绍
1、netty是由JBOSS公司提供的一个Java开源框架,现为github上的一个独立项目
2、netty是一个异步的、基于事件驱动的网络应用框架、用以快速开发高性能、高可靠性的网络IO程序。
3、netty主要针对TCP协议下,面向client端的高并发应用,或者peer-to-peer场景下的大量数据持续传输的应用。
4、netty本质是一个NIO框架,适用于服务器通讯相关的多种应用场景
5、要透彻理解netty,需要先学习NIO,我们才能阅读netty源码。
netty应用场景
1、应有于分布式系统
2、游戏行业
3、大数据领域
NIO三大组件
1、每一个channel都对应一个buffer
2、selector对应一个线程,一个线程对应多个channel
3、该图反应了有三个channel注册到了该selector
4、程序切换到哪一个channel是由事件决定的,Even就是一个重要的概念
5、selector会根据不同的事件,在各个channel上切换
6、buffer就是一个内存块,底层是一个数组
7、数据的读取写入是通过buffer,而BIO是数据流,不能双向;buffer可读可写,但是需要用一个重要的方法flip()进行切换。
8、channel是双向的,可以返回底层操作系统的情况,比如linux,底层的操作系统通道就是双向的
NIO核心组件关系图:
1、buffer
缓冲区实际上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。channel提供从文件、网络读取数据的渠道,但是读取或者写入数据都要经由buffer,如图
在NIO中,buffer是一个顶层父类,是一个抽象类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qMBR4K4Q-1606650391353)(F:\blog\hexo\source\assets\image-20201007211324447.png)]
用得最多的是bytebuffer。
四个标志位:
capacity//容量
position位置
limit限制
mark读写标志
一旦flip调用,那么limit就会等于写进来的position,position变成0
buffer 常用AIPI
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PMu4ns5Z-1606650391356)(F:\blog\hexo\source\assets\image-20201007212821368.png)]
buffer的一些注意事项和细节:
1、put()什么类型的数据,就get()什么类型,否则会抛出异常
2、可以设置buffer成只读,获得一个只读buffer,
ByteBuffer bytebuffer = buffer.asReadOnlyBuffer();
MappedByteBuffer
NIO提供了MappedByteBuffer,可以让文件直接在内存(堆外的内存)中进行修改,而如何同步文件由NIO来完成。操作系统不需要再拷贝一次,性能会比较高
//这里文件读写应该使用RandomAccessFile,
// 因为RandomAccessFile支持“随机访问”的方式,程序快可以直接跳转到文件的任意地方来读写数据。
RandomAccessFile fileOutputStream = new RandomAccessFile("test","rw");
FileChannel fileChannel = fileOutputStream.getChannel();
/**
* 第一个参数是Mapmod,使用的读写模型
* 第二个参数是修改的起始位置
* 第三个参数是映射到内存的大小,即test文件的多少个字节映射到内存
*/
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
mappedByteBuffer.put(0, (byte)9);
fileOutputStream.close();
2、channel
1、NIO的通道类似于流,但有些区别
- 通道可以进行读和写,而流只能进行读或者写也即是输入流和输出流
- 通道可以实现异步读写数据
- 通道可以从缓冲区读数据,也可以写数据到缓冲
2、channel在NIO中是一个接口
3、常用的channel子类有
filechannel、datagramchannel、serversocketchannel、socketchannel。
4、filechannel用于文件的数据读写,datagramchannel用于UDP数据的读写,
Serversocketchannel和Socketchannel用于TCP的读写。
Filechannel
3、selector选择器/多路复用器
selector能够检测多个注册的通道上是否有事件发生(多个channel可以以事件的方式注册到selector),如果有事件发生,便获取事件然后针每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
只有在连接/通道 真正有事件发生时,才会进行读写,就大大地减小了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,避免了线程上下文切换造成的开销。
特点说明
- netty的IO线程NioEventloop聚合了Selector,可以同时并发处理成百上千个客户端连接
- 当线程从某客户端Socket通道进行读写数据时,若没有数据可用时,该线程可以进行其它任务
- 线程通常将非阻塞IO的空闲时间用于其它通道上执行IO操作,所以单独的线程可以管理多个输入和输出通道
- 由于读写操作都是非阻塞的,这就可以充分提升IO线程的运行效率,避免由于频繁IO阻塞造成的线程挂起。
- 一个I/O线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞I/O一个连接一个线程的模型,架构的性能和弹性伸缩能力得到了极大提升。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QS4Wiij0-1606650391358)(F:\blog\hexo\source\assets\image-20201008202053360.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U2eGq1P6-1606650391359)(F:\blog\hexo\source\assets\image-20201008203724226.png)]
对上图过程的说明
1、当客户端连接时,会通过serversocketChannel得到SocketChannel。
2、将socketChannel注册到Selector上,register(Selector sel, int ops), 一个selector可以注册多个channel
3、注册后返回一个SeletionKey,会和该Selector进行关联(集合)。
4、Selector进行监听(调用select方法), 返回有事件发生的通道的个数.
5、进一步得到各个SelectionKey(有事件发生的通道的对应selectionkey)
6、通过selectionkey反向得到Socketchannel,调用方法channel();
7、可以通过得到的channel完成对业务的处理
4、代码示例
public class NIOServer {
public static void main(String[] args) throws IOException {
//通过open获取serversocketchannel,然后将其绑定到端口上
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open().bind(new InetSocketAddress(6666));
//选择serversocketchannel为非阻塞
serverSocketChannel.configureBlocking(false);
//通过open获取selector
Selector selector = Selector.open();
//将serversocketchannel注册到selector上,选择OP_ACCEPT事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//selector循环监听
while (true) {
//每一秒钟返回一次,注意select返回的是有事件发生的通道个数
int num = selector.select(1000);
if(num == 0){
System.out.println("没有事件发生");
continue;
}
//如果有事件发生,也就是num大于0,首先获取发生事件的通道集合,通过selectionkey获取
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//遍历得到的集合,集合的变量由迭代器完成,根据key的事件采取不同的操作
Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
while(selectionKeyIterator.hasNext()){
//得到集合中的selectionkey
SelectionKey key = selectionKeyIterator.next();
if(key.isAcceptable()){//有新的客户端连接
//为该连接生成一个channel
SocketChannel channel = serverSocketChannel.accept();
//将该channel注册到selector,设置为读事件,同时要给channel关联一个buffer
channel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(key.isReadable()){//发生读事件,要通过key反向得到channel,强制类型转换,向下转换是没问题的
SocketChannel channel = (SocketChannel) key.channel();
//通过attachment获得绑定的bytebuffer
ByteBuffer bytebuffer = (ByteBuffer) key.attachment();
channel.read(bytebuffer);
System.out.println("客户端:" + new String(bytebuffer.array()));
}
//集合的遍历,需要移除当前key
selectionKeyIterator.remove();
}
}
}
}
public class NIOClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
while(!socketChannel.connect(inetSocketAddress)){
System.out.println("循环地尝试连接服务器");
}
//注意wrap方法,即一步创建数组大小的buffer,并且将里面的元素放入buffer
ByteBuffer byteBuffer = ByteBuffer.wrap("你好啊NIO服务器".getBytes());
//将bytebuffer里面的数据写入通道
socketChannel.write(byteBuffer);
System.in.read();
}
}
总结
1、如何监听是否有客户端连接?
操作是把serversocketChannel注册到selector上,将事件设置为OP_ACCEPT。
2、selectionkey
表示selector和网络通道的注册关系,一共有四种
- int OP_ACCEPT 有新的网络连接可以接受,值为16
- int OP_CONNECT 代表连接已经建立,值为8
- int OP_WRITE 代表写操作,值为4
- int OP_READ 代表读操作,值为1
3、服务器怎么知道客户端断开连接
在读事件的时候读到的数据为 -1,就可以知道是客户端断开连接了。
或者说你读一个断开的客户端发来的数据时,会抛出IO异常,此时服务器就知道客户端断开连接了。
NIO与零拷贝
零拷贝是指从操作系统角度看的,即没有发生CPU拷贝。
传统的拷贝:(四次拷贝,3次切换)
硬盘 –(DMA)-> kernelbuffer ->userbuffer->socketbuffer->协议栈。
MMP拷贝(三次拷贝,3次切换)
kernelbuffer 和userbuffer共享内存,减少一次拷贝
硬盘 –(DMA)-> kernelbuffer(和userbuffer共享内存)–>socketbuffer->协议栈。
不经过userbuff,所以减少一次切换到用户态的次数。
零拷贝(Sendfile):(2次拷贝,2次切换(没有经过用户态))
硬盘 通过DMA(直接内存拷贝)拷贝到 kernelbuffer,然后从kernelbuff拷贝到协议栈
注意,kernelbuffer 其实有一次CPU拷贝到socketbuffer,但是信息量很少,比如length、offeset,消耗低,可以忽略。
NIO的TransferTo()方法使用的是零拷贝.
Netty高性能架构设计
不同的线程模型对程序的性能有很大的影响。
线程模型
1、传统阻塞的IO服务模型(类似于BIO)
IO阻塞
每个独立的连接都需要独立的线程完成数据的输入、处理、返回。
2、Reactor(反应器模型)
- 根据Reactor的数量和处理资源池线程数量的不同,可以由三种典型的实现。
- 单Reactor单线程、单Reactor多线程、主从Reactor多线程。
- Netty线程模型基于主从多线程模型改进。
针对传统阻塞IO的缺点,解决方案:
1、基于IO复用模型:多个连接共用一个阻塞对象,应用程序只需在一个阻塞对象等待,无需阻塞等待所有连接。当某个连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理。
2、基于线程池复用线程资源:不必再为每个连接创建线程,将连接完成后 的业务处理任务分配给线程进行处理,一个线程可以处理多个连接的任务。
架构图
单Reactor单线程
架构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gUGH8G2P-1606650391362)(F:\blog\hexo\source\assets\image-20201017201453964.png)]
单Reactor单线程,就是监听客户端事件(Reactor)、处理监听的事件(Handle)都在一个线程内完成,这样就带来了一个高并发的问题,多个客户端同时请求的话还是会阻塞。
这种编程编码简单,清晰明了,但是会带来高并发问题。
单Reactor多线程
架构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ej2uxzag-1606650391363)(F:\blog\hexo\source\assets\image-20201017210210223.png)]
Reactor分发事件给对应的Handle,handle只负责响应事件,不做具体的业务处理,通过Read读取数据后,会分发给后面的work线程池的某个work线程处理业务,并且work线程处理的结果返回给handle。最后handle得到结果发送给client。
优点:可以充分利用多核CPU的处理能力
缺点:多线程会数据共享,访问比较复杂,Reactor(单线程)处理所有的事件监听和响应,在高并发容易出现性能瓶颈。
多Reactor多线程(主从Reactor模式)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NNwMouE7-1606650391364)(F:\blog\hexo\source\assets\image-20201102200707444.png)]
Netty框架
1、简单模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zPAj5pVb-1606650391365)(F:\blog\hexo\source\assets\image-20201106202736306.png)]
1)BossGroup线程维护selector,只关注Accept事件,当接受到Accept事件,获取到对应的Socketchannel,封装成NIOSocketChannel并且注册到worker线程(事件循环),并进行维护。
3)当worker线程监听到selector中通道发生自己感兴趣的事件后,就进行处理(由handler进行处理),注意handler已经加入到通道。
2、进阶版
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mQtfUBXk-1606650391366)(F:\blog\hexo\source\assets\image-20201106203827230.png)]
多个主Reactor。
3、详细版
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OYQxSyI4-1606650391368)(F:\blog\hexo\source\assets\image-20201106203830612.png)]
1)netty抽象出两组线程池,BossGroup专门负责接受客户端的连接,WorkGrop专门负责网络的读写。
2)BossGroup和workerGroup类型都是NIOEventLoopGroup
3)NIOEventLoopGroup相当于事件循环组,这个组中含有多个事件循环,每一个事件循环是NIOEventLoop。
4)NIOEventLoop表示一个不断循环的执行处理任务的线程,每个NIOEventLoop都有一个selector,用于监听绑定在其上的socket网络通讯(网络IO)。
5)NIOEventLoopGroup可以由多个线程,即可以含有多个NIOEventLoop。(一个Group包含多个)
6)每个Boss NIOEventLoop执行的步骤有3步
1、轮询accept事件
2、处理accept事件,与client建立连接,生成NIOSocketChannel,并将其注册到某个worker NIOEventLoop 上的selector
3、处理任务队列的任务,即runAllTasks。
7)每个worker NIOEventLoop 循环执行的步骤
1、轮询read,write事件.
2、处理I/O事件,即read,write事件,在对应NIOSocketChannel进行处理
3、处理任务队列的任务,即runAllTakes.
)]
多个主Reactor。
3、详细版
[外链图片转存中…(img-OYQxSyI4-1606650391368)]
1)netty抽象出两组线程池,BossGroup专门负责接受客户端的连接,WorkGrop专门负责网络的读写。
2)BossGroup和workerGroup类型都是NIOEventLoopGroup
3)NIOEventLoopGroup相当于事件循环组,这个组中含有多个事件循环,每一个事件循环是NIOEventLoop。
4)NIOEventLoop表示一个不断循环的执行处理任务的线程,每个NIOEventLoop都有一个selector,用于监听绑定在其上的socket网络通讯(网络IO)。
5)NIOEventLoopGroup可以由多个线程,即可以含有多个NIOEventLoop。(一个Group包含多个)
6)每个Boss NIOEventLoop执行的步骤有3步
1、轮询accept事件
2、处理accept事件,与client建立连接,生成NIOSocketChannel,并将其注册到某个worker NIOEventLoop 上的selector
3、处理任务队列的任务,即runAllTasks。
7)每个worker NIOEventLoop 循环执行的步骤
1、轮询read,write事件.
2、处理I/O事件,即read,write事件,在对应NIOSocketChannel进行处理
3、处理任务队列的任务,即runAllTakes.
8)每个Worker NIOEventLoop处理业务时,会使用pipeline(管道),pipeline中包含了channel,即通过pipeline可以获取到对应通道,管道中维护了很多的处理器,可以用处理器进行对数据的初步处理(数据拦截。数据过滤等)。