返回顶部
首页 > 资讯 > 后端开发 > PHP编程 >详解PHP Swoole长连接常见问题
  • 898
分享到

详解PHP Swoole长连接常见问题

2024-04-02 19:04:59 898人浏览 薄情痞子
摘要

目录连接失效问题如何解决如何维持长连接结论连接失效问题 例子 其中,Redis常见的报错就是: 配置项:timeout 报错信息: Error while reading line

连接失效问题

例子

其中,Redis常见的报错就是:

配置项:timeout

报错信息:

Error while reading line from the server

Redis可以配置如果客户端经过多少秒还不给Redis服务器发送数据,那么就会把连接close掉。

Mysql常见的报错:

配置项:wait_timeout & interactive_timeout

报错信息:

has Gone away

和Redis服务器一样,mysql也会定时的去清理掉没用的连接。

如何解决

1、用的时候进行重连 。优点是简单,缺点是面临短连接的问题。

2、定时发送心跳维持连接(推荐)。

如何维持长连接

tcp协议中实现的tcp_keepalive

操作系统底层提供了一组tcp的keepalive配置:


tcp_keepalive_time (integer; default: 7200; since linux 2.2)
The number of seconds a connection needs to be idle before TCP
begins sending out keep-alive probes. Keep-alives are sent only
when the SO_KEEPALIVE Socket option is enabled. The default
value is 7200 seconds (2 hours). An idle connection is
terminated after approximately an additional 11 minutes (9
probes an interval of 75 seconds apart) when keep-alive is
enabled.

Note that underlying connection tracking mechanisms and
application timeouts may be much shorter.

tcp_keepalive_intvl (integer; default: 75; since Linux 2.4)
The number of seconds between TCP keep-alive probes.

tcp_keepalive_probes (integer; default: 9; since Linux 2.2)
The maximum number of TCP keep-alive probes to send before
giving up and killing the connection if no response is obtained
from the other end.
8

Swoole底层把这些配置开放出来了,例如:


?PHP

$server = new \Swoole\Server('127.0.0.1', 6666, SWOOLE_PROCESS);

$server->set([
'worker_num' => 1,
'open_tcp_keepalive' => 1,
'tcp_keepidle' => 4, // 对应tcp_keepalive_time
'tcp_keepinterval' => 1, // 对应tcp_keepalive_intvl
'tcp_keepcount' => 5, // 对应tcp_keepalive_probes
]);

其中:


'open_tcp_keepalive' => 1, // 总开关,用来开启tcp_keepalive
'tcp_keepidle' => 4, // 4s没有数据传输就进行检测
// 检测的策略如下:
'tcp_keepinterval' => 1, // 1s探测一次,即每隔1s给客户端发一个包(然后客户端可能会回一个ack的包,如果服务端收到了这个ack包,那么说明这个连接是活着的)
'tcp_keepcount' => 5, // 探测的次数,超过5次后客户端还没有回ack包,那么close此连接

我们来实战测试体验一下,服务端脚本如下:


<?php

$server = new \Swoole\Server('127.0.0.1', 6666, SWOOLE_PROCESS);

$server->set([
'worker_num' => 1,
'open_tcp_keepalive' => 1, // 开启tcp_keepalive
'tcp_keepidle' => 4, // 4s没有数据传输就进行检测
'tcp_keepinterval' => 1, // 1s探测一次
'tcp_keepcount' => 5, // 探测的次数,超过5次后还没有回包close此连接
]);

$server->on('connect', function ($server, $fd) {
var_dump("Client: Connect $fd");
});

$server->on('receive', function ($server, $fd, $Reactor_id, $data) {
var_dump($data);
});

$server->on('close', function ($server, $fd) {
var_dump("close fd $fd");
});

$server->start();

我们启动这个服务器:


~/codeDir/phpCode/hyperf-skeleton # php server.php

然后通过tcpdump进行抓包:


~/codeDir/phpCode/hyperf-skeleton # tcpdump -i lo port 6666
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes

我们此时正在监听lo上的6666端口的数据包。

然后我们用客户端去连接它:


~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666

此时服务端会打印出消息:

~/codeDir/phpCode/hyperf-skeleton # php server.php

string(17) "Client: Connect 1"

tcpdump的输出信息如下:

01:48:40.178439 IP localhost.33933 > localhost.6666: Flags [S], seq 43162537, win 43690, options [mss 65495,sackOK,TS val 9833698 ecr 0,nop,wscale 7], length 0

01:48:40.178484 IP localhost.6666 > localhost.33933: Flags [S.], seq 1327460565, ack 43162538, win 43690, options [mss 65495,sackOK,TS val 9833698 ecr 9833698,nop,wscale 7], length 0

01:48:40.178519 IP localhost.33933 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9833698 ecr 9833698], length 0

01:48:44.229926 IP localhost.6666 > localhost.33933: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0

01:48:44.229951 IP localhost.33933 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0

01:48:44.229926 IP localhost.6666 > localhost.33933: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0

01:48:44.229951 IP localhost.33933 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0

01:48:44.229926 IP localhost.6666 > localhost.33933: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0

// 省略了其他的输出

我们会发现最开始的时候,会打印三次握手的包:

01:48:40.178439 IP localhost.33933 > localhost.6666: Flags [S], seq 43162537, win 43690, options [mss 65495,sackOK,TS val 9833698 ecr 0,nop,wscale 7], length 0

01:48:40.178484 IP localhost.6666 > localhost.33933: Flags [S.], seq 1327460565, ack 43162538, win 43690, options [mss 65495,sackOK,TS val 9833698 ecr 9833698,nop,wscale 7], length 0

01:48:40.178519 IP localhost.33933 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9833698 ecr 9833698], length 0

然后,停留了4s没有任何包的输出。

之后,每隔1s左右就会打印出一组:

01:52:54.359341 IP localhost.6666 > localhost.43101: Flags [.], ack 1, win 342, options [nop,nop,TS val 9859144 ecr 9858736], length 0

01:52:54.359377 IP localhost.43101 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9859144 ecr 9855887], length 0

其实这就是我们配置的策略:


'tcp_keepinterval' => 1, // 1s探测一次
'tcp_keepcount' => 5, // 探测的次数,超过5次后还没有回包close此连接

因为我们操作系统底层会自动的给客户端回ack,所以这个连接不会在5次探测后被关闭。操作系统底层会持续不断的发送这样的一组包:

01:52:54.359341 IP localhost.6666 > localhost.43101: Flags [.], ack 1, win 342, options [nop,nop,TS val 9859144 ecr 9858736], length 0

01:52:54.359377 IP localhost.43101 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9859144 ecr 9855887], length 0

如果我们要测试5次探测后关闭这个连接,可以禁掉6666端口的包:


~/codeDir/phpCode/hyperf-skeleton # iptables -A INPUT -p tcp --dport 6666 -j DROP

这样会把所有从6666端口进来的包给禁掉,自然,服务器就接收不到从客户端那一边发来的ack包了。

然后服务器过5秒就会打印出close(服务端主动的调用了close方法,给客户端发送了FIN包):

~/codeDir/phpCode/hyperf-skeleton # php server.php

string(17) "Client: Connect 1"

string(10) "close fd 1"

我们恢复一下iptables的规则:


~/codeDir/phpCode # iptables -D INPUT -p tcp -m tcp --dport 6666 -j DROP

即把我们设置的规则给删除了。

通过tcp_keepalive的方式实现心跳的功能,优点是简单,不要写代码就可以完成这个功能,并且发送的心跳包小。缺点是依赖于系统的网络环境,必须保证服务器和客户端都实现了这样的功能,需要客户端配合发心跳包。还有一个更为严重的缺点是如果客户端和服务器不是直连的,而是通过代理来进行连接的,例如socks5代理,它只会转发应用层的包,不会转发更为底层的tcp探测包,那这个心跳功能就失效了。

所以,Swoole就提供了其他的解决方案,一组检测死连接的配置。


'heartbeat_check_interval' => 1, // 1s探测一次
'heartbeat_idle_time' => 5, // 5s未发送数据包就close此连接

swoole实现的heartbeat

我们来测试一下:


<?php

$server = new \Swoole\Server('127.0.0.1', 6666, SWOOLE_PROCESS);

$server->set([
'worker_num' => 1,
'heartbeat_check_interval' => 1, // 1s探测一次
'heartbeat_idle_time' => 5, // 5s未发送数据包就close此连接
]);

$server->on('connect', function ($server, $fd) {
var_dump("Client: Connect $fd");
});

$server->on('receive', function ($server, $fd, $reactor_id, $data) {
var_dump($data);
});

$server->on('close', function ($server, $fd) {
var_dump("close fd $fd");
});

$server->start();

然后启动服务器:


~/codeDir/phpCode/hyperf-skeleton # php server.php

然后启动tcpdump:

~/codeDir/phpCode # tcpdump -i lo port 6666

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode

listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes

然后再启动客户端:


~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666

此时服务器端打印:

~/codeDir/phpCode/hyperf-skeleton # php server.php

string(17) "Client: Connect 1"

然后tcpdump打印:

02:48:32.516093 IP localhost.42123 > localhost.6666: Flags [S], seq 1088388248, win 43690, options [mss 65495,sackOK,TS val 10193342 ecr 0,nop,wscale 7], length 0

02:48:32.516133 IP localhost.6666 > localhost.42123: Flags [S.], seq 80508236, ack 1088388249, win 43690, options [mss 65495,sackOK,TS val 10193342 ecr 10193342,nop,wscale 7], length 0

02:48:32.516156 IP localhost.42123 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 10193342 ecr 10193342], length 0

这是三次握手信息。

然后过了5s后,tcpdump会打印出:

02:48:36.985027 IP localhost.6666 > localhost.42123: Flags [F.], seq 1, ack 1, win 342, options [nop,nop,TS val 10193789 ecr 10193342], length 0

02:48:36.992172 IP localhost.42123 > localhost.6666: Flags [.], ack 2, win 342, options [nop,nop,TS val 10193790 ecr 10193789], length 0

也就是服务端发送了FIN包。因为客户端没有发送数据,所以Swoole关闭了连接。

然后服务器端会打印:

~/codeDir/phpCode/hyperf-skeleton # php server.php

string(17) "Client: Connect 1"

string(10) "close fd 1"

所以,heartbeat和tcp keepalive还是有一定的区别的,tcp keepalive有保活连接的功能,但是heartbeat存粹是检测没有数据的连接,然后关闭它,并且只可以在服务端这边配置,如果需要保活,也可以让客户端配合发送心跳。

如果我们不想让服务端close掉连接,那么就得在应用层里面不断的发送数据包来进行保活,例如我在nc客户端里面不断的发送包:

~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666

ping

ping

ping

ping

ping

ping

ping

ping

ping

我发送了9个ping包给服务器,tcpdump的输出如下:

// 省略了三次握手的包

02:57:53.697363 IP localhost.44195 > localhost.6666: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 10249525 ecr 10249307], length 5

02:57:53.697390 IP localhost.6666 > localhost.44195: Flags [.], ack 6, win 342, options [nop,nop,TS val 10249525 ecr 10249525], length 0

02:57:55.309532 IP localhost.44195 > localhost.6666: Flags [P.], seq 6:11, ack 1, win 342, options [nop,nop,TS val 10249686 ecr 10249525], length 5

02:57:55.309576 IP localhost.6666 > localhost.44195: Flags [.], ack 11, win 342, options [nop,nop,TS val 10249686 ecr 10249686], length 0

02:57:58.395206 IP localhost.44195 > localhost.6666: Flags [P.], seq 11:16, ack 1, win 342, options [nop,nop,TS val 10249994 ecr 10249686], length 5

02:57:58.395239 IP localhost.6666 > localhost.44195: Flags [.], ack 16, win 342, options [nop,nop,TS val 10249994 ecr 10249994], length 0

02:58:01.858094 IP localhost.44195 > localhost.6666: Flags [P.], seq 16:21, ack 1, win 342, options [nop,nop,TS val 10250341 ecr 10249994], length 5

02:58:01.858126 IP localhost.6666 > localhost.44195: Flags [.], ack 21, win 342, options [nop,nop,TS val 10250341 ecr 10250341], length 0

02:58:04.132584 IP localhost.44195 > localhost.6666: Flags [P.], seq 21:26, ack 1, win 342, options [nop,nop,TS val 10250568 ecr 10250341], length 5

02:58:04.132609 IP localhost.6666 > localhost.44195: Flags [.], ack 26, win 342, options [nop,nop,TS val 10250568 ecr 10250568], length 0

02:58:05.895704 IP localhost.44195 > localhost.6666: Flags [P.], seq 26:31, ack 1, win 342, options [nop,nop,TS val 10250744 ecr 10250568], length 5

02:58:05.895728 IP localhost.6666 > localhost.44195: Flags [.], ack 31, win 342, options [nop,nop,TS val 10250744 ecr 10250744], length 0

02:58:07.150265 IP localhost.44195 > localhost.6666: Flags [P.], seq 31:36, ack 1, win 342, options [nop,nop,TS val 10250870 ecr 10250744], length 5

02:58:07.150288 IP localhost.6666 > localhost.44195: Flags [.], ack 36, win 342, options [nop,nop,TS val 10250870 ecr 10250870], length 0

02:58:08.349124 IP localhost.44195 > localhost.6666: Flags [P.], seq 36:41, ack 1, win 342, options [nop,nop,TS val 10250990 ecr 10250870], length 5

02:58:08.349156 IP localhost.6666 > localhost.44195: Flags [.], ack 41, win 342, options [nop,nop,TS val 10250990 ecr 10250990], length 0

02:58:09.906223 IP localhost.44195 > localhost.6666: Flags [P.], seq 41:46, ack 1, win 342, options [nop,nop,TS val 10251145 ecr 10250990], length 5

02:58:09.906247 IP localhost.6666 > localhost.44195: Flags [.], ack 46, win 342, options [nop,nop,TS val 10251145 ecr 10251145], length 0

有9组数据包的发送。(这里的Flags [P.]代表Push的含义)

此时服务器还没有close掉连接,实现了客户端保活连接的功能。然后我们停止发送ping,过了5秒后tcpdump就会输出一组:

02:58:14.811761 IP localhost.6666 > localhost.44195: Flags [F.], seq 1, ack 46, win 342, options [nop,nop,TS val 10251636 ecr 10251145], length 0
02:58:14.816420 IP localhost.44195 > localhost.6666: Flags [.], ack 2, win 342, options [nop,nop,TS val 10251637 ecr 10251636], length 0
服务端那边发送了FIN包,说明服务端close掉了连接。服务端的输出如下:

~/codeDir/phpCode/hyperf-skeleton # php server.php

string(17) "Client: Connect 1"

string(5) "ping

"

string(5) "ping

"

string(5) "ping

"

string(5) "ping

"

string(5) "ping

"

string(5) "ping

"

string(5) "ping

"

string(5) "ping

"

string(5) "ping

"

string(10) "close fd 1"

然后我们在客户端那边ctrl + c来关闭连接:

~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666

ping

ping

ping

ping

ping

ping

ping

ping

ping

^Cpunt!

~/codeDir/phpCode/hyperf-skeleton #

此时,tcpdump的输出如下:

03:03:02.257667 IP localhost.44195 > localhost.6666: Flags [F.], seq 46, ack 2, win 342, options [nop,nop,TS val 10280414 ecr 10251636], length 0

03:03:02.257734 IP localhost.6666 > localhost.44195: Flags [R], seq 2678621620, win 0, length 0

应用层心跳

1、制定ping/pong协议(mysql等自带ping协议)

2、客户端灵活的发送ping心跳包

3、服务端OnRecive检查可用性回复pong

例如:


$server->on('receive', function (\Swoole\Server $server, $fd, $reactor_id, $data)
{
if ($data == 'ping')
{
checkDB();
checkServiceA();
checkRedis();
$server->send('pong');
}
});

结论

1、tcp的keepalive最简单,但是有兼容性问题,不够灵活

2、swoole提供的keepalive最实用,但是需要客户端配合,复杂度适中

3、应用层的keepalive最灵活但是最麻烦

以上就是详解PHP Swoole长连接常见问题的详细内容,更多关于PHP Swoole长连接常见问题的资料请关注编程网其它相关文章!

--结束END--

本文标题: 详解PHP Swoole长连接常见问题

本文链接: https://lsjlt.com/news/126977.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 详解PHP Swoole长连接常见问题
    目录连接失效问题如何解决如何维持长连接结论连接失效问题 例子 其中,Redis常见的报错就是: 配置项:timeout 报错信息: Error while reading line...
    99+
    2024-04-02
  • swoole安装常见问题怎么解决
    这篇文章主要讲解了“swoole安装常见问题怎么解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“swoole安装常见问题怎么解决”吧!第一步:安装Swoole在开始安装Swoole之前,确...
    99+
    2023-07-05
  • C++中常见的字符串连接问题详解
    C++中常见的字符串连接问题详解在C++编程中,字符串的连接是一项常见的操作。字符串连接指的是将两个或多个字符串拼接在一起形成一个新的字符串。本文将详细介绍C++中常见的字符串连接问题,并提供具体的代码示例。下面将从以下几个方面进行讨论。1...
    99+
    2023-10-22
    字符串拼接 字符串连接 C++字符串
  • PHP数据库连接的常见问题及解决方法
    php 数据库连接的常见问题和解决方法有:连接失败:检查连接信息和 mysql 服务状态;查询失败:检查查询语法、表和字段,以及连接有效性;插入、更新、删除失败:检查 sql 语句、目标...
    99+
    2024-05-21
    数据库 php mysql
  • MySQL SSL 连接常见问题及解决方法
    MySQL SSL 连接常见问题及解决方法概述:Secure Socket Layer(SSL)是一种加密传输协议,用于保护数据在网络上的传输安全。MySQL 支持通过 SSL 连接数据库服务器,以增强数据的保密性和完整性。然而,在使用 M...
    99+
    2023-10-22
  • php常见问题
    列举一些 PHP 中的设计模式? 单例模式:保证在整个应用程序的生命周期中,单例类的实例只存在一个 工厂模式:定义一个创建对象的接口,让子类去实例化具体类。 观察者模式 发布/订阅模式:当一个对象状态发生变化时,依赖它的对象全部会收到通知,...
    99+
    2023-09-03
    单例模式 php 安全
  • PHP 函数常见问题详解及解决方案
    常見 php 函數問題及解決方案:函數未定義:檢查函數存在性,確保正確定義或導入。缺少參數:根據函數聲明補充必需參數。參數類型錯誤:確認參數類型與聲明匹配,使用轉型或驗證解決不匹配。返回...
    99+
    2024-04-30
    php 常见问题
  • 详解Javascript 基于长连接的服务框架问题
    目录背景Webscoket 封装FakeHttpServerContextMiddleware请求处理Quick Start小结背景 经常使用 Node 进行服务端开发的同学,想必都...
    99+
    2024-04-02
  • PHP ZipArchive 扩展的常见问题解答:解决常见疑问
    PHP ZipArchive 扩展为处理 ZIP 压缩文件提供了强大且易于使用的功能。然而,在使用该扩展时可能会遇到一些常见问题。本文旨在解决这些常见问题并提供相应的解决方案。 问题 1:创建 ZIP 文件时无法向其中添加文件 解决方案...
    99+
    2024-03-08
    ZipArchive PHP 压缩 解压 常见问题
  • android连接设备常见问题有哪些
    1. 设备无法被识别:可能是由于USB驱动未安装或者设备未启用USB调试模式。2. USB连接断开:可能是由于USB线松动或者设备电...
    99+
    2023-09-04
    android
  • Navicat连接MySQL教程及常见问题解决方法
    “Navicat”是一套可创建多个连接的数据库管理工具,用以方便管理不同类型的数据库,Navicat 的功能足以满足专业开发人员的所有需求,对数据库服务器初学者来说又简单易操作。且Navicat 的用户界面设计良好应用广泛。 目录 一...
    99+
    2023-10-20
    mysql 数据库 sql 大数据
  • C++中常见的字符串拼接问题详解
    C++中常见的字符串拼接问题详解,需要具体代码示例在C++编程中,字符串拼接是一项常见的任务。无论是简单的拼接几个字符串还是复杂的字符串操作,都需要掌握一些基本的技巧和方法。本文将详细介绍C++中常见的字符串拼接问题,并提供具体的代码示例。...
    99+
    2023-10-22
    字符串拼接 C++ 中的字符串操作 字符串拼接问题详解
  • FileZilla无法连接服务器的常见问题
    什么是FilleZilla FileZilla是一款免费的TFP软件,具备大部分FTP的功能,界面操作简单,适合所有阶段的用户。FTP是英文File Transfer Protocol的缩写,也就是文件传输协议的意思。所以,FileZill...
    99+
    2023-08-31
    服务器 网络 运维
  • Java 线程池常见问题详解
    线程池是一个预定义线程集合,可按需提供给应用程序使用。它通过管理线程的创建和销毁,简化了线程处理,提高了应用程序的性能和可伸缩性。 为什么使用线程池? 使用线程池有以下好处: 减少线程创建和销毁的开销,提高性能。 限制并发线程数,防止系...
    99+
    2024-03-13
    线程池
  • php zookeeper常见问题解决方案
    在《PHP Zookeeper你需要知道的细节》(以下称为php_zk)一章中我们提出了问题,并且通过分析PHP-ZooKeeper源码找出了问题的原因,但是并没有给出解决方法。本章我们就来看一看解决的...
    99+
    2024-02-27
  • Swoole和Workerman对PHP与MySQL的长连接和持久连接的优化方法
    随着Web应用程序的发展和用户规模的增加,数据库查询成为了应用性能优化的重点之一。而在PHP开发中,常用的数据库连接方式有长连接和短连接。长连接是指在建立数据库连接后保持连接状态,多次重复使用同一个连接;而短连接则是每次查询完毕后关闭连接。...
    99+
    2023-10-21
    优化方法 长连接 持久连接
  • python常见问题django连接mysql数据库方法
    本文主要给大家介绍python常见问题django连接mysql数据库方法,文章内容都是笔者用心摘选和编辑的,具有一定的针对性,对大家的参考意义还是比较大的,下面跟笔者一起了解下python常见问题djan...
    99+
    2024-04-02
  • PHP PDO 常见问题解答:解决你的难题
    问题 1:如何连接到 MySQL 数据库? 代码: $dsn = "mysql:dbname=my_database;host=localhost"; $username = "root"; $password = "password"...
    99+
    2024-02-17
    PHP PDO 数据库交互 疑难解答 常见问题
  • PHP设计模式:常见问题解答
    php 设计模式主要用于解决常见编程问题,包含以下解决方案:观察者模式:通过分离对象和事件,实现松耦合。单例模式:确保一个类只有一个实例。策略模式:通过交换算法,实现可扩展性。 PHP...
    99+
    2024-05-13
    php 设计模式 冒泡排序
  • phpcms常见问题解答
    关键字描述:常见 频道 目录 建立 设置 ftp 标签   文章 可以 1.为什么phpcms首页幻灯片怎么显示不出来? 答:需要设置文章的 标题图片 如果设置标题图片,则可以在首页以及栏目页以图片方...
    99+
    2022-06-12
    常见 频道 目录 建立 设置 ftp 标签 文章 可以 图片 phpcms
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作