本人
如果学院那边改了project内容的话,估计这篇文章也不会有人看了。
如果还有人看,啊挪,最好是思考了之后再来看这篇文章。
先放福利:点我点我
这个project可能会比较重要,所以我会讲的详细些(自我感觉,甚至过于详细)。
项目内容如下:
1、用Kruskal算法给给定的网络计算一棵最小生成树;
2、用计算出的最小生成树完成广播业务;
3、将业务可视化展示。
网络如下:(数据都是助教大大给的。数据改变或拓扑结构改变的话,生成树可能会变。之后的project都会以这个网络为例。学院那边肯定会给你个更加复杂的拓扑,但是,这个例子懂了的fa,不管网络怎么变,只要过程没问题的话,结果还会是我们想要的)
7个结点

我们当时做project真正用到的是24个结点的。
24结点
上面那个网络中,每个交换机(switch)下面只下挂一个主机(host)哦。
网络知道了,怎么让Mininet创建了这个网络呢?
答一:B站是个学习网站。参考https://www.bilibili.com/video/BV1hT4y1374f。(视频里面小姐姐的声音好好听)
答二:什么你不喜欢这样拉?因为太麻烦了?emmm......还是看上面那个链接的视频,只要看懂这样拉来拉去之后,miniedit会创建出怎么样的py文件,然后根据创建出来的py文件中代码的格式进行自主的删减就行了。
答三:什么,你觉得上面的回答都不够帅气?代码量贼大贼麻烦??emmm......回忆一下当年的C/C++实验,我们是怎么拉取某个文件里面的信息的?以此类推,给实验的网络创建一个json文件,里面记录该网络的拓扑信息。大致长这样:
然后写一个可以拉取json文件的mininet创建网络脚本(我管它叫tp777.py)。
from mininet.topo import Topo
from collections import Counter
from mininet.link import TCLink
from mininet.log import setLogLevel
import os
import json
from mininet.net import Mininet
class Tp777(Topo):
def __init__(self):
Topo.__init__(self)
topoinfo=json.load(open('./topo7.json','r',encoding='utf-8-sig'))
num_host=topoinfo["host_no"]
num_switch=topoinfo["switch_no"]
link_list=topoinfo["links"]
for h in range(1,num_host+1):
self.addHost('h'+str(h))
for s in range(1,num_switch+1):
self.addSwitch('s'+str(s))
for link in link_list:
v=link["vertexs"]
d=link["delay"]
b=link["bw"]
self.addLink(v[0],v[1],delay=str(d)+'ms',bw=b)
topos={'mytopo':(lambda:Tp777())}
相信后浪们都懂上面写了啥。这个py文件要和对应的json放在一起哦。
json敲完了,py敲完了,文件怎么启动呢,阿巴阿巴。在tp777.py目录下叫出终端,然后
sudo mn --custom tp777.py --topo=mytopo --controller=remote,ip=127.0.0.1,port=6653 --mac
Mininet那边搞完了,开始搞Ryu的。
编写Ryu控制器逻辑脚本
学习那么久了,先看个视频放松放松吧。https://www.bilibili.com/video/BV1VJ41117vJ?p=17
照着敲一遍估计能大概了解Ryu是怎么工作的了。
如果俺没记错,上面那个视频中up主是把网络放在networkx中然后用networkx中的最短路算法。这个项目中我们也会用到networkx,但是仅仅只是用到它的可视化功能,不会使用到它自带的算法(u1s1,这样做助教肯定喂你吃鸡蛋,虽然视频没有错)
先告诉Ryu我们的网络长啥样。
这一步可以手敲二维列表来存网络:
也可以借鉴之前的方法,通过拉取json文件中的信息来存储网络信息:
self.Matrix就是用来存网络信息的二维列表。
之后编写Kruskal算法。这一步相信老师讲过,网上也有很多资料,这部分就不多说了。
上面所说的都是偏理论的部分,但是怎么把这些理论落实到仿真网络上面呢?
参考我之前分享的那个讲Ryu的视频。该视频讲到的
都不用修改。紫玛里,事件SwitchEnter和PachetIn是需要我们改写的。
SwitchEnter在检测到交换机加入时触发行为。那么,有人可能会问了,MIKU她老公呀,到底触发甚么行为了?
刚刚那个视频里面在这个事件下所做的事是拉取网络中的链路信息。其中我认为最重要的信息是端口信息,就是某个交换机是用哪个端口和另几台交换机连接的。除此之外,为了完成这个project我们还需在这个事件下调用我们刚刚写好的Kruskal算法(Kruskal算法所用到的网络信息通过json文件拉取)。Kruskal算法结束后,使用networkx的可视化功能画图(画图功能实现可以参考文章一开始的“福利”)。
下面写PacketIn事件下的行为。在SDN中交换机在没有对应流表项的时候,交换机会把接收到的数据包递给控制器,让控制器根据上传上来的包的信息给交换机下发对应的流表,这样交换机才知道这个数据包应该怎么走。他们的“对话”(该内容纯属虚构,如有雷同,纯属转生到了异世界)大致如下:
一号交换机:阿巴阿巴阿巴阿巴,嗯?!从西面飞来了一个包裹,让我看看哈,发送地址22号主机,目的地址33号主机。懵…不认识啊,33号主机在哪呀?交给控制器大佬,他肯定知道的。
控制器:哦,原来是佐田是33啊。根据我刚刚算出来的MST,你这个包应该交给你的西北面那个交换机。记住哦,以后收到这个目的地址的包都要这样做哦。
一号交换机:哇嘎哒!
之前不是在SwitchEnter里面算出了最小生成树和知道了网络中交换机之间的连接端口了吗。那么只要我们根据MST的信息,不就是可以知道某个交换机的哪些端口能用,哪些不能用了嘛。所以我们命令交换机,把收到的数据包,往所有没有被禁止使用的端口发送(数据包的入端口也算在本次行为的禁止端口中哦,不然数据包又回到之前的地方,这不就成环了嘛)
嗯?mouse写完了。那就运行一下吧。
也许你注意到了,打印出来的路径,怎么会有些是缺失了的?原因在于我们下发的流表所包含的信息不够多——里面只描述了是这个目的地址就应该这么走。但是如果我们把源地址也写进流表,打印的路径就不会缺失啦(这部分就交给聪明的你来写了)。
一些我遇到的坑:
1)Ryu逻辑文件放在桌面然后运行时候报错说,没有找到这个py文件:
把这个py文件放在这里试试
还是不行的话参考百度里面的其余做法。
2)怎么老是有这个报错?
1、是不是哪里多了或少了括号,冒号?多了分号?
2、多了或少了空格?多了或少了Tab?Python对缩进要求很高的哦。建议在你的文本编写器中开启空格与制表的可视化。
3)为什么拉取到的端口信息会缺失?
试试在SwitchEnter事件中加入sleep,并调试sleep的参数。
参考资料:
https://blog.csdn.net/qq_43734019/article/details/104334722?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-6&spm=1001.2101.3001.4242
https://www.bilibili.com/video/BV1hT4y1374f
https://www.bilibili.com/video/BV1VJ41117vJ?p=17
附录:
部分代码参考文章一开始的“福利”。
# encoding:utf-8
from ryu.base import app_manager
from ryu.ofproto import ofproto_v1_3
from ryu.controller.handler import set_ev_cls
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER,MAIN_DISPATCHER
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
from ryu.topology import event
from ryu.topology.api import get_switch,get_link
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
from collections import defaultdict
import time
import string
import json
class Topo(object): # algorithm
def __init__(self,logger):
self.Matrix = np.array(np.ones((7,7))*999999)
self.host_to_switch = np.array(np.ones((7,1))*8848)
self.logger=logger
def get_graph(self):
topoinfo=json.load(open('./topo7.json','r'))
link_list=topoinfo["links"]
for link in link_list: # get delay info and build a matrix to store delay info
v=link["vertexs"]
d=link["delay"]
if v[0][0]=='s' and v[1][0]=='s':
self.Matrix[int(v[0][1])-1][int(v[1][1])-1] = d
self.Matrix[int(v[1][1])-1][int(v[0][1])-1] = d
if v[0][0] == 'h':
self.host_to_switch[int(v[0][1])-1] = d
return self.Matrix
def get_nodenum(self,Matrix):
return len(Matrix)
def get_edgenum(self,Matrix):
count = 0
for j in range(len(Matrix)):
for i in range(j):
if Matrix[i][j] > 0 and Matrix[i][j] < 999999:
count += 1
return count
def kruskal(self, Matrix):
#get basic info of the matrix,which contains the topology
nodenum = self.get_nodenum(Matrix)
edgenum = self.get_edgenum(Matrix)
#result_list contains the edges (node1,node2,info of delay) on the mst
result_list = []
if nodenum <= 0 or edgenum < nodenum - 1:
return result_list
#however they are not sorted
edge_list = []
for i in range(nodenum):
for j in range(i+1, nodenum):
#the info of delay != 999999 means it is an edge in the topo
if Matrix[i][j] < 999999:
#(s1,s2,weight)
edge_list.append([i, j, Matrix[i][j]])
# sort by comparing the info of delay
edge_list.sort(key=lambda a: a[2])
a = [[i] for i in range(nodenum)]
# to make sure the edge in result_list cannot build a loop
for edge in edge_list:
for i in range(len(a)):
if edge[0] in a[i]:
m = i
if edge[1] in a[i]:
n = i
if m != n:
result_list.append(edge)
a[m] = a[m] + a[n]
a[n] = []
return result_list
#if we have edge(0,1) and (0,2) then links[0] = [1,2]
def comput_tree(self,src,result_list): # it returns adj-nodes
list1 = result_list
links = [[] for i in range(len(result_list) + 1)]
for i in range(len(result_list) + 1):
for edge in list1:
if i == edge[0]:
links[i].append(edge[1])
elif i == edge[1]:
links[i].append(edge[0])
return links
#it returns a list which cotains (previous node, node here we are, an adjacent node)
def compute_links(self,src,pre_src,links):
result = []
if len(links[src]) < 1:
return result
for node in links[src]:
if node != pre_src:
result.append((pre_src,src,node))
newresult = self.compute_links(node,src,links) # recursion
result.extend(newresult)
return result
# in this part we want to figure out which next switch is
#result from compute_links
def compute_next_switch(self,result):
next_switch = []
for i in range(7):
for j in range(6):
if result[j][1] == i:
k = result[j][2]
next_switch.append((i+1,k+1))
return next_switch
class KruskalTwo(app_manager.RyuApp): # event
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super (KruskalTwo, self).__init__(*args, **kwargs)
self.mac_to_port = {}
self.network = nx.DiGraph()
self.topology_api_app = self
self.paths = {}
self.topo = Topo(self.logger)
self.isKruskal_done = False
self.Matrix = np.array(np.ones((7,7))*999999)
self.linkList1 = []
self.links_node2node = []
self.next_switch = []
self.tree = [[] for i in range(7)] #if we have edge(0,1) and (0,2) then links[0] = [1,2]
self.port_can_be_used = [[] for i in range(7)]
self.count1 = 0
self.all_port = [[] for i in range(7)]
self.forbidden_port = [[] for i in range(7)]
self.passerby = {}
# handle switch features info
@set_ev_cls(ofp_event.EventOFPSwitchFeatures,CONFIG_DISPATCHER)
def switch_features_handler(self,ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser
# install a table-miss flow entry for each datapath
match = ofp_parser.OFPMatch()
actions = [ofp_parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath,0,match,actions)
def add_flow(self,datapath,priority,match,actions):
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser
# construct a flow_mod msg and send it to datapath
inst = [ofp_parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
mod = ofp_parser.OFPFlowMod(datapath=datapath,priority=priority,
match=match,instructions=inst)
datapath.send_msg(mod)
# get topology and store topology info into networkx
@set_ev_cls(event.EventSwitchEnter,[CONFIG_DISPATCHER,MAIN_DISPATCHER]) ## whlie a switch enters the net
def get_topology(self,ev):
# get links and add links now we can know that ,for example,src is 1, dst is 2, the port.no is the port in src that packet should get out
# get outports
links_list = get_link(self.topology_api_app,None) ### !!!!!!!!!
links = [(link.src.dpid,link.dst.dpid,link.src.port_no) for link in links_list] ### !!!!!!!!!
# print('links:',links)
# self.network.add_edges_from(links)
# print('tree:',self.tree)
if self.isKruskal_done == False: # the function in Topo should be done once then store all result
self.Matrix = self.topo.get_graph()
self.linkList1 = self.topo.kruskal(self.Matrix)
print 'edges on the tree:',self.linkList1
self.tree = self.topo.comput_tree(1-1,self.linkList1)
print 'adj-switch:',self.tree
print 'please wait......' # adj-switch
self.links_node2node = self.topo.compute_links(1-1,None,self.tree)
# print('links_node2node:',links_node2node)
self.next_switch = self.topo.compute_next_switch(self.links_node2node) # next_switch has 23 edges
# print('next_switch:',self.next_switch) # this is a list containing you-fang-xiang-de-edges
self.isKruskal_done = True
# in self.tree we get nodes' adj-nodes that in the tree, by using this message
#and links = [(link.src.dpid,link.dst.dpid,link.src.port_no) for link in links_list] , we can know the ports used
port_can_be_used = [[] for i in range(7)]
all_port = [[] for i in range(7)]
for m in range(7):
for s in range(len(links)):
if links[s][0] == m+1:
for t in range(len(self.tree[m])):
if links[s][1] == 1+self.tree[m][t]:
port_can_be_used[m].append(links[s][2])
# print('port_can_be_used:',port_can_be_used)
for m in range(7):
for s in range(len(links)):
if links[s][0] == m+1:
all_port[m].append(links[s][2])
self.count1 = self.count1+1
if self.count1 == 7:
self.port_can_be_used = port_can_be_used
self.all_port = all_port
for m in range(7):
for t in self.all_port[m]:
if t not in self.port_can_be_used[m]:
self.forbidden_port[m].append(t)
print 'self.forbidden_port:',self.forbidden_port
print 'Exit the networkx, if you would like to go on.'
#draw
G=nx.Graph()
weight={}
graph=[]
for i in range(len(self.Matrix)):
for j in range(i):
if self.Matrix[i][j] > 0 and self.Matrix[i][j] < 999999:
graph.append((i+1,j+1))
graph.append((j+1,i+1))
weight[(i+1,j+1)]=self.Matrix[i][j]
weight[(j+1,i+1)]=self.Matrix[i][j]
path=[]
for link1 in self.linkList1:
path.append((link1[0]+1,link1[1]+1))
path.append((link1[1]+1,link1[0]+1))
for gra in graph:
G.add_edge(gra[0],gra[1],color='black')
for pat in path:
G.add_edge(pat[0],pat[1],color='r')
pos=nx.spring_layout(G)
edges = G.edges()
colors = [G[u][v]['color'] for u,v in edges]
nx.draw_networkx_nodes(G,pos,node_size=233)
nx.draw_networkx_edges(G,pos,width=3,edge_color=colors)
nx.draw_networkx_labels(G,pos,font_size=10)
nx.draw_networkx_edge_labels(G,pos,weight,font_size=10)
plt.show()
#draw end
print 'Done!'
time.sleep(1.0) #mininet's add_nodes and add_links are not that fast,this function runs only if a switch enters,we may finish this function when all
#links have not been added
# handle packet_in msg
@set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER)
def packet_in_handler(self,ev):
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser
dpid = datapath.id
self.mac_to_port.setdefault(dpid,{})
pkt = packet.Packet(msg.data)
eth = pkt.get_protocol(ethernet.ethernet)
dst = eth.dst
src = eth.src
# print src
in_port = msg.match['in_port']
self.mac_to_port[dpid][src] = in_port
# print('mac_to_port',self.mac_to_port)
if dst in self.mac_to_port[dpid]:
out_port = self.mac_to_port[dpid][dst]
actions = [ofp_parser.OFPActionOutput(out_port)]
match = ofp_parser.OFPMatch(in_port=in_port,eth_dst=dst)
self.add_flow(datapath,1,match,actions)
self.passerby.setdefault(src,{})
self.passerby[src].setdefault(dst,[])
self.passerby[src][dst].append(dpid)
print("from h{} to h{}".format(int(src.split(':')[-1],16),int(dst.split(':')[-1],16)))
print 'switches passed by:',self.passerby[src][dst] #why sometimes the switches passby do not be printed?because
#the switches have been installed flow ,they donot have to
#ask the controller, and the event won't happen
# print('mac_to_port',self.mac_to_port[dpid])
else: #flood
actions = []
for i in datapath.ports:
if ((i not in self.forbidden_port[dpid-1]) and (i != in_port)):
actions.append(ofp_parser.OFPActionOutput(i))
out = ofp_parser.OFPPacketOut(datapath=datapath,buffer_id=ofproto.OFP_NO_BUFFER,in_port=in_port,
actions=actions,data=msg.data)
datapath.send_msg(out)