热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

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

引言:

一直都是从事客户端的开发工作,最近抽了点时间想了解一下服务器开发的相关知识,一番博客瞎逛之后,发现了一个不错的框架,云风大神的 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

      ^
      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

其他资料:

  • 云风对Skynet的介绍视频:
    云风:基于 Actor 模式的开源框架

参考:

  • Skynet 设计综述
  • 云风skynet服务端框架研究
  • 云风的 BLOG: Skynet 开源
  • 云风的 BLOG: Skynet 集群及 RPC
  • skynet/wiki
    • GettingStarted
    • Config

推荐阅读
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • redis知识汇总[随笔记录]
      ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • MySQL数据库锁机制及其应用(数据库锁的概念)
    本文介绍了MySQL数据库锁机制及其应用。数据库锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,数据是一种供许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决的问题。MySQL的锁机制相对简单,不同的存储引擎支持不同的锁机制,主要包括表级锁、行级锁和页面锁。本文详细介绍了MySQL表级锁的锁模式和特点,以及行级锁和页面锁的特点和应用场景。同时还讨论了锁冲突对数据库并发访问性能的影响。 ... [详细]
  • 如何方便地退订邮件列表,避免混乱和烦恼
    本文介绍了如何方便地退订邮件列表,避免混乱和烦恼。文章指出,退订邮件列表可能会造成混乱,特别是当被意外添加到列表中时。为了快速、轻松地取消订阅,建议不要将退订电子邮件发送到用于发布消息的电子邮件地址。文章还介绍了邮件列表由邮件列表软件控制,作为邮件列表成员,可以对该软件进行一些用户控制。一些邮件列表允许使用自动电子邮件地址退订,但这可能会带来一些混乱。最后,文章提到退订邮件列表需要向电子邮件服务器发送特殊命令来脱离列表。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • varbrowser{versions:function(){varunavigator.userAgent,appnavigator.appVersion;retur ... [详细]
  • php还能用多少年(php还行吗)
    导读:很多朋友问到关于php还能用多少年的相关问题,本文编程笔记就来为大家做个详细解答,供大家参考,希望对大家有所帮助!一起来看看吧!本文目录一览: ... [详细]
  • nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
author-avatar
小伊果果_679
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有