IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 多路icmp/tcp转发实验 -> 正文阅读

[网络协议]多路icmp/tcp转发实验

之前在cloudlab上做的都是单switch转发实验,这次试了多switch转发实验,并且在ping通(icmp转发)的基础上,增加了tcp转发,实验拓扑如上图

controller代码如下:

from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.ofproto import ether
from ryu.ofproto import inet
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
from ryu.lib.packet import arp
from ryu.lib.packet import ipv4
from ryu.lib.packet import icmp
from ryu.lib.packet import tcp
import random

class ExampleSwitch13(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
 
    def __init__(self, *args, **kwargs):
        super(ExampleSwitch13, self).__init__(*args, **kwargs)
        self.mac = {"h1e1":"00:04:23:b7:42:1f","h2e1":"00:04:23:b7:1d:f4","s1v1":"ae:0b:ab:2e:20:7f","s1v2":"0e:62:a1:df:6e:ad","s1v3":"ea:5c:c0:1e:3c:14","s1v4":"52:11:e8:91:5e:c5","s2v1":"a2:84:49:d3:fc:b5","s2v2":"de:34:a6:a1:a6:b6","s2v3":"ee:99:01:e0:e6:9a","s2v4":"76:7c:8b:c1:d2:3f","s3v1":"56:53:78:96:c6:d8","s3v2":"a2:0e:5f:be:57:f9","s4v1":"1e:95:c8:d1:92:74","s4v2":"7a:4b:5c:e6:11:9b","s5v1":"b6:2e:89:cb:48:5c","s5v2":"8e:00:b1:56:0f:99"}
        self.ip = {"h1e1":"10.10.1.1","h2e1":"10.10.8.2","s1v1":"10.10.3.1","s1v2":"10.10.4.1","s1v3":"10.10.1.2","s1v4":"10.10.2.1","s2v1":"10.10.7.1","s2v2":"10.10.8.1","s2v3":"10.10.5.1","s2v4":"10.10.6.1","s3v1":"10.10.5.2","s3v2":"10.10.2.2","s4v1":"10.10.6.2","s4v2":"10.10.3.2","s5v1":"10.10.4.2","s5v2":"10.10.7.2"}
        self.v2e = {"s1v1":5,"s1v2":6,"s1v3":7,"s1v4":8,"s2v1":5,"s2v2":6,"s2v3":7,"s2v4":8,"s3v1":3,"s3v2":4,"s4v1":3,"s4v2":4,"s5v1":3,"s5v2":4}
        self.s2s = {"s1":{"h1":"s1v3","s3":"s1v4","s4":"s1v1","s5":"s1v2"},"s2":{"h2":"s2v2","s3":"s2v3","s4":"s2v4","s5":"s2v1"},"s3":{"s1":"s3v2","s2":"s3v1"},"s4":{"s1":"s4v2","s2":"s4v1"},"s5":{"s1":"s5v1","s2":"s5v2"}}
    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        mac, ip = self.mac, self.ip
        v2e, s2s = self.v2e, self.s2s
        datapath = ev.msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
 
        # install the table-miss flow entry.
        match = parser.OFPMatch()
        actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)]
        self.add_flow(datapath, 0, match, actions)
        dpid = datapath.id
        print("******setting features for s" + str(dpid) + "******")
        self.send_set_config(datapath)
        if dpid == 1:
            #icmp:
            #h1->h2
            inport, outport, toport = s2s["s1"]["h1"], s2s["s1"]["s3"], s2s["s3"]["s1"]
            self.add_icmp_rules(datapath,v2e[inport],v2e[outport] ,ip["h1e1"],ip["h2e1"], mac[outport], mac[toport])
            inport, outport, toport = s2s["s1"]["s4"], s2s["s1"]["h1"], "h1e1"
            self.add_icmp_rules(datapath,v2e[inport],v2e[outport] ,ip["h2e1"],ip["h1e1"], mac[outport], mac[toport])
            #h2->h1
            #tcp:
            #h2->h1:
            inport, outport, toport = s2s["s1"]["s3"], s2s["s1"]["h1"], "h1e1"
            self.add_tcp_rules(datapath,v2e[inport],v2e[outport] ,ip["h2e1"],ip["h1e1"], mac[outport], mac[toport])
            inport, outport, toport = s2s["s1"]["s4"], s2s["s1"]["h1"], "h1e1"
            self.add_tcp_rules(datapath,v2e[inport],v2e[outport] ,ip["h2e1"],ip["h1e1"], mac[outport], mac[toport])
            inport, outport, toport = s2s["s1"]["s5"], s2s["s1"]["h1"], "h1e1"
            self.add_tcp_rules(datapath,v2e[inport],v2e[outport] ,ip["h2e1"],ip["h1e1"], mac[outport], mac[toport])

        elif dpid == 2:
            #icmp:
            #h1->h2
            inport, outport, toport = s2s["s2"]["s3"], s2s["s2"]["h2"], "h2e1"
            self.add_icmp_rules(datapath,v2e[inport],v2e[outport] ,ip["h1e1"],ip["h2e1"], mac[outport], mac[toport])
            #h2->h1
            inport, outport, toport = s2s["s2"]["h2"], s2s["s2"]["s4"], s2s["s4"]["s2"]
            self.add_icmp_rules(datapath,v2e[inport],v2e[outport] ,ip["h2e1"],ip["h1e1"], mac[outport], mac[toport])
            #tcp:
            #h1->h2:
            inport, outport, toport = s2s["s2"]["s3"], s2s["s2"]["h2"], "h2e1"
            self.add_tcp_rules(datapath,v2e[inport],v2e[outport] ,ip["h1e1"],ip["h2e1"], mac[outport], mac[toport])
            inport, outport, toport = s2s["s2"]["s4"], s2s["s2"]["h2"], "h2e1"
            self.add_tcp_rules(datapath,v2e[inport],v2e[outport] ,ip["h1e1"],ip["h2e1"], mac[outport], mac[toport])
            inport, outport, toport = s2s["s2"]["s5"], s2s["s2"]["h2"], "h2e1"
            self.add_tcp_rules(datapath,v2e[inport],v2e[outport] ,ip["h1e1"],ip["h2e1"], mac[outport], mac[toport])

        elif dpid == 3:
            #icmp:
            #h1->h2
            inport, outport, toport = s2s["s3"]["s1"], s2s["s3"]["s2"], s2s["s2"]["s3"]
            self.add_icmp_rules(datapath,v2e[inport],v2e[outport] ,ip["h1e1"],ip["h2e1"], mac[outport], mac[toport])
            #tcp:
            #h1->h2
            inport, outport, toport = s2s["s3"]["s1"], s2s["s3"]["s2"], s2s["s2"]["s3"]
            self.add_tcp_rules(datapath,v2e[inport],v2e[outport] ,ip["h1e1"],ip["h2e1"], mac[outport], mac[toport])
            #h2->h1
            inport, outport, toport = s2s["s3"]["s2"], s2s["s3"]["s1"], s2s["s1"]["s3"]
            self.add_tcp_rules(datapath,v2e[inport],v2e[outport] ,ip["h2e1"],ip["h1e1"], mac[outport], mac[toport])

        elif dpid == 4:
            #icmp:
            #h2->h1
            inport, outport, toport = s2s["s4"]["s2"], s2s["s4"]["s1"], s2s["s1"]["s4"]
            self.add_icmp_rules(datapath,v2e[inport],v2e[outport] ,ip["h2e1"],ip["h1e1"], mac[outport], mac[toport])
            #tcp:
            #h1->h2
            inport, outport, toport = s2s["s4"]["s1"], s2s["s4"]["s2"], s2s["s2"]["s4"]
            self.add_tcp_rules(datapath,v2e[inport],v2e[outport] ,ip["h1e1"],ip["h2e1"], mac[outport], mac[toport])
            #h2->h1
            inport, outport, toport = s2s["s4"]["s2"], s2s["s4"]["s1"], s2s["s1"]["s4"]
            self.add_tcp_rules(datapath,v2e[inport],v2e[outport] ,ip["h2e1"],ip["h1e1"], mac[outport], mac[toport])

        elif dpid == 5:
            #icmp:
            #tcp:
            #h1->h2
            inport, outport, toport = s2s["s5"]["s1"], s2s["s5"]["s2"], s2s["s2"]["s5"]
            self.add_tcp_rules(datapath,v2e[inport],v2e[outport] ,ip["h1e1"],ip["h2e1"], mac[outport], mac[toport])
            #h2->h1
            inport, outport, toport = s2s["s5"]["s2"], s2s["s5"]["s1"], s2s["s1"]["s5"]
            self.add_tcp_rules(datapath,v2e[inport],v2e[outport] ,ip["h2e1"],ip["h1e1"], mac[outport], mac[toport])

 
    #下发流表逻辑
    def add_flow(self, datapath, priority, match, actions):
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
 
        # construct flow_mod message and send it.
        inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)]
        mod = parser.OFPFlowMod(datapath=datapath, priority=priority, match=match, instructions=inst)
        datapath.send_msg(mod)
        	
    #对icmp单独的转发规则:h1-s1-s3-s4-h2/h2-s4-s3-s1-h1
    def add_icmp_rules(self, datapath, in_port, out_port, ips, ipd, macs, macd):
        priority = 20
        parser = datapath.ofproto_parser
        actions = [parser.OFPActionSetField(eth_src=macs),parser.OFPActionSetField(eth_dst=macd), parser.OFPActionOutput(out_port)]
        match = parser.OFPMatch(eth_type =ether.ETH_TYPE_IP,ip_proto=inet.IPPROTO_ICMP, in_port= in_port, ipv4_src=ips, ipv4_dst=ipd)
        self.add_flow(datapath, priority, match, actions)

    def add_tcp_rules(self, datapath, in_port, out_port, ips, ipd, macs, macd):
        priority = 20
        parser = datapath.ofproto_parser
        actions = [parser.OFPActionSetField(eth_src=macs),parser.OFPActionSetField(eth_dst=macd), parser.OFPActionOutput(out_port)]
        match = parser.OFPMatch(eth_type =ether.ETH_TYPE_IP,ip_proto=inet.IPPROTO_TCP, in_port= in_port, ipv4_src=ips, ipv4_dst=ipd)
        self.add_flow(datapath, priority, match, actions)

 
 
    def send_set_config(self, datapath):
        #把Normal先安装给datapath
        ofp = datapath.ofproto
        ofp_parser = datapath.ofproto_parser

        req = ofp_parser.OFPSetConfig(datapath, ofp.OFPC_FRAG_NORMAL, 256)
        datapath.send_msg(req)
 
    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def _pakcet_in_handler(self, ev):
        #如果发生了table-miss,就把对应的default forwarding安装上
        #如果miss的是icmp,就把对应的icmp forwarding安装上
        datapath = ev.msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        dpid = datapath.id
        msg = ev.msg
        print("****** receiving a packet in from " + str(dpid) + " ******")
        pkt = packet.Packet(msg.data)
        eth_pkt = pkt.get_protocol(ethernet.ethernet)
        in_port = msg.match["in_port"]
        ethertype = eth_pkt.ethertype
        if not eth_pkt:
            return
        pkt_arp = pkt.get_protocol(arp.arp)

        if ethertype == ether.ETH_TYPE_ARP:
            self.handle_arp(datapath, in_port, pkt)
            return
        if ethertype == ether.ETH_TYPE_IP:
            self.handle_ip(datapath, in_port, pkt)
            return

    def handle_arp(self, datapath, in_port, pkt):
        print("*************** handling arp packet in  from "+str(datapath.id)+" ****************")
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        # parse out the ethernet and arp packet
        eth_pkt = pkt.get_protocol(ethernet.ethernet)
        arp_pkt = pkt.get_protocol(arp.arp)
        # obtain the MAC of dst IP  
        dpid = datapath.id

        if dpid == 1:#sw1上的arp请求            
            arp_resolv_mac = self.mac["s1v1"]
        elif dpid == 2:#sw2上的arp请求
            arp_resolv_mac = self.mac["s2v4"]
        else:
            print("*** require handling arp on switch: dpid="+str(dpid)+" *****")
            return
        ### generate the ARP reply msg, please refer RYU documentation
        ### the packet library section
    # ARP Reply Msg
        ether_hd = ethernet.ethernet(dst = eth_pkt.src, 
                                src = arp_resolv_mac, 
                                ethertype = ether.ETH_TYPE_ARP);
        arp_hd = arp.arp(hwtype=1, proto = 2048, hlen = 6, plen = 4,
                         opcode = 2, src_mac = arp_resolv_mac, 
                         src_ip = arp_pkt.dst_ip, dst_mac = eth_pkt.src,
                         dst_ip = arp_pkt.src_ip);
        arp_reply = packet.Packet()
        arp_reply.add_protocol(ether_hd)
        arp_reply.add_protocol(arp_hd)
        arp_reply.serialize()
        
    # send the Packet Out mst to back to the host who is initilaizing the ARP
        actions = [parser.OFPActionOutput(in_port)];
        out = parser.OFPPacketOut(datapath, ofproto.OFP_NO_BUFFER, 
                                  ofproto.OFPP_CONTROLLER, actions,
                                  arp_reply.data)
        print("************** sending arp packet out **************")
        datapath.send_msg(out)

    def handle_ip(self, datapath, in_port, pkt):
        ipv4_pkt = pkt.get_protocol(ipv4.ipv4)
        if ipv4_pkt.proto == inet.IPPROTO_ICMP:
            self.handle_icmp(datapath, in_port, pkt)
        elif ipv4_pkt.proto == inet.IPPROTO_TCP:
            self.handle_tcp(datapath, in_port, pkt)

    def handle_tcp(self, datapath, in_port, pkt):
        print("************* handling tcp packet in ****************")
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        mac = self.mac
        v2e, s2s = self.v2e, self.s2s
        ipv4_pkt = pkt.get_protocol(ipv4.ipv4)
        src_ip = ipv4_pkt.src
        dst_ip = ipv4_pkt.dst
        tcp_pkt = pkt.get_protocol(tcp.tcp)
        src_port, dst_port = tcp_pkt.src_port, tcp_pkt.dst_port
        rand = random.randint(0, 2)
        inp = in_port
        if datapath.id == 1:
            if inp != v2e[s2s["s1"]["h1"]]:
                macs = mac[s2s["s1"]["h1"]]
                macd = mac["h1e1"]
                oup = v2e[s2s["s1"]["h1"]]
            elif rand == 0:
                macs=mac[s2s["s1"]["s3"]]
                macd=mac[s2s["s3"]["s1"]]
                oup = v2e[s2s["s1"]["s3"]]
            elif rand == 1:
                macs=mac[s2s["s1"]["s4"]]
                macd=mac[s2s["s4"]["s1"]]
                oup = v2e[s2s["s1"]["s4"]]
            else:
                macs=mac[s2s["s1"]["s5"]]
                macd=mac[s2s["s5"]["s1"]]
                oup = v2e[s2s["s1"]["s5"]]

        elif datapath.id == 2:
            if inp != v2e[s2s["s2"]["h2"]]:
                macs = mac[s2s["s2"]["h2"]]
                macd = mac["h2e1"]
                oup = v2e[s2s["s2"]["h2"]]
            elif rand == 0:
                macs=mac[s2s["s2"]["s3"]]
                macd=mac[s2s["s3"]["s2"]]
                oup = v2e[s2s["s2"]["s3"]]
            elif rand == 1:
                macs=mac[s2s["s2"]["s4"]]
                macd=mac[s2s["s4"]["s2"]]
                oup = v2e[s2s["s2"]["s4"]]
            else:
                macs=mac[s2s["s2"]["s5"]]
                macd=mac[s2s["s5"]["s2"]]
                oup = v2e[s2s["s2"]["s5"]]
        else:
            return

        match = parser.OFPMatch(eth_type = ether.ETH_TYPE_IP,
                                    ipv4_src = src_ip,
                                    ipv4_dst = dst_ip,
                                    tcp_dst = dst_port,
                                    tcp_src = src_port,
                                    ip_proto = inet.IPPROTO_TCP)

        actions = [parser.OFPActionSetField(eth_src=macs),parser.OFPActionSetField(eth_dst=macd),parser.OFPActionOutput(oup)]
        self.add_flow(datapath, 50, match, actions)



    def handle_icmp(self, datapath, in_port, pkt):
        print("************* handling icmp packet in ****************")
        #对于任意不是h1/h2互相传的icmp,这里的处理是switch一旦拿到这样的match不到的icmp packet,controller直接给一个回复(有时间想想是不是有更好的方法)
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        ipv4_pkt = pkt.get_protocol(ipv4.ipv4)
        src_ip, dst_ip = ipv4_pkt.src, ipv4_pkt.dst
        eth_pkt = pkt.get_protocol(ethernet.ethernet)
        icmp_pkt = pkt.get_protocol(icmp.icmp)
        #ethernet源和目的互换
        ether_hd = ethernet.ethernet(dst = eth_pkt.src, src = eth_pkt.dst, ethertype = ether.ETH_TYPE_IP)
        ipv4_hd = ipv4.ipv4(proto = 1, src = dst_ip, dst = src_ip)
        #icmp源和目的互换
        icmp_hd = icmp.icmp(type_=icmp.ICMP_ECHO_REPLY,code=icmp.ICMP_ECHO_REPLY_CODE,csum=0,data=icmp_pkt.data)

        icmp_reply = packet.Packet()
        icmp_reply.add_protocol(ether_hd)
        icmp_reply.add_protocol(ipv4_hd)
        icmp_reply.add_protocol(icmp_hd)
        icmp_reply.serialize()

        #从inport把包发出去
        actions = [parser.OFPActionOutput(in_port)];
        out = parser.OFPPacketOut(datapath, ofproto.OFP_NO_BUFFER, ofproto.OFPP_CONTROLLER, actions, icmp_reply.data)
        print("************* sending icmp packet out ***************")
        datapath.send_msg(out)
 

?需要注意:

(1)像上图那样设置了5个ovs后,如果不通过手动或者controller的方式添加流表(表项只有normal),因为topo中有环路,会发生icmp广播风暴。设置流表,包的转发规则,可以避免风暴

(2)可以登陆每个switch设置dpid,使用的命令如下:

ovs-vsctl set bridge br1 other-config:datapath-id=0000000000000001
ovs-vsctl set bridge br2 other-config:datapath-id=0000000000000002
ovs-vsctl set bridge br3 other-config:datapath-id=0000000000000003
ovs-vsctl set bridge br4 other-config:datapath-id=0000000000000004
ovs-vsctl set bridge br5 other-config:datapath-id=0000000000000005
ovs-ofctl show br2

设置controller的命令如下:

ovs-vsctl set-controller br1 tcp:155.98.38.235:6633
ovs-vsctl show

(3)上面的代码可以实现h1-h2之间的icmp/tcp转发,但功能不完善,switch之间的包的转发规则没有设置

(4)sw1和sw2是edge switch,本实验主要是靠tcp table-miss,第一个table-miss的packet被传到controller,controller通过随机数下发不同路径的流表项,来实现每次tcp流从不同的路线(sw3/sw4/sw5)来转发,所以tcp在edge switch上的转发规则是通过handle-tcp函数实现的,而不是在set-switches时实现的

(5)sw3/sw4/sw5是core switch,它们上面的tcp转发规则是在set-features阶段实现的

(6)icmp h1->h2通过sw3转发,h2->h1通过sw4转发

(7)cloudlab上的节点时间久了会自动关机

(8)做实验时用到的ep-vp对应表格,做了表格之后会比较方便写下发流表规则的逻辑

S1

ip

eport

emac

vport

vmac

num

toSwitch

10.10.3.1

enp10s3f0

00:04:23:b7:1a:b8

vp1

ae:0b:ab:2e:20:7f

5

s4

10.10.4.1

enp10s3f1

00:04:23:b7:1a:b9

vp2

0e:62:a1:df:6e:ad

6

s5

10.10.1.2

enp9s4f0

00:04:23:b7:20:46

vp3

ea:5c:c0:1e:3c:14

7

h1

10.10.2.1

enp9s4f1

00:04:23:b7:20:47

vp4

52:11:e8:91:5e:c5

8

s3

S2

ip

eport

emac

vport

vmac

num

toSwitch

10.10.7.1

enp10s3f0

00:04:23:b7:14:88

vp1

a2:84:49:d3:fc:b5

5

s5

10.10.8.1

enp10s3f1

00:04:23:b7:14:89

vp2

de:34:a6:a1:a6:b6

6

h2

10.10.5.1

enp9s4f0

00:04:23:b7:17:6c

vp3

ee:99:01:e0:e6:9a

7

s3

10.10.6.1

enp9s4f1

00:04:23:b7:17:6d

vp4

76:7c:8b:c1:d2:3f

8

s4

S3

ip

eport

emac

vport

vmac

num

toSwitch

10.10.5.2

enp10s3f0

00:04:23:b7:1e:26

vp1

56:53:78:96:c6:d8

3

s2

10.10.2.2

enp9s4f0

00:04:23:b7:1d:44

vp2

a2:0e:5f:be:57:f9

4

s1

S4

ip

eport

emac

vport

vmac

num

toSwitch

10.10.6.2

enp10s3f0

00:04:23:b7:12:ba

vp1

1e:95:c8:d1:92:74

3

s2

10.10.3.2

enp9s4f0

00:04:23:b7:13:02

vp2

7a:4b:5c:e6:11:9b

4

s1

S5

ip

eport

emac

vport

vmac

num

toSwitch

10.10.4.2

enp9s4f0

00:04:23:b7:23:a0

vp1

b6:2e:89:cb:48:5c

3

s1

10.10.7.2

enp9s4f1

00:04:23:b7:23:a1

vp2

8e:00:b1:56:0f:99

4

s2

h1

eport

emac

ip

enp10s3f1

00:04:23:b7:42:1f

10.10.1.1

h2

eport

emac

ip

enp9s4f0

00:04:23:b7:1d:f4

10.10.8.2

(9)一开始没ping通有可能是节点挂了,也有可能是逻辑有地方没写对,多试几次

(10)没了

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-02-01 20:56:37  更:2022-02-01 20:56:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/7 5:19:39-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码