当前位置: 首页 > news >正文

4.netty源码分析

1.pipeline调用handler的源码

//pipeline得到双向链表的头,next到尾部,

2.心跳源码 主要分析IdleStateHandler3个定时任务内部类
//考虑了网络传输慢导致出站慢的情况
//超时重新发送,然后关闭

ReadTimeoutHandler(继承IdleStateHandler 直接关闭连接)和WriteTimeoutHandler(继承ChannelOutboundHandlerAdapter 使用定时任务来完成)
//NioEventLoop进行事件循环

 @Overrideprotected void run() {for (;;) {try {switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {case SelectStrategy.CONTINUE:continue;case SelectStrategy.SELECT:select(wakenUp.getAndSet(false));if (wakenUp.get()) {selector.wakeup();}// fall throughdefault:}cancelledKeys = 0;needsToSelectAgain = false;final int ioRatio = this.ioRatio; //默认 50if (ioRatio == 100) { //如果被占用,就处理事件try {processSelectedKeys();} finally {// Ensure we always run tasks.runAllTasks(); //执行任务}} else {  //如果没有被占用final long ioStartTime = System.nanoTime();try {processSelectedKeys();  //选择一个key} finally {// Ensure we always run tasks.final long ioTime = System.nanoTime() - ioStartTime;runAllTasks(ioTime * (100 - ioRatio) / ioRatio);   //ioRatio默认是50 ,计算出来是1.所以定时任务执行了 ioTime没有响应的时间(妙啊,如果执行1s或者是其他时间那可能还是检测不到心跳)}}} catch (Throwable t) {handleLoopException(t);}// Always handle shutdown even if the loop processing threw an exception.try {if (isShuttingDown()) {closeAll();if (confirmShutdown()) {return;}}} catch (Throwable t) {handleLoopException(t);}}}

//IdleStateHandler

3.eventLoop执行定时任务的源代码(传入并执行队列)

1.select 默认阻塞1秒

4.任务加入异步线程池(处理耗时任务时)(因为执行任务读写 和执行读写后执行Loop任务是同一个线程)
就不会阻塞netty的IO

1.handler加入线程池(自己创建group线程池)
2.context加入线程池

/** Copyright 2012 The Netty Project** The Netty Project licenses this file to you under the Apache License,* version 2.0 (the "License"); you may not use this file except in compliance* with the License. You may obtain a copy of the License at:**   http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the* License for the specific language governing permissions and limitations* under the License.*/
package source.echo2;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;import java.util.concurrent.Callable;/*** Handler implementation for the echo server.*/
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {// group 就是充当业务线程池,可以将任务提交到该线程池// 这里我们创建了16个线程static final EventExecutorGroup group = new DefaultEventExecutorGroup(16);@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {System.out.println("EchoServer Handler 的线程是=" + Thread.currentThread().getName());//按照原来的方法处理耗时任务//解决方案2 用户程序自定义的普通任务ctx.channel().eventLoop().execute(new Runnable() {@Overridepublic void run() {try {Thread.sleep(5 * 1000);//输出线程名System.out.println("EchoServerHandler execute 线程是=" + Thread.currentThread().getName());ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));} catch (Exception ex) {System.out.println("发生异常" + ex.getMessage());}}});ctx.channel().eventLoop().execute(new Runnable() {@Overridepublic void run() {try {Thread.sleep(5 * 1000);//输出线程名System.out.println("EchoServerHandler execute 线程2是=" + Thread.currentThread().getName());ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));} catch (Exception ex) {System.out.println("发生异常" + ex.getMessage());}}});//方式1
//        //将任务提交到 group线程池
//        group.submit(new Callable<Object>() {
//            @Override
//            public Object call() throws Exception {
//
//                //接收客户端信息
                ByteBuf buf = (ByteBuf) msg;
                byte[] bytes = new byte[buf.readableBytes()];
                buf.readBytes(bytes);
                String body = new String(bytes, "UTF-8");
                //休眠10秒
                Thread.sleep(10 * 1000);
//                System.out.println("group.submit 的  call 线程是=" + Thread.currentThread().getName());
//                ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));
//                return null;
//
//            }
//        });
//
//        //将任务提交到 group线程池
//        group.submit(new Callable<Object>() {
//            @Override
//            public Object call() throws Exception {
//
//                //接收客户端信息
//                ByteBuf buf = (ByteBuf) msg;
//                byte[] bytes = new byte[buf.readableBytes()];
//                buf.readBytes(bytes);
//                String body = new String(bytes, "UTF-8");
//                //休眠10秒
//                Thread.sleep(10 * 1000);
//                System.out.println("group.submit 的  call 线程是=" + Thread.currentThread().getName());
//                ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));
//                return null;
//
//            }
//        });
//
//
//        //将任务提交到 group线程池
//        group.submit(new Callable<Object>() {
//            @Override
//            public Object call() throws Exception {
//
//                //接收客户端信息
//                ByteBuf buf = (ByteBuf) msg;
//                byte[] bytes = new byte[buf.readableBytes()];
//                buf.readBytes(bytes);
//                String body = new String(bytes, "UTF-8");
//                //休眠10秒
//                Thread.sleep(10 * 1000);
//                System.out.println("group.submit 的  call 线程是=" + Thread.currentThread().getName());
//                ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));
//                return null;
//
//            }
//        });//普通方式//接收客户端信息ByteBuf buf = (ByteBuf) msg;byte[] bytes = new byte[buf.readableBytes()];buf.readBytes(bytes);String body = new String(bytes, "UTF-8");//休眠10秒Thread.sleep(10 * 1000);System.out.println("普通调用方式的 线程是=" + Thread.currentThread().getName());ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));System.out.println("go on ");}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) {ctx.flush();}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {// Close the connection when an exception is raised.//cause.printStackTrace();ctx.close();}
}

5.netty源码
//面试

1.连接 ServerBootstrap空的构造器 作用为初始化类的成员
//启动bind

6.RPC(remote procedure call) call function like local machine

1.usual framework , ali Dubbo google gRPC ,Go rpcx
apache thrift ,spring cloud
pic 17.rpc procedure

  1. imitate dubbo RPC 通过jdk反射实现

// server handler规定 msg开头是规定的字符串(或者协议)
msg.toString().startsWith(“helloService#hello”)
//callable可以在服务器和客户端之间使用,wait()然后notify() (在同一机器)
//read调用只一次,然后调用call(netty的pipeline自动调用)
//server和client,只需按照需要的名字来调用

//下面是客户端传递参数给服务端,然后服务端调用被代理的实现的接口

//创建消费者,调用远程服务

public class ClientBootstrap {//这里定义协议头(调用的方法名)public static final String providerName = "HelloService#hello#";public static void main(String[] args) throws  Exception{//创建一个消费者NettyClient customer = new NettyClient();//创建代理对象HelloService service = (HelloService) customer.getBean(HelloService.class, providerName);for (;;) {Thread.sleep(2 * 1000);//通过代理对象调用服务提供者的方法(服务)String res = service.hello("你好 dubbo~");System.out.println("调用的结果 res= " + res);}}
}

//创建客户端创建代理对象,和初始化客户端添加handler

public class NettyClient {//创建线程池private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());private static NettyClientHandler client;private int count = 0;//编写方法使用代理模式,获取一个代理对象public Object getBean(final Class<?> serivceClass, final String providerName) {return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class<?>[]{serivceClass}, (proxy, method, args) -> {System.out.println("(proxy, method, args) 进入...." + (++count) + " 次");//{}  部分的代码,客户端每调用一次 hello, 就会进入到该代码if (client == null) {initClient();}//设置要发给服务器端的信息//providerName 协议头 args[0] 就是客户端调用api hello(???), 参数client.setPara(providerName + args[0]);return executor.submit(client).get();});}//初始化客户端private static void initClient() {client = new NettyClientHandler();//创建EventLoopGroupNioEventLoopGroup group = new NioEventLoopGroup();Bootstrap bootstrap = new Bootstrap();bootstrap.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new StringDecoder());pipeline.addLast(new StringEncoder());pipeline.addLast(client);}});try {bootstrap.connect("127.0.0.1", 7000).sync();} catch (Exception e) {e.printStackTrace();}}
}
//客户端handler,处理来自服务器的请求,服务器有数据通知线程,没有就等待public class NettyClientHandler extends ChannelInboundHandlerAdapter implements Callable {private ChannelHandlerContext context;//上下文private String result; //返回的结果private String para; //客户端调用方法时,传入的参数//与服务器的连接创建后,就会被调用, 这个方法是第一个被调用(1)@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println(" channelActive 被调用  ");context = ctx; //因为我们在其它方法会使用到 ctx}//收到服务器的数据后,调用方法 (4)//@Overridepublic synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {System.out.println(" channelRead 被调用  ");result = msg.toString();notify(); //服务器返回数据,唤醒等待的线程}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();}//被代理对象调用, 发送数据给服务器,-> wait -> 等待被唤醒(channelRead) -> 返回结果 (3)-》5@Overridepublic synchronized Object call() throws Exception {System.out.println(" call1 被调用  ");context.writeAndFlush(para); //这里的context是client自己的,//进行waitwait(); //等待channelRead 方法获取到服务器的结果后,唤醒System.out.println(" call2 被调用  ");return  result; //服务方返回的结果}//(2)void setPara(String para) {System.out.println(" setPara  ");this.para = para;}
}

//创建服务器端,添加服务器handler

public class NettyServer {public static void startServer(String hostName, int port) {startServer0(hostName,port);}//编写一个方法,完成对NettyServer的初始化和启动private static void startServer0(String hostname, int port) {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new StringDecoder());pipeline.addLast(new StringEncoder());pipeline.addLast(new NettyServerHandler()); //业务处理器}});ChannelFuture channelFuture = serverBootstrap.bind(hostname, port).sync();System.out.println("服务提供方开始提供服务~~");channelFuture.channel().closeFuture().sync();}catch (Exception e) {e.printStackTrace();}finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}public static void main(String[] args) {//代码代填..NettyServer.startServer("127.0.0.1", 7000);}
}
//服务器handler,如果接收到客户端的请求,得到字符,传入参数调用实现类方法public class NettyServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {//获取客户端发送的消息,并调用服务System.out.println("msg=" + msg);//客户端在调用服务器的api 时,我们需要定义一个协议//比如我们要求 每次发消息是都必须以某个字符串开头 "HelloService#hello#你好"if(msg.toString().startsWith(ClientBootstrap.providerName)) {msg.toString().lastIndexOf("#")String substring = msg.toString().substring(msg.toString().lastIndexOf("#") + 1);String result = new HelloServiceImpl().hello(substring);ctx.writeAndFlush(result); //向客户端写回返回的结果}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();}
}

//接口(客户端需要,使用jdk代理可以代理接口,使用cglib可以直接代理类)
//这个是接口,是服务提供方和 服务消费方都需要

public interface HelloService {String hello(String mes);
}//实现类
public class HelloServiceImpl implements HelloService {private static int count = 0;//当有消费方调用该方法时, 就返回一个结果@Overridepublic String hello(String mes) {System.out.println("收到客户端消息=" + mes);//根据mes 返回不同的结果if(mes != null) {return "你好客户端, 我已经收到你的消息 [" + mes + "] 第" + (++count) + " 次";} else {return "你好客户端, 我已经收到你的消息 ";}}
}

相关文章:

4.netty源码分析

1.pipeline调用handler的源码 //pipeline得到双向链表的头,next到尾部, 2.心跳源码 主要分析IdleStateHandler3个定时任务内部类 //考虑了网络传输慢导致出站慢的情况 //超时重新发送,然后关闭 ReadTimeoutHandler(继承IdleStateHandler 直接关闭连接)和WriteTimeoutHandler(继…...

性能优化点

Arts and Sciences - Computer Science | myUSF 索引3层&#xff08;高度为3&#xff09;一般对于数据库地址千万级别的表 大于2000万的数据进行分库分表存储 JVM整体结构及内存模型 JVM调优&#xff1a;主要为减少FULL GC的执行次数或者减少FULL GC执行时间 Spring Boot程序…...

leetcode301. 删除无效的括号(java)

删除无效的括号 leetcode301. 删除无效的括号题目描述暴力搜索 剪枝代码演示 回溯算法 leetcode301. 删除无效的括号 难度 困难 https://leetcode.cn/problems/remove-invalid-parentheses/description/ 题目描述 给你一个由若干括号和字母组成的字符串 s &#xff0c;删除最小…...

快速制作美容行业预约小程序

随着科技的不断进步&#xff0c;移动互联网的快速发展&#xff0c;小程序成为了很多行业迅速发展的利器。对于美容行业来说&#xff0c;一款美容预约小程序不仅可以方便用户进行预约&#xff0c;还可以提升美容店铺的服务质量和管理效率。下面&#xff0c;我们来介绍一下如何快…...

Golang之路---03 面向对象——结构体

结构体 结构体定义 在之前学过的数据类型中&#xff0c;数组与切片&#xff0c;只能存储同一类型的变量。若要存储多个类型的变量&#xff0c;就需要用到结构体&#xff0c;它是将多个任意类型的变量组合在一起的聚合数据类型。 每个变量都成为该结构体的成员变量。   可以理…...

【网络编程】poll

主旨思想 用一个结构体记录文件描述符集合&#xff0c;并记录用户态状态和内核态状态 函数说明 概览 #include <poll.h> struct pollfd { int fd; /* 委托内核检测的文件描述符 */ short events; /* 委托内核检测文件描述符的什么事件 */ short revents; /* 文件描述…...

配置VS Code 使其支持vue项目断点调试

起因 每个应用&#xff0c;不论大小&#xff0c;都需要理解程序是如何运行失败的。当我们写的程序没有按照自己写的逻辑走的时候&#xff0c;我们就会逐步一一排查问题。在平常开发过程中我们可能会借助 console.log 来排查,但是现在我们可以借助 VS Code 断点来调试项目。 前…...

第一百零一回 如何在组件树之间共享数据

文章目录 概念介绍使用方法示例代码 我们在上一章回中介绍了"如何实现文件存储"相关的内容&#xff0c;本章回中将介绍 如何实现组件之间共享数据。闲话休提&#xff0c;让我们一起Talk Flutter吧。 概念介绍 数据共享是程序中常用的功能&#xff0c;本章回介绍如何…...

Golang进阶学习

Golang进阶学习 视频地址&#xff1a;https://www.bilibili.com/video/BV1Pg41187AS?p35 1、包 1.1、包的引入 使用包的原因&#xff1a; 我们不可能把所有函数放在同一个源文件中&#xff0c;可以分门别类的放在不同的文件中 解决同名问题&#xff0c;同一个文件中不可以…...

【Linux】常用的基本指令

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…...

栈溢出几种情况及解决方案

一、局部数组过大。当函数内部的数组过大时&#xff0c;有可能导致堆栈溢出。 二、递归调用层次太多。递归函数在运行时会执行压栈操作&#xff0c;当压栈次数太多时&#xff0c;也会导致堆栈溢出。 三、指针或数组越界。这种情况最常见&#xff0c;例如进行字符串拷贝&#…...

go 内存分配

关注 go 语言内存分配策略&#xff0c;主要是想了解 go 的性能。申请不同大小的内存&#xff0c;性能开销是有差别的&#xff0c;申请内存越大&#xff0c;耗时也越久&#xff0c;性能也越差。 内存分配 参考 Go1.17.13 版本源码&#xff0c;从内存分配大小上区分了 tiny、sm…...

Maven pom.xml文件中build,plugin标签的具体使用

<build> 标签 <build> 标签是 pom.xml 文件中一个重要的标签&#xff0c;用于配置 Maven 项目的构建过程。在 <build> 标签下&#xff0c;可以配置构建相关的设置&#xff0c;包括源代码目录、输出目录、插件配置等。 以下是 <build> 标签的详细使用方…...

批量插入数据、MVC三层分离

八、批量插入数据 1、使用Statement&#xff08;&#xff09; 2、使用PreparedStatement() 3、使用批量操作API 4、优化 九、MVC三层分离...

【IMX6ULL驱动开发学习】21.Linux驱动之PWM子系统(以SG90舵机为例)

1.设备树部分 首先在 imx6ull.dtsi 文件中已经帮我们定义好了一些pwm的设备树节点&#xff0c;这里以pwm2为例 pwm2: pwm02084000 {compatible "fsl,imx6ul-pwm", "fsl,imx27-pwm";reg <0x02084000 0x4000>;interrupts <GIC_SPI 84 IRQ_TYP…...

el-cascader级联选择器加载远程数据、默认开始加载固定条、可以根据搜索加载远程数据。

加载用户列表分页请求、默认请求20条数据。想添加远程搜索用户功能。原有的方法filter-method不能监听到输入清空数据的时候。这样搜索完无法返回默认的20条数据。直接监听级联选择的v-model绑定的值是无法检测到用户自己输入的。 解决思路&#xff1a; el-cascader 没有提供…...

大数据技术之Clickhouse---入门篇---SQL操作、副本

星光下的赶路人star的个人主页 积一勺以成江河&#xff0c;累微尘以崇峻极 文章目录 1、SQL操作1.1 Insert1.2 Update 和 Delete1.3 查询操作1.4 alter操作1.5 导出数据 2、副本2.1 副本写入流程2.2 配置步骤 1、SQL操作 基本上来说传统关系型数据库&#xff08;以 MySQL 为例…...

【Rust 基础篇】Rust Sized Trait:理解Sized Trait与动态大小类型

导言 Rust是一门以安全性和性能著称的系统级编程语言。在Rust中&#xff0c;类型大小的确定在编译期是非常重要的。然而&#xff0c;有些类型的大小在编译期是无法确定的&#xff0c;这就涉及到了Rust中的动态大小类型&#xff08;DST&#xff09;。为了保证在编译期可以确定类…...

前端框架学习-Vue(三)

目录 初识VueVue模板语法数据绑定el和data的两种写法事件的基本使用$emit在子组件中定义方法&#xff0c;执行父组件的方法 Vue中的事件修饰符&#xff1a;键盘事件计算属性监视属性条件渲染列表渲染表单数据收集过滤器 笔记内容来自&#xff1a;尚硅谷Vue2.0Vue3.0全套教程丨v…...

HTML <rt> 标签

实例 一个 ruby 注释&#xff1a; <ruby> 漢 <rt> ㄏㄢˋ </rt> </ruby>浏览器支持 元素ChromeIEFirefoxSafariOpera<rt>5.05.538.05.015.0 Internet Explorer 9, Firefox, Opera, Chrome 以及 Safari 支持 <rt> 标签。 注释&#xf…...

VMware Linux Centos 配置网络并设置为静态ip

在root用户下进行以下操作 1. 查看子网ip和网关 &#xff08;1&#xff09;进入虚拟网络编辑器 &#xff08;2&#xff09;进入NAT设置 &#xff08;3&#xff09;记录子网IP和子网掩码 2. 修改网络配置文件 &#xff08;1&#xff09;cd到网络配置文件路径下 [rootlo…...

【Leetcode 30天Pandas挑战】学习记录

这个系列难度比较低&#xff0c;一题写一篇其实没必要&#xff0c;就全部放到一篇吧 题目列表&#xff1a; 595. Big Countries1757. Recyclable and Low Fat Products 595. Big Countries 原题链接&#xff1a;595. Big Countries Table: World ---------------------- | C…...

微信小程序使用 canvas 2d 实现签字板组件

本文是在微信小程序中使用 canvas 2d 来实现签字板功能&#xff1b; 效果图&#xff1a; 代码&#xff1a; 1、wxml <view><canvas id"canvas"type"2d"bindtouchstart"start"bindtouchmove"move"bindtouchend"end&qu…...

区块链赋能新时代司法体系,中移链打造可信存证服务

近期&#xff0c;某百万级粉丝网红的法律维权之路引发社会关注。其在面对网络造谣行为时积极搜集证据&#xff0c;使用区块链技术将相关信息上链保全&#xff0c;然后将造谣者全部起诉&#xff0c;一系列操作被广大网友喻为是教科书式网络维权。 科技在发展&#xff0c;时代在…...

ELK报错no handler found for uri and method [PUT] 原因

执行后提示no handler found for uri and method post&#xff0c;最新版8.2的问题&#xff1f; 原因&#xff1a; index.mapping.single_type: true在索引上 设置将启用按索引的单一类型行为&#xff0c;该行为将在6.0后强制执行。 原 {type} 要改为 _doc&#xff0c;格式如…...

Sublime操作技巧笔记

同时选中2个文件&#xff1a;自动切换成左右2个界面 格式化代码ctrlshifth&#xff1a; 使用快捷键ctrl shift p调出控制台&#xff0c;输入install package&#xff0c;然后输入html-css-js prettify&#xff0c;进行下载。具体的快捷键在preference > package setting &g…...

JVM | 基于类加载的一次完全实践

引言 我在上篇文章&#xff1a;JVM | 类加载是怎么工作的 中为你介绍了Java的类加载器及其工作原理。我们简单回顾下&#xff1a;我用一个易于理解的类比带你逐步理解了类加载的流程和主要角色&#xff1a;引导类加载器&#xff0c;扩展类加载器和应用类加载器。并带你深入了解…...

Termux实现电脑端远程操作【开启SSH的完整教程】

文章目录 前言一、安装软件1、安装2、启动服务3、特别说明4、添加key二、电脑端连接1、查看ip2、电脑端连接总结前言 上篇文章【安卓手机变身Linux服务器】讲了如何将你的上古安卓手机变废为宝,这节着重为大家解决一个痛点:“手机上操作实在是不方便”。 一、安装软件 1、安…...

java(Collection类)

文章目录 Collection接口继承树Collection接口及方法判断删除其它 Iterator(迭代器)接口迭代器的执行原理 foreach循环Collection子接口1&#xff1a;ListList接口特点List接口方法List接口主要实现类&#xff1a;ArrayListList的实现类之二&#xff1a;LinkedListList的实现类…...

VS2019编译安装OpenMesh8.0

文章目录 一、简介二、相关准备三、编译安装四、举个栗子参考资料一、简介 多边形网格一直以来就是交互式3D图形应用程序中最合适的几何表示,它们足够灵活,可以近似任意形状,并且可以通过当前的图形硬件有效地处理,即使在今天的低成本电脑上也是如此。OpenMesh便是其中一种…...