Netty学习(NIO基础)

news2025/2/24 7:26:31

NIO基础

三大组件

Channel and Buffer

在这里插入图片描述
常用的只有ByteBuffer
在这里插入图片描述

Selector(选择器)

结合服务器的设计演化来理解Selector

多线程版设计

最早在nio设计出现前服务端程序的设计是多线程版设计,即一个客户端对应一个socket连接,一个连接用一个线程处理,每个线程专管一个连接
在这里插入图片描述
由此造成的缺点有

  1. 内存占用高, 如果一个线程默认1m大小,1000个链接就是1g
  2. CPU在线程之间的上下文切换成本高
  3. 只适合连接数少的场景
线程池版设计

socketAPI工作在阻塞模式下,同一时刻内一个线程只能处理一个客户端的socket连接,切必须等待线程处理完成当前socket连接,且必须等断开旧的连接后才能处理新的socket连接,即使旧的连接没有任何读写请求,线程没有得到充分的利用

早期的Tomcat就采用的线程池版设计阻塞式io,比较适合http请求

在这里插入图片描述

Selector设计

Channel代表服务器和客户端连接,数据读写的通道,

将客户端连接服务器的各种事件操作通过Channel细致化,Selector是负责监听Channel请求的工具,将监听到的请求交给线程,如果Channel的流量太高,其他Channel会被搁置,所以只适合流量低连接数多的场景

在这里插入图片描述

ByteBuffer

基本使用

内存有限,缓存区不能跟文件一样大小增大,所以分多次读取

@Slf4j
public class AppTest {

    public static void main(String[] args) {

        // 获取fileChannel的方法 1.输入输出流  2.RandomAccessFile
        try (FileChannel channel = new FileInputStream("data.txt").getChannel()) {
            // 准备缓冲区  获取一块内存,大小由allocate决定
            ByteBuffer buffer = ByteBuffer.allocate(10);
            while (true){
                //从Channel读取数据向buffer写入

                int len = channel.read(buffer);
                log.debug("读取到的字节数{}", len);
                if (len == -1){ //没有内容了
                    break;
                }
                //打印buffer的内容
                buffer.flip();// 切换至读模式
                while (buffer.hasRemaining()) {// 是否还有未读数据
                    byte b = buffer.get();
                    log.debug("实际字节{}",(char) b);

                }

                buffer.clear();// 切换为写模式
            }

        } catch (IOException e) {
        }
    }
}

ByteBuffer结构

ByteBuffer有以下重要属性

  • capacity(容量)
  • position(读写指针)
  • limit(读写限制)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

常见方法

分配空间

在这里插入图片描述

在这里插入图片描述

获取数据

byte b = buffer.get();
byte b = buffer.get(1);// 不会改变position值

字符串转ByteBuffer方法

在这里插入图片描述

后两种会直接切换到读模式

Scattering Reads 分散读集中写

在这里插入图片描述
在这里插入图片描述

黏包、半包


public class TestByteBufferExam {
    public static void main(String[] args) {
         /*
         网络上有多条数据发送给服务端,数据之间使用 \n 进行分隔
         但由于某种原因这些数据在接收时,被进行了重新组合,例如原始数据有3条为
             Hello,world\n
             I'm zhangsan\n
             How are you?\n
         变成了下面的两个 byteBuffer (黏包,半包)
             Hello,world\nI'm zhangsan\nHo
             w are you?\n
         现在要求你编写程序,将错乱的数据恢复成原始的按 \n 分隔的数据
         */
        ByteBuffer source = ByteBuffer.allocate(32);
        source.put("Hello,world\nI'm zhangsan\nHo".getBytes());
        split2(source);
        source.put("w are you?\n".getBytes());//追加值
        split2(source);
    }
    
    private static void split(ByteBuffer source) {
        source.flip();
        for (int i = 0; i < source.limit(); i++) {
            // 找到一条完整消息
            if (source.get(i) == '\n') {//不改变position值
                int length = i + 1 - source.position();
                // 把这条完整消息存入新的 ByteBuffer
                ByteBuffer target = ByteBuffer.allocate(length);
                // 从 source 读,向 target 写
                for (int j = 0; j < length; j++) {
                    target.put(source.get());//改变了position值
                }
                debugAll(target);
            }
        }
        source.compact();//未读完部分向前压缩并切换为写模式
    }
}

文件编程

重点是网络编程所以文件编程了解就行

FileChannel

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

传输数据

在这里插入图片描述

Path

在这里插入图片描述
在这里插入图片描述

Files

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

FilesWalkFileTree

更便捷的遍历文件的API

private static void m1() throws IOException {
    AtomicInteger dirCount = new AtomicInteger();
    AtomicInteger fileCount = new AtomicInteger();
    Files.walkFileTree(Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91"), new SimpleFileVisitor<Path>(){
    //进入目录
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            System.out.println("进入目录====>"+dir);
            dirCount.incrementAndGet();
            return super.preVisitDirectory(dir, attrs);
        }
        //操作文件
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            Files.delete(file);
            return super.visitFile(file, attrs);
        }
        
	//退出目录
        @Override
        public FileVisitResult postVisitDirectory(Path file, BasicFileAttributes attrs) throws IOException {
            System.out.println(file);
            fileCount.incrementAndGet();
            return super.visitFile(file, attrs);
        }
    });
    System.out.println("dir count:" +dirCount);
    System.out.println("file count:" +fileCount);
}

网络编程

阻塞模式

阻塞模式下,服务器每次只能执行一个连接,单线程模式运行,必须等待客户端连接和客户端信息,否则阻塞,线程停止运行等待客户端,资源效率利用低

@Slf4j
public class Server {
    public static void main(String[] args) throws IOException {
        //使用nio来理解阻塞模式,单线程
        // 0. ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(16);
        // 1. 创建的了服务器
        ServerSocketChannel ssc = ServerSocketChannel.open();

        // 2. 绑定监听端口
        ssc.bind(new InetSocketAddress(8080));

        // 3. 连接集合
        List<SocketChannel> channels = new ArrayList<>();

        while (true) {
            // 4. accept建立与客户端连接,socketChannel用来与客户端之间通信
            log.debug("connecting...");
            SocketChannel sc = ssc.accept(); // 如果客户端没启动,将会阻塞在这里等待客户端启动连接的请求
            log.debug("connected... {}", sc);
            channels.add(sc);
            for (SocketChannel channel : channels) {
                // 5. 接受客户端发生的数据
                log.debug("before read... {}",channel);
                channel.read(buffer);//如果客户端没有数据返回,将会阻塞等待客户端返回数据
                buffer.flip();
                debugRead(buffer);
                buffer.clear();
                log.debug("after read... {}",channel);

            }

        }
    }
}

-------

public class Client {
    public static void main(String[] args) throws IOException {
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress("localhost",8080));
        System.out.println("waiting...");
    }
}

非阻塞模式

非阻塞模式下,不管客户端是否有链接或返回数据,线程都不会停止,但会一直轮训尝试获取连接和数据,资源浪费

@Slf4j
public class Server {
    public static void main(String[] args) throws IOException {
        //使用nio来理解阻塞模式,单线程
        // 0. ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(16);
        // 1. 创建的了服务器
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);//非阻塞模式

        // 2. 绑定监听端口
        ssc.bind(new InetSocketAddress(8080));

        // 3. 连接集合
        List<SocketChannel> channels = new ArrayList<>();
		//非阻塞模式下  循环一直进行,轮训等待连接,还是有资源浪费
        while (true) {
            SocketChannel sc = ssc.accept(); //  非阻塞模式,如果没有客户端连接,sc返回null 线程继续运行
            if (sc != null){
                // 4. accept建立与客户端连接,socketChannel用来与客户端之间通信
                log.debug("connected... {}", sc);
                channels.add(sc);
            }
            for (SocketChannel channel : channels) {
                // 5. 接受客户端发生的数据
                log.debug("before read... {}",channel);
                int read = channel.read(buffer);// 非阻塞,线程继续运行,如果没有返回数据read返回0
                if (read > 0){
                    buffer.flip();
                    debugRead(buffer);
                    buffer.clear();
                    log.debug("after read... {}",channel);
                }

            }

        }
    }
}

Selector

Selector底层有两个keys的集合,keys表示注册在selector中的Channel集合,selectedKeys表示上述Channel参数的事件集合,selectedKeys集合中的事件必须及时消费处理,并及时移除,否则selector会认为该事件未处理造成重复处理死循环
在这里插入图片描述

  • accept serverSocket独有事件,客户端发起连接请求就会触发此事件
  • connect 是客户端连接建立后触发的事件
  • read 数据可读事件
  • write 可写事件

第三步,把Channel注册在selector中时,在内部注册SelectionKey的set集合,它存储了各个事件key,当事件被处理时应当从SelectionKey集合中移除它,否则有key而没有待处理的事件会报错空指针,所以使用Iterator迭代器循环,能移除循环到的key


@Slf4j
public class Server {
    public static void main(String[] args) throws IOException {
        // 1. 创建 Selector,管理多个 Channel
        Selector selector = Selector.open();
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);

        // 2. 建立Selector和Channel的联系(注册)
        // selectionKey 绑定一个事件
        SelectionKey sscKey = ssc.register(selector, 0, null);
        // 这个key绑定accept事件,只关注accept事件
        sscKey.interestOps(SelectionKey.OP_ACCEPT);
        log.debug("register key:{}", sscKey);

        ssc.bind(new InetSocketAddress(8080));
        while (true) {
            // 3. select方法,没有事件发生则线程阻塞,有事件发生则线程恢复运行
            // select 在事件未处理时不会阻塞,事件发生后要么取消要么处理,不能置之不理
            selector.select();
            // 4. 处理事件,selectKeys 内部包含了所有发生的事件
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                //处理key时,要从selectKeys集合中删除,否则有key而没有事件则会报错空指针
                iterator.remove();
                log.debug("key: {}", key);
                // 5. 区分事件类型
                if (key.isAcceptable()) {//如果是链接
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    //若果不处理链接,则第三步 selector 会认为事件未处理,未完成连接的消费,不会阻塞
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    SelectionKey scKey = sc.register(selector, 0, null);
                    scKey.interestOps(SelectionKey.OP_READ);
                    log.debug("{}", sc);
                    log.debug("scKey:{}", scKey);
                } else if (key.isReadable()) {//如果是 read
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(16);
                    channel.read(buffer);
                    buffer.flip();
                    debugRead(buffer);

                }
                //key.cancel();  取消事件
            }
        }

    }
}

在这里插入图片描述

处理客户端断开

如果客户端异常断开,在catch块中删除key,因为无论是否有读数据,都会产生read返回值,正常断开的返回值是-1
在这里插入图片描述

消息边界问题

当Buffer小于消息长度时,消息接受不完整会造成消息乱码或失效
在这里插入图片描述
可能会出现的情况,10字节大小的Buffer在接受13字节大小的消息时,只打印了后3个字节的的消息,因为当Buffer一次读没有接受完,服务器会自动触发两次读事件,将没有读完的内容再次读取,第一次读到Buffer就被覆盖了。

Buffer应该重新设计成,可自动扩容和非局部变量
在这里插入图片描述

在这里插入图片描述

附件与扩容

在向selector中注册keys时,可以为此key添加att参数,即附件参数,附件参数在双方交互时都可以获取修改
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

处理内容过多问题

如果发生数据大于Buffer 缓冲区,会触发多次读事件,这时如果有其他的客户端事件是处理不了的,相当于被一个客户端连接阻塞了,一直在处理那个客户端的内容发送,因为发送缓冲区被占满暂时无法发送,这时要充分利用可以选择去读

在这里插入图片描述

服务端的写操作会尝试写入尽可能多的字节,但是写入缓冲区满了是无法写的,这时候write返回0,我们优化让他关注可写事件,不要做无谓的尝试,可写的时候再写入。

最终改造

服务器在向客户端第一次写操作没写完时,给当前只关注写操作SelectionKey事件追加加上 读操作事件的值,这样大数据量一次没写完,服务器自动触发的写操作可以被捕获,能写操作再写操作,不会因为缓冲区满产生不必要的写操作造成阻塞


public class WriteServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        ssc.bind(new InetSocketAddress(8088));

        while (true) {
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();

                if (key.isAcceptable()) {
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    SelectionKey scKey = sc.register(selector, 0, null);
                    scKey.interestOps(SelectionKey.OP_READ);

                    // 1. 向客户端发送大量数据
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < 5000000; i++) {
                        sb.append("a");
                    }
                    ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());

                    // 2. 返回代表实际写入的字节数
                    int write = sc.write(buffer);
                    System.out.println(write);

                    // 3. 判断是否有剩余字节未写完
                    if (buffer.hasRemaining()) {
                        // 4. 关键改造,让客户端关注可写事件,能写入时再写入  在原有关注数值上加上对应可写事件的数值常量
                        scKey.interestOps(scKey.interestOps() + SelectionKey.OP_WRITE);
                        // 5. 把未写完的数据挂到scKey的附件上
                        scKey.attach(buffer);
                    }

                } else if (key.isWritable()) {//关注写事件
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    SocketChannel sc = (SocketChannel) key.channel();
                    int write = sc.write(buffer);
                    System.out.println(write);
                    // 6. 清理操作
                    if (!buffer.hasRemaining()) {
                        // 清理Buffer  将附件设为空
                        key.attach(null);
                        // 写完毕 不再需要关注写事件
                        key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);
                    }
                }
            }
        }

    }
}

总结

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

多线程优化

在这里插入图片描述
boss线程的selector只处理连接事件,worker线程的selector则专门处理读写事件
在这里插入图片描述

线程队列优化

boss线程内调用 worker 的 register方法,其还是由boss线程调用,只有run方法中的方法才回另开线程,实现了 boss线程的selector只处理连接事件,worker线程的selector则专门处理读写事件。
其中利用线程队列的方法延迟select的注册,因为队列中的sc注册只消费一次,所以完成注册后select只监听read事件

@Slf4j
public class MultiThreadServer {
    public static void main(String[] args) throws IOException {
        Thread.currentThread().setName("boss");
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);// 非阻塞模式
        Selector bossSelect = Selector.open();
        SelectionKey bossKey = ssc.register(bossSelect, 0, null);
        bossKey.interestOps(SelectionKey.OP_ACCEPT);
        ssc.bind(new InetSocketAddress(8008));
        // 1. 创建固定数量的worker
        Worker worker = new Worker("worker-0");
        while (true) {
            log.debug("1  bossSelect before ...,{}", bossSelect);
            bossSelect.select(); // select 方法 没有事件则阻塞
            log.debug("2  bossSelect after ...,{}", bossSelect);
            Iterator<SelectionKey> iterator = bossSelect.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                if (key.isAcceptable()) {
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    log.debug("3  connected ...,{}", sc.getRemoteAddress());
                    // 2. 关联selector
                    log.debug("4  before ...,{}", sc.getRemoteAddress());
                    worker.register(sc);// 初始 selector 启动 worker 线程
                    log.debug("5  after ...,{}", sc.getRemoteAddress());
                }
            }
        }

    }

    static class Worker implements Runnable {
        private Thread thread;
        private Selector workerSelect;
        private String name;

        private volatile boolean start = false;//还未初始化

        private ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<>();

        public Worker(String name) {
            this.name = name;
        }

        public void register(SocketChannel sc) throws IOException {
            if (!start) {
                thread = new Thread(this, name);
                thread.start();
                workerSelect = Selector.open();
                start = true;
            }
            // 向队列添加任务,但这个任务没有立刻执行 boss
            queue.add(()->{

                log.debug("6  sc register ...,{}", workerSelect);
                try {
                    sc.register(workerSelect, SelectionKey.OP_READ, null);// 在select阻塞时会注册失败
                } catch (ClosedChannelException e) {
                    throw new RuntimeException(e);
                }
            });
            workerSelect.wakeup();// wakeup方法是一次性方法,无论在select阻塞前后执行 都会唤醒一次select让select不被阻塞
        }

        @Override
        public void run() {
            while (true) {
                try {
                    log.debug("7  workerSelect before ...,{}", workerSelect);
                    workerSelect.select();
                    Runnable task = queue.poll();
                    if (task != null){
                        log.debug("8  queue run ...,{}", workerSelect);
                        task.run();
                    }
                    log.debug("9  workerSelect after...,{}", workerSelect);
                    Iterator<SelectionKey> iterator = workerSelect.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        iterator.remove();
                        if (key.isReadable()) {

                            ByteBuffer buffer = ByteBuffer.allocate(16);
                            SocketChannel channel = (SocketChannel) key.channel();
                            log.debug("10  read ...,{}", channel.getRemoteAddress());
                            channel.read(buffer);
                            buffer.flip();
                            debugAll(buffer);
                        }
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }

            }

        }
    }
}

还可以去掉队列的操作,只调用select的唤醒
因为wakeup方法是一次性方法,无论在select阻塞前后执行 都会唤醒一次select让select不被阻塞
而客户端连接事件只在boss线程执行一次所以调用了一次register方法,而start方法在唤醒前执行了,此时可能阻塞了,而wakeup方法的特殊性,能保证select完成注册,所以只需一次唤醒一次select完成注册,即可保证客户端被正常注册,消息被正常接受而不会被先运行的workerSelect.select();方法阻塞
在这里插入图片描述

多worker改造

在这里插入图片描述
worker开几个线程比较合适,若要充分发挥服务器的多核优势,可以设置至少物理核的数量

在这里插入图片描述

Worker[] workers = new Worker[Runtime.getRuntime().availableProcessors()];   

NIO vs BIO

stream vs channel

在这里插入图片描述

IO模型

同步阻塞、同步非阻塞、同步多路复用、异步阻塞(不存在)、异步非阻塞
在这里插入图片描述

零拷贝

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

切换了三次,数据复制了4次

直接内存第一次优化

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

DMA是一个独立的硬件。

零拷贝指不在Java JVM内存中进行拷贝动作

AIO

Async异步IO
在这里插入图片描述

由主线程处产生的异步线程叫守护线程,如果主线程结束了,那么守护线程也会结束,哪怕守护线程还在工作中
所以我们使主线程阻塞,等待异步返回结果

@Slf4j
public class AioFileChannel {
    public static void main(String[] args) throws IOException {
        try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get("data.txt"), StandardOpenOption.READ)) {
            // 参数1 ByteBuffer
            // 参数2 读取的起始位置
            // 参数3 附件
            // 参数4 回调对象 CompletionHandler
            ByteBuffer buffer = ByteBuffer.allocate(16);
            log.debug("read begin...");
            channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                @Override // read 成功
                public void completed(Integer result, ByteBuffer attachment) {
                    log.debug("read completed...{}", result);
                    attachment.flip();
                    debugAll(attachment);
                }
                @Override // read 失败
                public void failed(Throwable exc, ByteBuffer attachment) {
                    exc.printStackTrace();
                }
            });
            log.debug("read end...");
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        System.in.read();//使主线程阻塞不结束
    }
}

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

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

相关文章

雷电模拟器报错remount of the / superblock failed: Permission denied remount failed

报错截图 解决方法 打开设置 设置配置system.vmdk可写入 解决

前端从业者的历史难题Vue和React的抉择:难度不亚于丈母娘和媳妇

**前端从业者的历史难题&#xff1a;Vue和React的抉择——难度不亚于丈母娘和媳妇** Vue和React这两个框架无疑是当下最为流行的两个选择。它们各自拥有独特的优势和特点&#xff0c;吸引了大量的前端从业者。然而&#xff0c;对于许多从业者来说&#xff0c;如何在Vue和React…

基于大数据+Hadoop的豆瓣电子图书推荐系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行交流合作✌ 主要内容&#xff1a;SpringBoot、Vue、SSM、HLM…

Automotive之CarService和Vehicle

目录 前言一、CarService1.1 CarService 组成1.2 编译产物1.3 CarService的使用1.3.1 第一步&#xff1a;判断是否支持车载功能1.3.2 创建Car&#xff0c;获取 Manager 1.4 CarService实现原理1.4.1 启动CarServiceHelperService服务1.4.2 绑定 CarService 服务1.4.3 CarServic…

swagger的接口文档导入到yapi上

一、访问swagger接口 swagger集成到项目后&#xff0c;通过http:\\ip:port/swagger-ui.html 访问。 说明&#xff1a;这里的路径是基于swagger2。如果用swagger3&#xff0c;需要用swagger3的路径进行访问。 访问如图&#xff1a; 这就是swagger接口首页。如果想导入到yapi上…

PD虚拟机怎么联网?PD虚拟机安装Win11无法上网 pd虚拟机连不上网怎么解决 mac安装windows虚拟机教程

PD虚拟机既可以联网使用&#xff0c;也可以单机使用。如需将PD虚拟机联网&#xff0c;可以共享Mac原生系统的网络&#xff0c;其使用体验与真实系统无异。本文会详细讲解PD虚拟机如何联网&#xff0c;并会进一步解决PD虚拟机安装Win10无法上网的问题。 如果有网络相关问题的小伙…

SQL 与 NoSQL 数据库:一场关于灵活性与结构的对话

文章目录 引言SQL 数据库&#xff1a;传统之光定义特征优势缺点 NoSQL 数据库&#xff1a;新时代的弹性定义特征优势缺点 何时选择 NoSQL&#xff1f;场景1&#xff1a;海量数据与高并发场景2&#xff1a;灵活性需求场景3&#xff1a;实时数据分析场景4&#xff1a;分布式系统 …

无人机水运应用场景

航行运输 通航管理&#xff08;海事通航管理处&#xff09; 配员核查流程 海事员通过VHF&#xff08;甚高频&#xff09;系统与船长沟通核查时间。 无人机根据AIS&#xff08;船舶自动识别系统&#xff09;报告的船舶位置&#xff0c;利用打点定位 功能飞抵船舶上方。 使用…

TikTok马来西亚直播网络怎么配置?

TikTok是一款全球流行的社交媒体应用&#xff0c;在东南亚地区拥有大量用户。在马来西亚这个多元化的国家&#xff0c;配置高效稳定的直播网络对TikTok的运营至关重要。 配置马来西亚直播网络的必要性 广泛的地理覆盖&#xff1a;马来西亚包括大片陆地和众多岛屿&#xff0c;网…

求 自然对数 ln(x)

np.log()函数是用来计算数组中每个元素的自然对数的。自然对数是以数学常数e&#xff08;约等于2.71828&#xff09;为底的对数。NumPy作为一个强大的数值计算库&#xff0c;提供了很多用于数组操作的函数&#xff0c;np.log()就是其中之一。 • 下面是一个简单的例子&#xff…

某某市信息科技学业水平测试软件打开加载失败逆向分析(笔记)

引言&#xff1a;笔者在工作过程中&#xff0c;用户上报某某市信息科技学业水平测试软件在云电脑上打开初始化的情况下出现了加载和绑定机器失败的问题。一般情况下&#xff0c;在实体机上用户进行登录后&#xff0c;用户的账号信息跟主机的机器码进行绑定然后保存到配置文件&a…

RNN文献综述

循环神经网络&#xff08;Recurrent Neural Network&#xff0c;RNN&#xff09;是一种专门用于处理序列数据的神经网络模型。它在自然语言处理、语音识别、时间序列预测等领域有着广泛的应用。本文将从RNN的历史发展、基本原理、应用场景以及最新研究进展等方面进行综述。 历…

阿里云RDS云数据库库表恢复操作

最近数据库中数据被人误删了,记录一下恢复操作方便以后发生时进行恢复. 1.打开控制台&#xff0c;进入云数据库实例. 2.进入实例后 &#xff0c;点击右侧的备份恢复&#xff0c;然后看一下备份时间点&#xff0c;中间这边都是阿里云自动备份的备份集&#xff0c;基本都是7天一备…

免密ssh和自定义服务器名字【远程连接服务器】

免密ssh和自定义服务器名字【远程连接服务器】 免密ssh和自定义服务器名字【远程连接服务器】服务器添加本地公钥ssh-copy-id使用别名登录config 免密ssh和自定义服务器名字【远程连接服务器】 原理 实现免密登录需要 本地的公钥id_rsa.pub放在服务器上的 authorized_keys 文件…

实战演练:Fail2Ban部署全攻略,确保您的服务器免受CVE-2024-6387侵害!

Fail2Ban是一个开源的入侵防护软件&#xff0c;它可以扫描日志文件&#xff0c;识别恶意行为&#xff08;如多次失败的登录尝试&#xff09;&#xff0c;并自动采取措施&#xff08;如更新防火墙规则&#xff09;来阻止攻击者。最近&#xff0c;CVE-2024-6387漏洞的爆出使我们更…

第一次的pentest show总结

第一次的pentest show总结 前言 开始之前&#xff0c;我特别感谢TryHackMe(英)、HackTheBox(美)、zero-point security(英)、offsec(美)等平台&#xff0c;使我们能够通过网络以线上的方式学习与练习&#xff0c;打破传统线下各地区教育资源差异大的限制&#xff0c;对网络教…

14-6 小型语言模型在商业应用中的使用指南

人工智能 (AI) 在商业领域的发展使众多工具和技术成为人们关注的焦点&#xff0c;其中之一就是语言模型。这些大小和复杂程度各异的模型为增强业务运营、客户互动和内容生成开辟了新途径。本指南重点介绍小型语言模型、它们的优势、实际用例以及企业如何有效利用它们。 基础知识…

01 企业网站架构部署于优化之Web基础与HTTP协议

目录 1.1 Web基础 1.1.1 域名和DNS 1. 域名的概念 2. Hosts文件 3. DNS 4. 域名注册 1.1.2 网页与HTML 1. 网页概述 2. HTML概述 3. HTML基本标签 4. 网站和主页 5. Web1.0与Web2.0 1.1.3 静态网页与动态网页 1. 静态网页 2. 动态网页 3. 动态网页语言 1.2 HTTP协议 1…

搭建vue3+vite+pinia项目步骤

方法一&#xff1a;使用vite生成项目&#xff08;确保你的 node 版本是16.0.0或更高版本&#xff09; Vite 是一个新型的前端构建工具&#xff0c;专为现代前端开发优化。 第一步&#xff1a;创建项目&#xff0c;命令如下&#xff1a; // 创建项目的命令 npm create vitela…

vue项目打包部署后 浏览器自动清除缓存问题(解决方法)

vue打包部署后 浏览器缓存问题&#xff0c;导致控制台报错ChunkLoadError: Loading chunk failed的解决方案 一、报错如下&#xff1a; 每次build打包部署到服务器上时&#xff0c;偶尔会出现前端资源文件不能及时更新到最新&#xff0c;浏览器存在缓存问题&#xff0c;这时在…