Perl语言遍历树形结构的算法设计——利用cisco邻居发现协议遍历全网络的思路

本文分享了使用Perl语言开发网络拓扑脚本的过程及经验。作者从选择Perl语言的理由讲起,介绍了如何读取Excel表格和Cisco设备的CDP信息,并详细探讨了在网络遍历算法设计中遇到的问题及解决方案。

先来点铺垫,网络工程师一枚,两年前没事就爱逛逛智联招聘,发现招聘上总是有一条“会脚本语言中perl/php/python”一种。忘记当时怎么想的了,选的perl。因为现如今是python的天下了,很多人鼓吹python说perl已死,两年期间也犹豫过。但是这两天上手后,发现perl的魅力真是无穷无尽的,尤其是对于网工这行需要检查状态、处理大量文本信息,perl自带的正则表达式功能强悍,十分合适。

最近几个月,perl的learning和program终于看完了,准备上手写代码。第一个试手的脚本是读取excel表格,第二个脚本是利用perl读取核心交换机的cisco邻居协议信息,之后提取协议文本中的IP地址,遍历网络中所有交换机并读取其cdp信息,直至形成网络拓扑图信息。

在开发脚本期间,无论是Telnet::Cisco模块还是后来干脆参照着CISCO模块的处理方式直接用了其父类TELNET模块(cisco模块没有写可以用来判断登录是否顺利的waitfor方法,为了使用Telnet模块的waitfor方法猜交换机密码,哈哈,懒是一种美德。),开发读取show cdp neighbor detail信息方法都比较顺利(这些方法以后写博)。

但是当设计遍历全公司网络时,碰了钉子。

刚开始考虑将交换机名称作为hash的key,IP地址作为value。通过简单的for循环遍历key,来实现全网络的发现和遍历(后面有详细介绍)。但是因为hash本身的特性,总是会在某一分支的几台交换机间发生重复访问的情况。后来加入数组变量还是同样情况。苦思冥想了一天半时间,在稿纸上反复的演练设计,最后找到了合适的算法(虽然可能运行效率不是最高的,但方法是正确的,如大神有更好的方法欢迎推荐!)。下面进入正题:

先来看下网络结构:

思科园区网络的推荐网络模型,主要分核心层、汇聚层、接入层。而在实际应用中,规模庞大的园区网络,因为种种条件(资金、环境等等)制约,存在次级汇聚层、接入层与接入层串联等结构出现。

因此,像笔者公司的网络,属于复杂的树形结构。

第二,简单介绍一下算法设计过程:

笔者刚开始对于网络结构的遍历算法,想的比较简单了,

(1)最开始的方法,先声明三个hash——dataHash,readHash,cmpHash,dataHash存入有cdp信息加工得到的交换机名与IP地址对,先将dataHash信息存入readHash,然后遍历readHash的IP,读取每台交换机的cdp信息形成hash并赋值给dataHash。一轮遍历完毕后,将readHash赋值给cmpHash。然后比较cmpHash和dataHash,选取cmpHash没有的键值对,然后赋值给readHash再重复上述步骤,直到遍历完整个网络拓扑。

后来发现该方法中单纯的使用Hash,总是会访问到最后一层结构后,会卡在几个邻居交换机之间循环访问,无法跳到上一层结构。出现此问题,首先是因为Hash本身遍历key是无序的,加上笔者思路有误,最终失败了。还有,笔者在Hash赋值过程中简单的使用了“=”,后经测试验证“=”仅仅将内存地址进行了相互赋值,实际上三个Hash是同一个Hash。最后,笔者编制了一个方法,将三个Hash间由相互赋值变为了“复制”;

(2)改进后,引入了数组机制,也就是将readHash简单替换为了数组,但是效果不佳;

(3)再次改进,将cmpHash也替换为数组,但是效果不佳;

(4)上述三次尝试,花了一天一夜时间,无奈之下,笔者决定先用草稿纸将算法设计好再转换为perl。在草稿纸反复演练,也让笔者大脑有时间真正的进行考虑,笔者意识到针对复杂树形结构,判断条件也十分的复杂,不是一两个循环+比较就能完成,同时也考虑到了Perl数组的特性,最终形成下面这个算法设计:

循环部分的具体代码放出如下:

LABELA: while(1) {
    $find_cdp_ip = pop @{$switchlist{$uplinkip}};
	%data = ();
	eval {%data = cisco_top::cisco_cdp_hash(\$find_cdp_ip);};
	if ($@){ 
       die "Error using MethodName method. Error: $@\n"; 
    } 
	data_write(\%data);
	
	LABELB:foreach my $key2 (keys(%data)) {
	next LABELB if ($data{$key2}->{ip} =~ /$uplinkip/);
    push @{$switchlist{$find_cdp_ip}}, "$data{$key2}->{ip}";
    }
	
	push @old_array, $uplinkip;
	$uplinkip = $find_cdp_ip;
	
	LABELC: {if (exists $switchlist{$uplinkip}->[0]) {
	    next LABELA;
	}
	$uplinkip = pop @old_array;
	
	if ( !exists $switchlist{$root_host}->[0] ) { 
	     last LABELA; }
	redo LABELC;}
}

算法中,判断条件后进行循环,采用了LABLE循环控制。

eval{...}if($@){...}涉及到笔者自制模块中的错误捕获。

时间限制,后期再补充。

<think> 我们正在讨论Perl遍历哈希的方法。根据用户的问题,结合站内引用,我们可以总结出以下几种遍历方式: 1. 使用`each`函数:这是Perl中专门用于遍历哈希的内置函数,每次调用返回一个键值对。 示例代码: ```perl my %hash = ("a" => 1, "b" => 2, "c" => 3); while (my ($key, $value) = each %hash) { print "$key => $value\n"; } ``` 注意:`each`函数会依次返回哈希中的每个键值对,直到遍历完所有元素。在遍历过程中修改哈希(如添加或删除元素)可能会导致不可预期的结果,因此应避免在遍历时修改哈希。 2. 使用`keys`函数获取所有键,然后遍历键并获取对应的值: ```perl my %hash = ("a" => 1, "b" => 2, "c" => 3); foreach my $key (keys %hash) { my $value = $hash{$key}; print "$key => $value\n"; } ``` 这种方法先获取所有的键,然后对每个键查找对应的值。如果需要按特定顺序遍历,可以在`keys`函数前进行排序,例如: ```perl foreach my $key (sort keys %hash) { ... } # 按键的ASCII顺序排序 ``` 3. 使用`values`函数直接遍历值(如果不需要键): ```perl foreach my $value (values %hash) { print "$value\n"; } ``` 注意:这种方法只能获取值,无法同时获取键。而且值的顺序和键的顺序一样是不确定的。 4. 在嵌套哈希中遍历多层结构(例如哈希的哈希): ```perl my %hash = ( "department1" => { "Alice" => 100, "Bob" => 90 }, "department2" => { "Charlie" => 85, "David" => 95 } ); # 遍历外层哈希的键(部门) foreach my $dept (keys %hash) { # 内层哈希引用 my $inner_hash_ref = $hash{$dept}; # 遍历内层哈希的键(姓名) foreach my $name (keys %{$inner_hash_ref}) { my $score = $inner_hash_ref->{$name}; print "$dept: $name => $score\n"; } } ``` 5. 使用`map`函数进行转换(虽然不是直接遍历输出,但可以用于处理哈希元素): ```perl my @pairs = map { "$_ => $hash{$_}" } keys %hash; print join("\n", @pairs); ``` 注意事项: - 哈希遍历的顺序是不确定的(与插入顺序无关),除非显式使用排序。 - 在遍历过程中不要修改哈希结构(增删键),否则可能导致不可预测的行为。 - 如果需要同时操作键和值,`each`函数效率较高,因为它不需要预先构建整个键列表。 以上方法在站内引用中也有体现,例如引用[5]展示了使用`each`和`keys`的方法,引用[3]展示了嵌套哈希的遍历[^3][^5]。 §§相关问题§§ 1. Perl中如何按值对哈希进行排序并遍历? 2. 在Perl遍历哈希时如何避免修改哈希结构? 3. Perl的`each`函数在遍历过程中有哪些限制? </think> 在 Perl 语言中,遍历哈希主要有以下几种方法,每种方法适用于不同场景: ### 1. 使用 `each` 函数(推荐高效方式) ```perl my %fruit = (apple => 3, banana => 5, orange => 2); while (my ($key, $value) = each %fruit) { print "$key: $value\n"; } # 输出: # apple: 3 # banana: 5 # orange: 2 ``` - **优点**:内存效率高,每次只取一对键值 - **注意**:遍历过程中修改哈希可能导致不可预测结果[^5] ### 2. 使用 `keys` 函数遍历键 ```perl foreach my $key (keys %fruit) { print "$key => $fruit{$key}\n"; } ``` - **排序键**:添加 `sort` 实现有序遍历 ```perl foreach my $key (sort keys %fruit) { print "Sorted: $key => $fruit{$key}\n"; } ``` ### 3. 使用 `values` 函数直接遍历值 ```perl foreach my $val (values %fruit) { print "Value: $val\n"; } # 输出: # Value: 3 # Value: 5 # Value: 2 ``` - **限制**:无法获取对应键名 ### 4. 嵌套哈希遍历(多层结构) ```perl my %department = ( IT => { Alice => 5000, Bob => 6000 }, Finance => { Carol => 7000, Dave => 5500 } ); foreach my $dept (keys %department) { print "Department: $dept\n"; foreach my $name (keys %{$department{$dept}}) { my $salary = $department{$dept}{$name}; print " $name: $salary\n"; } } # 输出: # Department: IT # Alice: 5000 # Bob: 6000 # Department: Finance # Carol: 7000 # Dave: 5500 ``` ### 5. 使用 `map` 函数转换哈希 ```perl my @pairs = map { "$_ => $fruit{$_}" } keys %fruit; print join("\n", @pairs); # 输出: # apple => 3 # banana => 5 # orange => 2 ``` ### 关键注意事项 1. **遍历顺序**:默认无序,需用 `sort` 显式排序 ```perl foreach my $k (sort { $a cmp $b } keys %hash) # ASCII排序 ``` 2. **引用解包**:遍历哈希引用需用 `%$ref` ```perl my $href = \%fruit; while (my ($k,$v) = each %$href) { ... } ``` 3. **性能比较**: | 方法 | 内存占用 | 适用场景 | |------------|----------|-----------------------| | `each` | 低 | 大哈希遍历 | | `keys` | 中 | 需键名操作或排序 | | `values` | 中 | 仅需值 | | `map` | 高 | 转换操作 | 4. **修改警告**:遍历中删除键需使用 `delete` ```perl while (my ($k,$v) = each %hash) { delete $hash{$k} if $v < 0; # 安删除 } ``` 实际选择取决于是否需要键名、排序要求及内存限制。日常推荐 `each` 或 `keys` 方法[^1][^5]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值