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

goenv

命令goenv用于打印Go语言的环境信息。其中的一些信息我们在之前已经多次提及,但是却没有进行详细的说明。在本小节,我们会对这些信息进行深入介绍。我们先

命令go env用于打印Go语言的环境信息。其中的一些信息我们在之前已经多次提及,但是却没有进行详细的说明。在本小节,我们会对这些信息进行深入介绍。我们先来看一看go env命令情况下都会打印出哪些Go语言通用环境信息。

go env命令可打印出的Go语言通用环境信息

> runtime 包 包含与 Go 的运行时系统交互的操作,例如控制 goroutines 的函数。
> 也包含 reflect 包使用的低级别的类型信息;查看 reflect 的文档了解运行时类型的可编程接口。
> 以下环境变量($name 或 %name% 取决于主机操作系统)控制 Go 程序的运行时行为,其含义和用途可能会随版本发布而变化

GOGC

GOGC变量设置初始垃圾收集目标百分比。

当新分配的数据与上一次收集后剩余的活动数据的比率达到此百分比时,将触发收集。

默认值为GOGC = 100

设置GOGC = off会完全禁用垃圾收集器。

runtime/debug包的SetGCPercent函数允许在运行时更改此百分比。


GODEBUG

GODEBUG变量控制运行时内的调试变量。

它是以逗号分隔的name = val对列表,用于设置这些命名变量:

allocfreetrace:设置 `allocfreetrace = 1` 会导致对每个对象的分配和释放进行概要分析和栈跟踪。clobberfree:设置 `clobberfree = 1` 使垃圾回收器在释放对象时用错误内容破坏对象的内存内容。cgocheck:设置 `cgocheck = 0` 禁用使用 cgo 将 Go 指针错误传递到非 Go 代码的所有包检查。设置 `cgocheck = 1`(默认值)可以启用相对便宜的检查,这些检查可能会遗漏一些错误。设置 `cgocheck = 2` 启用昂贵的检查,这些检查不会遗漏任何错误,但是会导致程序运行速度变慢。efence:设置 `efence = 1` 会导致分配器以某种模式运行,在该模式下每个对象都分配在一个唯一的页面上,地址永远不会被回收。gccheckmark:设置 `gccheckmark = 1` 可以通过在 STW 时执行第二次标记传递来验证垃圾收集器的并发标记阶段。
如果第二次传递找到了一个并发标记未找到的可达对象,垃圾收集器将 panic。gcpacertrace:设置 `gcpacertrace = 1` 会导致垃圾收集器打印有关并发 pacer 内部状态的信息。gcshrinkstackoff:设置 `gcshrinkstackoff = 1` 禁止将 goroutines 移动到较小的栈上。
在这种模式下,goroutine 的栈只能增长。gcstoptheworld:设置 `gcstoptheworld = 1` 禁用并发垃圾收集,使每个垃圾收集成为一个 STW 事件。设置 `gcstoptheworld = 2` 还会在垃圾收集完成后禁用并发清除。

gctrace:设置 `gctrace = 1` 会导致垃圾收集器在每次收集时向标准错误输出发出一行,
汇总收集的内存量和暂停的长度。此行的格式可能会发生变化。目前格式是:gc # @#s #%: #+#+# ms clock, #+#/#/#+# ms cpu, #->#-># MB, # MB goal, # P其中字段含义如下('#' 代表数字):gc # 每次 GC 时递增的 GC 编号@#s 程序运行的秒数#% 自程序启动后在 GC 中花费的时间百分比#+...+# GC 各阶段的挂钟时间(wall-clock)/CPU 时间#->#-># MB GC 开始时的堆大小、GC 结束时的堆大小和活动堆大小# MB goal 目标堆大小# P 使用的处理器数量这些阶段是 stop-the-world(STW)清除终止(sweep termination),
并发标记和扫描,以及 STW 标记终止(mark termination)。`mark/scan` 的 CPU 时间被分解为辅助时间(根据分配执行的 GC)、后台 GC 时间和空闲 GC 时间。如果该行以 `"(forced)"` 结尾,则此 GC 由 `runtime.GC()` 调用强制执行。

挂钟时间:

根据计算机的内部时钟流逝的时间,这应该与外界的时间相匹配。

这与 CPU 使用率无关; 它仅供参考。

如果挂钟时间

如果挂钟时间 > CPU时间,则表示您正在等待磁盘,网络或其他设备。

将 `gctrace` 设置为任何大于 0 的值也会导致垃圾收集器在将内存释放回系统时发出摘要。
将内存返回到系统的这个过程称为清除(`scavenging`)。
此摘要的格式可能会更改。目前格式是:scvg#: # MB released printed only if non-zeroscvg#: inuse: # idle: # sys: # released: # consumed: # (MB)其中字段含义如下('#' 代表数字):scvg# 清除周期数,每次清除时递增inuse: # MB 已使用或部分使用的 spansidle: # MB spans 待清除sys: # 从系统映射的 MBreleased: # 释放到系统的 MBconsumed: # 从系统中分配的 MB

madvdontneed:设置 madvdontneed = 1 当将内存返回到内核时,将在 Linux 上使用 MADV_DONTNEED 而不是 MADV_FREE。这效率较低,但会导致 RSS(resident set size 常驻内存集)数量下降得更快。

madvise()  系统调用允许一个了解其内存行为的进程将其描述给系统,给予使用内存的建议。

int
madvise(void *addr, size_t len, int advice);

系统可以使用传入的建议来更改其虚拟内存分页策略。

此建议可以提高应用程序和系统性能。建议有很多种其中两种:

MADV_DONTNEED:
表示应用程序不希望很快访问此地址范围。

MADV_FREE:
表示应用程序不需要此地址范围中包含的信息,因此可以立即重用这些页面。
地址范围仍然有效。

memprofilerate:设置 `memprofilerate = X` 将更新 runtime.MemProfileRate 的值。
设置为 0 时,禁用内存分析。MemProfileRate 控制在内存概要文件中记录和报告的内存分配比例。
分析器的目标是对每 `MemProfileRate` 字节的平均分配进行抽样。
要在概要文件中包含每个已分配的块,请将 `MemProfileRate` 设置为 1。
要完全关闭分析,请将 `MemProfileRate` 设置为 0。处理内存概要文件的工具假设概要文件速率在程序的整个生命周期中是恒定的,并且等于当前值。更改内存分析速率的程序应该只改变一次,在程序执行过程中越早越好(例如,在 `main` 的开头)。var MemProfileRate int = 512 * 1024


invalidptr:默认 `invalidptr = 1`,如果在指针类型的位置中发现无效的指针值(例如,1),
则会导致垃圾收集器和栈复制器使程序 crash。设置 `invalidptr = 0` 将禁用此检查。这应该只被用作诊断错误代码的临时解决方案。真正的解决方案是不将整数存储在指针类型的位置。

sbrk:设置 `sbrk = 1` 用一个简单的分配器替换内存分配器和垃圾收集器,
该分配器从操作系统获取内存并且永远不会回收任何内存。


scavenge:`scavenge = 1` 启用堆清除程序的调试模式。


scheddetail:设置 `schedtrace = X` 和 `scheddetail = 1` 会导致调度程序每 `X` 毫秒发出一次详细的多行信息,
描述`调度程序`,`处理器`,`线程` 和 `goroutines` 的状态。schedtrace:设置 `schedtrace = X` 使调度程序每 `X` 毫秒发出一行标准错误,汇总调度程序状态。


tracebackancestors:设置 `tracebackancestors = N` 使用创建 `goroutines` 的栈扩展回溯,
其中 `N` 限制要报告的祖先 `goroutines` 的数量。这也扩展了 `runtime.Stack` 返回的信息。祖先的 `goroutine IDs` 将引用创建时 goroutine 的 ID;
这个 ID 有可能被重用于另一个 goroutine。将 `N` 设置为 0 将不报告任何祖先信息。

net,net/httpcrypto/tls 也引用GODEBUG中的调试变量


GOMAXPROCS

GOMAXPROCS变量限制了可以同时执行用户级 Go 代码的操作系统线程数
(即,可以同时执行的最大CPU数)。

代表 Go 代码在系统调用中可以阻塞的线程数没有限制;那些不计入GOMAXPROCS限制。

该包的GOMAXPROCS函数查询并更改限制。


GOTRACEBACK

GOTRACEBACK变量控制 Go 程序因未恢复的panic或意外的运行时条件而失败时生成的输出量。

默认情况下,失败会打印当前goroutine的栈跟踪,省略运行时系统内部的函数,然后使用退出代码2退出。

如果当前goroutine或者故障都不在运行时内部,则故障会打印所有goroutines的栈跟踪。


  • GOTRACEBACK = none完全省略了 goroutine 栈跟踪。

  • GOTRACEBACK = single(默认值)的行为如上所述。

  • GOTRACEBACK = all为用户创建的所有 goroutines 添加栈跟踪。

  • GOTRACEBACK=system就像"all",但为运行时函数添加了栈帧,并显示了运行时内部创建的 goroutine。

  • GOTRACEBACK=crash就像"system",但是以特定于操作系统的方式 crash 而不是退出。例如,在Unix系统上,crash引发SIGABRT以触发核心转储(core dump)。

由于历史原因,GOTRACEBACK设置0,12分别是none,allsystem的同义词。

runtime/debug包的SetTraceback函数允许在运行时增加输出量,但不能减少到低于环境变量指定的输出量


CGO_ENABLED

通过上一小节的介绍,相信读者对cgo工具已经很熟悉了。我们提到过,标准go命令可以自动的使用cgo工具对导入了代码包C的代码包和源码文件进行处理。这里所说的“自动”并不是绝对的。因为当环境变量CGO_ENABLED被设置为0时,标准go命令就不能处理导入了代码包C的代码包和源码文件了。请看下面的示例:

hc@ubt:~/golang/goc2p/src/basic/cgo$ export CGO_ENABLED=0
hc@ubt:~/golang/goc2p/src/basic/cgo$ go build -x
WORK=/tmp/go-build775234613

我们临时把环境变量CGO_ENABLED的值设置为0,然后执行go build命令并加入了标记-x。标记-x会让命令程序将运行期间所有实际执行的命令都打印到标准输出。但是,在执行命令之后没有任何命令被打印出来。这说明对代码包basic/cgo的构建操作并没有被执行。这是因为,构建这个代码包需要用到cgo工具,但cgo工具已经被禁用了。下面,我们再来运行调用了代码包basic/cgo中函数的命令源码文件cgo_demo.go。也就是说,命令源码文件cgo_demo.go间接的导入了代码包C。还记得吗?这个命令源码文件被存放在goc2p项目的代码包basic/cgo中。示例如下:

hc@ubt:~/golang/goc2p/src/basic/cgo$ export CGO_ENABLED=0
hc@ubt:~/golang/goc2p/src/basic/cgo$ go run -work cgo_demo.go
WORK=/tmp/go-build856581210
# command-line-arguments
./cgo_demo.go:4: can't find import: "basic/cgo/lib"

在上面的示例中,我们在执行go run命令时加入了两个标记——-a-work。标记-a会使命令程序强行重新构建所有的代码包(包括涉及到的标准库),即使它们已经是最新的了。标记-work会使命令程序将临时工作目录的绝对路径打印到标准输出。命令程序输出的错误信息显示,命令程序没有找到代码包basic/cgo。其原因是由于代码包basic/cgo无法被构建。所以,命令程序在临时工作目录和工作区中都找不到代码包basic/cgo对应的归档文件cgo.a。如果我们使用命令ll /tmp/go-build856581210查看临时工作目录,也找不到名为basic的目录。

不过,如果我们在环境变量CGO_ENABLED的值为1的情况下生成代码包basic/cgo对应的归档文件cgo.a,那么无论我们之后怎样改变环境变量CGO_ENABLED的值也都可以正确的运行命令源码文件cgo_demo.go。即使我们在执行go run命令时加入标记-a也是如此。因为命令程序依然可以在工作区中找到之前在我们执行go install命令时生成的归档文件cgo.a。示例如下:

hc@ubt:~/golang/goc2p/src/basic/cgo$ export CGO_ENABLED=1
hc@ubt:~/golang/goc2p/src/basic/cgo$ go install ../basic/cgo
hc@ubt:~/golang/goc2p/src/basic/cgo$ export CGO_ENABLED=0
hc@ubt:~/golang/goc2p/src/basic/cgo$ go run -a -work cgo_demo.go
WORK=/tmp/go-build130612063
The square root of 2.330000 is 1.526434.
ABC
CFunction1() is called.
GoFunction1() is called.

由此可知,只要我们事先成功安装了引用了代码包C的代码包,即生成了对应的代码包归档文件,即使cgo工具在之后被禁用,也不会影响到其它Go语言代码对该代码包的使用。当然,命令程序首先会到临时工作目录中寻找需要的代码包归档文件。

关于cgo工具还有一点需要特别注意,即:当存在交叉编译的情况时,cgo工具一定是不可用的。在标准go命令的上下文环境中,交叉编译意味着程序构建环境的目标计算架构的标识与程序运行环境的目标计算架构的标识不同,或者程序构建环境的目标操作系统的标识与程序运行环境的目标操作系统的标识不同。在这里,我们可以粗略认为交叉编译就是在当前的计算架构和操作系统下编译和构建Go语言代码并生成针对于其他计算架构或/和操作系统的编译结果文件和可执行文件。


GOARCH

GOARCH的值的含义是程序构建环境的目标计算架构的标识,也就是程序在构建或安装时所对应的计算架构的名称。在默认情况下,它会与程序运行环境的目标计算架构一致。即它的值会与GOHOSTARCH的值是相同。但如果我们显式的设置了环境变量GOARCH,则它的值就会是这个环境变量的值。


GOBIN

GOBIN的值为存放可执行文件的目录的绝对路径。它的值来自环境变量GOBIN。在我们使用go tool install命令安装命令源码文件时生成的可执行文件会存放于这个目录中。


GOCHAR

GOCHAR的值是程序构建环境的目标计算架构的单字符标识。它的值会根据GOARCH的值来设置。当GOARCH的值为386时,GOCHAR的值就是8。当GOARCH的值为amd64时GOCHAR的值就是6。而GOCHAR的值5与GOARCH的值arm相对应。

GOCHAR主要有两个用途,列举如下:


  1. Go语言官方的平台相关的工具的名称会以它的值为前缀。的名称会以GOCHAR的值为前缀。比如,在amd64计算架构下,用于编译Go语言代码的编译器的名称是6g,链接器的名称是6l。用于编译C语言代码的编译器的名称是6c。而用于编译汇编语言代码的编译器的名称为6a。

  2. Go语言的官方编译器生成的结果文件会以GOCHAR的值作为扩展名。Go语言的官方编译器6g在对命令源码文件编译之后会把结果文件go.6存放到临时工作目录的相应位置中。


GOEXE

GOEXE的值会被作为可执行文件的后缀。它的值与GOOS的值存在一定关系,即只有GOOS的值为“windows”时GOEXE的值才会是“.exe”,否则其值就为空字符串“”。这与在各个操作系统下的可执行文件的默认后缀是一致的。


GOHOSTARCH

GOHOSTARCH的值的含义是程序运行环境的目标计算架构的标识,也就是程序在运行时所在的计算机系统的计算架构的名称。在通常情况下,它的值不需要被显式的设置。因为用来安装Go语言的二进制分发文件和MSI(Microsoft软件安装)软件包文件都是平台相关的。所以,对于不同计算架构的Go语言环境来说,它都会是一个常量。


GOHOSTOS

GOHOSTOS的值的含义是程序运行环境的目标操作系统的标识,也即程序在运行时所在的计算机系统的操作系统的名称。与GOHOSTARCH类似,它的值在不同的操作系统下是固定不变的,同样不需要显式的设置。


GOPATH

这个环境信息我们在之前已经提到过很多次。它的值指明了Go语言工作区目录的绝对路径。我们需要显式的设置环境变量GOPATH。如果有多个工作区,那么多个工作区的绝对路径之间需要用分隔符分隔。在windows操作系统下,这个分隔符为“;”。在其它操作系统下,这个分隔符为“:”。注意,GOPATH的值不能与GOROOT的值相同。


GORACE

GORACE的值包含了用于数据竞争检测的相关选项。数据竞争是在并发程序中最常见和最难调试的一类bug。数据竞争会发生在多个Goroutine争相访问相同的变量且至少有一个Goroutine中的程序在对这个变量进行写操作的情况下。

数据竞争检测需要被显式的开启。还记得标记-race吗?我们可以通过在执行一些标准go命令时加入这个标记来开启数据竞争检测。在这种情况下,GORACE的值就会被使用到了。支持-race标记的标准go命令包括:go test命令、go run命令、go build命令和go install命令。

GORACE的值形如“option1=val1 option2=val2”,即:选项名称与选项值之间以等号“=”分隔,多个选项之间以空格“ ”分隔。数据竞争检测的选项包括log_path、exitcode、strip_path_prefix和history_size。为了设置GORACE的值,我们需要设置环境变量GORACE。或者,我们也可以在执行go命令时临时设置它,像这样:

hc@ubt:~/golang/goc2p/src/cnet/ctcp$ GORACE="log_path=/home/hc/golang/goc2p /race/report strip_path_prefix=home/hc/golang/goc2p/" go test -race

关于数据竞争检测的更多细节我们将会在本书的第三部分予以说明。


GOROOT

GOROOT会是我们在安装Go语言时第一个碰到Go语言环境变量。它的值指明了Go语言的安装目录的绝对路径。但是,只有在非默认情况下我们才需要显式的设置环境变量GOROOT。这里所说的默认情况是指:在Windows操作系统下我们把Go语言安装到c:\Go目录下,或者在其它操作系统下我们把Go语言安装到/usr/local/go目录下。另外,当我们不是通过二进制分发包来安装Go语言的时候,也不需要设置环境变量GOROOT的值。比如,在Windows操作系统下,我们可以使用MSI软件包文件来安装Go语言。


GOTOOLDIR

GOTOOLDIR的值指明了Go工具目录的绝对路径。根据GOROOT、GOHOSTOS和GOHOSTARCH来设置。其值为$GOROOT/pkg/tool/$GOOS_$GOARCH。关于这个目录,我们在之前也提到过多次。

除了上面介绍的这些通用的Go语言环境信息,还两个针对于非Plan 9操作系统的环境信息。它们是CC和GOGCCFLAGS。环境信息CC的值是操作系统默认的C语言编译器的命令名称。环境信息GOGCCFLAGS的值则是Go语言在使用操作系统的默认C语言编译器对C语言代码进行编译时加入的参数。

如果我们要有针对性的查看上述的一个或多个环境信息,可以在go env命令的后面加入它们的名字并执行之。在go env命令和环境信息名称之间需要用空格分隔,多个环境信息名称之间也需要用空格分隔。示例如下:

hc@ubt:~$ go env GOARCH GOCHAR
386
8

上例的go env命令的输出信息中,每一行对一个环境信息的值,且其顺序与我们输入的环境信息名称的顺序一致。比如,386为环境信息GOARCH,而8则是环境信息GOCHAR的值。

go env命令能够让我们对当前的Go语言环境进行简要的了解。通过它,我们也可以对当前安装的Go语言的环境设置进行简单的检查。


推荐阅读
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 基于事件驱动的并发编程及其消息通信机制的同步与异步、阻塞与非阻塞、IO模型的分类
    本文介绍了基于事件驱动的并发编程中的消息通信机制,包括同步和异步的概念及其区别,阻塞和非阻塞的状态,以及IO模型的分类。同步阻塞IO、同步非阻塞IO、异步阻塞IO和异步非阻塞IO等不同的IO模型被详细解释。这些概念和模型对于理解并发编程中的消息通信和IO操作具有重要意义。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • Java 11相对于Java 8,OptaPlanner性能提升有多大?
    本文通过基准测试比较了Java 11和Java 8对OptaPlanner的性能提升。测试结果表明,在相同的硬件环境下,Java 11相对于Java 8在垃圾回收方面表现更好,从而提升了OptaPlanner的性能。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了Linux系统中正则表达式的基础知识,包括正则表达式的简介、字符分类、普通字符和元字符的区别,以及在学习过程中需要注意的事项。同时提醒读者要注意正则表达式与通配符的区别,并给出了使用正则表达式时的一些建议。本文适合初学者了解Linux系统中的正则表达式,并提供了学习的参考资料。 ... [详细]
  • 关于CMS收集器的知识介绍和优缺点分析
    本文介绍了CMS收集器的概念、运行过程和优缺点,并解释了垃圾回收器的作用和实践。CMS收集器是一种基于标记-清除算法的垃圾回收器,适用于互联网站和B/S系统等对响应速度和停顿时间有较高要求的应用。同时,还提供了其他垃圾回收器的参考资料。 ... [详细]
author-avatar
肉斯情-
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有