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

使用机器学习算法的入侵检测系统

使用机器学习算法的入侵检测系统原文:https://www.

使用机器学习算法的入侵检测系统

原文:https://www . geeksforgeeks . org/入侵检测-系统-使用-机器学习-算法/

问题陈述:任务是构建一个网络入侵检测器,一个能够区分不良连接(称为入侵或攻击)和良好正常连接的预测模型。

简介:
入侵检测系统是利用各种机器学习算法检测网络入侵的软件应用。入侵检测系统监控网络或系统的恶意活动,并保护计算机网络免受用户(可能包括内部人员)的未授权访问。入侵检测器的学习任务是建立一个能够区分“不良连接”(入侵/攻击)和“良好(正常)连接”的预测模型(即分类器)。

Attacks fall into four main categories:



  • DOS:拒绝服务,例如 syn flood


  • R2L:来自远程机器的未授权访问,例如猜测密码;


  • U2R:未经授权访问本地超级用户(root)权限,例如各种“缓冲区溢出”攻击;


  • 探测:监视和另一种探测,例如端口扫描。

使用的数据集 : KDD 杯 1999 数据集

数据集描述:数据文件:


  • 功能列表。

  • kddcup.data.gz:完整的数据集

  • 10%的子集。

  • kddcup . new testdata _ 10 _ percent _ unlabled . gz

  • kddcup.testdata.unlabeled.gz

  • kddcup . testdata . unlabled _ 10 _ percent . gz

  • corrected:带有修正标签的测试数据。

  • training _ attack _ types:入侵类型列表。

  • 错别字更正:对数据集中已更正的错别字的简要说明

特征:

| 功能名称 | 描述 | 类型 |
| 期间 | 连接的长度(秒) | 连续的 |
| 协议类型 | 协议的类型,例如 tcp、udp 等。 | 分离的 |
| 服务 | 目的地的网络服务,例如 http、telnet 等。 | 分离的 |
| src_bytes | 从源到目标的数据字节数 | 连续的 |
| dst_bytes | 从目标到源的数据字节数 | 连续的 |
| 旗 | 连接的正常或错误状态 | 分离的 |
| 陆地 | 如果连接来自/去往同一主机/端口,则为 1;否则为 0 | 分离的 |
| 错误的片段 | “错误”片段的数量 | 连续的 |
| 急迫的 | 紧急数据包数量 | 连续的 |

表 1:单个 TCP 连接的基本特征。

| 功能名称 | 描述 | 类型 |
| 热的 | “热门”指标的数量 | 连续的 |
| 失败的登录次数 | 失败的登录尝试次数 | 连续的 |
| 已登录 | 1 如果成功登录;否则为 0 | 分离的 |
| 数量已泄露 | “妥协”条件的数量 | 连续的 |
| root_shell | 如果获得根壳,则为 1;否则为 0 | 分离的 |
| su _ 尝试 | 1 如果尝试了“su root”命令;否则为 0 | 分离的 |
| num_root | “根”访问次数 | 连续的 |
| 数字 _ 文件 _ 创建 | 文件创建操作的次数 | 连续的 |
| 数字 _ 外壳 | 外壳提示的数量 | 连续的 |
| num _ access _ files | 对访问控制文件的操作数 | 连续的 |
| num_outbound_cmds | ftp 会话中出站命令的数量 | 连续的 |
| is _ hot _ 登录 | 1 如果登录属于“热门”列表;否则为 0 | 分离的 |
| is_guest_login | 1 如果登录是“客人”登录;否则为 0 | 分离的 |

表 2:由领域知识建议的连接中的内容特征。

| 功能名称 | 描述 | 类型 |
| 数数 | 过去两秒内与当前连接到同一主机的连接数 | 连续的 |
| | 注意:以下功能指的是这些同主机连接。 | |
| 错误率 | 有“SYN”错误的连接百分比 | 连续的 |
| 错误率 | 有“REJ”错误的连接百分比 | 连续的 |
| 相同的 _ srv _ 速率 | 同一服务的连接百分比 | 连续的 |
| 差异 _srv_rate | 不同服务的连接百分比 | 连续的 |
| srv _ 计数 | 过去两秒钟内与当前连接相同的服务的连接数 | 连续的 |
| | 注意:以下功能指的是这些相同服务的连接。 | |
| srv_serror_rate | 有“SYN”错误的连接百分比 | 连续的 |
| SRV _ re error _ rate | 有“REJ”错误的连接百分比 | 连续的 |
| srv _ diff _ 主机 _ 速率 | 不同主机的连接百分比 | 连续的 |

表 3:使用两秒时间窗口计算的交通特征。

应用的各种算法:高斯朴素贝叶斯、决策树、随机 Fprest、支持向量机、逻辑回归。

使用的方法:我在 KDD 数据集上应用了上面提到的各种分类算法,并比较这些结果来构建预测模型。

步骤 1–数据预处理:
代码:从‘kddcup . names’文件导入库和读取特征列表。

import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import time
# reading features list
with open("..\\kddcup.names", 'r') as f:
    print(f.read())

代码:向数据集追加列,并向数据集添加新的列名“target”。

cols ="""duration,
protocol_type,
service,
flag,
src_bytes,
dst_bytes,
land,
wrong_fragment,
urgent,
hot,
num_failed_logins,
logged_in,
num_compromised,
root_shell,
su_attempted,
num_root,
num_file_creations,
num_shells,
num_access_files,
num_outbound_cmds,
is_host_login,
is_guest_login,
count,
srv_count,
serror_rate,
srv_serror_rate,
rerror_rate,
srv_rerror_rate,
same_srv_rate,
diff_srv_rate,
srv_diff_host_rate,
dst_host_count,
dst_host_srv_count,
dst_host_same_srv_rate,
dst_host_diff_srv_rate,
dst_host_same_src_port_rate,
dst_host_srv_diff_host_rate,
dst_host_serror_rate,
dst_host_srv_serror_rate,
dst_host_rerror_rate,
dst_host_srv_rerror_rate"""
columns =[]
for c in cols.split(', '):
    if(c.strip()):
       columns.append(c.strip())
columns.append('target')
print(len(columns))

输出:

42

代码:读取‘攻击类型’文件。

with open("..\\training_attack_types", 'r') as f:
    print(f.read())

输出:

back dos
buffer_overflow u2r
ftp_write r2l
guess_passwd r2l
imap r2l
ipsweep probe
land dos
loadmodule u2r
multihop r2l
neptune dos
nmap probe
perl u2r
phf r2l
pod dos
portsweep probe
rootkit u2r
satan probe
smurf dos
spy r2l
teardrop dos
warezclient r2l
warezmaster r2l

代码:创建攻击类型字典

attacks_types = {
    'normal': 'normal',
'back': 'dos',
'buffer_overflow': 'u2r',
'ftp_write': 'r2l',
'guess_passwd': 'r2l',
'imap': 'r2l',
'ipsweep': 'probe',
'land': 'dos',
'loadmodule': 'u2r',
'multihop': 'r2l',
'neptune': 'dos',
'nmap': 'probe',
'perl': 'u2r',
'phf': 'r2l',
'pod': 'dos',
'portsweep': 'probe',
'rootkit': 'u2r',
'satan': 'probe',
'smurf': 'dos',
'spy': 'r2l',
'teardrop': 'dos',
'warezclient': 'r2l',
'warezmaster': 'r2l',
}

代码:读取数据集(' kddcup.data_10_percent.gz ')并在训练数据集中添加攻击类型特征,其中攻击类型特征有 5 个不同的值,即 dos、normal、probe、r2l、u2r。

path = "..\\kddcup.data_10_percent.gz"
df = pd.read_csv(path, names = columns)
# Adding Attack Type column
df['Attack Type'] = df.target.apply(lambda r:attacks_types[r[:-1]])
df.head()

编码:数据框的形状,获取各特征的数据类型

df.shape

输出:

(494021, 43)

代码:查找所有特征的缺失值。

df.isnull().sum()

输出:

duration 0
protocol_type 0
service 0
flag 0
src_bytes 0
dst_bytes 0
land 0
wrong_fragment 0
urgent 0
hot 0
num_failed_logins 0
logged_in 0
num_compromised 0
root_shell 0
su_attempted 0
num_root 0
num_file_creations 0
num_shells 0
num_access_files 0
num_outbound_cmds 0
is_host_login 0
is_guest_login 0
count 0
srv_count 0
serror_rate 0
srv_serror_rate 0
rerror_rate 0
srv_rerror_rate 0
same_srv_rate 0
diff_srv_rate 0
srv_diff_host_rate 0
dst_host_count 0
dst_host_srv_count 0
dst_host_same_srv_rate 0
dst_host_diff_srv_rate 0
dst_host_same_src_port_rate 0
dst_host_srv_diff_host_rate 0
dst_host_serror_rate 0
dst_host_srv_serror_rate 0
dst_host_rerror_rate 0
dst_host_srv_rerror_rate 0
target 0
Attack Type 0
dtype: int64

没有找到丢失的值,因此我们可以进一步进行下一步。

代码:寻找分类特征

# Finding categorical features
num_cols = df._get_numeric_data().columns
cate_cols = list(set(df.columns)-set(num_cols))
cate_cols.remove('target')
cate_cols.remove('Attack Type')
cate_cols

输出:

['service', 'flag', 'protocol_type']

使用条形图
可视化分类特征

协议类型:我们注意到,在使用的数据中,ICMP 最多,其次是 TCP 和近 20000 个 UDP 类型的数据包

log in _ in(如果成功登录,则为 1;否则为 0):我们注意到只有 70000 个数据包成功登录。

目标特征分布:

攻击类型(按攻击分组的攻击类型,这是我们将预测的)

代码:数据相关性–使用热图找到高度相关的变量,忽略它们进行分析。

df = df.dropna('columns')# drop columns with NaN
df = df[[col for col in df if df[col].nunique() > 1]]# keep columns where there are more than 1 unique values
corr = df.corr()
plt.figure(figsize =(15, 12))
sns.heatmap(corr)
plt.show()

输出:

代码:

# This variable is highly correlated with num_compromised and should be ignored for analysis.
#(Correlation = 0.9938277978738366)
df.drop('num_root', axis = 1, inplace = True)
# This variable is highly correlated with serror_rate and should be ignored for analysis.
#(Correlation = 0.9983615072725952)
df.drop('srv_serror_rate', axis = 1, inplace = True)
# This variable is highly correlated with rerror_rate and should be ignored for analysis.
#(Correlation = 0.9947309539817937)
df.drop('srv_rerror_rate', axis = 1, inplace = True)
# This variable is highly correlated with srv_serror_rate and should be ignored for analysis.
#(Correlation = 0.9993041091850098)
df.drop('dst_host_srv_serror_rate', axis = 1, inplace = True)
# This variable is highly correlated with rerror_rate and should be ignored for analysis.
#(Correlation = 0.9869947924956001)
df.drop('dst_host_serror_rate', axis = 1, inplace = True)
# This variable is highly correlated with srv_rerror_rate and should be ignored for analysis.
#(Correlation = 0.9821663427308375)
df.drop('dst_host_rerror_rate', axis = 1, inplace = True)
# This variable is highly correlated with rerror_rate and should be ignored for analysis.
#(Correlation = 0.9851995540751249)
df.drop('dst_host_srv_rerror_rate', axis = 1, inplace = True)
# This variable is highly correlated with srv_rerror_rate and should be ignored for analysis.
#(Correlation = 0.9865705438845669)
df.drop('dst_host_same_srv_rate', axis = 1, inplace = True)

输出:

代码:特征映射–对特征应用特征映射,例如:“protocol _ type”&“flag”。

# protocol_type feature mapping
pmap = {'icmp':0, 'tcp':1, 'udp':2}
df['protocol_type'] = df['protocol_type'].map(pmap)

代码:

# flag feature mapping
fmap = {'SF':0, 'S0':1, 'REJ':2, 'RSTR':3, 'RSTO':4, 'SH':5, 'S1':6, 'S2':7, 'RSTOS0':8, 'S3':9, 'OTH':10}
df['flag'] = df['flag'].map(fmap)

输出:

编码:建模前去除‘服务’等无关特征

df.drop('service', axis = 1, inplace = True)

步骤 2–建模

代码:导入库并拆分数据集

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler

代码:

# Splitting the dataset
df = df.drop(['target', ], axis = 1)
print(df.shape)
# Target variable and train set
y = df[['Attack Type']]
X = df.drop(['Attack Type', ], axis = 1)
sc = MinMaxScaler()
X = sc.fit_transform(X)
# Split test and train data 
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.33, random_state = 42)
print(X_train.shape, X_test.shape)
print(y_train.shape, y_test.shape)

输出:

(494021, 31)
(330994, 30) (163027, 30)
(330994, 1) (163027, 1)

应用各种机器学习分类算法,如支持向量机、随机森林、朴素贝叶斯、决策树、逻辑回归等,创建不同的模型。

代码:高斯朴素贝叶斯的 Python 实现

# Gaussian Naive Bayes
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import accuracy_score
clfg = GaussianNB()
start_time = time.time()
clfg.fit(X_train, y_train.values.ravel())
end_time = time.time()
print("Training time: ", end_time-start_time)

输出:

Training time: 1.1145250797271729

代码:

start_time = time.time()
y_test_pred = clfg.predict(X_train)
end_time = time.time()
print("Testing time: ", end_time-start_time)

输出:

Testing time: 1.543299674987793

代码:

print("Train score is:", clfg.score(X_train, y_train))
print("Test score is:", clfg.score(X_test, y_test))

输出:

Train score is: 0.8795114110829804
Test score is: 0.8790384414851528

代码:决策树的 Python 实现

# Decision Tree 
from sklearn.tree import DecisionTreeClassifier
clfd = DecisionTreeClassifier(criterion ="entropy", max_depth = 4)
start_time = time.time()
clfd.fit(X_train, y_train.values.ravel())
end_time = time.time()
print("Training time: ", end_time-start_time)

输出:

Training time: 2.4408750534057617

start_time = time.time()
y_test_pred = clfd.predict(X_train)
end_time = time.time()
print("Testing time: ", end_time-start_time)

输出:

Testing time: 0.1487727165222168

print("Train score is:", clfd.score(X_train, y_train))
print("Test score is:", clfd.score(X_test, y_test))

输出:

Train score is: 0.9905829108684749
Test score is: 0.9905230421954646

代码:随机森林的 Python 代码实现

from sklearn.ensemble import RandomForestClassifier
clfr = RandomForestClassifier(n_estimators = 30)
start_time = time.time()
clfr.fit(X_train, y_train.values.ravel())
end_time = time.time()
print("Training time: ", end_time-start_time)

输出:

Training time: 17.084914684295654

start_time = time.time()
y_test_pred = clfr.predict(X_train)
end_time = time.time()
print("Testing time: ", end_time-start_time)

输出:

Testing time: 0.1487727165222168

print("Train score is:", clfr.score(X_train, y_train))
print("Test score is:", clfr.score(X_test, y_test))

输出:

Train score is: 0.99997583037759
Test score is: 0.9996933023364228

代码:支持向量分类器的 Python 实现

from sklearn.svm import SVC
clfs = SVC(gamma = 'scale')
start_time = time.time()
clfs.fit(X_train, y_train.values.ravel())
end_time = time.time()
print("Training time: ", end_time-start_time)

输出:

Training time: 218.26840996742249

代码:

start_time = time.time()
y_test_pred = clfs.predict(X_train)
end_time = time.time()
print("Testing time: ", end_time-start_time)

输出:

Testing time: 126.5087513923645

代码:

print("Train score is:", clfs.score(X_train, y_train))
print("Test score is:", clfs.score(X_test, y_test))

输出:

Train score is: 0.9987552644458811
Test score is: 0.9987916112055059

代码:逻辑回归的 Python 实现

from sklearn.linear_model import LogisticRegression
clfl = LogisticRegression(max_iter = 1200000)
start_time = time.time()
clfl.fit(X_train, y_train.values.ravel())
end_time = time.time()
print("Training time: ", end_time-start_time)

输出:

Training time: 92.94222283363342

代码:

start_time = time.time()
y_test_pred = clfl.predict(X_train)
end_time = time.time()
print("Testing time: ", end_time-start_time)

输出:

Testing time: 0.09605908393859863

代码:

print("Train score is:", clfl.score(X_train, y_train))
print("Test score is:", clfl.score(X_test, y_test))

输出:

Train score is: 0.9935285835997028
Test score is: 0.9935286792985211

代码:梯度下降的 Python 实现

from sklearn.ensemble import GradientBoostingClassifier
clfg = GradientBoostingClassifier(random_state = 0)
start_time = time.time()
clfg.fit(X_train, y_train.values.ravel())
end_time = time.time()
print("Training time: ", end_time-start_time)

输出:

Training time: 633.2290260791779

start_time = time.time()
y_test_pred = clfg.predict(X_train)
end_time = time.time()
print("Testing time: ", end_time-start_time)

输出:

Testing time: 2.9503915309906006

print("Train score is:", clfg.score(X_train, y_train))
print("Test score is:", clfg.score(X_test, y_test))

输出:

Train score is: 0.9979304760811374
Test score is: 0.9977181693829856

代码:分析各模型的训练和测试精度。

names = ['NB', 'DT', 'RF', 'SVM', 'LR', 'GB']
values = [87.951, 99.058, 99.997, 99.875, 99.352, 99.793]
f = plt.figure(figsize =(15, 3), num = 10)
plt.subplot(131)
plt.bar(names, values)

输出:

代码:

names = ['NB', 'DT', 'RF', 'SVM', 'LR', 'GB']
values = [87.903, 99.052, 99.969, 99.879, 99.352, 99.771]
f = plt.figure(figsize =(15, 3), num = 10)
plt.subplot(131)
plt.bar(names, values)

输出:

代码:分析每个模型的训练和测试时间。

names = ['NB', 'DT', 'RF', 'SVM', 'LR', 'GB']
values = [1.11452, 2.44087, 17.08491, 218.26840, 92.94222, 633.229]
f = plt.figure(figsize =(15, 3), num = 10)
plt.subplot(131)
plt.bar(names, values)

输出:

代码:

names = ['NB', 'DT', 'RF', 'SVM', 'LR', 'GB']
values = [1.54329, 0.14877, 0.199471, 126.50875, 0.09605, 2.95039]
f = plt.figure(figsize =(15, 3), num = 10)
plt.subplot(131)
plt.bar(names, values)

输出:

实现链接:

结论:以上对不同模型的分析表明,考虑到准确性和时间复杂性,决策树模型最适合我们的数据。

链接:完整代码上传到我的 github 账号–https://github.com/mudgalabhay/intrusion-detection-system


推荐阅读
  • 大类|电阻器_使用Requests、Etree、BeautifulSoup、Pandas和Path库进行数据抓取与处理 | 将指定区域内容保存为HTML和Excel格式
    大类|电阻器_使用Requests、Etree、BeautifulSoup、Pandas和Path库进行数据抓取与处理 | 将指定区域内容保存为HTML和Excel格式 ... [详细]
  • 在尝试对 QQmlPropertyMap 类进行测试驱动开发时,发现其派生类中无法正常调用槽函数或 Q_INVOKABLE 方法。这可能是由于 QQmlPropertyMap 的内部实现机制导致的,需要进一步研究以找到解决方案。 ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • 属性类 `Properties` 是 `Hashtable` 类的子类,用于存储键值对形式的数据。该类在 Java 中广泛应用于配置文件的读取与写入,支持字符串类型的键和值。通过 `Properties` 类,开发者可以方便地进行配置信息的管理,确保应用程序的灵活性和可维护性。此外,`Properties` 类还提供了加载和保存属性文件的方法,使其在实际开发中具有较高的实用价值。 ... [详细]
  • Android 构建基础流程详解
    Android 构建基础流程详解 ... [详细]
  • 本文介绍了一种自定义的Android圆形进度条视图,支持在进度条上显示数字,并在圆心位置展示文字内容。通过自定义绘图和组件组合的方式实现,详细展示了自定义View的开发流程和关键技术点。示例代码和效果展示将在文章末尾提供。 ... [详细]
  • 本文介绍了如何使用Python的Paramiko库批量更新多台服务器的登录密码。通过示例代码展示了具体实现方法,确保了操作的高效性和安全性。Paramiko库提供了强大的SSH2协议支持,使得远程服务器管理变得更加便捷。此外,文章还详细说明了代码的各个部分,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 在Windows系统中安装TensorFlow GPU版的详细指南与常见问题解决
    在Windows系统中安装TensorFlow GPU版是许多深度学习初学者面临的挑战。本文详细介绍了安装过程中的每一个步骤,并针对常见的问题提供了有效的解决方案。通过本文的指导,读者可以顺利地完成安装并避免常见的陷阱。 ... [详细]
  • 技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统
    技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统 ... [详细]
  • 本文详细解析了客户端与服务器之间的交互过程,重点介绍了Socket通信机制。IP地址由32位的4个8位二进制数组成,分为网络地址和主机地址两部分。通过使用 `ipconfig /all` 命令,用户可以查看详细的IP配置信息。此外,文章还介绍了如何使用 `ping` 命令测试网络连通性,例如 `ping 127.0.0.1` 可以检测本机网络是否正常。这些技术细节对于理解网络通信的基本原理具有重要意义。 ... [详细]
  • PTArchiver工作原理详解与应用分析
    PTArchiver工作原理及其应用分析本文详细解析了PTArchiver的工作机制,探讨了其在数据归档和管理中的应用。PTArchiver通过高效的压缩算法和灵活的存储策略,实现了对大规模数据的高效管理和长期保存。文章还介绍了其在企业级数据备份、历史数据迁移等场景中的实际应用案例,为用户提供了实用的操作建议和技术支持。 ... [详细]
  • 如何将TS文件转换为M3U8直播流:HLS与M3U8格式详解
    在视频传输领域,MP4虽然常见,但在直播场景中直接使用MP4格式存在诸多问题。例如,MP4文件的头部信息(如ftyp、moov)较大,导致初始加载时间较长,影响用户体验。相比之下,HLS(HTTP Live Streaming)协议及其M3U8格式更具优势。HLS通过将视频切分成多个小片段,并生成一个M3U8播放列表文件,实现低延迟和高稳定性。本文详细介绍了如何将TS文件转换为M3U8直播流,包括技术原理和具体操作步骤,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 本文详细解析了Java类加载系统的父子委托机制。在Java程序中,.java源代码文件编译后会生成对应的.class字节码文件,这些字节码文件需要通过类加载器(ClassLoader)进行加载。ClassLoader采用双亲委派模型,确保类的加载过程既高效又安全,避免了类的重复加载和潜在的安全风险。该机制在Java虚拟机中扮演着至关重要的角色,确保了类加载的一致性和可靠性。 ... [详细]
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • Python 序列图分割与可视化编程入门教程
    本文介绍了如何使用 Python 进行序列图的快速分割与可视化。通过一个实际案例,详细展示了从需求分析到代码实现的全过程。具体包括如何读取序列图数据、应用分割算法以及利用可视化库生成直观的图表,帮助非编程背景的用户也能轻松上手。 ... [详细]
author-avatar
豪哥仔137600
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有