网络是什么-利用代码模拟理解网络的本质

引子

我个人是非计算机专业,从零开始学习的SDN,整个过程异常艰难。为此愿意降低大家学习SDN的难度,本系列的目标是,即使一个非计算机专业的人,只要有一定的编程能力,也能在学习了系列后,能够上手SDN相关的应用与实战。主要利用仿真技术来学习,以降低学习成本。

我们将使用第一性原理的思路,来开始这段旅程。首先第一个问题就是,网络的本质是什么?

网络的本质

如果要用一句话概括网络,网络就是将信息从A送到B。这和交通行业的本质很相似,交通的本质就是从A到B,只是将人、将物体运送。

如何理解现代互联网,是如何达成这个从A到B的过程的?我们平时访问一个网站,刷一段视频,并不知道这个信息从哪里来的。互联网的架构,将这些复杂性隐藏了起来。通常称为网络七层,你去看网络上所有的讲解网络七层的内容,大多是讲技术,但很少有讲这些技术为什么是这样的。

我尝试我的的经验和思考,来切开这个复杂系统。

水流的比喻

仔细观察下水,水能很快发现从高到底的那个最短路径。泼去出的水,总能流向那个最低的位置,并且,总会有一滴水,最先到达那个最低点。如果我们将这个模型抽象,就能得出:水总能找到最高到最低的最短路径。甚至你阻断了原本的最短路径,它也会找到新的最短路径。

人们甚至根据水的这个特性发明了Flood Fill算法。为什么会这样呢?仔细观察,水其实会浸湿更多的地方,而不是只浸湿最短路径。水其实是在一个广度优先搜索(BFS)。正是有了广度,也就知道了所有的路径。随着最短路径的流速增加,由于各种力学原因,从而会让更多的水流过这个路径。从宏观上看,就是水找到了最短路径。

请牢记这个图像,它将会贯穿整个互联网的系统。

交换机,最初也就是利用了这种原理,来进行通讯的。

交换机(Switch)

交换机,英文名字其实也可以翻译为开关。因为最初的交换机,真的就是一堆开关,用来切换线路。开关扳到B,就是给B发消息,扳到C,就是给C发消息。后来交换机出现,就取代了人工。

交换机是用寻址来实现寻找目的地。但如何找到目标地址的呢?可以模拟这个过程。

模拟

假设有个信息中转站S,收到信息后,无脑复制,全部发送给后续的其它节点。那么就一定能把信息发给目的地B。这就类似水流,就是Flood(或者叫广播,或者叫泛洪,名字只是个代号)。像水,不挑,所有的地方都流过。

6ac1a0836cc9490e983538eca8d5b487.png

但如果中间有很多节点,能否送到呢?肯定可以的。消息必然会流经所有的节点。

b5db143c0f244d728c031e627632453f.png

 那如何返回呢?如法炮制,所有的信息都复制给所有的节点。但这样效率太低了,并且消息会泄露给不相关的节点或者人(不用怀疑,这是真实网络上真实存在的问题)。

 

那么如何缓解这个问题呢?大家都走过陌生的路,摸索一次就行,下次就按照之前走通的路走。并不会再次陌生。交换机也是这样,如果消息返回了,那我就记录下从哪里返回的,下次直接照对应的位置发就行了呗。就是记下路,这谁都懂。

这真的可行吗?让我们用代码验证一下。

 

代码模拟

 

首先,模拟出几个节点,他们中间有N个交换机连接。

接下来,任何节点发来的消息,都无脑复制一份转发所有其它节点。

然后,在消息返回的时候,就记录下最先返回的那个位置,是从哪里返回的。最先的一定是最短路径,因为时间消耗是评价路径长短的唯一指标。既然如此,为什么不把最先发送来消息的位置也记录下来呢,这样也就知道了来源的最短路径。

 

按照这个思路可以将代码拆解为几部分:

1. 节点,也就是收发消息的来源和目标,一般代表人和计算机;

2. 交换机,用来中转消息的,并且用来寻找最短路径;

        1. 有记忆消息最先从哪里发送的功能。

        2. 有记忆消息最先从哪里返回的功能。

3. 整个网络的构建

4. 运行起来一切,收发消息。

以下是Python代码

from dataclasses import dataclass
import random
import time
from multiprocessing import Queue
import threading


def debug(*arg, **kwarg):
    return print(*arg, **kwarg)

@dataclass
class Message:
    src: int
    dst: int
    payload: str = 'ping'
    max_hop = 10

class Port():
    def __init__(self, num, parent=None):
        self.msgs = Queue()
        self.linked_sw = None
        self.linked_port = None
        self.num = num
        self.parent = parent

    def write(self, msg):
        self.msgs.put(msg)
    
    def send(self, msg):
        if self.linked_sw:
            self.linked_port.write(msg)
    
    def read(self):
        return self.msgs.get()
    
    def empty(self):
        return self.msgs.empty()
    

class Switch(threading.Thread):

    def __init__(self, name, ports=4, delay=100) -> None:
        super().__init__()
        self.name = name
        self.ports = [Port(i, self) for i in range(ports)]
        self.port_mapping = {}

        self.delay = delay # in ms
        self.mac = self.rnd_mac()
        self.running = True
        self.unit = 1000.0

    def __repr__(self) -> str:
        return "%s:%s"%(self.name, self.mac)

    def _replay(self, msg: Message):
        replay_dict = {
            '':'Please say something.',
            'ping':'pong'
        }
        return replay_dict.get(msg.payload, None)
        

    def rnd_mac(self):
        return random.randint(10000, 100000-1)
    
    def set_port(self, sw, this_n, that_n):
        if self.ports[this_n].linked_sw is not None:
            raise ValueError('SW:%s,起点端口%s被占用'%(self.name, this_n))
        
        if sw.ports[that_n].linked_sw is not None:
            raise ValueError('终点端口被占用')

        self.ports[this_n].linked_sw = sw
        sw.ports[that_n].linked_sw = self

        self.ports[this_n].linked_port = sw.ports[that_n]
        sw.ports[that_n].linked_port = self.ports[this_n]
    
    
    def remove_port(self, sw, this_n, that_n):
        if self.ports[this_n].linked_sw is None:
            raise ValueError('SW:%s,起点端口%s为空'%(self.name, this_n))
        
        if sw.ports[that_n].linked_sw is None:
            raise ValueError('终点端口为空')

        self.ports[this_n].linked_sw = None
        sw.ports[that_n].linked_sw = None

        self.ports[this_n].linked_port = None
        sw.ports[that_n].linked_port = None
        
        
    
    def broadcast(self, msg, exclude_port):
        for i in self.ports:
            if i is exclude_port:
                continue
            if i.linked_sw:
                i.send(msg)

    def write_port(self, msg, num):
        self.ports[num].write(msg)
    
    def send_port(self, msg, num):
        debug(self, 'send_port',msg, num)
        self.ports[num].send(msg)

    def replay(self, msg, port_num):
        ret = self._replay(msg)
        if ret is None:
            return
        msg = Message(
            src=msg.dst,
            dst=msg.src,
            payload=ret
        )
        self.write_port(msg, port_num)

    
    def handle_recv(self, port):
        
        msg = port.read()

        msg.max_hop -= 1
        if msg.max_hop <=0 :
            return

        debug('%s recvived:%s'%(self, msg))
        
        if msg.src not in self.port_mapping:
            self.port_mapping[msg.src] = port.num
            
        if msg.dst == self.mac:
            debug('Hit target %s, msg: %s'%(self, msg))
            self.replay(msg, port.num)
            return

        pn = self.port_mapping.get(msg.dst, None)

        if pn is None:
            self.broadcast(msg, exclude_port=port)
        else:
            
            if self.ports[pn].linked_sw is None:
                print('broadcast....')
                self.port_mapping[msg.dst] = None
                self.broadcast(msg, exclude_port=pn)
            self.send_port(msg, pn)

    def read_all(self):
        for p in self.ports:
            while not p.empty():
                self.handle_recv(p)

    def stop_running(self):
        self.running = False

    def run(self):
        while self.running:
            self.read_all()
            time.sleep(self.delay/self.unit)


sws = [Switch(name=i, delay=100) for i in range(10)]

sws[0].set_port(sws[1], 0, 0)
sws[1].set_port(sws[2], 1, 0)
sws[1].set_port(sws[3], 2, 0)
sws[1].set_port(sws[4], 3, 0)
sws[3].set_port(sws[5], 1, 0)
sws[3].set_port(sws[8], 2, 2)
sws[3].set_port(sws[6], 3, 0)
sws[4].set_port(sws[7], 2, 1)
sws[6].set_port(sws[7], 2, 2)
sws[7].set_port(sws[8], 3, 0)
sws[8].set_port(sws[9], 1, 2)


def run():
    for t in sws:
        debug(t)
        t.start()

    msg = Message(
        src=sws[0].mac,
        dst=sws[9].mac,
        payload='ping'
    )

    debug('----------第1次-------------')
    sws[0].ports[1].write(msg)

    time.sleep(5)
    debug('----------第2次-------------')
    sws[0].ports[1].write(msg)

    # 删除一个最短路径里的连接,看是否会自动寻找
    sws[3].remove_port(sws[8], 2, 2)
    debug('-'*30)
    msg = Message(
        src=sws[0].mac,
        dst=sws[9].mac,
        payload=''
    )
    sws[0].ports[1].write(msg)
    
    time.sleep(5)

    #再次发送看新路径
    debug('----------删除节点后-------------')
    msg = Message(
        src=sws[0].mac,
        dst=sws[9].mac,
        payload=''
    )
    sws[0].ports[1].write(msg)
    
    time.sleep(5)


    for t in sws:
        debug('stop:', t)
        t.stop_running()
        debug(t.is_alive())

run()

这段代码就是这些节点在通讯 :

19153bc60048469eb9d6863caa91bb6b.png

其输出如下,第一次通讯的时候,会有很多的冗余信息。但整体是能送达,并且返回的。这我们就不要强求什么了:

0:97497
1:77754
2:41864
3:67597
4:70479
5:77894
6:39914
7:63306
8:59644
9:45514
----------第1次-------------
0:97497 recvived:Message(src=97497, dst=45514, payload='ping')
1:77754 recvived:Message(src=97497, dst=45514, payload='ping')
4:70479 recvived:Message(src=97497, dst=45514, payload='ping')
2:41864 recvived:Message(src=97497, dst=45514, payload='ping')
3:67597 recvived:Message(src=97497, dst=45514, payload='ping')
5:77894 recvived:Message(src=97497, dst=45514, payload='ping')
7:63306 recvived:Message(src=97497, dst=45514, payload='ping')
8:59644 recvived:Message(src=97497, dst=45514, payload='ping')
8:59644 recvived:Message(src=97497, dst=45514, payload='ping')
9:45514 recvived:Message(src=97497, dst=45514, payload='ping')
Hit target 9:45514, msg: Message(src=97497, dst=45514, payload='ping')

....

7:63306 recvived:Message(src=45514, dst=97497, payload='pong')
7:63306 send_port Message(src=45514, dst=97497, payload='pong') 1
4:70479 recvived:Message(src=45514, dst=97497, payload='pong')
4:70479 send_port Message(src=45514, dst=97497, payload='pong') 0
1:77754 recvived:Message(src=45514, dst=97497, payload='pong')
1:77754 send_port Message(src=45514, dst=97497, payload='pong') 0
0:97497 recvived:Message(src=45514, dst=97497, payload='pong')
Hit target 0:97497, msg: Message(src=45514, dst=97497, payload='pong')

但在第二次,就直接使用了之前记忆的最短路径。直接使用的 0-1-3-8-9 这条路径,返回是9-8-3-1-0:

----------第2次-------------
------------------------------
0:25318 recvived:Message(src=25318, dst=68139, payload='ping')
0:25318 send_port Message(src=25318, dst=68139, payload='ping') 0
0:25318 recvived:Message(src=25318, dst=68139, payload='')
0:25318 send_port Message(src=25318, dst=68139, payload='') 0
1:77154 recvived:Message(src=25318, dst=68139, payload='ping')
1:77154 send_port Message(src=25318, dst=68139, payload='ping') 2
1:77154 recvived:Message(src=25318, dst=68139, payload='')
1:77154 send_port Message(src=25318, dst=68139, payload='') 2
3:33212 recvived:Message(src=25318, dst=68139, payload='ping')
3:33212 send_port Message(src=25318, dst=68139, payload='ping') 2
3:33212 recvived:Message(src=25318, dst=68139, payload='')
3:33212 send_port Message(src=25318, dst=68139, payload='') 2
8:21762 recvived:Message(src=25318, dst=68139, payload='ping')
8:21762 send_port Message(src=25318, dst=68139, payload='ping') 1
8:21762 recvived:Message(src=25318, dst=68139, payload='')
8:21762 send_port Message(src=25318, dst=68139, payload='') 1
9:68139 recvived:Message(src=25318, dst=68139, payload='ping')
Hit target 9:68139, msg: Message(src=25318, dst=68139, payload='ping')
9:68139 recvived:Message(src=25318, dst=68139, payload='')
Hit target 9:68139, msg: Message(src=25318, dst=68139, payload='')
9:68139 recvived:Message(src=68139, dst=25318, payload='pong')
9:68139 send_port Message(src=68139, dst=25318, payload='pong') 2
9:68139 recvived:Message(src=68139, dst=25318, payload='Please say something.')
9:68139 send_port Message(src=68139, dst=25318, payload='Please say something.') 2
8:21762 recvived:Message(src=68139, dst=25318, payload='pong')
8:21762 send_port Message(src=68139, dst=25318, payload='pong') 2
8:21762 recvived:Message(src=68139, dst=25318, payload='Please say something.')
8:21762 send_port Message(src=68139, dst=25318, payload='Please say something.') 2
3:33212 recvived:Message(src=68139, dst=25318, payload='pong')
3:33212 send_port Message(src=68139, dst=25318, payload='pong') 0
3:33212 recvived:Message(src=68139, dst=25318, payload='Please say something.')
3:33212 send_port Message(src=68139, dst=25318, payload='Please say something.') 0
1:77154 recvived:Message(src=68139, dst=25318, payload='pong')
1:77154 send_port Message(src=68139, dst=25318, payload='pong') 0
1:77154 recvived:Message(src=68139, dst=25318, payload='Please say something.')
1:77154 send_port Message(src=68139, dst=25318, payload='Please say something.') 0
0:25318 recvived:Message(src=68139, dst=25318, payload='pong')
Hit target 0:25318, msg: Message(src=68139, dst=25318, payload='pong')
0:25318 recvived:Message(src=68139, dst=25318, payload='Please say something.')
Hit target 0:25318, msg: Message(src=68139, dst=25318, payload='Please say something.')

其中删除了一个关键节点,改变了最短路径的走势,我们发现也能自适应。新的返回路径变成了9-8-7-4-1:

----------删除节点后-------------
0:66224 recvived:Message(src=66224, dst=66280, payload='')
0:66224 send_port Message(src=66224, dst=66280, payload='') 0
1:48729 recvived:Message(src=66224, dst=66280, payload='')
1:48729 send_port Message(src=66224, dst=66280, payload='') 2
3:53518 recvived:Message(src=66224, dst=66280, payload='')
6:13184 recvived:Message(src=66224, dst=66280, payload='')
7:33667 recvived:Message(src=66224, dst=66280, payload='')
7:33667 send_port Message(src=66224, dst=66280, payload='') 3
5:32869 recvived:Message(src=66224, dst=66280, payload='')
8:85701 recvived:Message(src=66224, dst=66280, payload='')
8:85701 send_port Message(src=66224, dst=66280, payload='') 1
9:66280 recvived:Message(src=66224, dst=66280, payload='')
Hit target 9:66280, msg: Message(src=66224, dst=66280, payload='')
9:66280 recvived:Message(src=66280, dst=66224, payload='Please say something.')
9:66280 send_port Message(src=66280, dst=66224, payload='Please say something.') 2
8:85701 recvived:Message(src=66280, dst=66224, payload='Please say something.')
7:33667 recvived:Message(src=66280, dst=66224, payload='Please say something.')
7:33667 send_port Message(src=66280, dst=66224, payload='Please say something.') 1
4:82756 recvived:Message(src=66280, dst=66224, payload='Please say something.')
4:82756 send_port Message(src=66280, dst=66224, payload='Please say something.') 0
1:48729 recvived:Message(src=66280, dst=66224, payload='Please say something.')
1:48729 send_port Message(src=66280, dst=66224, payload='Please say something.') 0
0:66224 recvived:Message(src=66280, dst=66224, payload='Please say something.')
Hit target 0:66224, msg: Message(src=66280, dst=66224, payload='Please say something.')

 于是我们成功的模拟了交换机的功能,并创造出来一个自适应网络。有以下特点:

1. 记忆功能,能记住最先接受到的消息,来自哪个位置;

2. 自适应,个别节点掉线,不影响整体网络的功能;

3. 仅首次使用flood,后续用最短路径通讯(除非网络结构发生改变)。

而其只是基于一个简单的水流模型加上一点纪录。

 

恭喜你,你发明了互联网。

 

你也可以直接运行这段模拟代码。 

 

9b2cb71c631b42379aba6df73d03f97f.png

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值