基于Simpy的通信网络仿真工具(一):数据包生成,转发和接收
前置知识
- 通信网络中的流量测量基本指标参考:18张图带你了解衡量网络性能的四大指标:带宽、时延、抖动、丢包
- Simpy是基于Python语言编写,实现离散时间仿真的库函数,关于Simpy的基础知识参考博客:python离散事件仿真库SimPy官方教程
- 关于利用Simpy进行的通信网络仿真的基本原理可参考教程:Basic Network Simulations and Beyond
in Python Introduction - 关于排队论知识可参考"wamg潇潇"的博客:排队论模型(一):基本概念、输入过程与服务时间的常用概率分布
本系列博客内容主要是在“Basic Network Simulations and Beyond in Python Introduction”的基础上进行创作。
Simpy安装
pip install simpy
数据包生成和接收仿真
包生成器仿真拓扑如下
Pg: 包生成器;Ps: 包接收器
# 包生成器仿真
import simpy
import random
def getPktSizes(env, host_num, min, max):
"""
定义: 生成制定大小的数据包
参数
----
env: 运行环境
host_num: 到达的主机数量
min: 数据包的最小值
max: 数据包的最大值
"""
if env.now < 10:
msg_list = []
for __ in range(host_num):
msg = random.randint(min, max)
msg_list.append((msg))
return msg_list
class Packet(object):
"""
功能:定义数据包的相关信息
参数
-------
time: 包发出时间
arrive_time: 包到达时间
size: 包大小
id: 数据包id
src: 源地址
dst: 目的地址
flow_id: 流id(用于大流量分包)
"""
def __init__(self, time, size, id, src="a", dst="z", flow_id=0):
self.time = time
self.arrive_time = None
self.size = size
self.id = id
self.src = src
self.dst = dst
self.flow_id = flow_id
def __repr__(self):
return "id: {}, src: {}, dst: {}, start time: {}, size: {}, arrive time: {}".\
format(self.id, self.src, self.dst, self.time, self.size, self.arrive_time)
class PacketGenerator(object):
"""
定义: 包生成器,用于按规则发出数据包
参数
-------
env : 仿真环境
src : 源地址
dst : 目的地址
interval : 数据包的间隔发送时间
initial_delay : 包生成器的间隔工作时间
finish : 包生成器工作的终止时间
"""
def __init__(self, env, src, dst_list, interval, initial_delay=0, finish=float("inf"), flow_id=0):
self.src = src
self.dst_list = dst_list
self.env = env
self.interval = interval
self.size_list = None
self.initial_delay = initial_delay
self.finish = finish
self.out = None
self.packets_sent = 0
self.action = env.process(self.run()) # starts the run() method as a SimPy process
self.flow_id = flow_id
def run(self):
#仿真过程
yield self.env.timeout(self.initial_delay)
while self.env.now < self.finish:
# 等待下一次开启传输
yield self.env.timeout(self.interval())
self.size_list = getPktSizes(self.env, 1, 0, 200)
if self.size_list is not None:
for i in range(len(self.dst_list)):
if self.size_list[i] > 0 :
self.packets_sent += 1
p = Packet(self.env.now, self.size_list[i], self.src + '-' + self.dst_list[i] + '-' + str(self.packets_sent), src=self.src, dst = self.dst_list[i], flow_id=self.flow_id)
self.out.put(p)
class PacketSink(object):
"""
定义: 包接收器,用于接收报文,并将延迟信息收集到等待列表中,并可以使用该列表查看流量的传输信息。
参数
-------
env: 仿真环境
debug : boolean 具体信息显示flag
rec_arrivals : 记录到达时间
absolute_arrivals : boolean 如果记录真实的绝对到达时间,否则记录连续到达之间的时间间隔。
rec_waits : boolean 记录每个数据包的等待时间
selector: 包选择器(规则匹配)
"""
def __init__(self, env, host, rec_arrivals=False, absolute_arrivals=False, rec_waits=True, debug=False, selector=None):
self.store = simpy.Store(env)
self.env = env
self.hostName = host # 接收节点地址
self.rec_waits = rec_waits
self.rec_arrivals = rec_arrivals
self.absolute_arrivals = absolute_arrivals
self.waits = [] # 记录等待时间
self.arrivals = [] # 记录到达时间
self.arrivals_id = [] # 记录到达的包名
self.arrivals_src = [] # 记录到达的包源地址
self.debug = debug
self.packets_rec = 0 # 记录接收包数量
self.bytes_rec = 0 # 记录接收字节数量
self.selector = selector # 按条件选择接收包
self.last_arrival = 0.0 # 最后到达的包
def put(self, pkt):
if not self.selector or self.selector(pkt):
now = self.env.now
if self.rec_waits:
self.waits.append(round(self.env.now - pkt.time, 3))
self.arrivals_id.append(pkt.id)
self.arrivals_src.append(pkt.src)
if self.rec_arrivals:
if self.absolute_arrivals:
self.arrivals.append(now)
else:
self.arrivals.append(now - self.last_arrival)
self.last_arrival = now
self.packets_rec += 1
self.bytes_rec += pkt.size
pkt.arrive_time = now
if self.debug:
print(self.hostName + " recv :", pkt)
# host1的发包间隔
def constArrival1():
return 1.5
# host2的发包间隔
def constArrival2():
return 2.0
# 定义包接收规则
def selector(pkt):
return pkt.src in ["EE", "SS"] and pkt.dst == "MM"
if __name__ == '__main__':
until_time = 10
env = simpy.Environment() # 创建simpy仿真环境
# 创建包生成器和包接收器
ps = PacketSink(env=env, host="MM", debug=True, selector=selector) # 开启信息打印
pg1 = PacketGenerator(env=env, src="EE", dst_list = ["MM"],interval=constArrival1)
pg2 = PacketGenerator(env=env, src="SS", dst_list = ["MM"], interval=constArrival2)
# 连接包生成器和包接收器
pg1.out = ps
pg2.out = ps
# 环境运行,终止时间为10
env.run(until=until_time)
仿真结果
MM recv : id: EE-MM-1, src: EE, dst: MM, start time: 1.5, size: 75, arrive time: 1.5
MM recv : id: SS-MM-1, src: SS, dst: MM, start time: 2.0, size: 84, arrive time: 2.0
MM recv : id: EE-MM-2, src: EE, dst: MM, start time: 3.0, size: 99, arrive time: 3.0
MM recv : id: SS-MM-2, src: SS, dst: MM, start time: 4.0, size: 7, arrive time: 4.0
MM recv : id: EE-MM-3, src: EE, dst: MM, start time: 4.5, size: 10, arrive time: 4.5
MM recv : id: SS-MM-3, src: SS, dst: MM, start time: 6.0, size: 94, arrive time: 6.0
MM recv : id: EE-MM-4, src: EE, dst: MM, start time: 6.0, size: 95, arrive time: 6.0
MM recv : id: EE-MM-5, src: EE, dst: MM, start time: 7.5, size: 103, arrive time: 7.5
MM recv : id: SS-MM-4, src: SS, dst: MM, start time: 8.0, size: 161, arrive time: 8.0
MM recv : id: EE-MM-6, src: EE, dst: MM, start time: 9.0, size: 183, arrive time: 9.0
由于没有设置包处理速度和传播速度,Ps接收包的时间和包发出时间一致。
单向端口转发仿真
拓扑连接关系图
# 单向Port仿真
import simpy
import functools
import random
class SwitchPortIn(object):
"""
功能: 交换机包接收端口,通过加入一个出口选择器,实现根据源地址,目的地址转发数据
参数
----------
env : 运行环境
trans_list : 出口列表(list)
valid_time : 流表有效时间(int)
rec_arrivals : boolean 记录到达时间
absolute_arrivals : boolean 如果为True,记录真实的绝对到达时间,否则记录连续到达之间的时间间隔。
rec_waits : boolean 记录每个数据包的等待时间
tables : 转发流表项
"""
def __init__(self, env, trans_list, valid_time, table, rec_arrivals=False, absolute_arrivals=False, rec_waits=True, debug=False):
self.env = env
self.out = [None for i in range(len(trans_list))] # 出口列表
self.trans_list = trans_list
self.table = dict() if table is None else table
self.byte_size = 0 # 当前队列中的字节数
self.debug = debug
self.valid_time = 100 if valid_time == 0 or valid_time is None else valid_time # 等待时间为0, 或者为空,设置为100
self.rec_waits = rec_waits
self.rec_arrivals = rec_arrivals
self.absolute_arrivals = absolute_arrivals
self.waits = [] # 记录等待时间
self.sends_time = [] # 记录发送时间
self.sends_id = [] # 记录发送的包名
self.sends_src = [] # 记录发送的包源地址
self.sends_dst = [] # 记录发送的包源地址
self.packets_rec = 0 # 记录发送包数量
self.bytes_rec = 0 # 记录发送字节数量
self.pkt_buffer = simpy.Store(env) # 包缓冲区
self.transflow_buffer = simpy.Store(env) # 流表缓冲区
self.action = env.process(self.waitTable())
# 执行转发
def transPkt(self, pkt):
pkt_key = pkt.src + '-' +pkt.dst
now = self.env.now
if self.rec_waits:
self.waits.append(self.env.now - pkt.time)
self.sends_id.append(pkt.id)
self.sends_src.append(pkt.src)
self.sends_dst.append(pkt.dst)
if self.rec_arrivals:
if self.absolute_arrivals:
self.sends_time.append(now)
else:
self.sends_time.append(now - self.last_arrival)
if pkt_key not in self.table: # 流表项不存在,将包放入缓冲区
return self.pkt_buffer.put(pkt)
target_port = self.trans_list.index(self.table[pkt_key]) # 通过包的源地址,目的地址查询流表中对应的转发端口
self.out[target_port].put(pkt)
self.packets_rec += 1
self.bytes_rec += pkt.size
if self.debug:
print("Port_in : ", pkt)
"""
流结构 : "src-dst" : [port_id, gen_time] 目标端口,生成时间
"""
def put(self, pkt):
pkt_key = pkt.src + '-' +pkt.dst
if pkt_key in self.table: # 流表项存在
now = self.env.now
if now - self.table[pkt_key] >= self.valid_time: # 流超时
print("流超时 : {}, time : {}".format(self.table[pkt_key], round(now, 3)))
# 删除超时流
self.table.pop(pkt_key)
else:
# 执行转发
self.transPkt(pkt)
else:
self.pkt_buffer.put(pkt)
def waitTable(self):
while True:
flow = yield self.transflow_buffer.get()
print(flow)
self.table[flow.pkt_key] = flow
pkt = yield self.pkt_buffer.get()
print(pkt)
pkt_key = pkt.src + '-' +pkt.dst
if pkt_key == flow.pkt_key:
self.transPkt(pkt)
else:
self.pkt_buffer.put(pkt)
def __repr__(self):
return "switch: {}".format(self.switch.address)
class SwitchPortOut(object):
"""
功能: 交换机发送端口,将数据包从当前端口传输到目的地
参数
----------
env : 运行环境
rate : float 发送速率
qlimit : integer (or None) 缓存队列大小
limit_bytes : 是否丢包
debug : 是否打印输出
"""
def __init__(self, env, address, rate, qlimit=None, limit_bytes=True, debug=False):
self.store = simpy.Store(env) # 包缓冲区
self.address = address # port地址
self.rate = rate # 发送速率
self.env = env # 运行环境
self.out = None # 目的连接方
self.packets_rec = 0 # 包接收数量
self.packets_drop = 0 # 包丢失数量
self.qlimit = qlimit # 队列大小
self.limit_bytes = limit_bytes
self.byte_size = 0 # 当前处理的数据量
self.debug = debug
self.busy = 0 # 忙绿标志位
self.queue = [] # 记录队列
self.out_drop = [] # 记录丢包
self.action = env.process(self.run()) # starts the run() method as a SimPy process
def run(self):
while True:
pkt = (yield self.store.get())
self.busy = 1
self.byte_size -= pkt.size # 这段时间能够处理的数据量
yield self.env.timeout(pkt.size*8.0/self.rate) # 发送时延
# yield self.env.timeout(distance/(2/3*c)) # 传播时延
self.out.put(pkt)
self.queue.remove(pkt)
self.busy = 0
if self.debug:
print("Port_out : ", pkt)
def put(self, pkt):
self.packets_rec += 1
tmp_byte_count = self.byte_size + pkt.size
# 无限长队列(无丢包)处理
if self.qlimit is None:
self.byte_size = tmp_byte_count
return self.store.put(pkt)
# 丢包处理,情况1:包大小超限制,直接丢弃
if self.limit_bytes and tmp_byte_count >= self.qlimit:
self.packets_drop += 1
self.out_drop.append(pkt)
return
# 情况2:队列满情况,直接丢弃
elif not self.limit_bytes and len(self.store.items) >= self.qlimit-1:
self.packets_drop += 1
self.out_drop.append(pkt)
# 正常转发
else:
self.byte_size = tmp_byte_count
self.queue.append(pkt)
return self.store.put(pkt)
class Port(object):
"""
功能: 接收和转发全功能端口,实现端口的接收和转发
参数
------
env: 运行环境
port_id: 端口的id
trans_list: 内部端口的连接列表, A_in --> B_out, C_out
table: 转发规则表, src-dst : port_out
valid_time: 流表有效时间
send_rate: out端口的传输速率
queue_limit: 队列最大长度
is_limit: 是否丢包
debug: 输出执行信息
"""
def __init__(self, env, port_id, trans_list, table=None, valid_time=None,
send_rate=0, queue_limit=None, is_limit=True, debug=False) -> None:
self.env = env
self.switch_id = None
self.port_id = port_id
self.queue = [] # 端口队列记录
self.drop = [] # 端口丢包记录
self.in_port = SwitchPortIn(env=env, trans_list=trans_list, valid_time=valid_time,
table=table, debug=debug)
self.out_port = SwitchPortOut(env=env, address=None, rate=send_rate, qlimit=queue_limit,
limit_bytes=is_limit, debug=debug)
# 设置转发表(判断包应该转发到哪个port的out)
def setTable(self, table):
self.in_port.table = table
# 设置out端口的连接对象
def setOutObj(self, out):
self.out_port.out = out
# 设置in端口的连接对象
def setTransPort(self, target_list):
for i in range(len(target_list)):
self.in_port.out[i] = target_list[i].out_port
# 获取out端口的队列
def getQueue(self):
return self.out_port.queue
# 输出out端口的丢包情况
def getOutDrop(self):
return self.out_port.out_drop
def __repr__(self):
return "switch_address :{}, port_id :{}".format(self.switch_id, self.port_id)
def constArrival():
return 1.0 # time interval
def selector(pkt):
return pkt.src in ["EE"] and pkt.dst == "MM"
def getTransList1(): # 2 代表 目标Port的编号 从1开始
return [2]
def getTransList2():
return [1]
def getTable1():
return {"EE-MM": 2}
def getTable2():
return {"MM-EE": 1}
if __name__=='__main__':
samp_dist = functools.partial(random.expovariate, 1.0)
env = simpy.Environment() # Create the SimPy environment
ps = PacketSink(env=env, host="MM", debug=True, rec_arrivals=True,
absolute_arrivals=True, selector=selector) # debugging enable for simple output
pg = PacketGenerator(env=env, src="EE", dst_list=["MM"],
interval=constArrival) # 不加括号表示,调用函数体,加括号表示调用结果
pt1 = Port(env=env, port_id = 1, trans_list=getTransList1(), table=getTable1(),
send_rate=400, queue_limit=301, debug=False)
pt2 = Port(env=env, port_id = 2, trans_list=getTransList2(), table=getTable2(),
send_rate=400, queue_limit=301, debug=False) # rate 是波特率
pg.out = pt1.in_port # pg.out --> pt1.in
pt1.setTransPort(target_list = [pt2]) # pt1.in --> pt2.out
pt2.setOutObj(ps) # pt2.out --> ps.in
env.run(until=15)
print("waits: {}".format(ps.waits))
print("sink arrival times: {}".format(ps.arrivals))
print("received: {}, dropped {}, sent {}".format(
ps.packets_rec, pt2.out_port.packets_drop, pg.packets_sent))
print("received ids: {}".format(ps.arrivals_id))
print("queue: {}".format([pkt.id for pkt in pt2.out_port.queue]))
print("drop : {}".format([pkt.id for pkt in pt2.out_port.out_drop]))
运行结果:
MM recv : id: EE-MM-1, src: EE, dst: MM, start time: 1.0, size: 5, arrive time: 1.1
MM recv : id: EE-MM-2, src: EE, dst: MM, start time: 2.0, size: 140, arrive time: 4.8
MM recv : id: EE-MM-3, src: EE, dst: MM, start time: 3.0, size: 143, arrive time: 7.66
MM recv : id: EE-MM-5, src: EE, dst: MM, start time: 5.0, size: 137, arrive time: 10.4
MM recv : id: EE-MM-6, src: EE, dst: MM, start time: 6.0, size: 70, arrive time: 11.8
MM recv : id: EE-MM-7, src: EE, dst: MM, start time: 7.0, size: 7, arrive time: 11.94
MM recv : id: EE-MM-8, src: EE, dst: MM, start time: 8.0, size: 149, arrive time: 14.92
waits: [0.1, 2.8, 4.66, 5.4, 5.8, 4.94, 6.92]
sink arrival times: [1.1, 4.8, 7.66, 10.4, 11.8, 11.94, 14.92]
received: 7, dropped 1, sent 9
received ids: ['EE-MM-1', 'EE-MM-2', 'EE-MM-3', 'EE-MM-5', 'EE-MM-6', 'EE-MM-7', 'EE-MM-8']
queue: ['EE-MM-9']
drop : ['EE-MM-4']
在数据包生成与接收的基础上,增加端口的转发和拓扑连接,实现数据包的成功转发和丢包,排队等。
欢迎通过企鹅(488128665)进行交流!
以上仿真内容代码下载:基于Simpy/Python的通信网络仿真工具