Skynet服务器框架(一) Linux下的安装和启动

引言:

一直都是从事客户端的开发工作,最近抽了点时间想了解一下服务器开发的相关知识,一番博客瞎逛之后,发现了一个不错的框架,云风大神的 skynet开源服务器框架,这不仅仅是针对于游戏服务器开发的框架,更是一个通用的服务器基础框架。

Skynet简介:

Skynet 主要工作是管理注册服务,并开启多线程协调服务之间的调用和通讯。

1.框架核心:

根据云风博客的描述,Skynet 的核心功能就是解决一个问题:

把一个符合规范的 C 模块,从 动态库(so文件)中启动起来,绑定一个永不重复(即使模块退出)的数字id做为其 handle模块 被称为 服务(Service),服务间可以自由发送消息。

  • 每个 模块 可以向 Skynet 框架注册一个 callback 函数,用来接收发给它的消息;
  • 每个服务都是被一个个 消息包 驱动,当没有包到来的时候,它们就会处于 挂起状态,此状态对 CPU 资源零消耗。如果需要自主逻辑,则可以利用 Skynet 系统提供的 timeout消息,定期触发。

名字服务:
Skynet 提供了 名字服务,还可以给特定的服务起一个易读的名字,而不是用 id 来指代它。id 和运行时态相关,无法保证每次启动服务,都有一致的 id ,但名字可以。

简而言之,这个框架完成的功能大概如下:
Skynet 只负责把一个数据包从一个服务内发送出去,让同一进程内的另一个服务收到,调用对应的callback 函数处理。它保证,模块的初始化过程,每个独立的 callback 调用,都是 相互线程安全 的。编写服务的人不需要特别的为多线程环境考虑任何问题,专心处理发送给它的一个个数据包。


2.框架优点:
  • 高低级语言配合:
    Skynet 是一个融合了低级语言(C)消息框架和高级动态语言(lua)的混合体,这种结构称为 hybrid framework。选择运行高效的C来写服务节点,也可以选择同样开发高效而且安全隔离的lua来写上层业务。Skynet的主要核心包括两部分:

    • C语言 实现的消息循环和组件加载机制;
    • lua 实现的以消息为中心的进入退出 coroutine(协程)的包装层。
  • 组件化能力:
    Skynet 内核(C部分)自身支持加载模块(.so 文件),你可以使用C语言去写性能有要求的服务节点,通过消息与其他节点配合。lua又是一个对C语言极为友好的动态语言,所以你可以找到很多现成的lua的C扩展,skynet/3rd 路径下可以放置你需要的各种组件,比如:CJSONsqliteOpenSSL等。

3.单进程:

很多服务器框架在构建之初,就设想着用多进程的方式来解决高并发的问题,但是所带来的问题就是多进程不可避免的进程安全锁,这样的框架经常会因为部分代码的报错而导致死锁或者内存占用不释放等问题。很多优秀的服务器框架都是使用单进程,然后通过线程池来做消息轮询和任务执行的方式来实现的,这样能够避开锁所带来的诸多问题。

Skynet也是单进程的服务器框架,在单一进程上启动一个线程池,其中包括多个 worker 线程 、一个 socket 网络线程和一个 timer 时间线程。当创建了多个 lua服务,每个服务都相当于Erlang中的一个 Actor (可以简单理解为:可以并行运行的对象),每个服务都有自己的消息队列,skynet也有一个全局的消息队列,线程池中的 worker 线程会随机从消息队列中取出消息来执行直到消息队列为空。此外,每个 lua服务 中又可以通过启动多个 coroutine (携程)来实现异步操作的目的。


Skynet下载配置:

要学习开源框架,第一步肯定是先拿来试用一下,然后再取剖析源码,接下来我们就尝试下载Skynet框架,并尝试使用这套开源的框架来搭建一个测试服务器:

1.资源下载:

Github源码地址:cloudwu/skynet
假如当前是在Linux环境下,并已经安装有 git 工具,则可以直接使用 git 指令 git clone 来拉取Github仓库的 Skynet 最新源码:

sudo git clone https://github.com/cloudwu/skynet.git

假如执行正常,输出如下:

linsh@ubuntu:/mnt/Windows$ sudo git clone https://github.com/cloudwu/skynet.git
正克隆到 'skynet'...
remote: Counting objects: 8079, done.
remote: Compressing objects: 100% (22/22), done.
remote: Total 8079 (delta 1), reused 0 (delta 0), pack-reused 8057
接收对象中: 100% (8079/8079), 2.72 MiB | 24.00 KiB/s, done.
处理 delta 中: 100% (5442/5442), done.
检查连接... 完成。

假如还没安装git工具,可以通过以下指令安装(我的操作系统是 Ubuntu14.04.4):

sudo apt-get install git
2.源码目录结构:

关于源码主要目录及其作用如下:

skynet-master
--3rd           //第三方代码,主要生产一些给lua用的so动态库
--example       //示例工程
--lualib        //lua库
--lualib-src    //luaclib:给lua用的c库
--service       //lua服务
--service-src   //csservice:c服务
--skynet-src    //skynet核心c源码主程序
--test          //一些类库和接口调用的客户端用例
--HISTORY.md    //版本更新日志
--LICENSE
--Makefile      //编译脚本
--platform.mk   //运行平台相关(支持Linux、MacOSX、freebsd操作系统)
3.源码编译:
  • 工具安装:
    在编译前还需要安装两个工具,不然会出现报错:

    • 安装autoconf

      sudo apt-get install autoconf

      否则会报如下错误:

      cd 3rd/jemalloc && ./autogen.sh --with-jemalloc-prefix=je_ --disable-valgrind
      autoconf
      ./autogen.sh: line 5: autoconf: command not found
      Error 0 in autoconf
      make[1]: *** [3rd/jemalloc/Makefile] Error 1
      make[1]: Leaving directory `/data/skynet'
      make: *** [linux] Error 2
    • 安装readline-devel

      sudo apt-get install libreadline-dev

      否则会报如下错误:

      lua.c:83:31: fatal error: readline/readline.h: 没有那个文件或目录
      
      #include <readline/readline.h>
      
                                 ^
      compilation terminated.
      make[3]: *** [lua.o] 错误 1
      make[3]:正在离开目录 `/application/skynet/3rd/lua'
      make[2]: *** [linux] 错误 2
      make[2]:正在离开目录 `/application/skynet/3rd/lua'
      make[1]: *** [3rd/lua/liblua.a] 错误 2
      make[1]:正在离开目录 `/application/skynet'
      make: *** [linux] 错误 2
  • 编译操作:
    由于下载的是源码,需要进过编译才能运行,编译过程就是:

    • 进入 clone 到本地的项目目录,执行make指令编译源码:

      cd skynet
      sudo make linux

      假如编译过程正常,编译完成后如下:

      linsh@ubuntu:/application/skynet$ sudo make linux
      make all PLAT=linux SKYNET_LIBS="-lpthread -lm -ldl -lrt" SHARED="-fPIC --shared" EXPORT="-Wl,-E" MALLOC_STATICLIB="3rd/jemalloc/lib/libjemalloc_pic.a" SKYNET_DEFINES=""
      make[1]: 正在进入目录 `/application/skynet'
      cc -g -O2 -Wall -I3rd/lua  -o skynet skynet-src/skynet_main.c skynet-src/skynet_handle.c skynet-src/skynet_module.c skynet-src/skynet_mq.c skynet-src/skynet_server.c skynet-src/skynet_start.c skynet-src/skynet_timer.c skynet-src/skynet_error.c skynet-src/skynet_harbor.c skynet-src/skynet_env.c skynet-src/skynet_monitor.c skynet-src/skynet_socket.c skynet-src/socket_server.c skynet-src/malloc_hook.c skynet-src/skynet_daemon.c skynet-src/skynet_log.c 3rd/lua/liblua.a 3rd/jemalloc/lib/libjemalloc_pic.a -Iskynet-src -I3rd/jemalloc/include/jemalloc  -Wl,-E -lpthread -lm -ldl -lrt 
      mkdir cservice
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared service-src/service_snlua.c -o cservice/snlua.so -Iskynet-src
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared service-src/service_logger.c -o cservice/logger.so -Iskynet-src
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared service-src/service_gate.c -o cservice/gate.so -Iskynet-src
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared service-src/service_harbor.c -o cservice/harbor.so -Iskynet-src
      mkdir luaclib
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared lualib-src/lua-skynet.c lualib-src/lua-seri.c -o luaclib/skynet.so -Iskynet-src -Iservice-src -Ilualib-src
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared lualib-src/lua-socket.c -o luaclib/socketdriver.so -Iskynet-src -Iservice-src
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared -Iskynet-src lualib-src/lua-bson.c -o luaclib/bson.so -Iskynet-src
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared lualib-src/lua-mongo.c -o luaclib/mongo.so -Iskynet-src
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared -I3rd/lua-md5 3rd/lua-md5/md5.c 3rd/lua-md5/md5lib.c 3rd/lua-md5/compat-5.2.c -o luaclib/md5.so 
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared lualib-src/lua-netpack.c -Iskynet-src -o luaclib/netpack.so 
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared lualib-src/lua-clientsocket.c -o luaclib/clientsocket.so -lpthread
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared -Iskynet-src lualib-src/lua-memory.c -o luaclib/memory.so 
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared lualib-src/lua-profile.c -o luaclib/profile.so 
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared -Iskynet-src lualib-src/lua-multicast.c -o luaclib/multicast.so 
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared -Iskynet-src lualib-src/lua-cluster.c -o luaclib/cluster.so 
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared lualib-src/lua-crypt.c lualib-src/lsha1.c -o luaclib/crypt.so 
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared -Iskynet-src lualib-src/lua-sharedata.c -o luaclib/sharedata.so 
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared -Iskynet-src lualib-src/lua-stm.c -o luaclib/stm.so 
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared -Ilualib-src/sproto lualib-src/sproto/sproto.c lualib-src/sproto/lsproto.c -o luaclib/sproto.so 
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared -I3rd/lpeg 3rd/lpeg/lpcap.c 3rd/lpeg/lpcode.c 3rd/lpeg/lpprint.c 3rd/lpeg/lptree.c 3rd/lpeg/lpvm.c -o luaclib/lpeg.so 
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared lualib-src/lua-mysqlaux.c -o luaclib/mysqlaux.so    
      cc -g -O2 -Wall -I3rd/lua  -fPIC --shared -Iskynet-src lualib-src/lua-debugchannel.c -o luaclib/debugchannel.so    
      make[1]:正在离开目录 `/application/skynet'
4.错误集:
  • 错误一:
    由于系统时间设置错误,导致编译进入死循环,报错如下:

    make[2]: *** 警告:文件“Makefile.in”的修改时间在将来1.8e+06

    解决方案:

    • 假如当前系统时间是错误的,修正系统时间即可;
    • 假如是文件时间戳有误,可以使用 ind ./* -exec touch {} + 修正文件的时间戳。
  • 错误二:
    在虚拟机的共享目录下安装 skynet,由于虚拟机共享目录不能设置软连接:

    ln -sf libjemalloc.so.2 lib/libjemalloc.so
    ln: 无法创建符号链接"lib/libjemalloc.so": 不支持的操作
    make[2]: *** [lib/libjemalloc.so] 错误 1
    make[2]:正在离开目录 `/mnt/Windows/skynet/3rd/jemalloc'
    make[1]: *** [3rd/jemalloc/lib/libjemalloc_pic.a] 错误 2
    make[1]:正在离开目录 `/mnt/Windows/skynet'
    make: *** [linux] 错误 2

    解决方案:
    选择其他非共享的目录进行安装,例如直接复制:

    sudo cp -r skynet /application/

启动流程:

skynet 由一个或多个进程构成,每个进程被称为一个 skynet 节点。接下来尝试实现 skynet 节点 的启动流程。

1.配置文件Config

上面完成了源码编译,但是运行启动指令的时候,需要传入一个 Config文件 名称作为启动参数,skynet 会读取这个 Config文件 获取一些启动的必要参数,所以在运行程序之前,还需要根据要求修改配置文件,可以参考 example/config 或直接对其进行修改:

root = "./"
thread = 8
logger = nil
harbor = 1
address = "127.0.0.1:2526"
master = "127.0.0.1:2013"
start = "main"  -- main script
bootstrap = "snlua bootstrap"   -- The service for bootstrap
standalone = "0.0.0.0:2013"
luaservice = root.."service/?.lua;"..root.."test/?.lua;"..root.."examples/?.lua"
lualoader = "lualib/loader.lua"
snax = root.."examples/?.lua;"..root.."test/?.lua"
cpath = root.."cservice/?.so"

不难看出,这个配置文件内存其实是一个lua代码,以 key-value 形式进行赋值,skynet 启动时读取必要配置项,其他项即便用不到也会以字符串的形式存入 env 表中,所有配置项都可通过 skynet.getenv 获取。

  • 必要的配置项有:

    • thread 启动多少个工作线程。通常不要将它配置超过你实际拥有的 CPU 核心数。
    • bootstrap skynet 启动的第一个服务以及其启动参数。默认配置为 snlua bootstrap ,即启动一个名为 bootstrap 的 lua 服务。通常指的是 service/bootstrap.lua 这段代码。
    • cpath 用 C 编写的服务模块的位置,通常指 cservice 下那些 .so 文件。如果你的系统的动态库不是以 .so 为后缀,需要做相应的修改。这个路径可以配置多项,以 ; 分割。
  • 在默认的 bootstrap 代码中还会进一步用到一些配置项:

    • logger 它决定了 skynet 内建的 skynet_error 这个 C API 将信息输出到什么文件中。如果 logger 配置为 nil ,将输出到标准输出。你可以配置一个文件名来将信息记录在特定文件中。
    • logservice 默认为 "logger" ,你可以配置为你定制的 log 服务(比如加上时间戳等更多信息)。可以参考 service_logger.c 来实现它。注:如果你希望用 lua 来编写这个服务,可以在这里填写 snlua ,然后在 logger 配置具体的 lua 服务的名字。在 examples 目录下,有 config.userlog 这个范例可供参考。
    • logpath 配置一个路径,当你运行时为一个服务打开 log 时,这个服务所有的输入消息都会被记录在这个目录下,文件名为服务地址。
      standalone 如果把这个 skynet 进程作为主进程启动(skynet 可以由分布在多台机器上的多个进程构成网络),那么需要配置 standalone 这一项,表示这个进程是主节点,它需要开启一个控制中心,监听一个端口,让其它节点接入。
    • master 指定 skynet 控制中心的地址和端口,如果你配置了 standalone 项,那么这一项通常和 standalone 相同。
    • address 当前 skynet 节点的地址和端口,方便其它节点和它组网。注:即使你只使用一个节点,也需要开启控制中心,并额外配置这个节点的地址和端口。
    • harbor 可以是 1-255 间的任意整数。一个 skynet 网络最多支持 255 个节点。每个节点有必须有一个唯一的编号。
      如果 harbor 为 0 ,skynet 工作在单节点模式下。此时 masteraddress 以及 standalone 都不必设置。
    • start 这是 bootstrap 最后一个环节将启动的 lua 服务,也就是你定制的 skynet 节点的主程序。默认为 main ,即启动 main.lua 这个脚本。这个 lua 服务的路径由下面的 luaservice 指定。
  • 集群服务用到的配置项:

    • cluster 它决定了集群配置文件的路径。
  • lua 服务由 snlua 提供,它会查找一些配置项以加载 lua 代码:

    • lualoader 用哪一段 lua 代码加载 lua 服务。通常配置为 lualib/loader.lua ,再由这段代码解析服务名称,进一步加载 lua 代码。snlua 会将下面几个配置项取出,放在初始化好的 lua 虚拟机的全局变量中。具体可参考实现。
    • SERVICE_NAME 第一个参数,通常是服务名。
    • LUA_PATH config 文件中配置的 lua_path
    • LUA_CPATH config 文件中配置的 lua_cpath
    • LUA_PRELOAD config 文件中配置的 preload
    • LUA_SERVICE config 文件中配置的 luaservice
    • luaservice lua 服务代码所在的位置。可以配置多项,以 ; 分割。 如果在创建 lua 服务时,以一个目录而不是单个文件提供,最终找到的路径还会被添加到 package.path 中。比如,在编写 lua 服务时,有时候会希望把该服务用到的库也放到同一个目录下。
    • lua_path 将添加到 package.path 中的路径,供 require 调用。
    • lua_cpath 将添加到 package.cpath 中的路径,供 require 调用。
    • preload 在设置完 package 中的路径后,加载 lua 服务代码前,loader 会尝试先运行一个 preload 制定的脚本,默认为空。
    • snaxsnax 框架编写的服务的查找路径。
    • profile 默认为 true, 可以用来统计每个服务使用了多少 cpu 时间。在 DebugConsole 中可以查看。会对性能造成微弱的影响,设置为 false 可以关闭这个统计。

    另外,你也可以把一些配置选项配置在环境变量中。比如,你可以把 thread 配置在 SKYNET_THREAD 这个环境变量里。你可以在 config 文件中写:

    thread=$SKYNET_THREAD

    这样,在 skynet 启动时,就会用 SKYNET_THREAD 这个环境变量的值替换掉 config 中的 $SKYNET_THREAD 了。

2.启动Skynet服务:

编译完成后,查询根目录的文件列表,发现生成了一个 skynet 可执行文件:

linsh@ubuntu:/application/skynet$ ls 
3rd       HISTORY.md  lualib      platform.mk  service-src  test
cservice  LICENSE     lualib-src  README.md    skynet
examples  luaclib     Makefile    service      skynet-src

在skynet的根目录运行以下指令 ./skynet examples/config 启动skynet服务:

linsh@ubuntu:/application/skynet$ ./skynet examples/config
[:01000001] LAUNCH logger 
[:01000002] LAUNCH snlua bootstrap
[:01000003] LAUNCH snlua launcher
[:01000004] LAUNCH snlua cmaster
[:01000004] master listen socket 0.0.0.0:2013
[:01000005] LAUNCH snlua cslave
[:01000005] slave connect to master 127.0.0.1:2013
[:01000004] connect from 127.0.0.1:34760 4
[:01000006] LAUNCH harbor 1 16777221
[:01000004] Harbor 1 (fd=4) report 127.0.0.1:2526
[:01000005] Waiting for 0 harbors
[:01000005] Shakehand ready
[:01000007] LAUNCH snlua datacenterd
[:01000008] LAUNCH snlua service_mgr
[:01000009] LAUNCH snlua main
[:01000009] Server start
[:0100000a] LAUNCH snlua protoloader
[:0100000b] LAUNCH snlua console
[:0100000c] LAUNCH snlua debug_console 8000
[:0100000c] Start debug console at 127.0.0.1:8000
[:0100000d] LAUNCH snlua simpledb
[:0100000e] LAUNCH snlua watchdog
[:0100000f] LAUNCH snlua gate
[:0100000f] Listen on 0.0.0.0:8888
[:01000009] Watchdog listen on 8888
[:01000009] KILL self
[:01000002] KILL self

其他资料:

参考:

  • 8
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 《Skynet框架教程-非常详细.pdf》是一本关于Skynet框架的教程,它提供了关于Skynet框架的全面和详细的介绍。 该教程首先介绍了Skynet框架的背景和起源,以及它的特点和优势。Skynet是一个高度可扩展的分布式服务框架,具有高性能和低延迟的特点。它采用了基于actor模型的并发编程模型,并提供了丰富的工具和库来帮助开发者构建和管理分布式应用。 教程的下一部分介绍了Skynet框架的基本概念和架构。它解释了Skynet节点、服务和消息传递等核心概念,并提供了示例代码来说明这些概念的使用方法。读者可以通过这一部分了解Skynet框架的基本原理和用法。 接下来的章节详细介绍了Skynet框架的各个组件和功能。其中包括服务注册与发现、负载均衡、容错机制、监控和调试等方面。每个组件和功能都有详细的说明和示例代码,读者可以通过实践来学习和理解。 教程的最后一部分是一些实际应用案例的介绍。这些案例涵盖了不同领域和规模的应用,包括游戏服务器、在线教育平台、电子商务网站等。每个案例都详细介绍了Skynet框架在该应用中的具体应用和实现过程,对于读者来说是一个很好的参考和借鉴资料。 总之,《Skynet框架教程-非常详细.pdf》是一本很好的Skynet框架学习资料,它提供了全面而详细的内容,涵盖了Skynet框架的各个方面。无论是初学者还是有一定经验的开发者,都可以通过这本教程来学习和掌握Skynet框架的使用和开发技巧。 ### 回答2: 《Skynet框架教程-非常详细.pdf》是一本关于Skynet框架的详细教程文档,其中包含了关于Skynet框架的基本概念、核心功能、使用方法等内容。 Skynet框架是一个高性能、轻量级的分布式服务框架,适用于开发网络游戏、实时通信等高并发场景。该框架基于事件驱动模型,通过异步消息传递和多线程技术实现高并发处理能力。 在《Skynet框架教程-非常详细.pdf》中,首先介绍了Skynet框架的背景与发展历程,帮助读者了解该框架的起源和应用领域。接着详细介绍了Skynet框架的核心架构,包括节点管理、服务管理、消息传递等模块的设计与实现原理。 教程还详细介绍了Skynet框架安装和配置,包括环境准备、编译与安装等步骤。然后,通过一系列实际案例演示了如何使用Skynet框架进行开发,包括创建节点、注册服务、消息处理、资源管理等方面的内容。 此外,教程还介绍了Skynet框架的调试和优化技巧,包括日志查看、性能测试工具的使用等方面的内容。最后,给出了一些常见问题的解答,帮助读者更好地解决在使用Skynet框架过程中遇到的困惑。 总的来说,《Skynet框架教程-非常详细.pdf》是一本适合初学者和有一定经验的开发人员的教程,通过阅读该教程可以深入了解Skynet框架的原理和使用方法,从而更好地应用于实际项目中。 ### 回答3: 《Skynet框架教程-非常详细.pdf》是一本非常详细的Skynet框架教程。Skynet框架是一个高性能、高可靠性的分布式服务框架,用于构建可扩展的游戏服务器、物联网平台等分布式应用。 这本教程从Skynet框架的基础知识讲起,介绍了Skynet框架的特点、架构和设计理念。然后详细介绍了Skynet框架安装和配置,包括环境准备、编译安装启动流程等。 接下来,教程深入讲解了Skynet框架的核心概念和基本用法,包括服务、消息、协议等。这些内容帮助读者理解Skynet框架的工作原理,并能够快速上手开发。 教程还介绍了Skynet框架的高级特性和扩展功能,如集群部署、负载均衡、动态扩容等。这些内容使读者能够在实际应用中解决复杂的问题,并提升系统的性能和可扩展性。 此外,教程还提供了大量的示例和实战案例,帮助读者将理论应用到实际项目中。通过这些实例,读者可以学习到如何使用Skynet框架构建真实的分布式应用,同时也能够了解到Skynet框架的一些最佳实践和常见错误。 综上所述,《Skynet框架教程-非常详细.pdf》是一本非常全面的Skynet框架教程,适合初学者和有一定经验的开发者阅读,对于了解Skynet框架的原理和使用方法都有很大帮助。无论是想要学习Skynet框架的基础知识,还是进一步提升Skynet框架的应用技巧,这本教程都是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值