BIO和NIO

news2024/11/16 19:26:16

前言

这段时间自己在看一些Java中BIO和NIO之类的东西,看了很多博客,发现各种关于NIO的概念说的天花乱坠头头是道,可以说是非常的完整,但是整个看下来之后,自己对NIO还是一知半解的状态,所以这篇文章不会提到很多的概念,而是站在一个实践的角度,写一些我自己关于NIO的见解,站在实践过后的高度下再回去看概念,应该对概念会有一个更好的理解。


实现一个简易单线程服务器

要讲明白BIO和NIO,首先我们应该自己实现一个简易的服务器,不用太复杂,单线程即可。

为什么使用单线程作为演示

因为在单线程环境下可以很好地对比出BIO和NIO的一个区别,当然我也会演示在实际环境中BIO的所谓一个请求对应一个线程的状况。

服务端

public class Server {
    public static void main(String[] args) {
        byte[] buffer = new byte[1024];
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("服务器已启动并监听8080端口");
            while (true) {
                System.out.println();
                System.out.println("服务器正在等待连接...");
                Socket socket = serverSocket.accept();
                System.out.println("服务器已接收到连接请求...");
                System.out.println();
                System.out.println("服务器正在等待数据...");
                socket.getInputStream().read(buffer);
                System.out.println("服务器已经接收到数据");
                System.out.println();
                String content = new String(buffer);
                System.out.println("接收到的数据:" + content);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

客户端

public class Consumer {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1",8080);
            socket.getOutputStream().write("向服务器发数据".getBytes());
            socket.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

代码解析

我们首先创建了一个服务端类,在类中实现实例化了一个SocketServer并绑定了8080端口。之后调用accept方法来接收连接请求,并且调用read方法来接收客户端发送的数据。最后将接收到的数据打印。

完成了服务端的设计后,我们来实现一个客户端,首先实例化Socket对象,并且绑定ip为127.0.0.1(本机),端口号为8080,调用write方法向服务器发送数据。

运行结果

当我们启动服务器,但客户端还没有向服务器发起连接时,控制台结果如下:

当客户端启动并向服务器发送数据后,控制台结果如下:

结论

从上面的运行结果,首先我们至少可以看到,在服务器启动后,客户端还没有连接服务器时,服务器由于调用了accept方法,将一直阻塞,直到有客户端请求连接服务器。


对客户端功能进行扩展

在上文中,我们实现的客户端的逻辑主要是,建立Socket --> 连接服务器 --> 发送数据,我们的数据是在连接服务器之后就立即发送的,现在我们来对客户端进行一次扩展,当我们连接服务器后,不立即发送数据,而是等待控制台手动输入数据后,再发送给服务端。(服务端代码保持不变)

代码

public class Consumer {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1",8080);
            String message = null;
            Scanner sc = new Scanner(System.in);
            message = sc.next();
            socket.getOutputStream().write(message.getBytes());
            socket.close();
            sc.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

测试

当服务端启动,客户端还没有请求连接服务器时,控制台结果如下:

当服务端启动,客户端连接服务端,但没有发送数据时,控制台结果如下:

当服务端启动,客户端连接服务端,并且发送数据时,控制台结果如下:

结论

从上文的运行结果中我们可以看到,服务器端在启动后,首先需要等待客户端的连接请求(第一次阻塞),如果没有客户端连接,服务端将一直阻塞等待,然后当客户端连接后,服务器会等待客户端发送数据(第二次阻塞),如果客户端没有发送数据,那么服务端将会一直阻塞等待客户端发送数据。

服务端从启动到收到客户端数据的这个过程,将会有两次阻塞的过程。这就是BIO的非常重要的一个特点,BIO会产生两次阻塞,第一次在等待连接时阻塞,第二次在等待数据时阻塞。


BIO

在单线程条件下BIO的弱点

在上文中,我们实现了一个简易的服务器,这个简易的服务器是以单线程运行的,其实我们不难看出,当我们的服务器接收到一个连接后,并且没有接收到客户端发送的数据时,是会阻塞在read()方法中的,那么此时如果再来一个客户端的请求,服务端是无法进行响应的。换言之,在不考虑多线程的情况下,BIO是无法处理多个客户端请求的。

BIO如何处理并发

在刚才的服务器实现中,我们实现的是单线程版的BIO服务器,不难看出,单线程版的BIO并不能处理多个客户端的请求,那么如何能使BIO处理多个客户端请求呢。

其实不难想到,我们只需要在每一个连接请求到来时,创建一个线程去执行这个连接请求,就可以在BIO中处理多个客户端请求了,这也就是为什么BIO的其中一条概念是服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理。

多线程BIO服务器简易实现

public class Server {
    public static void main(String[] args) {
        byte[] buffer = new byte[1024];
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("服务器已启动并监听8080端口");
            while (true) {
                System.out.println();
                System.out.println("服务器正在等待连接...");
                Socket socket = serverSocket.accept();
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("服务器已接收到连接请求...");
                        System.out.println();
                        System.out.println("服务器正在等待数据...");
                        try {
                            socket.getInputStream().read(buffer);
                        } catch (IOException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println("服务器已经接收到数据");
                        System.out.println();
                        String content = new String(buffer);
                        System.out.println("接收到的数据:" + content);
                    }
                }).start();

            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

运行结果

很明显,现在我们的服务器的状态就是一个线程对应一个请求,换言之,服务器为每一个连接请求都创建了一个线程来处理。

多线程BIO服务器的弊端

多线程BIO服务器虽然解决了单线程BIO无法处理并发的弱点,但是也带来一个问题:如果有大量的请求连接到我们的服务器上,但是却不发送消息,那么我们的服务器也会为这些不发送消息的请求创建一个单独的线程,那么如果连接数少还好,连接数一多就会对服务端造成极大的压力。所以如果这种不活跃的线程比较多,我们应该采取单线程的一个解决方案,但是单线程又无法处理并发,这就陷入了一种很矛盾的状态,于是就有了NIO。


NIO

NIO的引入

我们先来看看单线程模式下BIO服务器的代码,其实NIO需要解决的最根本的问题就是存在于BIO中的两个阻塞,分别是等待连接时的阻塞和等待数据时的阻塞。

public class Server {
    public static void main(String[] args) {
        byte[] buffer = new byte[1024];
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("服务器已启动并监听8080端口");
            while (true) {
                System.out.println();
                System.out.println("服务器正在等待连接...");
                //阻塞1:等待连接时阻塞
                Socket socket = serverSocket.accept();
                System.out.println("服务器已接收到连接请求...");
                System.out.println();
                System.out.println("服务器正在等待数据...");
                //阻塞2:等待数据时阻塞
                socket.getInputStream().read(buffer);
                System.out.println("服务器已经接收到数据");
                System.out.println();
                String content = new String(buffer);
                System.out.println("接收到的数据:" + content);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

我们需要再老调重谈的一点是,如果单线程服务器在等待数据时阻塞,那么第二个连接请求到来时,服务器是无法响应的。如果是多线程服务器,那么又会有为大量空闲请求产生新线程从而造成线程占用系统资源,线程浪费的情况。

那么我们的问题就转移到,如何让单线程服务器在等待客户端数据到来时,依旧可以接收新的客户端连接请求。

模拟NIO解决方案

如果要解决上文中提到的单线程服务器接收数据时阻塞,而无法接收新请求的问题,那么其实可以让服务器在等待数据时不进入阻塞状态,问题不就迎刃而解了吗?

第一种解决方案(等待连接时和等待数据时不阻塞)

public class Server {
    public static void main(String[] args) throws InterruptedException {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        try {
            //Java为非阻塞设置的类
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(8080));
            //设置为非阻塞
            serverSocketChannel.configureBlocking(false);
            while(true) {
                SocketChannel socketChannel = serverSocketChannel.accept();
                if(socketChannel==null) {
                    //表示没人连接
                    System.out.println("正在等待客户端请求连接...");
                    Thread.sleep(5000);
                }else {
                    System.out.println("当前接收到客户端请求连接...");
                }
                if(socketChannel!=null) {
                    //设置为非阻塞
                    socketChannel.configureBlocking(false);
                    byteBuffer.flip();//切换模式  写-->读
                    int effective = socketChannel.read(byteBuffer);
                    if(effective!=0) {
                        String content = Charset.forName("utf-8").decode(byteBuffer).toString();
                        System.out.println(content);
                    }else {
                        System.out.println("当前未收到客户端消息");
                    }
                }
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

运行结果

不难看出,在这种解决方案下,虽然在接收客户端消息时不会阻塞,但是又开始重新接收服务器请求,用户根本来不及输入消息,服务器就转向接收别的客户端请求了,换言之,服务器弄丢了当前客户端的请求。

解决方案二(缓存Socket,轮询数据是否准备好)

public class Server {
    public static void main(String[] args) throws InterruptedException {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        List<SocketChannel> socketList = new ArrayList<SocketChannel>();
        try {
            //Java为非阻塞设置的类
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(8080));
            //设置为非阻塞
            serverSocketChannel.configureBlocking(false);
            while(true) {
                SocketChannel socketChannel = serverSocketChannel.accept();
                if(socketChannel==null) {
                    //表示没人连接
                    System.out.println("正在等待客户端请求连接...");
                    Thread.sleep(5000);
                }else {
                    System.out.println("当前接收到客户端请求连接...");
                    socketList.add(socketChannel);
                }
                for(SocketChannel socket:socketList) {
                    socket.configureBlocking(false);
                    int effective = socket.read(byteBuffer);
                    if(effective!=0) {
                        byteBuffer.flip();//切换模式  写-->读
                        String content = Charset.forName("UTF-8").decode(byteBuffer).toString();
                        System.out.println("接收到消息:"+content);
                        byteBuffer.clear();
                    }else {
                        System.out.println("当前未收到客户端消息");
                    }
                }
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

运行结果

代码解析

在解决方案一中,我们采用了非阻塞方式,但是发现一旦非阻塞,等待客户端发送消息时就不会再阻塞了,而是直接重新去获取新客户端的连接请求,这就会造成客户端连接丢失,而在解决方案二中,我们将连接存储在一个list集合中,每次等待客户端消息时都去轮询,看看消息是否准备好,如果准备好则直接打印消息。

可以看到,从头到尾我们一直没有开启第二个线程,而是一直采用单线程来处理多个客户端的连接,这样的一个模式可以很完美地解决BIO在单线程模式下无法处理多客户端请求的问题,并且解决了非阻塞状态下连接丢失的问题。

存在的问题(解决方案二)

从刚才的运行结果中其实可以看出,消息没有丢失,程序也没有阻塞。但是,在接收消息的方式上可能有些许不妥,我们采用了一个轮询的方式来接收消息,每次都轮询所有的连接,看消息是否准备好,测试用例中只是三个连接,所以看不出什么问题来,但是我们假设有1000万连接,甚至更多,采用这种轮询的方式效率是极低的。

另外,1000万连接中,我们可能只会有100万会有消息,剩下的900万并不会发送任何消息,那么这些连接程序依旧要每次都去轮询,这显然是不合适的。

真实NIO中如何解决

在真实NIO中,并不会在Java层上来进行一个轮询,而是将轮询的这个步骤交给我们的操作系统来进行,他将轮询的那部分代码改为操作系统级别的系统调用(select函数,在linux环境中为epoll),在操作系统级别上调用select函数,主动地去感知有数据的socket。


关于使用select/epoll和直接在应用层做轮询的区别

我们在之前实现了一个使用Java做多个客户端连接轮询的逻辑,但是在真正的NIO源码中其实并不是这么实现的,NIO使用了操作系统底层的轮询系统调用 select/epoll(windows:select,linux:epoll),那么为什么不直接实现而要去调用系统来做轮询呢?

select底层逻辑

假设有A、B、C、D、E五个连接同时连接服务器,那么根据我们上文中的设计,程序将会遍历这五个连接,轮询每个连接,获取各自数据准备情况,那么和我们自己写的程序有什么区别呢?

首先,我们写的Java程序其本质在轮询每个Socket的时候也需要去调用系统函数,那么轮询一次调用一次,会造成不必要的上下文切换开销。

而Select会将五个请求从用户态空间全量复制一份到内核态空间,在内核态空间来判断每个请求是否准备好数据,完全避免频繁的上下文切换。所以效率是比我们直接在应用层写轮询要高的。

如果select没有查询到到有数据的请求,那么将会一直阻塞(是的,select是一个阻塞函数)。如果有一个或者多个请求已经准备好数据了,那么select将会先将有数据的文件描述符置位,然后select返回。返回后通过遍历查看哪个请求有数据。

select的缺点:

  • 底层存储依赖bitmap,处理的请求是有上限的,为1024。

  • 文件描述符是会置位的,所以如果当被置位的文件描述符需要重新使用时,是需要重新赋空值的。

  • fd(文件描述符)从用户态拷贝到内核态仍然有一笔开销。

  • select返回后还要再次遍历,来获知是哪一个请求有数据。

poll函数底层逻辑

poll的工作原理和select很像,先来看一段poll内部使用的一个结构体。

struct pollfd{
    int fd;
    short events;
    short revents;
}

poll同样会将所有的请求拷贝到内核态,和select一样,poll同样是一个阻塞函数,当一个或多个请求有数据的时候,也同样会进行置位,但是它置位的是结构体pollfd中的events或者revents置位,而不是对fd本身进行置位,所以在下一次使用的时候不需要再进行重新赋空值的操作。poll内部存储不依赖bitmap,而是使用pollfd数组的这样一个数据结构,数组的大小肯定是大于1024的。解决了select 1、2两点的缺点。

epoll

epoll是最新的一种多路IO复用的函数。这里只说说它的特点。

epoll和上述两个函数最大的不同是,它的fd是共享在用户态和内核态之间的,所以可以不必进行从用户态到内核态的一个拷贝,这样可以节约系统资源;另外,在select和poll中,如果某个请求的数据已经准备好,它们会将所有的请求都返回,供程序去遍历查看哪个请求存在数据,但是epoll只会返回存在数据的请求,这是因为epoll在发现某个请求存在数据时,首先会进行一个重排操作,将所有有数据的fd放到最前面的位置,然后返回(返回值为存在数据请求的个数N),那么我们的上层程序就可以不必将所有请求都轮询,而是直接遍历epoll返回的前N个请求,这些请求都是有数据的请求。


Java中BIO和NIO的概念

通常一些文章都是在开头放上概念,但是我这次选择将概念放在结尾,因为通过上面的实操,相信大家对Java中BIO和NIO都有了自己的一些理解,这时候再来看概念应该会更好理解一些了。

概念整理于:

https://blog.csdn.net/guanghuichenshao/article/details/79375967

先来个例子理解一下概念,以银行取款为例:

  • 同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写)。

  • 异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API)。

  • 阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回)。

  • 非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)。

Java对BIO、NIO的支持:

  • Java BIO (blocking I/O): 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

  • Java NIO (non-blocking I/O): 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

BIO、NIO适用场景分析:

  • BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。

  • NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

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

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

相关文章

Java ssm框架 mysql实现的酒店管理系统源码+运行教程+文档

今天给大家演示一下由ssmmysql实现的一款酒店管理系统&#xff0c;教大家怎么配置运行起来&#xff0c;以及在运行过程中遇到一些小问题的解决方法。该系统实现了酒店客房预订管理的基本功能&#xff0c;还增加了图表显示统计结果的功能&#xff0c;对于Java初学者及学生来说非…

自动化测试平台(二):开发用户认证接口

一、前言 对于一个系统来讲&#xff0c;用户模块是非常基本且重要的。搭建的测试平台也需要对用户、用户权限等进行管理。下面为你讲解如何通过DRF来快速的做一个用户登录的验证接口。 二、验证登录讲解 1&#xff09;创建用户 进入django的项目目录&#xff0c;执行下面的命…

Renderbus瑞云渲染正式支持UE云渲染!离线渲染+实时渲染=渲染起飞!

2022年已经到了尾声&#xff0c;回顾今年CG圈里最具讨论性的话题&#xff0c;除了AI绘图&#xff0c;就是虚幻引擎了&#xff0c;这两者如同一股风潮&#xff0c;从概念创意到后期制作&#xff0c;一路以“席卷”之势影响到了视觉领域的各个行业。 Renderbus瑞云渲染农场作为亚…

CMake中add_custom_target的使用

CMake中的add_custom_target命令用于添加一个没有输出的target&#xff0c;以便始终构建它&#xff0c;其格式如下&#xff1a; add_custom_target(Name [ALL] [command1 [args1...]][COMMAND command2 [args2...] ...][DEPENDS depend depend depend ... ][BYPRODUCTS [files…

vue中使用visibilitychange事件来获取页面当前可见性

前言 在系统中&#xff0c;如果有打开新页面进行相关操作&#xff0c;若是有关联操作就需要通过判断页面的可见性来进行后绪的操作 一、触发visibilitychange变更的情况 页面的可见性有三个层面 页面可见时&#xff0c;用户关闭 Tab 页或浏览器窗口。页面可见时&#xff0c…

ASP.NET Zero Core系统配置工具

ASP.NET Zero Core系统配置工具 ASP。NET Zero是具有现代复杂应用程序连接的新web应用程序的起点。使用高级页面和强大的下属&#xff0c;您将能够从您的时间中受益。您可以使用ASP.NET ZERO作为基础程序&#xff0c;直接开始开发自己的代码和业务。 ASP.NET ZERO工具选项和功能…

sklearn中的特征选择feature_selection

特征选择 概念&#xff1a;就是从所有的特征中&#xff0c;选择出有意义&#xff0c;对模型有帮助的特征&#xff0c;以避免必须将所有特征都导入模型去训练的情况。特征选择常用的方法有&#xff1a;过滤法&#xff0c;嵌入法&#xff0c;包装法&#xff0c;和降维算法 过滤…

BN128曲线

1. 引言 BN系列椭圆曲线E(Fp):y2x3b&#xff0c;其中b≠0E(\mathbb{F}_p):y^2x^3b&#xff0c;其中b\neq 0E(Fp​):y2x3b&#xff0c;其中b​0&#xff0c;由Paulo S. L. M. Barreto1 和 Michael Naehrig 在2005年论文 Pairing-Friendly Elliptic Curves of Prime Order中首…

增长思维 —— 撬动企业增长的杠杆

增长一定不是只适用于互联网公司 营销&#xff1a;获客 增长&#xff1a;研究的是用户全生命周期 增长思维&#xff1a;以供需分析为基础&#xff0c;从用户全生命周期寻找增长点的方法论 这个模型的核心是啊哈时刻&#xff0c;指的是一个产品对用户价值感最强的那个点 一个…

画饼画到世界地图上:按比例呈现多组数据

地图是数据可视化的一部分&#xff0c;做群体遗传学、动物学、植物学、微生物学等的朋友经常用到世界地图&#xff0c;比如绘制不同小麦品种的世界分布。一般情况下&#xff0c;我们根据经纬度将数据标注在地图上&#xff0c;然而有些时候&#xff0c;我们会需要更高级的标注&a…

Apache doris 1.2.0 release

亲爱的社区小伙伴们&#xff0c;再一次经历数月的等候后&#xff0c;我们很高兴地宣布&#xff0c;Apache Doris 于 2022 年 12 月 7 日迎来 1.2.0 Release 版本的正式发布&#xff01;有近 118 位 Contributor 为 Apache Doris 提交了超 2400 项优化和修复&#xff0c;感谢每一…

Ubuntu 22.04 桌面美化之Mac Big Sur风格

Ubuntu 默认的桌面也是一个不错的样式&#xff0c;但千篇一律的主题很容易让人疲惫。本文描述了如何通过安装 gnome 桌面主题和动画&#xff0c;使桌面趋向于 Mac 的样式。 美化后的样式如下&#xff0c;请参考&#xff1a; 一、主题和图标美化 1.1 安装 tweaks 打开终端&…

抽奖小程序怎么做?

抽奖小程序怎么做&#xff1f;大概需要多少钱&#xff1f; 价格方面&#xff0c;平台按年收费&#xff0c;一年1498至2498元。 明码标价&#xff0c;7天退款制度&#xff0c;随时退。 抽奖小程序怎么做步骤: 1.进入第三方抽奖小程序制作平台官网注册账号并登录。 抽奖小程…

Kubernetes集群安装卸载

生命无罪&#xff0c;健康万岁&#xff0c;我是laity。 我曾七次鄙视自己的灵魂&#xff1a; 第一次&#xff0c;当它本可进取时&#xff0c;却故作谦卑&#xff1b; 第二次&#xff0c;当它在空虚时&#xff0c;用爱欲来填充&#xff1b; 第三次&#xff0c;在困难和容易之…

银河麒麟服务器系统V10开启root用户自动登录的图文教程

HI,最近公司拿回来一个联想服务器,配置还可以啊,所以就想着在本地搭建一套常用的测试环境,但是这个服务器是扁平的,不像是主机那样,这种是锁定在主机箱上的,而且噪音很大,一直嗡嗡嗡的,所以打算弄好之后放在离我远一些的地方,不然真的没法工作,全是噪音,系统都部署…

transformer14

太强了都连载14了~~ 这次是无残差连接或归一化层&#xff0c;也能成功训练深度 尽管取得了很多显著的成就&#xff0c;但训练深度神经网络&#xff08;DNN&#xff09;的实践进展在很大程度上独立于理论依据。大多数成功的现代 DNN 依赖残差连接和归一化层的特定排列&#xff…

ROS多机通信(ssh控制)

这种方法需要在局域网范围内进行&#xff0c;通信距离取决于WIFI模块的传播距离 1、连接同局域网 将主机和从机连接相同的网络&#xff0c;在同一局域网内。例如192.168.0.*&#xff08;前三位相同&#xff09; 网络会自动给每个机器一个IP 2、安装SSH sudo apt-get insta…

SpringBoot自动配置的原理-@SpringBootApplication

文章目录1自动配置原理1.1 SpringBootConfiguration1.2.ComponentScan1.3 EnableAutoConfiguration2 为什么不使用 Import 直接引入自动配置类学完这篇文章&#xff0c;可以了解到 SpringBoot 自动配置原理1自动配置原理 SpringBootConfiguration 是一个组合注解&#xff0c;由…

Pinely Round 1 (Div. 1 + Div. 2) E.Make It Connected(思维题/并查集+分类讨论)

题目 n(n<4e3)个点不包含自环和重边的无向图&#xff0c; 你可以执行以下操作若干次&#xff1a; 1. 选择一个点u 2. 对于每个点v(v≠u)来说&#xff0c;若u、v之间当前有一条边相连&#xff0c;则断开这条边&#xff0c; 否则在u、v之间加一条边&#xff0c;使之相连 …

C++Mysql8.0数据库跨平台编程实战(下)

CMysql8.0数据库跨平台编程实战&#xff08;下&#xff09;第六章 跨平台中文乱码问题和mysql锁1、MySQLAPIC封装策略和方法说明windows上字符集gbk和utf8互转开始写代码把测试框架搭起来linux上字符集GBK和UTF8互转ZPMysql库添加字符集转换函数并测试GBK插入utf-8的数据我们把…