Java网络编程

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源码。

image-20201006211803086

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核心组件关系图:

image-20201007205220842

1、buffer

缓冲区实际上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。channel提供从文件、网络读取数据的渠道,但是读取或者写入数据都要经由buffer,如图

image-20201007211036304

在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
image-20201007215732038

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
image-20201009195048172

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、基于线程池复用线程资源:不必再为每个连接创建线程,将连接完成后 的业务处理任务分配给线程进行处理,一个线程可以处理多个连接的任务。

架构图

image-20201010220102174

单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可以获取到对应通道,管道中维护了很多的处理器,可以用处理器进行对数据的初步处理(数据拦截。数据过滤等)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值