【吃透Java手写】6-Netty-NIO-BIO-简易版

news2024/11/13 11:23:59

Netty

1 BIO&NIO模型

在这里插入图片描述

1.1 BIO

在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程响应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端线程会等待请求结束后才继续执行。

在这里插入图片描述

1.1.1 BIO-demo

1.1.1.1 SocketClient

创建com.sjb.bio.BIOSocketServer

public class BIOSocketServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9000);
        while (true) {
            System.out.println("等待连接...");
            // 阻塞方法
            Socket clientSocket = serverSocket.accept();
            System.out.println("有客户端连接了...");
            handler(clientSocket);
        }

    }

    private static void handler(Socket clientSocket) {
        byte[] bytes = new byte[1024];
        System.out.println("准备read...");
        // 接收客户端的数据,阻塞方法,没有数据可读时就阻塞
        try {
            int read = clientSocket.getInputStream().read(bytes);
            System.out.println("read完毕...");
            if (read != -1) {
                System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));
            }
            //clientSocket.getOutputStream().write("HelloClient".getBytes());
            //clientSocket.getOutputStream().flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试一下,运行起来,用命令行

telnet localhost 9000

ctrl+】进入命令行模式

在这里插入图片描述

发送

send hello csdn-bblb

在这里插入图片描述

很基础的socket代码,没什么好说的。如果使用C语言完成网络编程的话这块应该非常熟悉。

但是我们这个代码有点问题,就是并发能力太弱了,自然而然的想到用线程来进行处理,创建线程或者线程池用来处理这个问题。

1.1.1.2 为SocketClient添加线程池

创建定长线程池

public class BIOSocketServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9000);
        //创建定长线程池
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        while (true) {
            System.out.println("等待连接...");
            // 阻塞方法
            Socket clientSocket = serverSocket.accept();
            System.out.println("有客户端连接了...");
            executorService.execute(new SocketProcessor(clientSocket));
        }

    }
}

executorService.execute(new SocketProcessor(clientSocket));new一个SocketProcessor对象再调用execute执行线程逻辑

创建com.sjb.bio.SocketProcessor处理socket逻辑

public class SocketProcessor implements Runnable{
    private Socket socket;

    public SocketProcessor(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        processSocket(socket);
    }

    private void processSocket(Socket clientSocket) {
        //处理socket
        byte[] bytes = new byte[1024];
        System.out.println("准备read...");
        // 接收客户端的数据,阻塞方法,没有数据可读时就阻塞
        try {
            int read = clientSocket.getInputStream().read(bytes);
            System.out.println("read完毕...");
            if (read != -1) {
                System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));
            }
            //clientSocket.getOutputStream().write("HelloClient".getBytes());
            //clientSocket.getOutputStream().flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试运行三个客户端

在这里插入图片描述

显示客户端端口不一样,说明我们的线程还是有用的。

但是当我们在处理业务逻辑时,会遇到一些类似的问题C10K或者C10M,意思是瞬间有10K或者10M的线程需要并发处理,这时候我们的无论我们选择怎样的线程池,都会面临创建线程内存爆掉的问题。这时候就引入我们的NIO了。

1.2 NIO

  • 同步非阻塞的IO(non-blocking IO)

    • 三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)
    • NIO面向缓冲区编程 ,增加了操作的灵活性。
    • NIO读和写都是非阻塞的,当 读操作–>当前资源未就绪 或者 写操作–>当前未完全写入 时,当前线程都可以做其他处理。也就是说,多个请求发过来,不必分配相同线程数去处理请求(例如1000个请求,根据实际情况可以分配5-10个线程处理即可)。
  • NIO 和 BIO 对比

    • BIO 以流的方式处理数据,而 NIO 以缓冲区的方式处理数据,缓冲区 I/O 的效率比流 I/O 高很多
    • BIO 是阻塞的,NIO则是非阻塞的
    • BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据 总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的 事件(比如:连接请求, 数据到达等),因此使用单个线程就可以监听多个客户端通道。

1.2.1 NIO-demo

1.2.2.1 NIOServer

创建com.sjb.nio.NIOServer

public class NIOServer {
    //保存客户端连接
    private static List<SocketChannel> clientList = new ArrayList<>();

    public static void main(String[] args) throws IOException {
        //创建NIO ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(9000));
        //设置ServerSocketChannel为非阻塞
        serverSocketChannel.configureBlocking(false);
        System.out.println("服务启动成功");

        while (true){
            //非阻塞模式accept方法不会阻塞,否则会阻塞
            //NIO的非阻塞就是通过设置为非阻塞模式,底层调用了Linux内核的accept函数
            SocketChannel socketChannel = serverSocketChannel.accept();
            if (socketChannel != null){ //有客户端连接
                System.out.println("连接成功");
                //设置SocketChannel为非阻塞
                socketChannel.configureBlocking(false);
                //保存客户端连接在List中
                clientList.add(socketChannel);
            }
            //遍历连接进行数据读取
            Iterator<SocketChannel> iterator= clientList.iterator();
            while (iterator.hasNext()){
                SocketChannel sc = iterator.next();
                //读取数据
                ByteBuffer buffer = ByteBuffer.allocate(128);
                //设置为非阻塞模式read方法不会阻塞
                int len = sc.read(buffer);
                //如果有数据,把数据打印出来
                if (len > 0){
                    System.out.println("接收到消息:" + new String(buffer.array()));
                }else if (len == -1){ //如果客户端断开连接,把SocketChannel从List中去掉
                    iterator.remove();
                    System.out.println("客户端断开连接");
                }
            }
        }
    }
}

这里非阻塞就可以让一个进程处理很多客户端连接,但是也有缺点:

  • 没有客户端连接的时候,在空转
  • 有和多客户端连接的时候,每次都要遍历list去找到谁发的read。

我们需要改成事件驱动。

1.2.2.2 NIOSelectorServer

在这里插入图片描述

创建com.sjb.nio.NIOSelectorServer

public class NIOSelectorServer {
    public static void main(String[] args) throws IOException {
        //创建NIO ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(9000));
        //设置ServerSocketChannel为非阻塞
        serverSocketChannel.configureBlocking(false);
        //打开Selector处理Channel,即创建epoll
        Selector selector = Selector.open();
        //把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务启动成功");

        while(true){
            //阻塞等待需要处理的事件发生
            selector.select();

            //获取selector中注册的全部事件的 SelectionKey 实例
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();

            //遍历SelectionKey对事件进行处理
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                //如果是OP_ACCEPT事件,则进行连接获取和事件注册
                if (key.isAcceptable()){
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = server.accept();
                    socketChannel.configureBlocking(false);
                    //这里只注册了读事件,如果需要给客户端发送数据可以注册写事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("客户端连接成功");
                }else if (key.isReadable()){ //如果是OP_READ事件,则进行读取和打印
                    SocketChannel client = (SocketChannel) key.channel();
                    client.configureBlocking(false);
                    ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                    int len = client.read(byteBuffer);
                    //如果有数据,把数据打印出来
                    if (len > 0){
                        System.out.println("接收到消息:" + new String(byteBuffer.array()));
                    }else if (len == -1){ //如果客户端断开连接,关闭Socket
                        System.out.println("客户端断开连接");
                        client.close();
                    }
                }
                //从事件集合里删除本次处理的key,防止下次select重复处理
                iterator.remove();
            }
        }

    }
}

2 Epoll非阻塞反应堆

2.1 Epoll

2.1.1 LT 水平触发

当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你

2.1.2 ET 边缘触发

当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你

2.1.3 socket文件描述符

关于socket中的阻塞与非阻塞,首先明白的是,阻塞与非阻塞是文件(文件描述符)的性质,而不是函数的性质

  • 客户端:

    • connfd:客户端创建socket时候得到的文件描述符。connect使用这个描述符主动发起链接。
  • 服务端:

    • listenfd:创建socket得到的文件描述符,同时bind和listen使用的也是这个文件描述符
    • clientfd:调用accept得到的文件描述符,也就是用于通信的文件描述符
int lfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(lfd, F_SETFL, O_NONBLOCK);                                            //将socket设为非阻塞

2.2 Epoll非阻塞过程

简单来说就是 epoll的ET模式+非阻塞轮询+多态下针对不同的文件描述符的回调函数,反应堆不仅要监听读事件,也要监听写事件。

这里处理的事件是大小写转换。

2.2.1 服务器端

第一阶段:

  1. lfd=socket()创建lfd套接字
  2. bind()绑定地址
  3. listen()设置监听上限
  4. epoll_create()创建监听红黑树()
  5. epoll_ctl()把lfd加入红黑树
  6. while(1) 服务器端上线等待连接。

第二阶段:

  1. epoll_wait()服务器监听
  2. 有事件发生,epoll_wait()返回监听满足数组
  3. 一旦有事件发生则lfd一定在满足数组中,lfd进行accept()
  4. 用lfd进行accept()返回的连接套接字cfd放到红黑树中
  5. 执行读操作—>进行大小写转换操作—>把cfd节点的属性从EPOLLIN变为EPOLLOUT(可读变可写)
  6. 再把cfd重新挂上红黑树去监听写事件
  7. 等待epoll_wait()返回—>有返回说明能写—>执行写操作
  8. 把cfd从红黑树中拿下来—>再把cfd节点的属性从EPOLLOUT变为EPOLLIN
  9. 再把cfd重新挂上红黑树去监听读事件—>epoll_wait()服务器监听。

2.2.2 挂上红黑树的操作

void eventadd(int efd, int events, struct myevent_s *ev)
{
    struct epoll_event epv = {0, {0}};
    int op;
    epv.data.ptr = ev;
    epv.events = ev->events = events;       //EPOLLIN 或 EPOLLOUT

    if (ev->status == 0) {                                          //已经在红黑树 g_efd 里
        op = EPOLL_CTL_ADD;                 //将其加入红黑树 g_efd, 并将status置1
        ev->status = 1;
    }

    if (epoll_ctl(efd, op, ev->fd, &epv) < 0)                       //实际添加/修改
        printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);
    else
        printf("event add OK [fd=%d], op=%d, events[%0X]\n", ev->fd, op, events);

    return ;
}

epoll_ctl 是用来控制 epoll 实例的函数,可以用于添加、修改或删除文件描述符和其对应的事件。

  • efd:epoll 实例的文件描述符,通过 epoll_create 或者 epoll_create1 函数创建得到。
  • op:操作类型,可以是 EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL
    • EPOLL_CTL_ADD:添加一个新的文件描述符到 epoll 实例中。
    • EPOLL_CTL_MOD:修改一个已经在 epoll 实例中的文件描述符的事件属性。
    • EPOLL_CTL_DEL:从 epoll 实例中删除一个文件描述符。
  • ev->fd:要进行操作的文件描述符。
  • &epv:指向 struct epoll_event 结构体的指针,用于设置文件描述符的事件属性。

2.3 select、poll、epoll的区别

在这里插入图片描述

3 响应式编程

为什么Redis是单线程却能支持上万的并发?是因为Redis也是多路复用的机制。

3.1 Netty

Netty 是一个 NIO 客户端服务器框架,可快速轻松地开发网络应用程序,例如协议服务器和客户端。它极大地简化和简化了网络编程,例如 TCP 和 UDP 套接字服务器。

“快速简便”并不意味着最终的应用程序将遭受可维护性或性能问题的困扰。Netty 经过精心设计,结合了许多协议(例如FTP,SMTP,HTTP 以及各种基于二进制和文本的旧式协议)的实施经验。结果,Netty 成功地找到了一种无需妥协即可轻松实现开发,性能,稳定性和灵活性的方法。

在这里插入图片描述

3.1.1 核心组件

Channel

Channel是 Java NIO 的一个基本构造。可以看作是传入或传出数据的载体。因此,它可以被打开或关闭,连接或者断开连接。

EventLoop 与 EventLoopGroup

EventLoop 定义了Netty的核心抽象,用来处理连接的生命周期中所发生的事件,在内部,将会为每个Channel分配一个EventLoop。

EventLoopGroup 是一个 EventLoop 池,包含很多的 EventLoop。

Netty 为每个 Channel 分配了一个 EventLoop,用于处理用户连接请求、对用户请求的处理等所有事件。EventLoop 本身只是一个线程驱动,在其生命周期内只会绑定一个线程,让该线程处理一个 Channel 的所有 IO 事件。

一个 Channel 一旦与一个 EventLoop 相绑定,那么在 Channel 的整个生命周期内是不能改变的。一个 EventLoop 可以与多个 Channel 绑定。即 Channel 与 EventLoop 的关系是 n:1,而 EventLoop 与线程的关系是 1:1。

ServerBootstrap 与 Bootstrap

Bootstarp 和 ServerBootstrap 被称为引导类,指对应用程序进行配置,并使他运行起来的过程。Netty处理引导的方式是使你的应用程序和网络层相隔离。

  • Bootstrap 是客户端的引导类,Bootstrap 在调用 bind()(连接UDP)和 connect()(连接TCP)方法时,会新创建一个 Channel,仅创建一个单独的、没有父 Channel 的 Channel 来实现所有的网络交换。

  • ServerBootstrap 是服务端的引导类,ServerBootstarp 在调用 bind() 方法时会创建一个 ServerChannel 来接受来自客户端的连接,并且该 ServerChannel 管理了多个子 Channel 用于同客户端之间的通信。

ChannelHandler 与 ChannelPipeline

ChannelHandler 是对 Channel 中数据的处理器,这些处理器可以是系统本身定义好的编解码器,也可以是用户自定义的。这些处理器会被统一添加到一个 ChannelPipeline 的对象中,然后按照添加的顺序对 Channel 中的数据进行依次处理。

ChannelFuture

Netty 中所有的 I/O 操作都是异步的,即操作不会立即得到返回结果,所以 Netty 中定义了一个 ChannelFuture 对象作为这个异步操作的“代言人”,表示异步操作本身。如果想获取到该异步操作的返回值,可以通过该异步操作对象的addListener() 方法为该异步操作添加监 NIO 网络编程框架 Netty 听器,为其注册回调:当结果出来后马上调用执行。

Netty 的异步编程模型都是建立在 Future 与回调概念之上

3.2 Netty源码

3.2.1 NettyServer

public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        //创建两个线程组bossGroup和workerGroup,含有的子线程NioEventLoop的个数默认为cpu核数的两倍
        //bossGroup只是处理连接请求,真正的和客户端业务处理,会交给workerGroup完成
        EventLoopGroup bossGroup =new NioEventLoopGroup(1);
        EventLoopGroup workerGroup =new NioEventLoopGroup(10);
        try{
            //创建服务器端的启动对象
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //使用链式编程来配置参数
            serverBootstrap.group(bossGroup, workerGroup) //设置两个线程组
                    //使用NioServerSocketChannel作为服务器的通道实现
                    .channel(NioServerSocketChannel.class)
                    //初始化服务器连接队列大小,服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接
                    //多个客户端同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    //创建通道初始化对象,设置初始化参数
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //对workerGroup的SocketChannel设置处理器
                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    });
            System.out.println("...服务器 is ready...");
            //绑定一个端口并且同步,生成一个ChannelFuture对象,通过isDone()等方法可以判断异步事件的执行情况
            //启动服务器(并绑定端口),bind是异步操作,sync方法是等待异步操作执行完毕
            ChannelFuture channelFuture = serverBootstrap.bind(9000).sync();
            System.out.println("...服务器 is starting...");
            //对关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

3.2.2 NettyServerHandler

ch.pipeline().addLast(new NettyServerHandler());通过这个来添加处理方法进行回调

public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端连接通道建立成功...");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        System.out.println("Client Address ====== " + ctx.channel().remoteAddress());
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("Client Message ====== " + buf.toString(CharsetUtil.UTF_8));
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,
                                Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1678997.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

暴利 选品大课:选品决定成败,教你多种大爆款选品方法(12节课)

课程目录 001.第一讲:选品决定成败.mp4 002.第二讲:选品也有生辰八字,mp4 003.第三讲:高热度选品底层逻辑,mp4 004,第四讲:高动销选品底层逻辑,mp4 005,第五讲:高点击选品底层逻辑,mp4 006.第六讲:高转化选品底层逻辑.mp4 007.第七讲:低付费选品底层逻辑.mp4 008,第八讲…

运维别卷系列 - 云原生监控平台 之 04.prometheus 查询语句 promql 实践

文章目录 [toc]PromQL 简介什么是时间序列 PromQL 数据类型即时向量 Instant vector范围向量 Range vectorTime DurationsOffset modifier modifier 浮点值 Scalar字符串 String PromQL FUNCTIONSfloor()irate()rate()round()sort()sort_desc() PromQL 运算符算术运算符比较运算…

web安全学习笔记(15)

记一下第25-26课的内容。弱口令爆破的四种模式与判断成功失败的三种方案&#xff1b;爆破中的验证码拦截错误次数IP限制密码加密绕过 IP获取的原理以及绕过CDN获取客户真实IP 一、弱口令的分类 1.常规弱口令&#xff1a;如123456&#xff0c;666666&#xff0c;888888等 2.…

网络安全等级保护在工业控制系统中的应用

工业控制系统(Industrial Control Systems,ICS)&#xff0c;是由各种自动化控制组件和实时数据采集、监测的过程控制组件共同构成。其组件包括数据采集与监控系统(SCADA)、分布式控制系统(DCS)、可编程逻辑控制器(PLC)、远程终端(RTU)、智能电子设备(IED)&#xff0c;以及确保各…

jspXMl标记语言基础

1.打开命令框进入数据库 打开eclipse创建需要连接的项目 粘贴驱动程序 查看驱动器 使用sql的包 int代表个 conlm代表列名 <%page import"java.sql.ResultSet"%> <%page import"java.sql.Statement"%> <%page import"java.sql.Connect…

Elasticsearch 实现word、pdf、txt、excel文档内容快速检索(保姆级教程)

本文主要讲解ES如何从提取文档中提取内容&#xff08;word、pdf、txt、excel等文件类型&#xff09;&#xff0c;实现快速检索文档内容实现。 特别说明一下&#xff0c;为什么用7.10.0版本&#xff0c;因为在项目中除了精确匹配的要求&#xff0c;也会有模糊查询&#xff08;关…

合并K个升序链表

题目 解法一 优先级队列 思想 将每个链表中的一个节点存放到优先级队列中&#xff0c;本题采用小根堆&#xff0c;将小根堆中的根节点取出&#xff0c;插入到最终的链表中&#xff0c;并且将该节点在原链表中的下一个节点插入小根堆中&#xff08;需要向下调整&#xff09;&a…

Day_5

1. Apache ECharts Apache ECharts 是一款基于 Javascript 的数据可视化图表库&#xff0c;提供直观&#xff0c;生动&#xff0c;可交互&#xff0c;可个性化定制的数据可视化图表 官网地址&#xff1a;https://echarts.apache.org/zh/index.html 入门案例 快速入门&#x…

LearnOpenGL(十八)之面剔除

一、面剔除 对于一个3D立方体&#xff0c;无论我们从哪个方向&#xff0c;最多只能同时看到3个面。如果我们能够以某种方式丢弃另外几个看不见的面&#xff0c;我们就能省下超过50%的片段着色器执行数&#xff01; 这正是面剔除(Face Culling)所做的。OpenGL能够检查所有面向…

ClassificationPrimitive 内部原理

ClassificationPrimitive 内部原理 发明 ClassificationPrimitive的真是个天才。其原理是利用 webgl 的模板缓冲区实现。 渲染两次, 首先是绘制模板, 然后绘制真正的内容。 示意图: function createClass() {const { program, uniforms } WebGLProgram.buildPrograms(gl, …

PMR-440N7Q韩国施耐德三和相序继电器EOCR-PMR

韩国施耐德三和EOCR继电器PMR-440N7Q PMR-440-N 直流电动机保护器:DCL、DOCR-S/H 欠电流继电器:EUCR-3C 交流电压继电器:EOVR、EVR-PD、EVR-FD、EUVR 韩国三和EOCR电动机保护器:EOCR-SS、EOCR-SS1/SS2、EOCR-AR、EOCR-ST、EOCR-SP、EOCR-SP1/SP2、EOCR-SE、EOCR-SE2/SE PMR-44…

会员网站如何创建具有不同仪表盘结构的用户帐户页面

用户帐户页面是中央用户仪表盘&#xff0c;用户可以在其中添加和编辑信息、发布和编辑帖子以及保存收藏夹项目。本教程介绍如何使用“内容”和“重写”模板模式设置帐户页面、为帐户页面创建子页面以及设置个人资料菜单等。 在本教程中&#xff0c;我们将介绍如何使用招聘网站…

Netty的组件和设计

目录 Channel、EventLoop和ChannelFuture Channel接口 EventLoop接口 ChannelFuture接口 ChannelHandler和ChannelPipeline ChannelHandler接口 ChannelPipeline接口 更加深入地了解ChannelHandler 编码器和解码器 抽象类SimpleChannelInboundHandler 引导 从高层次…

vue网页端控制台展示独有标记

效果展示 实现步骤 1. 新建js文件 定义一个类 用于提供控制台打印日志显示样式的方法 src\libs\util.log.js class Logger {// 定义静态方法static typeColor(type "default") {let color "";switch (type) {case "default":color "#3…

Lambda 表达式详解

LAMBDA ⚪ λ 希腊字母表中排序第十一位的字母, 英语名称为Lambda ⚪ 避免匿名内部类定义过多 ⚪ 其实质属于函数式编程的概念 ⚪ 也可称为闭包 ⚪ Lambda允许把一个函数作为方法的参数&#xff08;函数作为参数传递进方法中&#xff09;。 Lambda是在jdk8之后出现的所以现…

Xed编辑器开发第一期:使用Rust从0到1写一个文本编辑器

这是一个使用Rust实现的轻量化文本编辑器。学过Rust的都知道&#xff0c;Rust 从入门到实践中间还隔着好几个Go语言的难度&#xff0c;因此&#xff0c;如果你也正在学习Rust,那么恭喜你&#xff0c;这个项目被你捡到了。本项目内容较多&#xff0c;大概会分三期左右陆续发布&a…

WebLogic SSL应用

SSL 安全套接字层(SSL)是通过在客户端和Web服务器端之间进行身份验证,并对双方交换的数据进行加密,从而提供安全连接。 验证类型: 单向:客户端验证Web服务器端证书 双向:客户端验证Web服务器证书, Web服务器验证客户端证书 Weblogic Server12c 支持 SSL 3.0 和 TLS1.0 …

HCIP【Hybird实验】

目录 一、实验拓扑图&#xff1a; 二、实验要求&#xff1a; 三、实验思路&#xff1a; 四、实验过程&#xff1a; 1、配置PC的IP地址&#xff08;不用配置网关&#xff0c;这个拓扑图没有使用到三层设备&#xff09; 2、交换机配置 3、PC间进行测试&#xff1a; 一、实…

如何设计实用的ITSM自助服务台

在现代IT服务管理&#xff08;ITSM&#xff09;领域中&#xff0c;自助服务台已成为IT运维环境的核心组件。它作为企业内部信息中心与其他部门用户之间的桥梁&#xff0c;一个以用户为中心的平台&#xff0c;更注重用户的自主性和自助能力&#xff0c;使用户能够直接访问所需的…

ModuleNotFoundError: No module named ‘openpyxl‘的解决方案

问题描述&#xff1a; ModuleNotFoundError: No module named ‘openpyxl’ 这个错误表示你的 Python 环境中没有安装 openpyxl 这个模块。openpyxl 是一个用于读写 Excel 2010 xlsx/xlsm/xltx/xltm 文件的 Python 库。 解决方案&#xff1a; 要解决这个问题&#xff0c;你需…