本人
如果學院那邊改了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)