本文参考了文章:

弈心:网络工程师的Python之路---Scapy基础篇​zhuanlan.zhihu.com

弈心:网络工程师的Python之路---Scapy应用篇​zhuanlan.zhihu.com

什么是Scapy?

根据scapy官方的定义:

Scapy is a Python program that enables the user to send, sniff and dissect and forge network packets. This capability allows construction of tools that can probe, scan or attack networks.
In other words, Scapy is a powerful interactive packet manipulation program. It is able to forge or decode packets of a wide number of protocols, send them on the wire, capture them, match requests and replies, and much more. Scapy can easily handle most classical tasks like scanning, tracerouting, probing, unit tests, attacks or network discovery. It can replace hping, arpspoof, arp-sk, arping, p0f and even some parts of Nmap, tcpdump, and tshark).

大意就是:Scapy是一个强大的,用Python编写的交互式数据包处理程序,它能让用户发送、嗅探、解析,以及伪造网络报文,从而用来侦测、扫描和向网络发动攻击。Scapy可以轻松地处理扫描(scanning)、路由跟踪(tracerouting)、探测(probing)、单元测试(unit tests)、攻击(attacks)和发现网络(network discorvery)之类的传统任务。它可以代替hping,arpspoof,arp-sk,arping,p0f 甚至是部分的Nmap,tcpdumptshark 的功能。

Scapy实验运行环境和拓扑:

网络环境同前几篇:

几台交换机的管理ip地址为:

  • L3-1 192.168.56.10/24
  • L2-1 192.168.11.253/24
  • L2-2 192.168.12.253/24
  • L2-3 192.168.13.253/24
  • L2-4 192.168.14.253/24
  • L2-5 192.168.15.253/24

用户名: python
密 码: 123

scapy运行在win7或ubuntu上,安装很简单,网上有很多教程。

https://www.cnblogs.com/qingkongwuyun/p/8508733.html

测试环境:

ubuntu下:

win7下:

用ls()函数来查看scapy支持的网络协议, (由于输出内容太长,只截取部分以供参考)。

可以看到网工们耳熟能详的ARP, BOOTP, Dot1Q, DHCP, DNS, GRE, HSRP, ICMP, IP, NTP, RIP, SNMP, STP, PPPoE, TCP, TFTP, UDP等等统统都支持。

除了ls()外,还可以用lsc()函数来查看scapy的指令集(函数)。比较常用的函数包括arpcachepoison(用于arp毒化攻击,也叫arp欺骗攻击),arping(用于构造一个ARP的who-has包) ,send(用于发3层报文),sendp(用于发2层报文), sniff(用于网络嗅探,类似Wireshark和tcpdump), sr(发送+接收3层报文),srp(发送+接收2层报文)等等

这里还可以用使用ls()的携带参数模式,比如ls(IP)来查看IP包的各种默认参数。

实验1

实验目的:使用IP()函数构造一个目的地址为192.168.12.101(即拓扑中的PC2)的IP报文,然后用send()函数将该IP报文发送给PC2,在PC2上开启抓包软件以验证是否收到该报文。

a. 首先用IP()函数构造一个目的地址为192.168.12.101的IP报文,将它实例化给ip这个变量。

ip = IP(dst='192.168.192.168.12.101')

b. 用ls(ip)查看该IP报文的内容,可以发现src已经变为192.168.56.1(本机的IP),dst变为了192.168.12.101。 一个最基本的IP报文就构造好了。

ls(ip)

c. 构造好了IP报文(src=192.168.56.1, dst=192.168.12.101)后,我们就可以用send()这个函数来把它发送给PC2了。

为了验证PC2确实接收到了我们发送的报文,在PC2上开启抓包功能。

在scapy上输入 send(ip,iface='VirtualBox Host-Only Network')将该报文发出去,注意后面的iface参数用来指定端口,该参数可选。

抓包结果:

这时可以看到我们已经抓到了从192.168.56.1发来的IP报文,注意Protocol: IPv6 Hop-by-Hop Option (0),这是因为该包的proto位为0, 不代表任何协议。


实验2

实验目的:除了send()外,scapy还有个sendp()函数,两者的区别是前者是发送三层报文,后者则是发送二层报文,实验2将演示如何用sendp()来构造二层报文。

a. 用sendp()配合Ether()和arp()函数来构造一个ARP报文,命令如下

sendp(Ether(dst='ff:ff:ff:ff:ff:ff') / ARP(hwsrc = '01:23:45:67:89:01', psrc = '192.168.56.1', hwdst = 'ff:ff:ff:ff:ff:ff', pdst = '192.168.12.101') / 'abc', iface='VirtualBox Host-Only Network') 

这里我们构造了一个源MAC地址为01:23:45:67:89:01, 源IP地址为192.168.56.1, 目标MAC地址为ff:ff:ff:ff:ff:ff,目标IP地址为192.168.12.101,payload为abc的ARP报文。

b. 可以另开一个控制台,启用sniff()来抓包,并将抓包的内容实例化到data这个变量上。

>>> data = sniff()

使用show()命令监控本地的收发包情报

>>> data.show()

注意是arp包,所以不能跨网段,需要在56段监听才行:

可以看到该报文ARP部分的内容和ARP报文的结构完全一致

hardware type(HTPYE)为0x0001的时候,表示Ethernet

protocol type(PTPYE)为0x0800的时候,表示IPv4

hardware length (HLEN)为0x06的时候,表示MAC地址长度为6byte

protocol length(PLEN)为0x04的时候,表示IP地址长度为4byte

ARP包有request和response之分,request包的OPER(Opcode)位为0x0001 (也就是这里的who has), response包的OPER位为0x0002。

最后的payload位(padding)即为我们自己定制的内容'abc'。


实验3

实验目的:从实验1和实验2的例子可以看出:send()和sendp()函数只能发送报文,不能接收返回的报文。如果要想查看返回的3层报文,需要用到sr()函数,实验3将演示如何使用sr()函数。

a. 用sr()向PC2发一个ICMP包,可以看到返回的结果是一个tuple(元组),该元组里的元素是两个列表,其中一个列表叫Results(响应),另一个叫Unanswered(未响应)。

>>> sr(IP(dst = '192.168.43.1') / ICMP())

这里可以看到192.168.43.1响应了这个ICMP包,所以在Results后面的ICMP:显示1。

b. 如果向一个不存在的IP,比如192.168.43.2发ICMP包,那么这时会看到scapy在找不到该IP的MAC地址(因为目标IP 192.168.43.2和我们的主机192.168.43.1在同一个网段下,这里要触发ARP寻找目标IP对应的MAC地址)的时候,转用广播。当然广播也找不到目标IP,这里可以Ctrl+C强行终止。

sr(IP(dst = '192.168.43.2') / ICMP())

c. 我们可以将sr()函数返回的元组里的两个元素分别赋值给两个变量,第一个变量叫ans,对应Results(响应)这个元素,第二个变量叫unans,对应Unanswered(未响应)这个元素。

ans, unans = sr(IP(dst = '192.168.43.2') / ICMP())

d. 这里还可以进一步用show(), summary(), nsummary()等方法来查看ans的内容,这里可以看到192.168.43.114向192.168.43.1发送了echo-request的ICMP包,192.168.43.1向192.168.43.114回了一个echo-reply的ICMP包。

e. 如果想要查看该ICMP包更多的信息,还可以用ans[0](ans本身是个列表)来查看,因为这里我们只向192.168.2.11发送了一个echo-request包,所以用[0]来查看列表里的第一个元素。

ans[0]

可以看到ans[0]本身又是一个包含了两个元素的元组,我们可以继续用ans[0][0]和ans[0][1]查看这两个元素。

ans[0][0]
ans[0][1]


实验4

实验目的:实验3讲到了sr(),它是用来接收返回的3层报文。实验4将使用srp()来接收返回的2层报文。

a. 用srp()配合Ether()和ARP()构造一个arp报文,二层目的地址为ff:ff:ff:ff:ff:ff,三层目的地址为192.168.56.0/24, 因为我们是向整个/24网络发送arp, 耗时会很长,所以这里用timeout = 5,表示将整个过程限制在5秒钟之内完成,最后的iface参数前面讲过就不解释了。

ans, unans = srp(Ether(dst = "ff:ff:ff:ff:ff:ff") / ARP(pdst = "192.168.56.0/24"), timeout = 5, iface = "VirtualBox Host-Only Network")

b. 实验环境56段有5台设备,从上图可以看到我们收到了5个answers,符合我们的实验环境,下面用ans.summary()来具体看看到底是哪5个IP响应了我们的'who has'类型的arp报文。

ans.summary()

这里可以看到192.168.56.1, 192.168.56.100, 192.168.56.101, 192.168.56.10, 192.168.56.253响应了我们的'who has'类型的arp报文,并且能看到它们各自对应的MAC地址。

c. 用unans.summary()来查看那些没有回复'who has'类型arp报文的IP地址

unans.summary()


实验5

实验目的:使用tcp()函数构造四层报文,理解和应用RandShort(),RandNum()和Fuzz()函数。

a. 实验开始前,首先对外网络接口进行抓包。

b. 在scapy上使用ip()和tcp()函数来构造一个目的地IP为http://www.baidu.com,源端口为30,目的端口为80的TCP SYN报文。

>>> ans, unans = sr(IP(dst = "www.baidu.com") / TCP(sport = 30, dport = 80, flags = "S"))

c. TCP SYN报文发送后,监听网卡的抓包软件上发现了发出的报文

d. 在scapy上输入ans[0]继续验证从主机发出的包,以及远端收到的包。

ans[0]

e. TCP端口号除了手动指定外,还可以使用RandShort(), RandNum()和Fuzz()这几个函数来让scapy帮你自动生成一个随机的端口号,通常可以用作sport(源端口号)。

首先来看RandShort(),RandShort()会在1-65535的范围内随机生成一个TCP端口号,将上面的sport = 30 替换成 sport = RandShort()即可使用。

>>> ans, unans = sr(IP(dst = "www.baidu.com") / TCP(sport = RandShort(), dport = 80, flags = "S"))

这里可以看到RandShort()替我们随机生成了13116这个TCP源端口号

f. 如果你想指定scapy生成端口号的范围,可以使用RandNum(),比如你只想在1000-1500这个范围内生成端口号,可以使用RandNum(1000,1500)来指定,举例如下:

ans, unans = sr(IP(dst = "www.bing.com") / TCP(sport = RandNum(1000,1500), dport = 80, flags = "S"))

这里RandNum()帮我们生成了1246这个源端口号

由于我们指定的范围是1000-1500,很有可能和一些知名的端口号重复,这个时候会出现sport显示的不是端口号,而是具体的网络协议名字的情况。

g. 最后来讲下fuzz()函数,前面的RandShort()和RandNum()都是写在sport后面的(当然也可以写在dport后面用来随机生成目的端口号),用fuzz()的话则可以省略sport这部分,fuzz()会帮你检测到你漏写了sport,然后帮你随机生成一个sport也就是源端口号。

使用fuzz()的命令如下:

ans, unans = sr(IP(dst = "www.163.com") / fuzz(TCP(dport = 80, flags = "S")))

这里看到fuzz()函数已经替我们随机生成了7673这个源端口号


此后的内容为Scapy的实际应用,包括怎么使用Scapy进行TCP的SYN扫描、ACK扫描、FIN扫描、Xmas扫描, Null扫描,怎么使用Scapy执行TCP SYN flooding攻击,ARP欺骗攻击,DHCP饥饿攻击,怎么用Scapy探测rogue DHCP服务器等等。

实验6 -- TCP SYN扫描

实验目的:使用TCP SYN扫描交换机L2-1(192.168.11.101)的53(DNS), 80 (HTTP), 23 (FTP)端口,知道如何判断端口是被关闭了(closed)还是被过滤了(filtered),两者各自有什么特征。

实验原理:TCP三次握手的原理和过程相信大家都知道。根据RFC 793,当发送端的TCP SYN包发出后,大致会有下面四种情况发生:

  1. 目的端口在接收端打开(也可以说接收端正在侦听该端口),收到SYN包的接收端回复SYN/ACK包给发送端,收到SYN/ACK包的发送端此时知道目的端口是打开的(open)。
  2. 目的端口在接收端被关闭,收到SYN包的接收端回复RST包给发送端,告知发送端该目的端口已经被关闭了(closed)。
  3. 如果发送端和接收端之间有防火墙或者使用ACL进行包过滤的路由器,那么SYN包在到达接收端之前就被防火墙或路由器拦截下来,此时防火墙或路由器会回复一个类型3(Unreachable,不可达)的ICMP包(注意不再是TCP包)给发送端告知该目的端口已经被过滤了(filtered)。
  4. 如果ICMP在防火墙或路由器上被关闭了,这时SYN包会被防火墙、路由器"静悄悄"地丢弃,路由器和防火墙不会发送类型3的ICMP包告知发送端。此时发送端收不到任何回应(no response),这里我们同样可以判断该目的端口已经被过滤了(filtered)。

知道实验原理后来看代码:

#!/usr/bin/env python
# _*_ coding: utf-8 _*_
"""@author: antenna@license: (C) Copyright 2019, Antenna.@contact: lilyef2000@gmail.com@software: @file: synscan.py@time: 2019/3/30 3:11@desc:
"""
from scapy.all import *target = 'www.cnblog.com'ans, unans = sr(IP(dst = target) / TCP(sport = RandShort(), dport = [21, 80, 53], flags = "S"), timeout = 5)for sent, received in ans:if received.haslayer(TCP) and str(received[TCP].flags) == "SA":print("Port " + str(sent[TCP].dport) + " of " + target + " is OPEN!")elif received.haslayer(TCP) and str(received[TCP].flags) == "RA":print("Port " + str(sent[TCP].dport) + " of " + target + " is closed!")elif received.haslayer(ICMP) and str(received[ICMP].type) == "3":print("Port " + str(sent[TCP].dport) + " of " + target + " is filtered!")for sent in unans:print(str(sent[TCP].dport) + " is filtered!")

  • 这里我们使用sr()函数对'www.cnblog.com'做端口21,80,53的TCP SYN扫描(注意flags = "S"), timeout设为5秒。sr()函数返回的是一个元组,该元组下面有两个元素,一个是Results,一个是Unanswered,我们用ans来表示Results,也就是被响应的包,用unans来表示Unanswered,表示没有被响应的包。
ans, unans = sr(IP(dst = target) / TCP(sport = RandShort(), dport = [21, 80, 123], flags = "S"), timeout = 5)

  • ans和unans各自又含两个包,一个是发出去的包,一个是接收到的包,以ans[0][0]和ans[0][1]为例,第一个[0]表示抓到的第一个包,第二个[0]和[1]分别表示第一个包里发出的包和接收到的包。举例如下:
>>> target = 'www.cnblog.com'
>>> ans, unans = sr(IP(dst = target) / TCP(sport = RandShort(), dport = [21, 80, 53], flags = "S"),timeout = 5)
Begin emission:
Finished sending 3 packets.
.....***
Received 8 packets, got 3 answers, remaining 0 packets
>>> ans[0][0]  # (第一个包里发出的包)
<IP  frag=0 proto=tcp dst=150.129.42.39 |<TCP  sport=32179 dport=ftp flags=S |>>
>>> ans[0][1]  # (第一个包里接收到的包)
<IP  version=4 ihl=5 tos=0x0 len=72 id=6115 flags= frag=0 ttl=47 proto=icmp chksum=0xc70f src=150.12
9.42.39 dst=192.168.43.114 |<ICMP  type=dest-unreach code=host-prohibited chksum=0x1dc6 reserved=0 l
ength=0 nexthopmtu=0 |<IPerror  version=4 ihl=5 tos=0x0 len=44 id=1 flags= frag=0 ttl=46 proto=tcp c
hksum=0xe008 src=192.168.43.114 dst=150.129.42.39 |<TCPerror  sport=32179 dport=ftp seq=0 ack=0 data
ofs=6 reserved=0 flags=S window=8192 chksum=0xdd48 urgptr=0 options=[('MSS', 536)] |>>>>
>>>

  • 正因如此,所以下面for loop里的sent, received分别代表的是ans[0][0]、ans[0][1](抓到的第一个端口为22的包),ans[1][0]、ans[1][1](抓到的第二个端口为80的包)以及ans[2][0]、ans[2][1](抓到的第三个端口为53的包)里的内容
for sent, received in ans:

  • haslayer()函数返回的是布尔值,用来判断从接收端返回的包(received)里所含协议的类型,这里用来判断该received包是否包含TCP协议,并且该包里TCP的flag位是否为SA, SA代表SYN/ACK,如果这两个条件都满足,则说明该端口在接收端是打开的(Open),然后将该信息打印出来。
        if received.haslayer(TCP) and str(received[TCP].flags) == "SA":print("Port " + str(sent[TCP].dport) + " of " + target + " is OPEN!")

  • 同理,如果返回的包是TCP包,并且该TCP包的flag位为RA(RA表示Reset+),则说明该端口在接收端已经被关闭(closed),将该信息打印出来。
        elif received.haslayer(TCP) and str(received[TCP].flags) == "RA":print("Port " + str(sent[TCP].dport) + " of " + target + " is closed!")

  • 如果返回的包是ICMP包,并且该ICMP包的类型为3,则说明该端口被路由器或者防火墙过滤了(filtered),将该信息打印出来。
        elif received.haslayer(ICMP) and str(received[ICMP].type) == "3":print("Port " + str(sent[TCP].dport) + " of " + target + " is filtered!")

  • 最后,如果发送端没有收到任何回复(no response),我们同样可以判断该端口被路由器或者防火墙过滤了(filtered),将该信息打印出来。
for sent in unans:print(str(sent[TCP].dport) + " is filtered!")

执行代码看效果:

  • 目标网站如何,我也不清楚,查查看:
antenna@pythonmanager:~$ sudo python3 synscan.py
Begin emission:
.Finished sending 3 packets.
***
Received 4 packets, got 3 answers, remaining 0 packets
Port 21 of www.cnblog.com is OPEN!
Port 80 of www.cnblog.com is OPEN!
Port 53 of www.cnblog.com is OPEN!

  • 执行代码后,会发现所有端口都是打开的

可以尝试搭建环境,添加acl或防火墙来验证更多的效果。

实验7 -- 使用scapy分析路由

不说废话,直接上代码:

import os,sys,time,subprocess
import warnings,loggingwarnings.filterwarnings("ignore", category=DeprecationWarning)
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import traceroute
domains = "113.108.238.121 180.96.12.11"  #  raw_input('Please input one or more IP/domain: ')
target =  domains.split(' ')
dport = [80]
if len(target) >= 1 and target[0]!='':res,unans = traceroute(target,dport=dport,retry=-2)res.graph(target="> test.svg", type="svg")time.sleep(1)subprocess.Popen(r"C:ImageMagickconvert test.svg test.png", shell=True)
else:print("IP/domain number of errors,exit")

代码说明:

需要下载ImageMagick转换生成的svg格式的路由图到png格式。

代码执行结果:

Begin emission:
*********.*******.........*********Finished sending 60 packets.
*******.Begin emission:
Finished sending 28 packets.
Begin emission:
Finished sending 28 packets.Received 43 packets, got 32 answers, remaining 26 packets113.108.238.121:tcp80 180.96.12.11:tcp80
1  192.168.43.1    11    192.168.43.1    11
4  114.247.23.185  11    114.247.23.185  11
5  -                     221.219.202.213 11
6  202.96.12.21    11    202.96.12.5     11
7  -                     219.158.4.158   11
8  219.158.9.38    11    -
9  202.97.17.153   11    202.97.88.253   11
10 202.97.63.213   11    -
11 -                     202.102.69.14   11
12 -                     180.96.35.46    11
13 -                     180.96.65.162   11
14 -                     180.96.65.157   11
15 -                     180.96.65.10    11
16 -                     180.96.65.9     11
17 -                     180.96.65.10    11
18 -                     180.96.65.9     11
19 -                     180.96.65.10    11
20 -                     180.96.65.161   11
21 -                     180.96.65.10    11
22 -                     180.96.65.157   11
23 -                     180.96.65.162   11
24 -                     180.96.65.141   11
25 -                     180.96.65.162   11
26 -                     180.96.65.9     11
27 -                     180.96.65.10    11
28 -                     180.96.65.157   11
29 -                     180.96.65.10    11
30 -                     180.96.65.161   11 Process finished with exit code 0

怎么早没发现scapy还有这功能,太牛了。

cdt规约报文用程序解析_用Python运维网络(5):scapy相关推荐

  1. cdt规约报文用程序解析_用 Python 撸一个 Web 服务器第3章:使用 MVC 构建程序

    Todo List 程序介绍 我们将要编写的 Todo List 程序包含四个页面,分别是注册页面.登录页面.首页.编辑页面.以下分别为四个页面的截图. 注册页面: 登录页面: 首页: 编辑页面: 程 ...

  2. cdt规约报文用程序解析_程序员必备的学习笔记《TCP/IP详解(二)》

    把这三个协议放到一起学习是因为这三个协议处于同一层,ARP 协议用来找到目标主机的 Ethernet 网卡 Mac 地址,IP 则承载要发 送的消息.数据链路层可以从 ARP 得到数据的传送信息,而从 ...

  3. cdt规约报文用程序解析_DNS协议(报文解析)

    一.概述 DNS通常由其他应用层协议使用(如HTTP.SMTP.FTP),将主机名解析为IP地址,其运行在UDP(非连接)之上,使用53号端口.DNS除了提供主机名到IP地址转换外,还提供如下服务:主 ...

  4. python将学号与成绩匹配_用Python运维网络(1):基础知识

    近日发现一个专栏叫<网络行者>,作者是阿卜杜拉国王科技大学的Senior Network Consultant,读了一下他的<网络工程师的Python之路>系列文章,大受启发, ...

  5. shell python运维脚本_【Python运维】最简单的Python运维脚本

    背景 最近在Windows 10上使用Linux子系统,发现它有一个非常坑爹的特点:Linux子系统是没有开机关机状态的,每次进入Bash shell就自动载入,退出后Linux子系统的所有进程都会被 ...

  6. python运维开发前景_【python运维开发工程师就业前景怎么样|做python运维开发工程师有前途吗】-看准网...

    职位薪资:20000-40000 经验:5-10年 学历:本科 类型:全职 岗位职责: --负责腾讯云基础IAAS运维支撑平台设计和开发: --负责优化.改进运维支撑系统,并保证其安全高效稳定的运行: ...

  7. python运维开发培训_运维架构师-Python 自动化运维开发-014

    运维架构师-Python 自动化运维开发-014 九.标准数据类型 1.为什么编程语言中要有类型 类型有以下几个重要角色:对机器而言,类型描述了内存中的电荷是怎么解释的. 对编译器或者解释器而言,类型 ...

  8. shell for循环命令行_七年老运维实战中的 Shell 开发经验总结

    来源:良许Linux ID:liangxuxiansheng 无论是系统运维,还是应用运维,均可分为"纯手工"-> "脚本化"-> "自动 ...

  9. python运维面试题_百度linux运维开发工程师面试真题经历+试题

    看大企业的面试题,是非常重要的一件事,这样你能看到大神运维的一个情况,今天小编收集的第二个是百度,这个企业有非常多的人想进去,但是不容易进去,以下为百度面试题实录,内容从网络收集整理的,仅供大家学习参 ...

最新文章

  1. 写的函数符号表里没有_你有没有想过,C语言 main 函数到底为啥这么写?
  2. C++复习 第一部分c到c++的过度
  3. 嵌入式linux入门-常用命令介绍
  4. PL/SQL 语言 一
  5. c++大文本比较_Excel – 将文本转换为数值,第二种方法会的请举左手
  6. 对MATLAB课程教程的建议,关于MATLAB课程教学的几点探讨
  7. Axure RP 9基础教程(2)——交互样式
  8. android 通过platform.pk8,platform.x509.pem生成jks签名文件
  9. 登陆器生成登陆器不显示服务器列表,GeeM2单机登录器读取不到列表怎么办?
  10. Lenovo(IBM) 使用BoMC工具制作微码升级U盘刷新System x系列
  11. 登录北京住房公积金,使用已注册过账号
  12. 百面机器学习和百面深度学习-测试1
  13. python爬取网站m3u8视频,将ts解密成mp4,合并成整体视频
  14. 优思学院|六西格玛设计方法IDDOV是什么?
  15. Java HashMap中在resize()时候的rehash,即再哈希法的理解
  16. N2N组建虚拟局域网
  17. 论文阅读:CNN+GCN
  18. IBM服务器端口IP设置
  19. 会计学原理学习笔记——第三章——账户与复式记账(3.2资金筹集业务的核算——借入资本的核算)
  20. 苹果a10处理器_苹果“芯”基建简史:自己掌握不了核心技术 乔布斯也得干着急...

热门文章

  1. memcached协议
  2. Oracle触发器的语法详解
  3. 浅谈ADO.NET中的五个主要对象
  4. PHP类实例教程(七):析构函数与PHP的垃圾回收
  5. Redis 数据同步机制分析
  6. 反思代码能力提升方法:重构 多写 知识面
  7. 脉冲宽度调制pdm_0-500V可调0-30A高频脉冲电源广元厂家
  8. 开发一个出生年份的下拉选择框供用户选择_你的下拉式菜单设计对了吗?
  9. 用友老是显示服务器错误,客户端连服务器出现这样的错误框
  10. ERROR: Could not find a version that satisfies the requirement requests (from v ersions: none)