Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >逛到底层看NIO的零拷贝

逛到底层看NIO的零拷贝

原创
作者头像
止术
修改于 2021-05-13 06:37:11
修改于 2021-05-13 06:37:11
4680
举报
文章被收录于专栏:求道求道

本章还是关于NIO的概念铺底,有关NIO相关的代码,我还是希望大家闲余时间取网上找一下有关使用JDK NIO开发服务端、客户端的代码,我会取写这些,但是具体的代码我不会很详细的取介绍,下一章的话可能就要上代码了,具体的规划如下:

讲一下NIO基础API的使用、分析Netty的核心思想,使用Reactor模式仿写一个多线程版的Nio程序、再然后就是关于Netty的源码分析了!

回归正题,NIO的高性能除了体现在Epoll模型之外,还有很重要的一点,就是零拷贝!首先大家要先明白一点,所谓的0拷贝,并不是一次拷贝都没有,而是数据由内核空间向用户空间的相互拷贝被取消了,所以称之为零拷贝!

系统如何操作底层数据文件

在了解整个IO的读写的过程中,我们需要知道我们的应用程序是如何操作一些内存、磁盘数据的!

我们在开发中,假设要向硬盘中写入一段文本数据,我们并不需要操作太多的细节,而是只需要简单的将数据转为字节然后在告诉程序,我们要写入的位置以及名称就可以了,为什么这么简单呢?因为操作系统全部帮我们开发好了,我们只需要调用就可以了,但是我们想一下,如果我们的操作系统的全部权限,包括内存都可以让用户随意操作那是一个很危险的事情,例如某些病毒可以随意篡改内存中的数据,以达到某些不轨的目的,那就很难受了!所以,我们的操作系统就必须对这些底层的API进行一些限制和保护!

但是如何保护呢?一方面,我们希望外部系统能够调用我的系统API,另一方面我又不想外部随意访问我的API怎么办呢? 此时,我们就要引申出来一个组件叫做kernel,你可以把它理解为一段程序,他在机器启动的时候被加载进来,被用于管理系统底层的一些设备,例如硬盘、内存、网卡等硬件设备!当我们又了kernel之后,会发生什么呢?

我们还是以写出文件为例,当我们调用了一个write api的时候,他会将write的方法名以及参数加载到CPU的寄存器中,同时执行一个指令叫做 int 0x80的指令,int 0x80是 interrupt 128(0x80的10进制)的缩写,我们一般叫80中断,当调用了这个指令之后,CUP会停止当前的调度,保存当前的执行中的线程的状态,然后在中断向量表中寻找 128代表的回调函数,将之前写到寄存器中的数据(write /参数)当作参数,传递到这个回调函数中,由这个回调函数去寻找对应的系统函数write进行写出操作!

大家回想一下,当系统发起一个调用后不再是用户程序直接调用系统API的而是切换成内核调用这些API,所以内核是以这种方式来保护系统的而且这也就是 用户态切换到内核态

传统的I/O读写

场景:读取一个图片通过socket传输到客户端展示。

image-20210314222351485.png
image-20210314222351485.png
  1. 程序发起read请求,调用系统read api由用户态切换至内核态!
  2. CPU通过DMA引擎将磁盘数据加载到内核缓冲区,触发中止指令,CPU将内核缓冲区的数据拷贝到用户空间!由内核态切换至用户态!
  3. 程序 发起write调用,调用系统API,由用户态切换只内核态,CPU将用户空间的数据拷贝到Socket缓冲区!再由内核态切换至用户态!
  4. DMA引擎异步将Socket缓冲区拷贝到网卡通过底层协议栈发送至对端!

我们可以了解一下,这当中发生了4次上下文的切换和4次数据拷贝!我们大致分析一下,那些数据拷贝是多余的:

  • 磁盘文件拷贝到内核缓冲区是必须的不能省略,因为这个数据总归要读取出来的!
  • 内核空间拷贝到用户空间,如果我们不准备对数据做修改的话,好像没有必要呀,直接拷贝到Socket缓冲区不就可以了
  • Socket到网卡,好像也有点多余,为什么这么说呢?因为我们直接从内核空间里面直接怼到网卡里面,中间不就少了很多的拷贝和上下文的切换看吗?

sendfile

我们通过Centos man page指令查看该函数的定义!

也可以通过该链接下载:sendfile()函数介绍

基本介绍:sendfile——在文件描述符之间传输数据描述ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);sendfile()在一个文件描述符和另一个文件描述符之间复制数据。因为这种复制是在内核中完成的,所以sendfile()比read(2)和write(2)的组合更高效,后者需要在用户空间之间来回传输数据。in_fd应该是打开用于读取的文件描述符,而out_fd应该是打开用于写入的文件描述符。如果offset不为NULL,则它指向一个保存文件偏移量的变量,sendfile()将从这个变量开始从in_fd读取数据。当sendfile()返回时,这个变量将被设置为最后一个被读取字节后面的字节的偏移量。如果offset不为NULL,则sendfile()不会修改当前值租用文件偏移in_fd;否则,将调整当前文件偏移量以反映从in_fd读取的字节数。如果offset为NULL,则从当前文件偏移量开始从in_fd读取数据,并通过调用更新文件偏移量。count是要在文件描述符之间复制的字节数。in_fd参数必须对应于支持类似mmap(2)的操作的文件(也就是说,它不能是套接字)。在2.6.33之前的Linux内核中,out_fd必须引用一个套接字。从Linux 2.6.33开始,它可以是任何文件。如果是一个常规文件,则sendfile()适当地更改文件偏移量。

简单来说,sendfile函数可以将两个文件描述符里面的数据来回复制,再Linux中万物皆文件!内核空间和Socket也是一个个的对应的文件,sendfile函数可以将两个文件里面的数据来回传输,这也造就了,我们后面的零拷贝优化!

sendfile - linux2.4之前

image-20210314230009773
image-20210314230009773
  1. 用户程序发起read请求,程序由用户态切换至内核态!
  2. DMA引擎将数据从磁盘拷贝出来到内核空间!
  3. 调用sendfile函数将内核空间的数据直接拷贝到Socket缓冲区!
  4. 上下文从内核态切换至用户态
  5. Socket缓冲区通过DMA引擎,将数据拷贝到网卡,通过底层协议栈发送到对端!

这个优化不可谓不狠,上下文切换次数变为两次,数据拷贝变为两次,这基本符合了我们上面的优化要求,但是我们还是会发现,从内核空间到Socket缓冲区,然后从内核缓冲区到网卡似乎也有点鸡肋,所以,Linux2.4之后再次进行了优化!

sendfile - linux2.4之后

image-20210314231519642
image-20210314231519642
  1. 用户程序发起read请求,程序由用户态切换至内核态!
  2. DMA引擎将数据从磁盘拷贝出来到内核空间!
  3. 调用sendfile函数将内核空间的数据再内存中的起始位置和偏移量写入Socket缓冲区!然后内核态切换至用户态!
  4. DMA引擎读取Socket缓冲区的内存信息,直接由内核空间拷贝至网卡!

这里的优化是原本将内核空间的数据拷贝至Socket缓冲区的步骤,变成了只记录文件的起始位置和偏移量!然后程序直接返回,由DMA引擎异步的将数据从内核空间拷贝到网卡!

为什么不是直接拷贝,而是多了一步记录文件信息的步骤呢?因为相比于内核空间,网卡的读取速率实在是太慢了,这一步如果由CPU来操作的话,会严重拉低CPU的运行速度,所以要交给DMA来做,但是因为是异步的,DMA引擎又不知道为这个Socket到底发送多少数据,所以要在Socket上记录文件起始量和数据长度,再由DMA引擎读取这些文件信息,将文件发送只网卡数据!

mmap

我们通过Centos man page指令查看该函数的定义!

mmap()函数介绍

名字mmap, munmap -将文件或设备映射到内存中void mmap(void addr, size_t length, int prot, int flags, int fd, off_t offset);int munmap(void *addr, size_t length);描述:mmap()在调用进程的虚拟地址空间中创建一个新的映射。新映射的起始地址在addr中指定。length参数指定映射的长度,如果addr为空,则内核选择创建映射的地址;这是创建新映射的最可移植的方法。如果addr不为空,则内核将其作为提示!关于在哪里放置映射;在Linux上,映射将在附近的页面边界创建。新映射的地址作为调用的结果返回。

mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

什么叫区域共享,这个不能被理解为我们的应用程序就可以直接到内核空间读取数据了,而是我们在用户空间里面再开辟一个空间,将内核空间的数据的起始以及偏移量映射到用户空间!简单点说 也就是用户空间的内存,持有对内核空间这一段内存区域的引用!这样用户空间在操作读取到的数据的时候,就可以像直接操作自己空间下的数据一样操作内核空间的数据!

image-20210315092915111
image-20210315092915111
  1. 用户程序发起read请求,然后上下文由用户态切换至内核态!
  2. cpu通知DMA,由DMA引擎异步将数据读取至内核区域,同时在用户空间建立地址映射!
  3. 上下文由内核态切换至用户态
  4. 发起write请求,上下文由用户态切换至内核态!
  5. CPU通知DMA引擎将数据拷贝至Socket缓存!程序切换至用户态!
  6. DMA引擎异步将数据拷贝至网卡!

很明白的发现mmap函数在read数据的时候,少了异步由内核空间到用户空间的数据复制,而是直接建立一个映射关系,操作的时候,直接操作映射数据,但是上下文的切换没有变!

mmap所建立的虚拟空间,空间量事实上可以远大于物理内存空间,假设我们想虚拟内存空间中写入数据的时候,超过物理内存时,操作系统会进行页置换,根据淘汰算法,将需要淘汰的页置换成所需的新页,所以mmap对应的内存是可以被淘汰的(若内存页是"脏"的,则操作系统会先将数据回写磁盘再淘汰)。这样,就算mmap的数据远大于物理内存,操作系统也能很好地处理,不会产生功能上的问题。

sendfile: 只经历两次上线文的切换和两次数据拷贝,但是缺点也显而易见,你无法对数据进行修改操作!适合大文件的数据传输!而且是没有没有修改数据的需求!

mmap: 经历4次上下文的切换、三次数据拷贝,但是用户操作读取来的数据,异常简单!适合小文件的读写和传输!

nio的堆外内存

堆外内存的实现类是DirectByteBuffer, 我们查看SocketChannel再向通道写入数据的时候的代码:

image-20210315121518393
image-20210315121518393

这段代码是当你调用SocketChannel.write的时候的源代码,我们从代码中可以得知,无论你是否使用的是不是堆外内存,在内部NIO都会将其转换为堆外内存,然后在进行后续操作,那么堆外内存究竟有何种魔力呢?

何为堆外内存,要知道我们的JAVA代码运行在了JVM容器里面,我们又叫做Java虚拟机,java开发者为了方便内存管理和内存分配,将JVM的空间与操作系统的空间隔离了起来,市面上所有的VM程序都是这样做的,VM程序的空间结构和操作系统的空间结构是不一样的,所以java程序无法直接的将数据写出去,必须先将数据拷贝到C的堆内存上也就是常说的堆外内存,然后在进行后续的读写,在NIO中直接使用堆外内存可以省去JVM内部数据向本次内存空间拷贝的步骤,加快处理速度!

而且NIO中每次写入写出不在是以一个一个的字节写出,而是用了一个Buffer内存块的方式写出,也就是说只需要告诉CPU 我这个数据块的数据开始的索引以及数据偏移量就可以直接读取,但是JVM通过垃圾回收的时候,通过会做垃圾拷贝整理,这个时候会移动内存,这个时候如果内存地址改变,就势必会出现问题,所以我们要想一个办法,让JVM垃圾回收不影响这个数据块!

总结来说:它可以使用Native 函数库直接分配堆外内存,然后通过一个存储在Java 堆里面的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java 堆和Native 堆中来回复制数据。

能够避免JVM垃圾回收过程中做内存整理,所产生的的问题,当数据产生在JVM内部的时候,JVM的垃圾回收就无法影响这部分数据了,而且能够变相的减轻JVM垃圾回收的压力!因为不用再管理这一部分数据了!

他的内存结构看起来像这样:

image-20210315125336866
image-20210315125336866

为什么DirectByteBuffer就能够直接操作JVM外的内存呢?我们看下他的源码实现:

代码语言:txt
AI代码解释
复制
DirectByteBuffer(int cap) { 
		.....忽略....
        try {
            //分配内存
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            ....忽略....
        }
        ....忽略....
        if (pa && (base % ps != 0)) {
            //对齐page 计算地址并保存
            address = base + ps - (base & (ps - 1));
        } else {
            //计算地址并保存
            address = base;
        }
        //释放内存的回调
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        ....忽略..
    }

我们主要关注:unsafe.allocateMemory(size);

代码语言:txt
AI代码解释
复制
public native long allocateMemory(long var1);

我们可以看到他调用的是 native方法,这种方法通常由C++实现,是直接操作内存空间的,这个是被jdk进行安全保护的操作,也就是说你通过Unsafe.getUnsafe()是获取不到的,必须通过反射,具体的实现,自行翻阅浏览器!

如此NIO就可以通过本地方法去操作JVM外的内存,但是大家有没有发现一点问题,我们现在是能够让操作系统直接读取数据了,而且也能够避免垃圾回收所带来的影响了还能减轻垃圾回收的压力,可谓是一举三得,但是大家有没有考虑过一个问题,这部分空间不经过垃JVM管理了,他该什么时候释放呢?JVM都管理不了了,那么堆外内存势必会导致OOM的出现,所以,我们必须要去手动的释放这个内存,但是手动释放对于编程复杂度难度太大,所以,JVM对堆外内存的管理也做了一部分优化,首先我们先看一下上述DirectByteBuffer中的cleaner = Cleaner.create(this, new Deallocator(base, size, cap));,这个对象,他主要用于堆外内存空间的释放;

代码语言:txt
AI代码解释
复制
public class Cleaner extends PhantomReference<Object> {....}

虚引用

Cleaner继承了一个PhantomReference,这代表着Cleaner是一个虚引用,有关强软弱虚引用的使用,请大家自行百度,Netty更新完成之后,我会写一篇文章做单独的介绍,这里就不一一介绍了,这里直接说PhantomReference虚引用:

代码语言:txt
AI代码解释
复制
public class PhantomReference<T> extends Reference<T> {
    public T get() {
        return null;
    }
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

虚引用的构造函数中要求必须传递的两个参数,被引用对象、引用队列!

这两个参数的用意是什么呢,看个图

image-20210315131401311
image-20210315131401311

JVM中判断一个对象是否需要回收,一般都是使用可达性分析算法,什么是可达性分析呢?就是从所谓的方法区、栈空间中找到被标记为root的节点,然后沿着root节点向下找,被找到的都任务是存活对象,当所有的root节点寻找完毕后,剩余的节点也就被认为是垃圾对象

依据上图,我们明显发现栈空间中持有对direct的引用,我们将该对象传递给弱引用和,弱引用也持有该对象,现在相当于direct引用和ref引用同时引用堆空间中的一块数据,当direct使用完毕后,该引用断开:

image-20210315131857691
image-20210315131857691

JVM通过可待性分析算法,发现除了 ref引用之外,其余的没有人引用他,因为ref是虚引用,所以本次垃圾回收一定会回收它,回收的时候,做了一件什么事呢?

我们在创建这个虚引用的时候传入了一个队列,在这个对象被回收的时候,被引用的对象会进入到这个回调!

代码语言:txt
AI代码解释
复制
public class MyPhantomReference {
    static ReferenceQueue<Object> queue = new ReferenceQueue<>();
    public static void main(String[] args) throws InterruptedException {
        byte[] bytes = new byte[10 * 1024];
        //将该对象被虚引用引用
        PhantomReference<Object> objectPhantomReference = new PhantomReference<Object>(bytes,queue);
        //这个一定返回null  因为实在接口定义中写死的
        System.out.println(objectPhantomReference.get());
        //此时jvm并没有进行对象的回收,该队列返回为空
        System.out.println(queue.poll());
        //手动释放该引用,将该引用置为无效引用
        bytes = null;
        //触发gc
        System.gc();
        //这里返回的还是null  接口定义中写死的
        System.out.println(objectPhantomReference.get());
        //垃圾回收后,被回收对象进入到引用队列
        System.out.println(queue.poll());
    }
}

基本了解了虚引用之后,我们再来看DirectByteBuffer对象,他在构造函数创建的时候引用看一个虚引用Cleaner!当这个DirectByteBuffer使用完毕后,DirectByteBuffer被JVM回收,触发Cleaner虚引用!JVM垃圾线程会将这个对象绑定到Reference对象中的pending属性中,程序启动后引用类Reference类会创建一条守护线程:

代码语言:txt
AI代码解释
复制
static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        //设置优先级为系统最高优先级
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();
		//.......................
    }

我们看一下该线程的定义:

代码语言:txt
AI代码解释
复制
static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {
                if (pending != null) {
                   //......忽略
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                    //队列中没有数据结阻塞  RefQueue入队逻辑中有NF操作,感兴趣可以自己去看下
                    if (waitForNotify) {
                        lock.wait();
                    }
                    // retry if waited
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            //发生OOM之后就让出线程的使用权,看能不能内部消化这个OOM
            Thread.yield();
            return true;
        } catch (InterruptedException x) {
            // 线程中断的话就直接返回
            return true;
        }

        // 这里是关键,如果虚引用是一个 cleaner对象,就直接进行清空操作,不在入队
        if (c != null) {
            //TODO 重点关注
            c.clean();
            return true;
        }
		//如果不是 cleaner对象,就将该引用入队
        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }

那我们此时就应该重点关注c.clean();方法了!

代码语言:txt
AI代码解释
复制
this.thunk.run();

重点关注这个,thunk是一个什么对象? 我们需要重新回到 DirectByteBuffer创建的时候,看看他传递的是什么。

代码语言:txt
AI代码解释
复制
 cleaner = Cleaner.create(this, new Deallocator(base, size, cap));

我们可以看到,传入的是一个 Deallocator对象,那么他所调用的run方法,我们看下逻辑:

代码语言:txt
AI代码解释
复制
public void run() {
    if (address == 0) {
        // Paranoia
        return;
    }
    //释放内存
    unsafe.freeMemory(address);
    address = 0;
    Bits.unreserveMemory(size, capacity);
}

重点关注unsafe.freeMemory(address);这个就是释放内存的!

至此,我们知道了JVM是如何管理堆外内存的了!

123.png
123.png

公众号:源码学徒

欢迎关注公众号,学习更多源码知识

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
腾讯云服务器使用教程
内容简介:本文介绍了如何使用腾讯云服务器模拟海wai IP 地址。 这种方法的优点是:价格低,按时计费,每小时不到1元钱;缺点是:有数据延迟(离的远的地方,可能会慢)。 目录
2023/01/04
11.3K0
【腾讯云的1001种玩法】云服务器搭建Python环境
钟志远
2017/03/02
23.5K5
【腾讯云的1001种玩法】云服务器搭建Python环境
【Linux】腾讯云服务器搭建环境
Linux是一种自由和开放源代码的类UNIX操作系统,该操作系统的内核由林纳斯托瓦兹在1991年首次发布,之后,在加上用户空间的应用程序之后,就成为了Linux操作系统。严格来讲,Linux只是操作系统内核本身,但通常采用“Linux核”来表达该意思。而Linux则常用来指基于Linux内核的完整操作系统,它包括GUI组件和许多其他实用工具。
平凡的人1
2022/11/15
15.1K0
【Linux】腾讯云服务器搭建环境
腾讯云服务器如何搭建远程桌面 电脑正式版才可实现远程
任何网络系统的搭建都需要建立在服务器的基础上,通过服务器完成系统的运作和基础数据的存储等,从而实现各项功能。而在服务器的选择上,更多的企业倾向于腾讯这样的大品牌。那么腾讯云服务器如何搭建远程桌面自然也是在进行系统搭建运作当中需要了解到的问题,因为如果经常需要外出的话,携带电脑会不方便。
用户8715145
2021/11/03
17.4K0
如何登陆云服务器?登录云服务器有哪些方式可以选择?
云服务器在近年来已经成为很多企业存放文件和日常办公的主要方式,使用云服务器具有维护简单文件更加安全更众多的优势。但对于很多刚刚接触云服务器的用户而言,可能还存在很多常识性问题上的不足,那么下面就来介绍一下如何登陆云服务器?登录云服务器有哪些方式可以选择呢?
用户8715145
2021/08/27
12.7K0
快速搭建云服务器
云服务器最大的优势在于它是按小时收费的,操作界面简单,你可以用这个云服务器来搭建各种服务,比如参考《WordPress搭建个人博客》搭建WordPress。这篇文章教你怎么快速搭建云服务器。
无代码Dev
2024/03/13
1170
快速搭建云服务器
腾讯云服务器配置选购教程【新手指南】
(2)操作系统统一,选择图中这个(方便统一维护,项目组统一用ubuntu),其他默认
用户2416682
2019/10/08
7.7K0
腾讯云服务器配置选购教程【新手指南】
如何连接云服务器 使用云服务器具有哪些好处
在互联网的推动下,各行各业已经衍生了多种产品与技术。云服务器的出现帮助人们解决了文件存储以及内存小等问题,但是云服务器是一种新鲜事物。人们还不是很熟悉它的使用方法,接下来一起了解下如何连接云服务器。
用户8715145
2021/08/27
21.6K0
手把手教你购买腾讯云服务器
腾讯云是腾讯公司旗下的云计算服务提供商,提供一系列基础设施和云服务,涵盖了计算、存储、数据库、人工智能、大数据分析、物联网等领域。腾讯云在全球范围内建立了多个数据中心,提供多地域、多可用区的服务支持,为用户提供高可靠性和稳定性的服务。它还提供灵活的付费模式和全面的技术支持,适应了各种规模和类型的用户需求。这里我们将手把手教你如何快速购买腾讯云服务器。
无代码Dev
2024/01/26
1.2K0
手把手教你购买腾讯云服务器
腾讯云服务器如何登录?腾讯云服务器的三种登录方法
WebShell 为腾讯云推荐的登录方式。无论您的本地系统为 Windows,Linux 或者 Mac OS,只要实例购买了公网 IP,都可以通过 WebShell 登录。本文介绍如何使用标准登录方式(WebShell)登录 Linux 实例。 WebShell 优点如下:
云小子
2022/04/24
85.2K0
腾讯云服务器如何登录?腾讯云服务器的三种登录方法
用腾讯云服务器快速搭建 Discuz! 论坛
Discuz!全称:Crossday Discuz! Board,是一套免费使用的社区论坛软件系统,由北京康盛新创科技有限责任公司推出,目前最新版本是Discuz! X3.4。自面世以来,Discuz!已拥有18年以上的应用历史和数百万网站用户案例,是全球成熟度最高、覆盖率最大的论坛软件系统之一。用户可以在不需要任何编程的基础上,通过简单的设置和安装,在互联网上搭建起具备完善功能、很强负载能力和可高度定制的论坛服务。Discuz!的基础架构采用世界上最流行的web编程组合PHP+MySQL实现,是一个经过完善设计,适用于各种服务器环境的高效论坛系统解决方案,无论在稳定性、负载能力、安全保障等方面都居于国内外同类产品领先地位。
用户6511152
2019/10/20
12.5K0
用腾讯云服务器快速搭建 Discuz! 论坛
云服务器注册教程
本文介绍了云服务器的优势,以及如何快速注册账户,并演示了如何创建的虚拟专用服务器(VPS)。
无代码Dev
2024/03/21
1720
云服务器注册教程
生物信息学入门- Xshell 和 Xftp 的安装 远程链接云服务器
https://www.aliyun.com/daily-act/ecs/activity_selection?source=5176.11533457&userCode=3enjgk6n
用户7010445
2024/05/18
1460
生物信息学入门- Xshell 和 Xftp 的安装 远程链接云服务器
云服务器怎么设置ftp密码呢?搭建云服务器花钱吗?
云服务器非常地简单高效,安全可靠,对很多中小型企业或者个人来说,使用云服务器是很不错的选择,不管是建站还是作为小程序后端,都是很不错的选择。再加上云服务器可以帮助中小型企业进行后期的扩容和升级,不需要在后期对软硬件进行相应的升级操作了,这样可以帮助中小型企业解决很多问题,所以云服务器的稳定性以及安全可靠性让云服务器越来越受欢迎。那么云服务器怎么设置域名解析?接下来会为大家简单说一下设置步骤。
用户8715145
2021/09/03
32.8K0
轻云服务器远程桌面怎样连接?轻云服务器有哪些优势?
近几年来轻云服务器技术变得越来越成熟和普及,这种技术产品在多个行业领域都发挥着作用,包括游戏娱乐、电子商务以及物流管理等,它具有便捷可靠以及简单安全等特点,轻云服务器远程桌面如何连接?轻云服务器的优势是什么?
用户8715145
2021/11/10
15.2K0
【玩转腾讯云】GPU云服务器(开发篇)
在上一篇中大家已经熟悉了GPU服务器创建及初始化步骤,那么接下来该如何具体远程开发?如何调试代码、同步数据呢?
溪歪歪
2020/04/05
31K4
【玩转腾讯云】GPU云服务器(开发篇)
零基础快速上手云服务器
  反正不花白不花,于是我又花了11块大洋买了俩月的服务器,送给了朋友,顺便写下了这篇教程。
小孙同学
2022/01/17
2.1K0
零基础快速上手云服务器
快速入门 Linux 云服务器
如果是首次购买和使用云服务器实例的个人用户,推荐按照本文介绍的流程快速配置、购买和连接实例。
云服务器教程
2019/04/10
3.7K0
快速入门 Linux 云服务器
腾讯云服务器创建 SSH 密钥及如何使用 提高 SSH 连接安全性
腾讯云服务器内置创建 SSH 密钥功能,创建并使用之后就是有密钥才能使用 SSH 连接服务器,相对于几位数的登陆密码来说,2048 位密钥显然更安全。
魏艾斯博客www.vpsss.net
2018/07/30
12K0
腾讯云服务器创建 SSH 密钥及如何使用 提高 SSH 连接安全性
将node项目部署到云服务器详细教程
这里我已经安装过Xshell了,无法演示安装完毕的提示内容。我就演示输入 bt default 的提示
CRMEB商城源码
2022/04/20
5.7K0
将node项目部署到云服务器详细教程
推荐阅读
相关推荐
腾讯云服务器使用教程
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文