5.4.1 Step 1: Search
The search step is an extended version of the corresponding code for BST insertion in <BST item insertion function 32>. The earlier code had only two variables to maintain: the current node the direction to descend from p. The AVL code does this, but it maintains some other variables, too. During each iteration of the for loop, p is the node we are examining, q is p's parent, y is the most recently examined node with nonzero balance factor, z is y's parent, and elements 0...k - 1 of array da[] record each direction descended, starting from z, in order to arrive at p. The purposes for many of these variables are surely uncertain right now, but they will become clear later.
148. <Step 1: Search AVL tree for insertion point 148> =
z = (struct avl_node *) &tree->avl_root;
y = tree->avl_root;
dir = 0;
for (q = z, p = y; p != NULL; q = p, p = p->avl_link[dir])
{
int cmp = tree->avl_compare (item, p->avl_data, tree->avl_param);
if (cmp == 0)
return &p->avl_data;
if (p->avl_balance != 0)
z = q, y = p, k = 0;
da[k++] = dir = cmp > 0;
}
This code is included in 146.
posted @
2010-10-05 01:40 sohu2000000 阅读(865) |
评论 (0) |
编辑 收藏
5.3 Operations
Now we'll implement for AVL trees all the operations that we did for BSTs. Here's the outline. Creation and search of AVL trees is exactly like that for plain BSTs, and the generic table functions for insertion convenience, assertion, and memory allocation are still relevant, so we just reuse the code. Of the remaining functions, we will write new implementations of the insertion and deletion functions and revise the traversal and copy functions.
145. <AVL functions 145> =
<BST creation function; bst => avl 30>
<BST search function; bst => avl 31>
<AVL item insertion function 146>
<Table insertion convenience functions; tbl => avl 592>
<AVL item deletion function 164>
<AVL traversal functions 178>
<AVL copy function 185>
<BST destruction function; bst => avl 84>
<Default memory allocation functions; tbl => avl 6>
<Table assertion functions; tbl => avl 594>
This code is included in 143.
posted @
2010-10-05 01:39 sohu2000000 阅读(831) |
评论 (0) |
编辑 收藏
5.1 Balancing Rule
A binary search tree is an AVL tree if the difference in height between the subtrees of each of its nodes is between -1 and +1. Said another way, a BST is an AVL tree if it is an empty tree or if its subtrees are AVL trees and the difference in height between its left and right subtree is between -1 and +1.
Here are some AVL trees:
![[Click here for plain-text rendering]](http://www.stanford.edu/~blp/avl/libavl.html/avlex.png)
These binary search trees are not AVL trees:
![[Click here for plain-text rendering]](http://www.stanford.edu/~blp/avl/libavl.html/notavlex.png)
In an AVL tree, the height of a node's right subtree minus the height of its left subtree is called the node's balance factor (see balance factor). Balance factors are always -1, 0, or +1. They are often represented as one of the single characters -, 0, or +. Because of their importance in AVL trees, balance factors will often be shown in this chapter in AVL tree diagrams along with or instead of data items. Here are the AVL trees from above, but with balance factors shown in place of data values:
![[Click here for plain-text rendering]](http://www.stanford.edu/~blp/avl/libavl.html/avlbalex.png)
See also: [Knuth 1998b], section 6.2.3.
posted @
2010-10-05 01:38 sohu2000000 阅读(799) |
评论 (0) |
编辑 收藏
5 AVL Trees
In the last chapter, we designed and implemented a table ADT using binary search trees. We were interested in binary trees from the beginning because of their promise of speed compared to linear lists.
But we only get these speed improvements if our binary trees are arranged more or less optimally, with the tree's height as small as possible. If we insert and delete items in the tree in random order, then chances are that we'll come pretty close to this optimal tree.1
In “pathological” cases, search within binary search trees can be as slow as sequential search, or even slower when the extra bookkeeping needed for a binary tree is taken into account. For example, after inserting items into a BST in sorted order, we get something like the vines on the left and the right below. The BST in the middle below illustrates a more unusual case, a “zig-zag” BST that results from inserting items from alternating ends of an ordered list.
Unfortunately, these pathological cases can easily come up in practice, because sorted data in the input to a program is common. We could periodically balance the tree using some heuristic to detect that it is “too tall”. In the last chapter, in fact, we used a weak version of this idea, rebalancing when a stack overflow force it. We could abandon the idea of a binary search tree, using some other data structure. Finally, we could adopt some modifications to binary search trees that prevent the pathological case from occurring.
For the remainder of this book, we're only interested in the latter choice. We'll look at two sets of rules that, when applied to the basic structure of a binary search tree, ensure that the tree's height is kept within a constant factor of the minimum value. Although this is not as good as keeping the BST's height at its minimum, it comes pretty close, and the required operations are much faster. A tree arranged to rules such as these is called a balanced tree (see balanced tree). The operations used for minimizing tree height are said to rebalance (see rebalance) the tree, even though this is different from the sort of rebalancing we did in the previous chapter, and are said to maintain the tree's “balance.”
A balanced tree arranged according to the first set of rebalancing rules that we'll examine is called an AVL tree (see AVL tree), after its inventors, G. M. Adel'son-Vel'skii< and E. M. Landis. AVL trees are the subject of this chapter, and the next chapter will discuss red-black trees, another type of balanced tree.
In the following sections, we'll construct a table implementation based on AVL trees. Here's an outline of the AVL code:
142. <avl.h 142> =
<License 1>
#ifndef AVL_H
#define AVL_H 1
#include <stddef.h>
<Table types; tbl => avl 14>
<BST maximum height; bst => avl 28>
<BST table structure; bst => avl 27>
<AVL node structure 144>
<BST traverser structure; bst => avl 61>
<Table function prototypes; tbl => avl 15>
#endif /* avl.h */
143. <avl.c 143> =
<License 1>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "avl.h"
<AVL functions 145>
See also: [Knuth 1998b], sections 6.2.2 and 6.2.3; [Cormen 1990], section 13.4.
Footnotes
[1] This seems true intuitively, but there are some difficult mathematics in this area. For details, refer to [Knuth 1998b] theorem 6.2.2H, [Knuth 1977], and [Knuth 1978].
posted @
2010-10-05 01:32 sohu2000000 阅读(859) |
评论 (0) |
编辑 收藏
在pthreads标准库里面, 库本身并不提供对信号量的支持,因为POSIX标准并没有对信号量做出定义,但是如果你一定要使用信号量来完成程序的话,那么所有的内容都会包含在semphore.h文件里
请注意:不要混合着使用系统V自带的信号量,系统V的信号量位于sys/sem.h文件中
1
#include <semaphore.h>
2
#include <pthread.h>
3
#include <stdio.h>
4
#define THREADS 20
5
sem_t OKToBuyMilk;
6
int milkAvailable;
7
void* buyer(void *arg)
8

{
9
//P()
10
sem_wait(&OKToBuyMilk);
11
if(!milkAvailable)
12
{
13
//Buy Some Milk
14
++milkAvailable;
15
}
16
//V()
17
sem_post(&OKToBuyMilk);
18
return;
19
}
20
int main(int argc, char* argv[])
21

{
22
int i;
23
pthread_t threads[THREADS];
24
milkAvailable=0;
25
//initlization the semphore with a value of 1.
26
//Note the second argument: passing zero denotes
27
//that the semaphore is shared between threads (and
28
//not processes)
29
if(sem_init(&OKToBuyMilk,0,1))
30
{
31
printf("Could not initialization a semphore\n");
32
return -1;
33
}
34
for(i=0;i<THREADS; ++i)
35
{
36
if(pthread_create(&threads[i],NULL,&buyer,NULL))
37
{
38
printf("Could not create thread %d\n",i);
39
return -1;
40
}
41
}
42
for(i=0;i<THREADS; ++i)
43
{
44
if(pthread_join(threads[i],NULL))
45
{
46
printf("Could not join thread %d\n",i);
47
return -1;
48
}
49
}
50
sem_destroy(&OKToBuyMilk);
51
//Make sure we don't have too much milk,
52
printf("Total milk: %d\n", milkAvailable);
53
return 0;
54
}
55
运行程序

关于Semphore的API有下面几点说明:
- sem_init: 初始化一个新的semphore变量,第二个参数指出信号量的共享方式,0意味着该信号量是在线程间共享的而不是在进程间共享,最后的一个参数说明了信号量初始化时候的初始值
- sem_destroy: 析构掉一个已经退出的semphore变量
- sem_wait: 相当于P()操作
- sem_post: 相当于V()操作
下面让我们用表格的形式总结一下关于线程操作的一些方法
| |
基本操作 |
绝缘量 |
互斥量 |
信号量 |
| 生成 |
pthread_create |
pthread_barrier_init |
pthread_mutex_init |
sem_init |
| 析构 |
pthread_exit |
pthread_barrier_destroy |
pthread_mutex_destroy |
sem_destroy |
| 挂起等待 |
pthread_join |
pthread_barrier_wait |
--- |
--- |
| 获得资源 |
--- |
--- |
pthread_mutex_lock |
sem_wait |
| 释放 |
--- |
--- |
pthread_mutex_unlock |
sem_post |
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/asiainfolf/archive/2010/10/02/5918625.aspx
posted @
2010-10-02 23:54 sohu2000000 阅读(1237) |
评论 (2) |
编辑 收藏
某些并行计算需要面临某些在计算进行前的某些单通瓶颈点,这种情况下,当然可以使用信号量的方式来进行处理,但是还存在着另外的一种处理方式是更加方便的,它就是:栅栏(在pthread库里面被定义成为类型 pthread_barrier_t),下面我们来看一段程序作为示例
1
#define _XOPEN_SOURCE 600
2
3
#include <pthread.h>
4
#include <stdlib,h>
5
#include <stdio.h>
6
7
#define ROWS 10000
8
#define COLS 10000
9
#define THREADS 10
10
11
double initial_matrix[ROWS][COLS];
12
double final_matrix[ROWS][COLS];
13
14
//Barrier variable
15
pthread_barrier_t barr
16
17
extern void DotProduct(int row, int col, double source[ROWS][COLS], double destination[ROWS][COLS]);
18
extern void determinant(double matrix[ROWS][COLS]);
19
20
void * entry_point(void * arg)
21

{
22
int rank = (int)arg;
23
int row;
24
for(row=rank*ROWS/THREADS; row < (rank+1)*THREADS;++row)
25
for(int col=0;col<COLS;++col)
26
DotProduct(row,col,initial_matrix,final_matrix);
27
28
//synchronization pointer
29
int rc = pthread_barrier_wait(&barr);
30
if(rc!=0 && rc != PTHREAD_BARRIER_SERIAL_THREAD)
31
{
32
printf("Could not wait on barrier\n");
33
exit(-1);
34
}
35
36
for(row=rank*ROWS/THREADS; row < (rank+1)*THREADS;++row)
37
for(int col=0;col<COLS;++col)
38
DotProduct(row,col,final_matrix,initial_matrix);
39
}
40
41
int main(int argc, char* argv[])
42

{
43
int i;
44
pthread_t thr[THREADS];
45
46
//Barrier initialization
47
if(pthread_barrierattr_init(&barr,NULL,THREADS))
48
{
49
50
printf("Could not create a barrier\n");
51
return -1;
52
}
53
54
for(i=0;i<THREADS;++i)
55
{
56
if(pthread_create(&thr[i],NULL,&entry_point, (void*)i))
57
{
58
59
printf("Could not create thread %d\n", i);
60
return -1;
61
}
62
}
63
64
for(i=0;i<THREADS;++i)
65
{
66
if(pthread_join(thr[i],NULL))
67
{
68
69
printf("Could not join thread %d\n", i);
70
return -1;
71
}
72
}
73
74
double det = determinant(initial_matrix);
75
printf("The determinant of M^4 = %f\n", det);
76
77
return 0;
78
79
}
这段程序产生出许多个线程,并且分配给每个线程计算矩阵乘法的一部分,然后每个线程使用这次计算的结果,继续进行下一步的计算:另一个矩阵的乘法
几点关于API的说明:
- barrier 变量必须在最开始声名为全局变量
- barrier 变量的初始化必须在main函数里进行初始化
- 在点上每一个线程都会等待它的对端完成工作
注意
在程序顶部的宏定义 _XOPEN_SOURCE 是非常重要的;如果没有这个变量,那么barrier类型就会在pthread.h中被屏蔽掉,这个定义必须在所有的头文件引用之前被定义出来
posted @
2010-10-02 23:47 sohu2000000 阅读(1121) |
评论 (0) |
编辑 收藏
将任意正整数转换为四进制或八进制数。
刘峰六:
(1) 请小心一个问题,对于8禁止,相当于3位一段,32位是不能被三整除的,也就会导致结果不正确
(2) 对于1111111000011这样的数字,如果数字式正数,如果前面补足N个0,那么其实数字的大小是不变的,所以这里我传入了sizeof(int)+1,也就是33
但是如果是负数,那么只有2,4进制是正确的,因为它们不需要补位,但是对于8进制的话,如果开始是1,表示的是负数,那么就会涉及符号位的问题
是补零还是补充一是不一定的,不同的编译器有着不同的处理,有的补1,有的补0(见《C语言教程》-- 谭浩强,昨儿刚查滴… … )(因为int是32位,所以
永远是2和1滴倍数)
(3) 本体米有涉及16进制,涉及了也很简单,只要多个大于10就用 ‘A’ + ((unsigned )(x&(1<<(n-1))) >>(n-1))); 就OK了,因为32也是4的倍数,所以
还是八进制最复杂~~~~~~~
1:
2: /*
3: * A converter for Decimal to Binary Quaternary or Octal
4: */
5:
6: #include <stdio.h>
7:
8: void printbin(int, int);
9: void printQuater(int,int);
10: void printOtc(int,int);
11:
12: int main(void)
13: { 14: int x;
15: printf("Input Number: "); 16: scanf("%d", &x); 17:
18: //Binary
19: printf("it's binary form: "); 20: printbin(x, sizeof(int)*8);
21: putchar('\n'); 22:
23: //Quaternary
24: printf("it's Quaternary form: "); 25: printQuater(x, (sizeof(int)*8));
26: putchar('\n'); 27:
28: //OTC
29: printf("it's Otc form: "); 30: printOtc(x, (sizeof(int)*8+1));
31: putchar('\n'); 32:
33: return 0;
34: }
35:
36: //Binary
37: void printbin(int x, int n)
38: { 39: if(n>0)
40: { 41: putchar('0'+((unsigned) ( x & (1 << (n-1))) >> (n-1))); 42: printbin(x,n-1);
43: }
44: }
45:
46:
47: // Quaternary
48: void printQuater(int x,int n)
49: { 50: if(n>0)
51: { 52: putchar('0'+ ((unsigned)( x & ( 3 << (n-2))) >> (n-2) ) ); 53: printQuater(x,n-2);
54: }
55: }
56:
57: //Octal
58: void printOtc(int x, int n)
59: { 60: if(n>0)
61: { 62: putchar('0'+ ((unsigned)( x & ( 7 << (n-3))) >> (n-3) ) ); 63: printOtc(x,n-3);
64: }
65:
66: }
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/asiainfolf/archive/2010/09/07/5867499.aspx
posted @
2010-09-07 01:34 sohu2000000 阅读(1368) |
评论 (0) |
编辑 收藏
在VRP平台中,实现了strncpy类似的函数,定义如下
1: #define CHAR char
2: #define ULONG unsigned long
3: #define VOID void
4:
5: #define MACRO_COPYWORLDLENGTH 4
6:
7: CHAR *VOS_strncpy(CHAR *pcDest, const CHAR *szSrc, ULONG ulLength)
8: { 9: CHAR *pcPoint=pcDest;
10:
11: if((NULL==szSrc)||(NULL==pcDest))
12: { 13: return NULL;
14: }
15:
16: while(ulLength && (*pcPoint=*szSrc))
17: { 18: pcPoint++;
19: szSrc++;
20: ulLenght--;
21: }
22: if(!ulLength)
23: { 24: *pcPoint='\0';
25: }
26: return pcDest;
27: }
28:
29: void main(void)
30: { 31: CHAR szStrBuf[] ="1234567890";
32: CHAR szStrBuf1[]="1234567890";
33: CHAR *szHelloWorld = "Hello World!"
34: strncpy(szStrBuf, szHelloWorld, MACRO_COPYWORLDLENGTH);
35: VOS_strncpy(szStrBuf1, szHelloWorld, MACRO_COPYWORLDLENGTH);
36: printf("%s %s", szStrBuf, szStrBuf1); 37: }
程序的输出结果是_____
【分析与解答】
这个程序考察的是对C字符串处理内部机制的了解情况。
cstring一族中: strlen(const char* str) 返回的是字符串的长度,不包含 ‘\0’
所以对strcpy的调用我们一般都是 :
char * copy = new char[strlen(src)+1];
strncpy(copy,src,strlen(src)); // strncpy 如果没有指定长度,那么同样也是不会复制’\0’的
copy[strlen(src)+1]=’\0’
为什么会不去复制’\0’呢?
因为cstring库是memory.h库的底层实现,也就是说memcpy函数的底层实现是由unsigned char 类型的strncpy实现的,所以如果加上了NULL
那么就会发生内存的截取,从而会导致的程序退出时,不能完全的删除内存(NULL后面的部分没有删除),就会内存泄露,所以在标准库里面的
strncpy是没有NULL的
下面是某标注库的memory.h的定义:
1: /*
2: * This file is part of the Mingw32 package.
3: *
4: * memory.h maps to the standard string.h header.
5: */
6:
7: #include <string.h>
有了这个知识点,就很容易解答上面的程序了
第一个输出,使用的是strncpy,所以只是起到了内存的覆盖的作用,输出是覆盖掉前4个字符的字符串 Hell567890
第二个输出,调用了我们自己的程序,其中使用了字符串截断符NULL,所以输出的结果是前四个字符 Hell
【答案】 Hell1234567890 Hell
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/asiainfolf/archive/2010/09/05/5864093.aspx
posted @
2010-09-05 03:40 sohu2000000 阅读(1454) |
评论 (4) |
编辑 收藏
在屏幕上显示杨辉三角形
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
………………………………..
【问题分析与算法设计】
杨辉三角形中的数,正是(x+y)的N次方幂展开式各项的系数。从杨辉三角形的特点出发,可以总结出:
1)第N行有N+1个值(设起始行为第0行)
2)对于第N行的第J个值:(N>=2)
当J=1或J=N+1时: 其值为1
当J!=1且J!=N+1时:其值为第N-1行的第J-1个值与第N-1行第J个值之和
将这些特点提炼成数学公式可表示为:
1)x=1或x=N+1
2)c(x,y)= c(x-1,y-1)+c(x-1,y) 这样便可以根据递归数学表达式来进行程序编写了。
【程序说明与注释】
1: #include <stdio.h>
2: #define WIDTH 4
3:
4: int c(int,int);
5:
6: int main(void)
7: {
8: int i,j,n=13;
9: printf("N=");
10:
11: /*控制输入正确的值以保证屏幕显示的图形正确*/
12: while(n>12) scanf("%d",&n);
13:
14: /*控制输出N行*/
15: for(i=0;i<=n;++i)
16: {
17: //every line
18: for(j=0;j<(12-i)/2*WIDTH;j++)
19: printf(" "); /*控制输出第i行前面的空格*/
20: for(j=1;j<i+2;j++)
21: printf("%4d",c(i,j)); /*输出第i行的第j个值*/
22: printf("\n");
23: }
24: system("PAUSE");
25: return 0;
26:
27: }
28:
29: int c(int x, int y)
30: {
31: int z;
32: if((y==1)||(y==x+1))
33: return 1;
34: z=c(x-1,y-1)+c(x-1,y);
35: return z;
36: }
37:
38:
输出结果
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
1 9 36 84 126 126 84 36 9 1
1 10 45 120 210 252 210 120 45 10 1
1 11 55 165 330 462 462 330 165 55 11 1
1 12 66 220 495 792 924 792 495 220 66 12 1
posted @
2010-09-04 22:27 sohu2000000 阅读(1353) |
评论 (0) |
编辑 收藏
VRRP
本文介绍了VRRP的基本原理、特点和应用。
----------------------------------
VRRP概述
随着Internet的发展,人们对网络的可靠性的要求越来越高。对于局域网用户来说,能够时刻与外部网络保持联系是非常重要的。
通常情况下,内部网络中的所有主机都设置一条相同的缺省路由,指向出口网关(即图1中的路由器RouterA),实现主机与外部网络的通信。当出口网关发生故障时,主机与外部网络的通信就会中断。
配置多个出口网关是提高系统可靠性的常见方法,但局域网内的主机设备通常不支持动态路由协议,如何在多个出口网关之间进行选路是个问题。

IETF(Internet Engineering Task Force,因特网工程任务组)推出了VRRP(Virtual Router Redundancy Protocol)虚拟路由冗余协议,来解决局域网主机访问外部网络的可靠性问题。
VRRP是一种容错协议,它通过把几台路由设备联合组成一台虚拟的路由设备,并通过一定的机制来保证当主机的下一跳设备出现故障时,可以及时将业务切换到其它设备,从而保持通讯的连续性和可靠性。
使用VRRP的优势在于:既不需要改变组网情况,也不需要在主机上配置任何动态路由或者路由发现协议,就可以获得更高可靠性的缺省路由。
VRRP协议对应的是RFC3768,该协议仅适用于IPv4。
----------------------------------------
VRRP的基本概念
以下是与VRRP协议相关的基本概念:
|
概念
|
解释
|
| VRRP路由器(VRRP Router |
运行VRRP的设备,它可能属于一个或多个虚拟路由器。 |
| 虚拟路由器(Virtual Router) |
由VRRP管理的抽象设备,又称为VRRP备份组,被当作一个共享局域网内主机的缺省网关。 它包括了一个虚拟路由器标识符和一组虚拟IP地址。 |
| 虚拟IP地址(Virtual IP Address) |
虚拟路由器的IP地址,一个虚拟路由器可以有一个或多个IP地址,由用户配置。 |
| IP地址拥有者(IP Address Owner) |
如果一个VRRP路由器将虚拟路由器的IP地址作为真实的接口地址,则该设备是IP地址拥有者。 当这台设备正常工作时,它会响应目的地址是虚拟IP地址的报文,如ping、TCP连接等。 |
| 虚拟MAC地址 |
是虚拟路由器根据虚拟路由器ID生成的MAC地址。 一个虚拟路由器拥有一个虚拟MAC地址,格式为:00-00-5E-00-01-{VRID}。 当虚拟路由器回应ARP请求时,使用虚拟MAC地址,而不是接口的真实MAC地址。 |
| 主IP地址(Primary IP Address) |
从接口的真实IP地址中选出来的一个主用IP地址,通常选择配置的第一个IP地址。 VRRP广播报文使用主IP地址作为IP报文的源地址。 |
| Master路由器(Virtual Router Master) |
是承担转发报文或者应答ARP请求的VRRP路由器,转发报文都是发送到虚拟IP地址的。 如果IP地址拥有者是可用的,通常它将成为Master。 |
| Backup路由器(Virtual Router Backup) |
一组没有承担转发任务的VRRP路由器,当Master设备出现故障时,它们将通过竞选成为新的Master。 |
| 抢占模式 |
在抢占模式下,如果Backup的优先级比当前Master的优先级高,将主动将自己升级成Master。
|
-------------------------------------------------------------------
VRRP的工作原理
VRRP将局域网的一组路由器构成一个备份组,相当于一台虚拟路由器。局域网内的主机只需要知道这个虚拟路由器的IP地址,并不需知道具体某台设备的IP地址,将网络内主机的缺省网关设置为该虚拟路由器的IP地址,主机就可以利用该虚拟网关与外部网络进行通信。
VRRP将该虚拟路由器动态关联到承担传输业务的物理路由器上,当该物理路由器出现故障时,再次选择新路由器来接替业务传输工作,整个过程对用户完全透明,实现了内部网络和外部网络不间断通信。

如图1所示,虚拟路由器的组网环境如下:
-
RouterA、RouterB和RouterC属于同一个VRRP组,组成一个虚拟路由器,这个虚拟路由器有自己的IP地址10.110.10.1。虚拟IP地址可以直接指定,也可以借用该VRRP组所包含的路由器上某接口地址。
-
物理路由器RouterA、RouterB和RouterC的实际IP地址分别是10.110.10.5、10.110.10.6和10.110.10.7。
-
局域网内的主机只需要将缺省路由设为10.110.10.1即可,无需知道具体路由器上的接口地址。
主机利用该虚拟网关与外部网络通信。路由器工作机制如下:
从上述分析可以看到,主机不需要增加额外工作,与外界的通信也不会因某台路由器故障而受到影响。
-----------------------------------------------------------
VRRP协议介绍
----------------------------------------------------
VRRP的报文结构
VRRP协议只有一种报文,即VRRP报文。VRRP报文用来将Master设备的优先级和状态通告给同一虚拟路由器的所有VRRP路由器。
VRRP报文封装在IP报文中,发送到分配给VRRP的IPv4组播地址。在IP报文头中,源地址为发送报文的主接口地址(不是虚拟地址或辅助地址),目的地址是224.0.0.18,TTL是255,协议号是112。VRRP报文的结构如图1所示。
图1 VRRP报文结构

各字段的含义:
Version:协议版本号,现在的VRRP为版本2。
Type:报文类型,只有一种取值,1,表示Advertisement。
Virtual Rtr ID(VRID):虚拟路由器ID,取值范围是1~255。
Priority:发送报文的VRRP路由器在虚拟路由器中的优先级。取值范围是0~255,其中可用的范围是1~254。0表示设备停止参与VRRP,用来使备份路由器尽快成为主路由器,而不必等到计时器超时;255则保留给IP地址拥有者。缺省值是100。
Count IP Addrs:VRRP广播中包含的虚拟IP地址个数。
Authentication Type:验证类型,协议中指定了3种类型:
-
0:Non Authentication
-
1:Simple Text Password
-
2:Reserved
各字段的含义:
-
随后的RFC3768中将Authentication Type取值变更如下:
-
0 - No Authentication
-
1 - Reserved
-
2 - Reserved
说明:
变更的原因:实践和分析证明,这些认证方式不能提供真正的安全。而限制TTL=255可以阻止大多数对本地脆弱性的攻击。
-
实现了Simple Text Password认证方式
-
Advertisement Interval:发送通告报文的时间间隔,缺省为1秒。
-
Checksum:校验和。
-
IP Address(es):虚拟路由器IP地址,地址个数是Count IP Addrs的值。
-
Authentication Data:验证字,目前只有明文认证才用到该部分,对于其它认证方式,一律填0。
---------------------------------
VRRP的状态机
VRRP协议中定义了三种状态机:初始状态(Initialize)、活动状态(Master)、备份状态(Backup)。其中,只有处于活动状态的设备才可以转发那些发送到虚拟IP地址的报文。
VRRP状态转换如图1所示。

Initialize
设备启动时进入此状态,当收到接口Startup的消息,将转入Backup或Master状态(IP地址拥有者的接口优先级为255,直接转为Master)。在此状态时,不会对VRRP报文做任何处理。
Master
当路由器处于Master状态时,它将会做下列工作:
-
定期发送VRRP报文。
-
以虚拟MAC地址响应对虚拟IP地址的ARP请求。
-
转发目的MAC地址为虚拟MAC地址的IP报文。
-
如果它是这个虚拟IP地址的拥有者,则接收目的IP地址为这个虚拟IP地址的IP报文。否则,丢弃这个IP报文。
-
如果收到比自己优先级大的报文则转为Backup状态。
-
如果收到优先级和自己相同的报文,并且发送端的主IP地址比自己的主IP地址大,则转为Backup状态。
-
当接收到接口的Shutdown事件时,转为Initialize。
Backup
当路由器处于Backup状态时,它将会做下列工作:
-
接收Master发送的VRRP报文,判断Master的状态是否正常。
-
对虚拟IP地址的ARP请求,不做响应。
-
丢弃目的MAC地址为虚拟MAC地址的IP报文。
-
丢弃目的IP地址为虚拟IP地址的IP报文。
-
Backup状态下如果收到比自己优先级小的报文时,丢弃报文,不重置定时器;如果收到优先级和自己相同的报文,则重置定时器,不进一步比较IP地址。
-
当Backup接收到MASTER_DOWN_TIMER定时器超时的事件时,才会转为Master。
-
当接收到接口的Shutdown事件时,转为Initialize。
------------------------------------------------
提供的VRRP功能,包括主备备份、负载分担备份、VRRP监视接口状态、VRRP快速切换等。
-----------------------------------------
主备备份
这是VRRP提供IP地址备份功能的基本方式。主备备份方式需要建立一个虚拟路由器,该虚拟路由器包括一个Master和若干Backup设备。
---------------------------------------------------------------------------------------
负载分担
使用一台路由器为多个作备份。通过多虚拟路由器设置可以实现负载分担。
负载分担方式是指多台路由器同时承担业务,因此需要建立两个或更多的备份组。
负载分担方式具有以下特点。

如图1所示:
-
配置两个备份组:组1和组2;
-
RouterA在备份组1中作为Master,在备份组2中作为Backup;
-
RouterB在备份组1和2中都作为Backup;
-
RouterC在备份组2中作为Master,在备份组1中作为Backup。
-
一部分主机使用备份组1作网关,另一部分主机使用备份组2作为网关。
这样,以达到分担数据流,而又相互备份的目的。
----------------------------------------------------------------------------------
监视接口状态
VRRP可以监视所有接口的状态。当被监视的接口Down或Up时,该路由器的优先级会自动降低或升高一定的数值,使得备份组中各设备优先级高低顺序发生变化,VRRP路由器重新进行Master竞选。
-------------------------------------------------
VRRP快速切换
双向转发检测BFD(Bidirectional Forwarding Detection)机制,能够快速检测、监控网络中链路或者IP路由的连通状况,VRRP通过监视BFD会话状态实现主备快速切换,主备切换的时间控制在1秒以内。
对于以下情况,BFD都能够将检测到的故障通知接口板,从而加快VRRP主备倒换的速度。
BFD对Backup和Master之间的实际地址通信情况进行检测,如果通信不正常,Backup就认为Master已经不可用,升级成Master。在以下两种情况下Backup转换为Master:
VRRP快速切换的环境要求:
---------------------------------------------------------------
虚拟IP地址Ping开关
RFC3768并没有规定虚拟IP地址应不应该Ping通。不能Ping通虚拟IP地址,会给监控虚拟路由器的工作情况带来一定的麻烦,能够Ping通虚拟IP地址可以比较方便的监控虚拟路由器的工作情况,但是带来可能遭到ICMP攻击的隐患。控制Ping通虚拟IP地址的开关命令,用户可以选择是否打开。
--------------------------------------------------------------------------------
VRRP的安全功能
对于安全程度不同的网络环境,可以在报头上设定不同的认证方式和认证字。
在一个安全的网络中,可以采用缺省设置:路由器对要发送的VRRP报文不进行任何认证处理,收到VRRP报文的路由器也不进行任何认证,认为收到的都是真实的、合法的VRRP报文。这种情况下,不需要设置认证字。
在有可能受到安全威胁的网络中,VRRP提供简单字符认证,可以设置长度为1~8的认证字。
--------------------------------------------------------
VRRP平滑倒换
概述
CE设备作为业务系统的网关,需要启用VRRP(Virtual Router Redundancy Protocol)冗余备份功能。
在路由器主板和备板状态都正常的情况下,VRRP备份组中的Master设备会以Advertisement_Interval间隔定时发送VRRP广播报文,Backup通过不断检测接收到的广播报文来判断Master状态是否正常。
当Master设备发生主备倒换后,从发生主备倒换到新主板正常工作,需要一段时间,该时间随不同设备和不同配置差别较大,结果可能导致Master设备不能正常处理VRRP协议报文,Backup设备因为收不到广播报文而抢占到Master状态,并针对每一个虚拟路由器的虚IP地址发送免费ARP,给相关绑定模块发送状态变化通知。
由于倒换过程中系统过于繁忙,Master端的Hello协议报文无法正常发送,而Backup端无法及时收到报文,会抢占成为Master,引起链路切换,导致丢包。因此需要启用了VRRP功能的CE设备支持VRRP的平滑倒换(SS,Smoth Switch)功能,避免因主备倒换影响业务流量。
基本原理

在VRRP平滑倒换的过程中,Master和Backup分工不同,相互配合,共同保证业务的平滑传输。
- 要进行VRRP整机平滑倒换处理,必须分别在Master和Backup上使能VRRP协议报文时间间隔学习功能。如图所示,设备1和设备2都使能VRRP协议报文时间间隔学习功能。
- 如果使能了VRRP协议报文时间间隔学习功能,Master状态的VRRP不学习也不检查协议报文时间间隔的一致性。
- 非Master状态的VRRP收到Master状态VRRP发来的协议报文后,会检查报文中的时间间隔值,如果和自己的不同,非Master状态的VRRP就会学习到报文中的时间间隔,并调整自己的协议报文时间间隔值,与报文中的值保持一致。
- 设备1配置整机VRRP平滑倒换功能。设备主备倒换新的主板启动后,VRRP根据设备主备倒换前的状态判断,保存当前配置的VRPP协议报文时间间隔,并对Master状态的VRRP进行协议报文时间间隔调整,然后以当前配置的时间间隔发出VRRP平滑倒换报文,报文中携带着新的时间间隔发送到对端设备2。
- 设备2收到的VRRP协议报文中携带的时间间隔和自己本地的间隔不一致,将对自己的运行时间间隔调整,并调整自己的定时器,与其保持一致。
- 设备1平滑结束时将发出VRRP恢复报文,报文中携带着主备倒换前配置的时间间隔,此时设备2上的VRRP会再进行一次时间间隔学习。
注意事项
学习功能优先于抢占功能,即如果收到的协议报文时间间隔和自己当前的不一致,并且报文中携带的优先级低于自己当前的配置优先级,这种情况VRRP首先考虑的是学习功能和重置定时器,而后才会考虑是否抢占。
VRRP整机平滑倒换功能还依赖于系统本身,如果设备自身从主备倒换一开始系统便非常繁忙,无法调度VRRP模块运行的情况,VRRP整机平滑倒换功能无效。
VRRP加入了VGMP之后,VRRP的运行将依赖于VGMP,此时的VRRP将不受平滑倒换的影响。该功能不能用于业务VRRP。
----------------------------
VRRP管理组
在配置大量VRRP备份组时:
此外,每个VRRP备份组状态相对独立,无法保证同一路由器上相关联的接口上VRRP状态都为主用,在严格要求来回路径一致的应用中存在局限性:
-
基于NAT网关的可靠性组网
-
基于Proxy服务器的可靠性组网
-
基于状态防火墙的可靠性组网
为防止VRRP状态不一致现象的发生,华为公司在VRRP的基础上自主开发了扩展协议VGMP(VRRP Group Management Protocol),即VRRP组管理协议。基于VGMP协议建立的VRRP管理组负责统一管理加入其中的各VRRP备份组的状态,保证一台路由器上的接口同时处于主用或备用状态,实现路由器VRRP状态的一致性。
VRRP管理组有Master设备和Slave设备之分。
-
Master设备:VRRP管理组状态为Master的设备,该路由器上被管理的VRRP备份组状态都是Master(因接口Down而变成Initialize的除外),承担流量传输的任务,并定时发送Hello报文。
-
Slave设备:VRRP管理组状态为Slave的设备,该路由器上被管理的VRRP备份组状态都是非Master,不传输流量,处于监听状态,一旦Master设备出现故障,Slave将竞选成为Master。
VRRP管理组相当于在VRRP备份组的基础上叠加了一层逻辑层。VRRP备份组加入VGMP之后,不再发送传统VRRP报文,由VRRP管理组负责统一管理加入其中的各VRRP备份组的状态。
VRRP备份组感知到接口状态变化后,会改变自身的状态。VGMP将感知到这种状态迁移,然后来确定是否切换VGMP的状态,从而切换VGMP组内VRRP备份组的状态。
VRRP管理组提供的功能
-
状态一致性管理
VRRP管理组对所属VRRP组的主备切换进行裁决,改变了传统VRRP中各设备VRRP状态相对独立的现象,从而确保了同一路由器上VRRP备份组的状态一致性。
-
抢占管理
对于加入VRRP管理组的VRRP备份组来说,无论各备份组内路由器是否启动了抢占功能,抢占行为发生与否必须由VRRP管理组统一决定。
-
通道管理
配置专门的数据通道传输VGMP报文,提高VGMP报文传输的可靠性。
一个VRRP管理组中至少要有一条数据通道。数据通道可以和业务通道在同一条物理链路上。
图1描述了业务通道和数据通道的关系。A1-S-B1、A2-S-B2、A3-S-B3可以是数据通道也可以是业务通道,A4-H-B4只能作为数据通道。

VRRP管理组工作方式
VRRP管理组的工作方式有主备备份和负载分担。
mVRRP
mVRRP是指管理VRRP。管理VRRP备份组从本质上讲就是普通的VRRP备份组,唯一特殊之处在于:普通的VRRP备份组被配置为管理VRRP备份组之后,可以绑定其他的业务备份组,并根据绑定关系,决定相关业务备份组的状态。
一个管理VRRP备份组可以绑定多个业务备份组,但它不能作为业务备份组与其他管理备份组进行绑定。
管理VRRP备份组也可以作为一般成员加入VGMP组中。在将管理VRRP备份组加入VGMP组后,也可以配置管理VRRP监视Peer BFD和Link BFD会话状态,但管理VRRP备份组状态机会丧失自己的独立性,除了Initialize状态之外,Backup和Master状态需要根据所加入的VGMP组的状态来决定。
posted @
2010-09-04 15:37 sohu2000000 阅读(997) |
评论 (2) |
编辑 收藏