NSString

一月 22nd, 2011
#include  /* 说明 malloc, NULL, size_t */
#include  /* 说明 va_ 相关类型和函数 */
#include  /* 说明 strcat 等 */
char *vstrcat(const char *first, ...)
{
	size_t len;
	char *retbuf;
	va_list argp;
	char *p;
	if(first == NULL)
		return NULL;
	len = strlen(first);
	va_start(argp, first);
	while((p = va_arg(argp, char *)) != NULL)
		len += strlen(p);
	va_end(argp);

	retbuf = malloc(len + 1); /* +1 包含终止符 \0 */
	if(retbuf == NULL)
		return NULL; /* 出错 */
	(void)strcpy(retbuf, first);
	va_start(argp, first); /* 重新开始扫描 */
	while((p = va_arg(argp, char *)) != NULL)
		(void)strcat(retbuf, p);
	va_end(argp);
	retbuf = malloc(len + 1); /* +1 包含终止符 \0 */
	if(retbuf == NULL)
		return NULL; /* 出错 */
	(void)strcpy(retbuf, first);
	va_start(argp, first); /* 重新开始扫描 */
	while((p = va_arg(argp, char *)) != NULL)
		(void)strcat(retbuf, p);
	va_end(argp);
	return retbuf;
}

%c 一个单一的字符
%d 一个十进制整数
%i 一个整数
%e, %f, %g 一个浮点数
%o 一个八进制数
%s 一个字符串
%x 一个十六进制数
%p 一个指针
%n 一个等于读取字符数量的整数
%u 一个无符号整数
%[] 一个字符集
%% 一个精度符号

//一、NSString
/*—————-创建字符串的方法—————-*/

1、创建常量字符串。
NSString *astring = @”This is a String!”;

2、创建空字符串,给予赋值。
NSString *astring = [[NSString alloc] init];
astring = @”This is a String!”;
NSLog(@”astring:%@”,astring);
[astring release];

3、在以上方法中,提升速度:initWithString方法
NSString *astring = [[NSString alloc] initWithString:@”This is a String!”];
NSLog(@”astring:%@”,astring);
[astring release];

4、用标准c创建字符串:initWithCString方法
char *Cstring = “This is a String!”;
NSString *astring = [[NSString alloc] initWithCString:Cstring];
NSLog(@”astring:%@”,astring);
[astring release];

5、创建格式化字符串:占位符(由一个%加一个字符组成)
int i = 1;
int j = 2;
NSString *astring = [[NSString alloc] initWithString:[NSString stringWithFormat:@"%d.This is %i string!",i,j]];
NSLog(@”astring:%@”,astring);
[astring release];

6、创建临时字符串
NSString *astring;
astring = [NSString stringWithCString:"This is a temporary string"];
NSLog(@”astring:%@”,astring);

/*—————-从文件读取字符串:initWithContentsOfFile方法 —————-*/
NSString *path = @”astring.text”;
NSString *astring = [[NSString alloc] initWithContentsOfFile:path];
NSLog(@”astring:%@”,astring);
[astring release];

/*—————-写字符串到文件:writeToFile方法 —————-*/
NSString *astring = [[NSString alloc] initWithString:@”This is a String!”];
NSLog(@”astring:%@”,astring);
NSString *path = @”astring.text”;
[astring writeToFile: path atomically: YES];
[astring release];

/*—————- 比较两个字符串—————-*/
用C比较:strcmp函数
char string1[] = “string!”;
char string2[] = “string!”;
if(strcmp(string1, string2) = = 0)
{
NSLog(@”1″);
}

isEqualToString方法
NSString *astring01 = @”This is a String!”;
NSString *astring02 = @”This is a String!”;
BOOL result = [astring01 isEqualToString:astring02];
NSLog(@”result:%d”,result);

compare方法(comparer返回的三种值)
NSString *astring01 = @”This is a String!”;
NSString *astring02 = @”This is a String!”;
BOOL result = [astring01 compare:astring02] = = NSOrderedSame;
NSLog(@”result:%d”,result);
NSOrderedSame 判断两者内容是否相同

NSString *astring01 = @”This is a String!”;
NSString *astring02 = @”this is a String!”;
BOOL result = [astring01 compare:astring02] = = NSOrderedAscending;
NSLog(@”result:%d”,result);
//NSOrderedAscending 判断两对象值的大小(按字母顺序进行比较,astring02大于astring01为真)

NSString *astring01 = @”this is a String!”;
NSString *astring02 = @”This is a String!”;
BOOL result = [astring01 compare:astring02] = = NSOrderedDescending;
NSLog(@”result:%d”,result);
//NSOrderedDescending 判断两对象值的大小(按字母顺序进行比较,astring02小于astring01为真)

不考虑大 小写比较字符串1
NSString *astring01 = @”this is a String!”;
NSString *astring02 = @”This is a String!”;
BOOL result = [astring01 caseInsensitiveCompare:astring02] = = NSOrderedSame;
NSLog(@”result:%d”,result);
//NSOrderedDescending判断两对象值的大小(按字母顺序进行比较,astring02小于astring01为 真)

不考虑大小写比较字符串2
NSString *astring01 = @”this is a String!”;
NSString *astring02 = @”This is a String!”;
BOOL result = [astring01 compare:astring02
options:NSCaseInsensitiveSearch | NSNumericSearch] = = NSOrderedSame;
NSLog(@”result:%d”,result);

//NSCaseInsensitiveSearch:不区分大小写比较 NSLiteralSearch:进行完全比较,区分大小写 NSNumericSearch:比较字符串的字符个数,而不是字符值。

/*—————-改变字符串的大小写—————-*/
NSString *string1 = @”A String”;
NSString *string2 = @”String”;
NSLog(@”string1:%@”,[string1 uppercaseString]);//大写
NSLog(@”string2:%@”,[string2 lowercaseString]);//小写
NSLog(@”string2:%@”,[string2 capitalizedString]);//首字母大小

/*—————-在串中搜索子串 —————-*/
NSString *string1 = @”This is a string”;
NSString *string2 = @”string”;
NSRange range = [string1 rangeOfString:string2];
int location = range.location;
int leight = range.length;
NSString *astring = [[NSString alloc] initWithString:[NSString stringWithFormat:@"Location:%i,Leight:%i",location,leight]];
NSLog(@”astring:%@”,astring);
[astring release];

/*—————-抽取子串 —————-*/
-substringToIndex: 从字符串的开头一直截取到指定的位置,但不包括该位置的字符
NSString *string1 = @”This is a string”;
NSString *string2 = [string1 substringToIndex:3];
NSLog(@”string2:%@”,string2);

-substringFromIndex: 以指定位置开始(包括指定位置的字符),并包括之后的全部字符
NSString *string1 = @”This is a string”;
NSString *string2 = [string1 substringFromIndex:3];
NSLog(@”string2:%@”,string2);

-substringWithRange: //按照所给出的位置,长度,任意地从字符串中截取子串
NSString *string1 = @”This is a string”;
NSString *string2 = [string1 substringWithRange:NSMakeRange(0, 4)];
NSLog(@”string2:%@”,string2);

const char *fieldValue = [value cStringUsingEncoding:NSUTF8StringEncoding];
const char *fieldValue = [value UTF8String];

NSString 转 NSData
NSString* str= @”kilonet”;
NSData* data=[str dataUsingEncoding:NSUTF8StringEncoding];

Date format用法:
-(NSString *) getDay:(NSDate *) d
{
NSString *s ;
NSDateFormatter *format = [[NSDateFormatter alloc] init];
[format setDateFormat:@"YYYY/MM/dd hh:mm:ss"];
s = [format stringFromDate:d];
[format release];
return s;
}

各地时区获取:

代码
NSDate *nowDate = [NSDate new];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy/MM/dd HH:mm:ss"];
// 根据时区名字获取当前时间,如果该时区不存在,默认获取系统当前时区的时间
// NSTimeZone* timeZone = [NSTimeZone timeZoneWithName:@"Europe/Andorra"];
// [formatter setTimeZone:timeZone];
//获取所有的时区名字
NSArray *array = [NSTimeZone knownTimeZoneNames];
// NSLog(@”array:%@”,array);
//for循环
// for(int i=0;i<[array count];i++)
// {
// NSTimeZone* timeZone = [NSTimeZone timeZoneWithName:[array objectAtIndex:i]];
// [formatter setTimeZone:timeZone];
// NSString *locationTime = [formatter stringFromDate:nowDate];
// NSLog(@"时区名字:%@ : 时区当前时间: %@",[array objectAtIndex:i],locationTime);
// //NSLog(@"timezone name is:%@",[array objectAtIndex:i]);
// }
//快速枚举法
for(NSString *timeZoneName in array){
[formatter setTimeZone:[NSTimeZone timeZoneWithName:timeZoneName]];
NSLog(@"%@,%@",timeZoneName,[formatter stringFromDate:nowDate]);
}

[formatter release];
[nowDate release];

NSCalendar用法:

-(NSString *) getWeek:(NSDate *) d {
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
unsigned units = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSWeekdayCalendarUnit;
NSDateComponents *components = [calendar components:units fromDate:d];
[calendar release];

switch ([components weekday]) {
case 2:
return @"Monday";
break;
case 3:
return @"Tuesday";
break;
case 4:
return @"Wednesday";
break;
case 5:
return @"Thursday";
break;
case 6:
return @"Friday";
break;
case 7:
return @"Saturday";
break;
case 1:
return @"Sunday";
break;
default:
return @"No Week";
break;
}

// 用components,我们可以读取其他更多的数据。

}

4. 用Get方式读取网络数据:

将网络数读取为字符串
- (NSString *) getDataByURL:(NSString *) url {
return [[NSString alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]] encoding:NSUTF8StringEncoding];
}

//读取网络图片
- (UIImage *) getImageByURL:(NSString *) url {
return [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]];
}

多线程
[NSThread detachNewThreadSelector:@selector(scheduleTask) toTarget:self withObject:nil];

-(void) scheduleTask {
//create a pool
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

//release the pool;
[pool release];
}

//如果有参数,则这么使用:
[NSThread detachNewThreadSelector:@selector(scheduleTask:) toTarget:self withObject:[NSDate date]];

-(void) scheduleTask:(NSDate *) mdate {
//create a pool
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

//release the pool;
[pool release];
}

//注意selector里有冒号。
//在线程里运行主线程里的方法

[self performSelectorOnMainThread:@selector(moveToMain) withObject:nil waitUntilDone:FALSE];

6. 定时器NSTimer用法:

代码
// 一个可以自动关闭的Alert窗口

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
message:[@"一个可以自动关闭的Alert窗口"
delegate:nil
cancelButtonTitle:nil //NSLocalizedString(@"OK", @"OK") //取消任何按钮
otherButtonTitles:nil];
//[alert setBounds:CGRectMake(alert.bounds.origin.x, alert.bounds.origin.y, alert.bounds.size.width, alert.bounds.size.height+30.0)];
[alert show];

UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];

// Adjust the indicator so it is up a few pixels from the bottom of the alert
indicator.center = CGPointMake(alert.bounds.size.width/2, alert.bounds.size.height-40.0);
[indicator startAnimating];
[alert insertSubview:indicator atIndex:0];
[indicator release];

[NSTimer scheduledTimerWithTimeInterval:3.0f
target:self
selector:@selector(dismissAlert:)
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:alert, @"alert", @"testing ", @"key" ,nil] //如果不用传递参数,那么可以将此项设置为nil.
repeats:NO];

NSLog(@"release alert");
[alert release];

-(void) dismissAlert:(NSTimer *)timer{

NSLog(@"release timer");
NSLog([[timer userInfo] objectForKey:@"key"]);

UIAlertView *alert = [[timer userInfo] objectForKey:@"alert"];
[alert dismissWithClickedButtonIndex:0 animated:YES];

}

定时器停止使用:

[timer invalidate];
timer = nil;

7. 用户缺省值NSUserDefaults读取:

//得到用户缺省值
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];

//在缺省值中找到AppleLanguages, 返回值是一个数组
NSArray* languages = [defs objectForKey:@"AppleLanguages"];
NSLog(@"all language语言 is %@", languages);

//在得到的数组中的第一个项就是用户的首选语言了
NSLog(@"首选语言 is %@",[languages objectAtIndex:0]);

//get the language & country code
NSLocale *currentLocale = [NSLocale currentLocale];

NSLog(@"Language Code is %@", [currentLocale objectForKey:NSLocaleLanguageCode]);
NSLog(@"Country Code is %@", [currentLocale objectForKey:NSLocaleCountryCode

8. View之间切换的动态效果设置:

SettingsController *settings = [[SettingsController alloc]initWithNibName:@"SettingsView" bundle:nil];
settings.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal; //水平翻转
[self presentModalViewController:settings animated:YES];
[settings release];

9.NSScrollView 滑动用法:

-(void) scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(@"正在滑动中...");
}

//用户直接滑动NSScrollView,可以看到滑动条
-(void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView {

}

// 通过其他控件触发NSScrollView滑动,看不到滑动条
- (void) scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {

}

11.键盘处理系列

//set the UIKeyboard to switch to a different text field when you press return

//switch textField to the name of your textfield
[textField becomeFirstResponder];

srandom(time(NULL)); //随机数种子

id d = random(); // 随机数

4. iPhone的系统目录:

//得到Document目录:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

//得到temp临时目录:
NSString *tempPath = NSTemporaryDirectory();

//得到目录上的文件地址:
NSString *文件地址 = [目录地址 stringByAppendingPathComponent:@"文件名.扩展名"];

5. 状态栏显示Indicator:

[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

6.app Icon显示数字:

- (void)applicationDidEnterBackground:(UIApplication *)application{
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:5];
}

7.sqlite保存地址:

代码
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *thePath = [paths objectAtIndex:0];
NSString *filePath = [thePath stringByAppendingPathComponent:@"kilonet1.sqlite"];

NSString *dbPath = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:@"kilonet2.sqlite"];

8.Application退出:exit(0);

9. AlertView,ActionSheet的cancelButton点击事件:

代码
-(void) actionSheet :( UIActionSheet *) actionSheet didDismissWithButtonIndex:(NSInteger) buttonIndex {
NSLog(@"cancel actionSheet........");
//当用户按下cancel按钮
if( buttonIndex == [actionSheet cancelButtonIndex]) {
exit(0);
}
// //当用户按下destructive按钮
// if( buttonIndex == [actionSheet destructiveButtonIndex]) {
// // DoSomething here.
// }
}

- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex {
NSLog(@"cancel alertView........");
if (buttonIndex == [alertView cancelButtonIndex]) {
exit(0);
}
}

10.给Window设置全局的背景图片:
window.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"coolblack.png"]];

11. UITextField文本框显示及对键盘的控制:

代码
#pragma mark -
#pragma mark UITextFieldDelegate
//控制键盘跳转
- (BOOL)textFieldShouldReturn:(UITextField *)textField {

if (textField == _txtAccount) {
if ([_txtAccount.text length]==0) {
return NO;
}
[_txtPassword becomeFirstResponder];
} else if (textField == _txtPassword) {
[_txtPassword resignFirstResponder];
}

return YES;
}

//输入框背景更换
-(BOOL) textFieldShouldBeginEditing:(UITextField *)textField{

[textField setBackground:[UIImage imageNamed:@"ctext_field_02.png"]];

return YES;
}

-(void) textFieldDidEndEditing:(UITextField *)textField{
[textField setBackground:[UIImage imageNamed:@"ctext_field_01.png"]];
}

12.UITextField文本框前面空白宽度设置以及后面组合按钮设置:

代码
//给文本输入框后面加入空白
_txtAccount.rightView = _btnDropDown;
_txtAccount.rightViewMode = UITextFieldViewModeAlways;

//给文本输入框前面加入空白
CGRect frame = [_txtAccount frame];
frame.size.width = 5;
UIView *leftview = [[UIView alloc] initWithFrame:frame];
_txtAccount.leftViewMode = UITextFieldViewModeAlways;
_txtAccount.leftView = leftview;

13. UIScrollView 设置滑动不超出本身范围:

[fcScrollView setBounces:NO];

14. 遍历View里面所有的Subview:

代码
NSLog(@"subviews count=%d",[self.view.subviews count]);
if ([self.view.subviews count] > 0) {
for (UIView *curView in self.view.subviews) {
NSLog(@”view.subviews=%@”, [NSString stringWithUTF8String:object_getClassName(curView)]);
}
}

14. 在drawRect里画文字:

UIFont * f = [UIFont systemFontOfSize:20];

[[UIColor darkGrayColor] set];

NSString * text = @”hi \nKiloNet”;

[text drawAtPoint:CGPointMake(center.x,center.y) withFont:f];

15. NSArray查找是否存在对象时用indexOfObject,如果不存在则返回为NSNotFound.

16. NString与NSArray之间相互转换:

array = [string componentsSeparatedByString:@","];
string = [[array valueForKey:@"description"] componentsJoinedByString:@”,”];

17. TabController随意切换tab bar:

[self.tabBarController setSelectedIndex:tabIndex];

或者 self.tabBarController.selectedIndex = tabIndex;

或者实现下面的delegate来扑捉tab bar的事件:

代码-(BOOL) tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController { if ([viewController.tabBarItem.title isEqualToString: NSLocalizedString(@"Logout",nil)]) { [self showLogout]; return NO; } return YES;}

18. 自定义View之间切换动画:
代码
- (void) pushController: (UIViewController*) controller
withTransition: (UIViewAnimationTransition) transition
{
[UIView beginAnimations:nil context:NULL];
[self pushViewController:controller animated:NO];
[UIView setAnimationDuration:.5];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationTransition:transition forView:self.view cache:YES];
[UIView commitAnimations];
}

CATransition *transition = [CATransition animation];
transition.duration = kAnimationDuration;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromTop;
transitioning = YES;
transition.delegate = self;
[self.navigationController.view.layer addAnimation:transition forKey:nil];

self.navigationController.navigationBarHidden = NO;
[self.navigationController pushViewController:tableViewController animated:YES];

20.计算字符串长度:

CGFloat w = [title sizeWithFont:[UIFont fontWithName:@"Arial" size:18]].width;

23.在使用UISearchBar时,将背景色设定为clearColor,或者将translucent设为YES,都不能使背景透明,经过一番研究,发现了一种超级简单和实用的方法:

1
[[searchbar.subviews objectAtIndex:0]removeFromSuperview];

背景完全消除了,只剩下搜索框本身了。

24. 图像与缓存 :

UIImageView *wallpaper = [[UIImageView alloc] initWithImage:

[UIImage imageNamed:@"icon.png"]]; // 会缓存图片

UIImageView *wallpaper = [[UIImageView alloc] initWithImage:

[UIImage imageWithContentsOfFile:@"icon.png"]]; // 不会缓存图片

25. iphone-常用的对视图图层(layer)的操作

对图层的操作:

(1.给图层添加背景图片:
myView.layer.contents = (id)[UIImage imageNamed:@"view_BG.png"].CGImage;

(2.将图层的边框设置为圆脚
myWebView.layer.cornerRadius = 8;
myWebView.layer.masksToBounds = YES;

(3.给图层添加一个有色边框
myWebView.layer.borderWidth = 5;
myWebView.layer.borderColor = [[UIColor colorWithRed:0.52 green:0.09 blue:0.07 alpha:1] CGColor];

H.264 RTP PAYLOAD 格式

九月 14th, 2010

http://www.cppblog.com/czanyou/archive/2009/12/25/67940.html

H.264 视频 RTP 负载格式
1. 网络抽象层单元类型 (NALU)
NALU 头由一个字节组成, 它的语法如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+

F: 1 个比特.
forbidden_zero_bit. 在 H.264 规范中规定了这一位必须为 0.
NRI: 2 个比特.
nal_ref_idc. 取 00 ~ 11, 似乎指示这个 NALU 的重要性, 如 00 的 NALU 解码器可以丢弃它而不影响图像的回放. 不过一般情况下不太关心
这个属性.
Type: 5 个比特.
nal_unit_type. 这个 NALU 单元的类型. 简述如下:
0 没有定义
1-23 NAL单元 单个 NAL 单元包.
24 STAP-A 单一时间的组合包
25 STAP-B 单一时间的组合包
26 MTAP16 多个时间的组合包
27 MTAP24 多个时间的组合包
28 FU-A 分片的单元
29 FU-B 分片的单元
30-31 没有定义
2. 打包模式
下面是 RFC 3550 中规定的 RTP 头的结构.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | sequence number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| synchronization source (SSRC) identifier |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| contributing source (CSRC) identifiers |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

负载类型 Payload type (PT): 7 bits
序列号 Sequence number (SN): 16 bits
时间戳 Timestamp: 32 bits

H.264 Payload 格式定义了三种不同的基本的负载(Payload)结构. 接收端可能通过 RTP Payload
的第一个字节来识别它们. 这一个字节类似 NALU 头的格式, 而这个头结构的 NAL 单元类型字段
则指出了代表的是哪一种结构,
这个字节的结构如下, 可以看出它和 H.264 的 NALU 头结构是一样的.
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+

字段 Type: 这个 RTP payload 中 NAL 单元的类型. 这个字段和 H.264 中类型字段的区别是, 当 type
的值为 24 ~ 31 表示这是一个特别格式的 NAL 单元, 而 H.264 中, 只取 1~23 是有效的值.

24 STAP-A 单一时间的组合包
25 STAP-B 单一时间的组合包
26 MTAP16 多个时间的组合包
27 MTAP24 多个时间的组合包
28 FU-A 分片的单元
29 FU-B 分片的单元
30-31 没有定义
可能的结构类型分别有:
1. 单一 NAL 单元模式
即一个 RTP 包仅由一个完整的 NALU 组成. 这种情况下 RTP NAL 头类型字段和原始的 H.264的
NALU 头类型字段是一样的.
2. 组合封包模式
即可能是由多个 NAL 单元组成一个 RTP 包. 分别有4种组合方式: STAP-A, STAP-B, MTAP16, MTAP24.
那么这里的类型值分别是 24, 25, 26 以及 27.
3. 分片封包模式
用于把一个 NALU 单元封装成多个 RTP 包. 存在两种类型 FU-A 和 FU-B. 类型值分别是 28 和 29.
2.1 单一 NAL 单元模式
对于 NALU 的长度小于 MTU 大小的包, 一般采用单一 NAL 单元模式.
对于一个原始的 H.264 NALU 单元常由 [Start Code] [NALU Header] [NALU Payload] 三部分组成, 其中 Start Code 用于标示这是一个
NALU 单元的开始, 必须是 “00 00 00 01″ 或 “00 00 01″, NALU 头仅一个字节, 其后都是 NALU 单元内容.
打包时去除 “00 00 01″ 或 “00 00 00 01″ 的开始码, 把其他数据封包的 RTP 包即可.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI| type | |
+-+-+-+-+-+-+-+-+ |
| |
| Bytes 2..n of a Single NAL unit |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

如有一个 H.264 的 NALU 是这样的:
[00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]
这是一个序列参数集 NAL 单元. [00 00 00 01] 是四个字节的开始码, 67 是 NALU 头, 42 开始的数据是 NALU 内容.
封装成 RTP 包将如下:
[ RTP Header ] [ 67 42 A0 1E 23 56 0E 2F ]
即只要去掉 4 个字节的开始码就可以了.

2.2 组合封包模式
其次, 当 NALU 的长度特别小时, 可以把几个 NALU 单元封在一个 RTP 包中.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 Data |
: :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | NALU 2 Size | NALU 2 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 Data |
: :
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

2.3 Fragmentation Units (FUs).
而当 NALU 的长度超过 MTU 时, 就必须对 NALU 单元进行分片封包. 也称为 Fragmentation Units (FUs).

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator | FU header | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| FU payload |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :…OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 14. RTP payload format for FU-A
The FU indicator octet has the following format:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
The FU header has the following format:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+

3. SDP 参数
下面描述了如何在 SDP 中表示一个 H.264 流:
. “m=” 行中的媒体名必须是 “video”
. “a=rtpmap” 行中的编码名称必须是 “H264″.
. “a=rtpmap” 行中的时钟频率必须是 90000.
. 其他参数都包括在 “a=fmtp” 行中.
如:
m=video 49170 RTP/AVP 98
a=rtpmap:98 H264/90000
a=fmtp:98 profile-level-id=42A01E; sprop-parameter-sets=Z0IACpZTBYmI,aMljiA==
下面介绍一些常用的参数.
3.1 packetization-mode:
表示支持的封包模式.
当 packetization-mode 的值为 0 时或不存在时, 必须使用单一 NALU 单元模式.
当 packetization-mode 的值为 1 时必须使用非交错(non-interleaved)封包模式.
当 packetization-mode 的值为 2 时必须使用交错(interleaved)封包模式.
这个参数不可以取其他的值.
3.2 sprop-parameter-sets:
这个参数可以用于传输 H.264 的序列参数集和图像参数 NAL 单元. 这个参数的值采用 Base64 进行编码. 不同的参数集间用”,”号隔开.

3.3 profile-level-id:
这个参数用于指示 H.264 流的 profile 类型和级别. 由 Base16(十六进制) 表示的 3 个字节. 第一个字节表示 H.264 的 Profile 类型, 第
三个字节表示 H.264 的 Profile 级别:

3.4 max-mbps:
这个参数的值是一个整型, 指出了每一秒最大的宏块处理速度.

STUN协议简介(部分翻译自rfc3489)

九月 2nd, 2010

STUN( Simple Traversal of User Datagram Protocol (UDP) Through Network Address Translators (NATs))是一种轻量级的协议,它允许应用程序发现它们和公网之间是否存在NATs和防火墙,并确定NATs和防火墙的类型。它也可以让应用程序确定NAT分配给它们的公网IP地址和端口号。STUN是一种client-server的协议,也是一种request-response的协议,STUN的默认端口是3478。

NAT的类型:

Full Cone NAT:

所有从同一个内网IP和端口号发送过来的请求都会被映射成同一个外网IP地址和端口号。而且任何一个外网主机都可以通过这个映射的外网IP和端口号向这台内网主机发关怉。

Restricted Cone:

它也是所有从同一个内网IP和端口号发送过来的请求都会被映射成同一个外网IP和端口号。和Full-Cone NAT不同的是,一个拥有IP地址X的外网主机如果想要给内网主机发送数据包,必须是这台内网主机之前给IP地址X发送过数据包才可以。

Port Restricted Cone:

它和Restricted Cone很相似,只不过它包括端口号,也就是,一台IP地址X和端口P的外网主机想给内网主机发送数据包,必须是这台内网主机之前给这个IP地址X和端口P发送过数据包才可以。

Symmetric:

对称NAT就是,所有从同一个内网IP和端口发送到一个特定的目的IP和端口的请求,都会被映射到同一个IP和端口。如果同一台主机用相同的源地址和端口号发送数据包,但是发往不同的目的地,NAT将会使用不同的映射。进一步说,只有当外网主机收到内网主机发送来的数据包之后才能向内网主机往回发送数据包。

STUN Binding Request使用UDP协议发送到STUN服务器,当Binding Request消息到达服务器的时候它可能经过了一个或者多个NAT。结果是STUN服务器收到的request消息的源IP地址被映射成最靠近STUN服务器的NAT的IP地址,STUN服务器把这个源IP地址和端口号复制到一个Bind Response消息中,通过发送回拥有这个IP地址和端口号的客户端,对于上面提到的所有类型的NAT,这个消息都会到达客户端。

当STUN客户端收到STUN Binding Response消息之后,它会将自己发送Request时bind的本地IP地址和端口号同Response消息中的IP地址端口号进行比较,如果不匹配,就表示客户端正处于一个或者多个NAT的前面。在Full-Cone NAT的情况下,在STUN Response消息中的IP地址和端口是属于公网的,公网上的任何主机都可以使用这个IP地址和端口号向这个应用程序发送数据包,应用程序只需要在刚才发送STUN Binding Request的IP地址和端口上监听即可。

当然,主机可能并不在一个full-core NAT的前面,实际上,它并不知道自己在一个什么类型的NAT的前面。为了确定NAT的类型,客户端使用附加的STUN Binding Request.具体过程是很灵活的,但一般都会像下面这样工作。客户端再发送一个STUN Binding Request,这次发往另一个IP地址,但是使用的是跟上一次同一个源IP地址和源端口号,如果返回的数据包里面的IP地址和端口号和第一次返回的数据包中的不同,客户端就会知道它是在一个对称NAT的前面。客户端为了确定自己是否在一个完全锥形NAT的前面,客户端可以发送一个带有标志的STUN Binding Request,这个标志告诉STUN Server另一个IP地址和端口发送Response,这个IP地址和端口要和刚才收到Request的IP地址和端口不同。换句话说,如果客户端使用x:y的IP地址:端口对向A:B的IP地址:端口对发送Binding Request,STUN Server会使用源IP地址和源端口号为C:D的地址对向X:Y发送Response.如果客户端收到了这个Response,它就知道它是在一个Full-Cone NAT前面。

STUN协议允许客户端请求服务器从收到Request的IP地址往回发Binding Response,但是要使用不同的端口号。这可以用来检查客户端是在Port Restricted Cone NAT的前面还是在Restricted Cone NAT的前面。

STUN 消息是使用大端字节流编码的TLV(type-length-value).所有的STUN消息都以一个STUN头开始,紧跟着STUN的载核数据(Payload)。Payload是一系列的STUN属性集合,它们取决于STUN消息的类型。STUN消息的type可以是Binding Request,Binding Response,Binding ERROR Response, Shared Secret Request,Shared Secrect Response 或 Shared Secret Error Response.

Transaction ID的作用是将请求(Request)和响应(Response)联系起来。长度字段代表STUN Payload数据的整个长度。Shared Secret Requests一直都是承载于TCP之上发送的(实际上,是使用了承载于TCP这上的TLS发送的)。

STUN协议也定义了很多的STUN属性。第一个是MAPPED-ADDRESS属性,它是一个IP地址和端口对,Binding Response里面一直都有它,它代表了服务器在Binding Request中看到的源IP地址和源端口号。还有一个RESPONSE-ADDRESS属性,包含一个IP地址和端口。RESPONSE-ADDRESS可以被放到Binding Request中,它告诉服务器Binding Request将会被发送到哪里。它是可选的,当不填写的时候,Binding Request会被卧发送到Binding Request的源IP地址和源端口号。

第三个属性是CHANGE-REQUEST 属性,它包含了两个flag,这两个flag控制用来发送Response的IP地址和端口号。这两个标志被称为“change IP”和“change port”标志,CHANGE-REQUEST标志只允许在Binding Request中出现,在确定客户端是在Restricted Cone NAT之前还是Port Restricted Cone NAT之前的时候,这两个标志是很有用的。它们指示Server从不同的源地址和源端口发送Binding Response。在Binding Request中CHANGE-REQUEST属性是可选的。

第四个属性是CHANGED-ADDRESS属性,它出现在Binding Response中。如果客户端请求使用”change IP”和”change port”行为,它会通知客户端将会使用的源IP地址和源端口号。

第五个属性是SOURCE-ADDRESS属性,它只出现在Binding Response中,它指示发送Response的源IP地址和源端口号,它在检测两个NAT配置的时候是很有用的。

第五个是USERNAME,它只在Shared Secret Response中出现,忽略了…

第六个属性是ERROR-CODE属性,它出现在Binding Error Response和Shared Secret Error Response中,它指出发生的错误。

忽略了三四个属性…..

消息头:
所有的STUN消息都包含20个字节的消息头:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| STUN Message Type | Message Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Transaction ID
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Message Type可以取下面这些值:

0×0001 : Binding Request
0×0101 : Binding Response
0×0111 : Binding Error Response
0×0002 : Shared Secret Request
0×0102 : Shared Secret Response
0×0112 : Shared Secret Error Response

Message Length是载核数据的字节长度,不包含消息头的长度。

Transaction ID是一个128位的标识符,可以随机生成。

消息属性

STUN消息头后面跟着0个或多个属性,所有的属性都是TLV形式的,包含16位的类型,16位的长度,和变长的值。

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Value ….
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

下面是类型的定义:

0×0001: MAPPED-ADDRESS
0×0002: RESPONSE-ADDRESS
0×0003: CHANGE-REQUEST
0×0004: SOURCE-ADDRESS
0×0005: CHANGED-ADDRESS
0×0006: USERNAME
0×0007: PASSWORD
0×0008: MESSAGE-INTEGRITY
0×0009: ERROR-CODE
0×000a: UNKNOWN-ATTRIBUTES
0×000b: REFLECTED-FROM

MAPPED-ADDRESS

这个属性表示映射的IP地址和端口。

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| x x x x x x x x | Family | Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Family一般都是0×01,表示IPV4

RESPONSE-ADDRESS

这个属性的数据包形式跟MAPPED-ADDRESS相同。

其它的就不列出来了,具体去看rfc就好了

http://www.faqs.org/rfcs/rfc3489.html

Delphi窗口抖动代码

八月 20th, 2010
var
  i, t, l     : Integer;
begin
  btnDemo.Enabled := False;
  t := Self.Top;
  l := Self.Left;

  for i := 0 to 20 do
  begin

    case (i mod 4) of
    0:
      begin
        Self.Top  := t + 2;
        Self.Left := l + 2;
      end;
    1:
      begin
        Self.Top  := t + 2;
        Self.Left := l - 2;
      end;
    2:
      begin
        Self.Top  := t - 2;
        Self.Left := l - 2;
      end;
    3:
      begin
        Self.Top  := t - 2;
        Self.Left := l + 2;
      end;
    end;
    Application.ProcessMessages;

    //Sleep(50);
    SleepEx(50, True);
  end;
  Self.Top  := t;
  Self.Left := l;

  btnDemo.Enabled := True;

RGB模式下,alpha原理公式(转贴)

八月 8th, 2010

先简单说一下图像alpha的原理。在RGB模式下,alpha原理公式很简单:

displayColor = sourceColor × alpha / 255 + backgroundColor × (255 – alpha) / 255.

alaph取值范围是0-255.

那么,如果将此公式放在YV12下,又会如何呢?YV12是在一个平面中排列,先是Y, 然后是U, 后是V, 如下表述:Y的区域大小 = Width * Height,   U的区域大小=Width/2 * Height/2, V的区域大小=Width/2*Height/2, 4个Y值共享一个U和V. 所以,如果我们将所有的YUV都通过alpha公式运算后,与 RGBF运算后的效果应该是相同的。试验结果会证明这点吗?看下图就知道了.

原贴:http://hi.baidu.com/zwshare/blog/item/97213bdc0d269cabcd116629.html

基于产品结构树的图文档管理系统的设计与实现

一月 12th, 2009

    O 引言
   
    近年来在机械制造加工行业,随着”甩图板”工程的迅猛发展, CAD 应用及企业信息化建设方面已取得了巨大成效,企业逐渐积累了大量的图纸、文档等技术资料。这些电子图档是企业产品设计的主要信息资源,为企业今后进一步实现信息化集成打下了良好的基础。但目前国内绝大部分企业还只停留在单纯使用CAD 进行产品设计的初期阶段,也没考虑到如何对大量的设计技术图档进行合理、有效、安全的管理,设计资料的重用率低;设计人员仍各自为政,难以避免图档的重复设计;设计数据的传递只能通过最传统的文件传递方式来完成,因此数据安全性无法保证,实对交流不便,对图文档的分类管理也无从谈起,图档版本混乱,更无法实时监控图档的整个设计流程;当然还有很多其它弊端。内地的中小型企业此类现象尤为突出。
   
    1 系统设计目标
   
    1.1 提高图文档的管理水平
   
    对图文档及用户进行分级管理,有效控制未授权人员的非法使用;建立与图文档相对应的产品结构树,使产品零件与图文裆进行关联,有利于对各类产品及相关数据信息的集中管理。
   
    1.2 提高工作效率
   
    对各类图文档进行综合管理,便于设计图挡的适时更新和共享,加快技术文挡的查询速度;对同一产品零件图挡的不同版本进行管理,从前提高设计部门的运作效率;通过权限设置实现图文挡的安全共享。
   
    1.3 确保技术文档安全性
   
    建立两种数据库的备份机制,一种是利用DBMS 系统提供的维护计划自动定时备份数据库,另一种通过客户端操作实现备份或恢复。另外,如果在硬件条件允许的情况下可增加一台数据库备份服务器。在客户端,连接数据库服务器设置参数经过加密后以二进制方式存入配置文件,并将此配置文件属性设置为系统、隐藏,以防数据库账号外泄。要对用户进行身份验证,并对用户密码进行双重加密后保存到数据库内,避免用户通过读取数据库获取用户密码。另外,用户要经过授权后方可访问其图文档数据信息。所有用户操作内容都记入日志,以备后查。
   
    1 .4提供数据集中、管理分散的操作模式
   
    本软件采用C/S 构架;通过企业局域网,客户端可以分布在网络的任何节点上;最终的操作结果均存储在数据库服务器上,通过网络实现多个用户之间的协同工作。
   
    2 系统解决方案

   
    系统设计采用基于.Net 技术的Visual C# 为前台开发语言和ADO.NET 数据库访问接口技术,并以SQL Server2000 作为网络数据库系统平台,充分发挥C# 真正面向对象(OOP) 的编程优点,系统采用N 层结构模式,在系统的实现过程中,分别将相对独立的功能模块及代码封装在不同的类中,通过创建类的实例可方便调用各功能函数。
    系统采用一级身份验证和二级权限分配机制。用户登录后首先从用户权限表获取该用户的权限。在产品结构树中只显示该用户有预览权的项目目录。
    用户(或部门)对产品结构树中某一项(或组)的权限级别有:入库、预览、修改、删除、出库及批量出库六种。系统管理员具有所有权限,并能对各部门负责人进行委托授权,有权查看及管理所有日志,并对系统参数进行设置,如设置图档的保留版本数,图档归档流程及再修改授权流程等。系统操作流程及功能模块如图l 所示。
   

    3 系统的实现
   
    本系统主要完成企业设计技术文挡的分类管理,实现对各类技术文档的预览、版本管理、入库、出库、批量出库、图纸与产品结构树的动态关联等。具有用户权限的动态设置及自顶向下的委托授权机制,同时强调系统的安全性和稳定性;数据库结构的设计符合2NF 范式;在代码编写过程中,将可能产生错误的程序代码加入异常处理机制,捕捉错误并加以适当解决,尽量避免异常现象的发生。以下对主要的几个核心功能的实现方法加以阐述。
   
    3.1 产品结构树设计

   

    具有的基本功能:通过右键菜单可动态设计产品结构树项目。操作界面如图2 所示。
    此功能的实现主要是基于数据库的树状结构的实时显示,经分析采用递归算法来实现,并充分利用C# 语言的OOP 程序开发优点,添加基于数据库的树状目录结构动态构建函数及公用类。代码如下:
   
    public class GenTree
    { public void genTreeNode(TreeNodeCollection nodes,
    DataRowColleclion data, string parentld)
    { foreach (DataRow dr in data)
    {if(dr["parentld"[.ToString() == parentld)
    { TreeNode newNode = new TreeNode(dn"name"].
    ToString()};
    newNode.Name =dr["id"].ToString();
    newNode.Tag =drrisChild1;
    if(bool)dr["isChild"]
    { newNode.lmagelndex =3; nodes. Add(newNode);}
    else
    { newNode.lmagelndex =0; nodes.Add(newNode);
    genTreeNode(newNode.Nodes , data,
    dr["id"].ToString()}
    }
               }
            }
         }
    }

    在需要显示产品结构树窗体的Load 事件内,置入下面的代码:
   
    private void inputDocFrm_Load(object sender, EventArgs e)
    { SqlDataAdapter cmd =new SqlDataAdapter (“select* from
    M_ProductorTree “sqlConnection1 );
    cmd.Fill(ds,”treeInfo”); GenTree myTree =new GenTree();
    myTree.genTreeNode (ProTree.Nodes [O].Nodes,ds.Tables
    ["treelnfo"].Rows,
    ProTree.Nodes[O].Name};
    }// ProTree 是TreeView 控件实例
   
    以上公用类也可在人员部门结构树及其它结构树的实现时直接调用。
   
    3.2 图文档文件的版本管理
   

    假设系统参数设置图文档需保留五个版本,最新版本保留在M_Doc 数据表内,其它版本保存到M_DocBak.数据表内。图文档存储麦、产品结构树表和图档关联表结构关系如图3 所示。
    M_DocBak 表和M_Doc 表内的Doc 字段为Image 类型,用于存放文件。设计更新版本的存储过程UpdateDoc ,最后在业务逻辑层直接调用此存储过程并指定参数完成版本的更新,此模块采用分层设计模式来实现。创建对数据表M_DocFile 的更新图文档存储过程,代码如下:
   
    CREATE PROCEDURE UpdateDoc(@DclD int,@Doc image,
    @Modifier nvarchar(10),@Demo nvarchar(30),@Vercount int,
    @Newver int output)
    AS declare @baksum int;
    begin transaction
    select @baksum=count(*) from M_DocBak;
    // 存储现有版本数
    if(@baksum<@Vercount)
    begin
    Insert into dbo.M_DocBak(DocID, Ver, Doc, DocType,
    Modifier, Demo)
    select@D00ID , @baksum+1 ,Doc , docType , inputName ,
    Demo from dbo.M Doc
    where docld=@DoclD;
    update dbo.M_Doc set Doc=@Doc,inputName=@Modifier,
    inputDate=getdate() ,authorized =false
    where docId=@DocID;
    select @Newver=@baksum+1;
    return;
    end //设置返回版本号
    else
    begin // 如果版本已满
    update dbo.M_DocBak set Ver=Ver-1
    where DocID=@DoclD; // 版本号均减1
    declare @Tinyver nehar(6):
    select @Tinyver=min(Ver) from dbo.M_DocBak
    where DocID=@DoclD;
    delete from dbo.M_DocBak where Ver-@Tinyver;
    Insert into dbo.M_DocBak(DocID, Ver, Doc, DocType,
    Modifier, Demo)
    select @DocID , @baksum , Doc ,也cType.inputName ,@Demo
    from dbo.M_Doc where docld=@DocID;
    update dbo.M_Doc set Doc=@Doc.inputName=@Modifier,
    inputDate=getdate(). authorized=false
    where docld=@DoclD;
    select @Newver=@baksum;
    return:
    end commit GO// 提交事务
   
    在其它模块中可调用以上存储过程实现图档版本的更新。
   
    3.3 图文挡的实时预览

   
    传统的文档预览及修改功能一般是通过OLE 技术来实现的。OLE 即对象连接与嵌入,是应用程序共享对象的一种工业标准,为程序协同工作提供了一种方式。但利用OLE 技术首先需要客户方计算机已安装有此应用程序,且占用内存多,预览速度慢,不利于软件的后期部署。另外也可调用AutoCAD 自带的Vola View Express 来预览dwg 、dwf 及dxf 等常用格式的图档文件,但也需要客户端先安装有此类应用程序。
    综合以上考虑,本系统采用由AutoDesk 公司提供的一个ActiveX 控件来实现对dwg 文件的预览功能。此ActiveX 控件以ocx 格式提供.对应AutoCAD 安装盘内MIGRATE\AMA\Ocx 目录下的DwgThumbnail. ocx 文件。操作步骤如下:
    首先将DwgThumbnail. ocx 文件从安装光盘考入Winodws系统目录下的System32 目录内,然后在Windows 的开始→运行中输入regsvr32 DwgThumbnail.00x 并确定,注册此控件。
    打开C# 开发环境,通过菜单P叫eet→Add Reference… 调出刚拷入系统目录下的DwgThumbnaiL.ocx 文件,此时在C# 的解决方案管理器的References 目录下自动添加了DWGTHUMBNAILL 、stdole 和StdType 三个ActiveX 引用。
   
    4 系统的安装部署
   
    本系统是基于N盯架构和SQL Server2,创始后台DBMS的一个应用软件,必须首先安装.NET FrameWork 框架,然后再安装SQL Server2则客户端连接(数据库连接驱动程序) ,系统方能正常运行。
   
    5 结束语

   
    本系统实现了企业对大量图纸、文件、技术资料的规范管理,可以将企业的所有设计文档全部按产品结构树进行分类管理,并且提供了完善的版本及权限管理机制,确保技术文档管理的安全性和有效性;另外,系统提供的图档关联功能可以将同一图档关联到其它多个产品结构树分支上,实现→图多用,保证了图档的一致性。
    本系统能改善企业现有的图文档管理现状,提高管理规范性、安全保密性、操作可控性和版本一致性,从而提高产品数据信息的利用率,改善电子设计资料管理混乱的局面,为提高企业的技术创新能力和产品设计及研发效率起到非常重要的作用,同时也为后期的PDM 系统集成奠定良好的基础。

在MySQL中管理分级数据

十二月 15th, 2008

翻译至:http://dev.mysql.com/tech-resources/articles/hierarchical-data.html
作者网址:

介绍

Most users at one time or another have dealt with hierarchical data in a SQL database and no doubt learned that the management of hierarchical data is not what a relational database is intended for. The tables of a relational database are not hierarchical (like XML), but are simply a flat list. Hierarchical data has a parent-child relationship that is not naturally represented in a relational database table.

我们认为,分层的数据结合是这样的,每个项有一个父节点同时可以有0个或多个子项(根项父节点)。分层数据在大量的数据库应用程序中使用,包括论坛、邮件列表主题、商业组织结构、内容管理分类和产品分类。我们采用产品商店的产品分类作为示例:

hierarchical-data-1

 

大多数分层数据的组织都和上述类似,本文我们将介绍两种模式处理MySQL中的分层数据。我们从毗邻目录模式谈起吧。

毗邻目录模式

通常我们对于上述的示例分配采用下面的方式的存储与数据表之中:

CREATE TABLE category(
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
parent INT DEFAULT NULL);

INSERT INTO category
VALUES(1,'ELECTRONICS',NULL),(2,'TELEVISIONS',1),(3,'TUBE',2),
(4,'LCD',2),(5,'PLASMA',2),(6,'PORTABLE ELECTRONICS',1),
(7,'MP3 PLAYERS',6),(8,'FLASH',7),
(9,'CD PLAYERS',6),(10,'2 WAY RADIOS',6);

SELECT * FROM category ORDER BY category_id;

+-------------+----------------------+--------+
| category_id | name                 | parent |
+-------------+----------------------+--------+
|           1 | ELECTRONICS          |   NULL |
|           2 | TELEVISIONS          |      1 |
|           3 | TUBE                 |      2 |
|           4 | LCD                  |      2 |
|           5 | PLASMA               |      2 |
|           6 | PORTABLE ELECTRONICS |      1 |
|           7 | MP3 PLAYERS          |      6 |
|           8 | FLASH                |      7 |
|           9 | CD PLAYERS           |      6 |
|          10 | 2 WAY RADIOS         |      6 |
+-------------+----------------------+--------+
10 rows in set (0.00 sec)

在毗邻目录模式中,表中的每一个记录都包含一个父节点的指向。顶端的对象(本例中的电子产品)的父指向为NULL。毗邻目录模式的优点是简单、可以简单的看到mp3播放器是便携式电子产品的子项,同时也是电子产品的子项。While the adjacency list model can be dealt with fairly easily in client-side code, working with the model can be more problematic in pure SQL.

恢复完整的树结构

第一常见的工作便是用这些分层数据来显示一个完成树,

The first common task when dealing with hierarchical data is the display of the entire tree, usually with some form of indentation. The most common way of doing this is in pure SQL is through the use of a self-join:

SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS';

+-------------+----------------------+--------------+-------+
| lev1        | lev2                 | lev3         | lev4  |
+-------------+----------------------+--------------+-------+
| ELECTRONICS | TELEVISIONS          | TUBE         | NULL  |
| ELECTRONICS | TELEVISIONS          | LCD          | NULL  |
| ELECTRONICS | TELEVISIONS          | PLASMA       | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS  | FLASH |
| ELECTRONICS | PORTABLE ELECTRONICS | CD PLAYERS   | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | 2 WAY RADIOS | NULL  |
+-------------+----------------------+--------------+-------+
6 rows in set (0.00 sec)

找出所有的叶节点

We can find all the leaf nodes in our tree (没有子项) by using a LEFT JOIN query:

SELECT t1.name FROM
category AS t1 LEFT JOIN category as t2
ON t1.category_id = t2.parent
WHERE t2.category_id IS NULL;

+--------------+
| name         |
+--------------+
| TUBE         |
| LCD          |
| PLASMA       |
| FLASH        |
| CD PLAYERS   |
| 2 WAY RADIOS |
+--------------+

Retrieving a Single Path恢复单一路径

The self-join also allows us to see the full path through our hierarchies:

SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS' AND t4.name = 'FLASH';

+-------------+----------------------+-------------+-------+
| lev1        | lev2                 | lev3        | lev4  |
+-------------+----------------------+-------------+-------+
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS | FLASH |
+-------------+----------------------+-------------+-------+
1 row in set (0.01 sec)

The main limitation of such an approach is that you need one self-join for every level in the hierarchy, and performance will naturally degrade with each level added as the joining grows in complexity.

毗邻目录模式的不足

Working with the adjacency list model in pure SQL can be difficult at best. Before being able to see the full path of a category we have to know the level at which it resides. In addition, special care must be taken when deleting nodes because of the potential for orphaning an entire sub-tree in the process (delete the portable electronics category and all of its children are orphaned). Some of these limitations can be addressed through the use of client-side code or stored procedures. With a procedural language we can start at the bottom of the tree and iterate upwards to return the full tree or a single path. We can also use procedural programming to delete nodes without orphaning entire sub-trees by promoting one child element and re-ordering the remaining children to point to the new parent.

嵌套集合模式

What I would like to focus on in this article is a different approach, commonly referred to as the Nested Set Model. In the Nested Set Model, we can look at our hierarchy in a new way, not as nodes and lines, but as nested containers. Try picturing our electronics categories this way:

Notice how our hierarchy is still maintained, as parent categories envelop their children.We represent this form of hierarchy in a table through the use of left and right values to represent the nesting of our nodes:

CREATE TABLE nested_category (
 category_id INT AUTO_INCREMENT PRIMARY KEY,
 name VARCHAR(20) NOT NULL,
 lft INT NOT NULL,
 rgt INT NOT NULL
);

INSERT INTO nested_category
VALUES(1,'ELECTRONICS',1,20),(2,'TELEVISIONS',2,9),(3,'TUBE',3,4),
(4,'LCD',5,6),(5,'PLASMA',7,8),(6,'PORTABLE ELECTRONICS',10,19),
(7,'MP3 PLAYERS',11,14),(8,'FLASH',12,13),
(9,'CD PLAYERS',15,16),(10,'2 WAY RADIOS',17,18);

SELECT * FROM nested_category ORDER BY category_id;

+-------------+----------------------+-----+-----+
| category_id | name                 | lft | rgt |
+-------------+----------------------+-----+-----+
|           1 | ELECTRONICS          |   1 |  20 |
|           2 | TELEVISIONS          |   2 |   9 |
|           3 | TUBE                 |   3 |   4 |
|           4 | LCD                  |   5 |   6 |
|           5 | PLASMA               |   7 |   8 |
|           6 | PORTABLE ELECTRONICS |  10 |  19 |
|           7 | MP3 PLAYERS          |  11 |  14 |
|           8 | FLASH                |  12 |  13 |
|           9 | CD PLAYERS           |  15 |  16 |
|          10 | 2 WAY RADIOS         |  17 |  18 |
+-------------+----------------------+-----+-----+

之所以我们使用 lft 和 rgt ,因为 left 和 right 是MySQL的保留关键字, 参见保留关键字列表 http://dev.mysql.com/doc/mysql/en/reserved-words.html

那么我们想知道怎么去设置左右的值呢?我们从最左边的边框开始编号,一直到最右边:

这个设计转化成树结构就是这个样子:

When working with a tree, we work from left to right, one layer at a time, descending to each node’s children before assigning a right-hand number and moving on to the right. This approach is called the modified preorder tree traversal algorithm.

恢复整个树

We can retrieve the full tree through the use of a self-join that links parents with nodes on the basis that a node’s lft value will always appear between its parent’s lft and rgt values:

SELECT node.name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND parent.name = 'ELECTRONICS'
ORDER BY node.lft;

+----------------------+
| name                 |
+----------------------+
| ELECTRONICS          |
| TELEVISIONS          |
| TUBE                 |
| LCD                  |
| PLASMA               |
| PORTABLE ELECTRONICS |
| MP3 PLAYERS          |
| FLASH                |
| CD PLAYERS           |
| 2 WAY RADIOS         |
+----------------------+

Unlike our previous examples with the adjacency list model, this query will work regardless of the depth of the tree. We do not concern ourselves with the rgt value of the node in our BETWEEN clause because the rgt value will always fall within the same parent as the lft values.

找出所有的叶结点

Finding all leaf nodes in the nested set model even simpler than the LEFT JOIN method used in the adjacency list model. If you look at the nested_category table, you may notice that the lft and rgt values for leaf nodes are consecutive numbers. To find the leaf nodes, we look for nodes where rgt = lft + 1:

SELECT name
FROM nested_category
WHERE rgt = lft + 1;

+--------------+
| name         |
+--------------+
| TUBE         |
| LCD          |
| PLASMA       |
| FLASH        |
| CD PLAYERS   |
| 2 WAY RADIOS |
+--------------+

恢复单一路径

With the nested set model, we can retrieve a single path without having multiple self-joins:

SELECT parent.name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.name = 'FLASH'
ORDER BY parent.lft;

+----------------------+
| name                 |
+----------------------+
| ELECTRONICS          |
| PORTABLE ELECTRONICS |
| MP3 PLAYERS          |
| FLASH                |
+----------------------+

查找节点的深度

We have already looked at how to show the entire tree, but what if we want to also show the depth of each node in the tree, to better identify how each node fits in the hierarchy? This can be done by adding a COUNT function and a GROUP BY clause to our existing query for showing the entire tree:

SELECT node.name, (COUNT(parent.name) - 1) AS depth
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;

+----------------------+-------+
| name                 | depth |
+----------------------+-------+
| ELECTRONICS          |     0 |
| TELEVISIONS          |     1 |
| TUBE                 |     2 |
| LCD                  |     2 |
| PLASMA               |     2 |
| PORTABLE ELECTRONICS |     1 |
| MP3 PLAYERS          |     2 |
| FLASH                |     3 |
| CD PLAYERS           |     2 |
| 2 WAY RADIOS         |     2 |
+----------------------+-------+

We can use the depth value to indent our category names with the CONCAT and REPEAT string functions:

SELECT CONCAT( REPEAT(' ', COUNT(parent.name) - 1), node.name) AS name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;

+-----------------------+
| name                  |
+-----------------------+
| ELECTRONICS           |
|  TELEVISIONS          |
|   TUBE                |
|   LCD                 |
|   PLASMA              |
|  PORTABLE ELECTRONICS |
|   MP3 PLAYERS         |
|    FLASH              |
|   CD PLAYERS          |
|   2 WAY RADIOS        |
+-----------------------+

Of course, in a client-side application you will be more likely to use the depth value directly to display your hierarchy. Web developers could loop through the tree, adding <li></li> and <ul></ul> tags as the depth number increases and decreases.

子树的深度

When we need depth information for a sub-tree, we cannot limit either the node or parent tables in our self-join because it will corrupt our results. Instead, we add a third self-join, along with a sub-query to determine the depth that will be the new starting point for our sub-tree:

SELECT node.name, (COUNT(parent.name) - (sub_tree.depth + 1)) AS depth
FROM nested_category AS node,
	nested_category AS parent,
	nested_category AS sub_parent,
	(
		SELECT node.name, (COUNT(parent.name) - 1) AS depth
		FROM nested_category AS node,
		nested_category AS parent
		WHERE node.lft BETWEEN parent.lft AND parent.rgt
		AND node.name = 'PORTABLE ELECTRONICS'
		GROUP BY node.name
		ORDER BY node.lft
	)AS sub_tree
WHERE node.lft BETWEEN parent.lft AND parent.rgt
	AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt
	AND sub_parent.name = sub_tree.name
GROUP BY node.name
ORDER BY node.lft;

+----------------------+-------+
| name                 | depth |
+----------------------+-------+
| PORTABLE ELECTRONICS |     0 |
| MP3 PLAYERS          |     1 |
| FLASH                |     2 |
| CD PLAYERS           |     1 |
| 2 WAY RADIOS         |     1 |
+----------------------+-------+

This function can be used with any node name, including the root node. The depth values are always relative to the named node.

Find the Immediate Subordinates of a Node

Imagine you are showing a category of electronics products on a retailer web site. When a user clicks on a category, you would want to show the products of that category, as well as list its immediate sub-categories, but not the entire tree of categories beneath it. For this, we need to show the node and its immediate sub-nodes, but no further down the tree. For example, when showing the PORTABLE ELECTRONICS category, we will want to show MP3 PLAYERS, CD PLAYERS, and 2 WAY RADIOS, but not FLASH.

This can be easily accomplished by adding a HAVING clause to our previous query:

SELECT node.name, (COUNT(parent.name) - (sub_tree.depth + 1)) AS depth
FROM nested_category AS node,
	nested_category AS parent,
	nested_category AS sub_parent,
	(
		SELECT node.name, (COUNT(parent.name) - 1) AS depth
		FROM nested_category AS node,
		nested_category AS parent
		WHERE node.lft BETWEEN parent.lft AND parent.rgt
		AND node.name = 'PORTABLE ELECTRONICS'
		GROUP BY node.name
		ORDER BY node.lft
	)AS sub_tree
WHERE node.lft BETWEEN parent.lft AND parent.rgt
	AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt
	AND sub_parent.name = sub_tree.name
GROUP BY node.name
HAVING depth <= 1
ORDER BY node.lft;

+----------------------+-------+
| name                 | depth |
+----------------------+-------+
| PORTABLE ELECTRONICS |     0 |
| MP3 PLAYERS          |     1 |
| CD PLAYERS           |     1 |
| 2 WAY RADIOS         |     1 |
+----------------------+-------+

If you do not wish to show the parent node, change the HAVING depth <= 1 line to HAVING depth = 1.

Aggregate Functions in a Nested Set

Let’s add a table of products that we can use to demonstrate aggregate functions with:

CREATE TABLE product(
product_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(40),
category_id INT NOT NULL
);

INSERT INTO product(name, category_id) VALUES('20" TV',3),('36" TV',3),
('Super-LCD 42"',4),('Ultra-Plasma 62"',5),('Value Plasma 38"',5),
('Power-MP3 5gb',7),('Super-Player 1gb',8),('Porta CD',9),('CD To go!',9),
('Family Talk 360',10);

SELECT * FROM product;

+------------+-------------------+-------------+
| product_id | name              | category_id |
+------------+-------------------+-------------+
|          1 | 20" TV            |           3 |
|          2 | 36" TV            |           3 |
|          3 | Super-LCD 42"     |           4 |
|          4 | Ultra-Plasma 62"  |           5 |
|          5 | Value Plasma 38"  |           5 |
|          6 | Power-MP3 128mb   |           7 |
|          7 | Super-Shuffle 1gb |           8 |
|          8 | Porta CD          |           9 |
|          9 | CD To go!         |           9 |
|         10 | Family Talk 360   |          10 |
+------------+-------------------+-------------+

Now let’s produce a query that can retrieve our category tree, along with a product count for each category:

SELECT parent.name, COUNT(product.name)
FROM nested_category AS node ,
nested_category AS parent,
product
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.category_id = product.category_id
GROUP BY parent.name
ORDER BY node.lft;

+----------------------+---------------------+
| name                 | COUNT(product.name) |
+----------------------+---------------------+
| ELECTRONICS          |                  10 |
| TELEVISIONS          |                   5 |
| TUBE                 |                   2 |
| LCD                  |                   1 |
| PLASMA               |                   2 |
| PORTABLE ELECTRONICS |                   5 |
| MP3 PLAYERS          |                   2 |
| FLASH                |                   1 |
| CD PLAYERS           |                   2 |
| 2 WAY RADIOS         |                   1 |
+----------------------+---------------------+

This is our typical whole tree query with a COUNT and GROUP BY added, along with a reference to the product table and a join between the node and product table in the WHERE clause. As you can see, there is a count for each category and the count of subcategories is reflected in the parent categories.

添加一个新的节点

Now that we have learned how to query our tree, we should take a look at how to update our tree by adding a new node. Let’s look at our nested set diagram again:

If we wanted to add a new node between the TELEVISIONS and PORTABLE ELECTRONICS nodes, the new node would have lft and rgt values of 10 and 11, and all nodes to its right would have their lft and rgt values increased by two. We would then add the new node with the appropriate lft and rgt values. While this can be done with a stored procedure in MySQL 5, I will assume for the moment that most readers are using 4.1, as it is the latest stable version, and I will isolate my queries with a LOCK TABLES statement instead:

LOCK TABLE nested_category WRITE;

SELECT @myRight := rgt FROM nested_category
WHERE name = 'TELEVISIONS';

UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft + 2 WHERE lft > @myRight;

INSERT INTO nested_category(name, lft, rgt) VALUES('GAME CONSOLES', @myRight + 1, @myRight + 2);

UNLOCK TABLES;

We can then check our nesting with our indented tree query:

SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;

+-----------------------+
| name                  |
+-----------------------+
| ELECTRONICS           |
|  TELEVISIONS          |
|   TUBE                |
|   LCD                 |
|   PLASMA              |
|  GAME CONSOLES        |
|  PORTABLE ELECTRONICS |
|   MP3 PLAYERS         |
|    FLASH              |
|   CD PLAYERS          |
|   2 WAY RADIOS        |
+-----------------------+

If we instead want to add a node as a child of a node that has no existing children, we need to modify our procedure slightly. Let’s add a new FRS node below the 2 WAY RADIOS node:

LOCK TABLE nested_category WRITE;

SELECT @myLeft := lft FROM nested_category

WHERE name = '2 WAY RADIOS';

UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myLeft;
UPDATE nested_category SET lft = lft + 2 WHERE lft > @myLeft;

INSERT INTO nested_category(name, lft, rgt) VALUES('FRS', @myLeft + 1, @myLeft + 2);

UNLOCK TABLES;

In this example we expand everything to the right of the left-hand number of our proud new parent node, then place the node to the right of the left-hand value. As you can see, our new node is now properly nested:

SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;

+-----------------------+
| name                  |
+-----------------------+
| ELECTRONICS           |
|  TELEVISIONS          |
|   TUBE                |
|   LCD                 |
|   PLASMA              |
|  GAME CONSOLES        |
|  PORTABLE ELECTRONICS |
|   MP3 PLAYERS         |
|    FLASH              |
|   CD PLAYERS          |
|   2 WAY RADIOS        |
|    FRS                |
+-----------------------+

删除节点

The last basic task involved in working with nested sets is the removal of nodes. The course of action you take when deleting a node depends on the node’s position in the hierarchy; deleting leaf nodes is easier than deleting nodes with children because we have to handle the orphaned nodes.

When deleting a leaf node, the process if just the opposite of adding a new node, we delete the node and its width from every node to its right:

LOCK TABLE nested_category WRITE;

SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
FROM nested_category
WHERE name = 'GAME CONSOLES';

DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight;

UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight;

UNLOCK TABLES;

And once again, we execute our indented tree query to confirm that our node has been deleted without corrupting the hierarchy:

SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;

+-----------------------+
| name                  |
+-----------------------+
| ELECTRONICS           |
|  TELEVISIONS          |
|   TUBE                |
|   LCD                 |
|   PLASMA              |
|  PORTABLE ELECTRONICS |
|   MP3 PLAYERS         |
|    FLASH              |
|   CD PLAYERS          |
|   2 WAY RADIOS        |
|    FRS                |
+-----------------------+

This approach works equally well to delete a node and all its children:

LOCK TABLE nested_category WRITE;

SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
FROM nested_category
WHERE name = 'MP3 PLAYERS';

DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight;

UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight;

UNLOCK TABLES;

And once again, we query to see that we have successfully deleted an entire sub-tree:

SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;

+-----------------------+
| name                  |
+-----------------------+
| ELECTRONICS           |
|  TELEVISIONS          |
|   TUBE                |
|   LCD                 |
|   PLASMA              |
|  PORTABLE ELECTRONICS |
|   CD PLAYERS          |
|   2 WAY RADIOS        |
|    FRS                |
+-----------------------+

The other scenario we have to deal with is the deletion of a parent node but not the children. In some cases you may wish to just change the name to a placeholder until a replacement is presented, such as when a supervisor is fired. In other cases, the child nodes should all be moved up to the level of the deleted parent:

LOCK TABLE nested_category WRITE;

SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
FROM nested_category
WHERE name = 'PORTABLE ELECTRONICS';

DELETE FROM nested_category WHERE lft = @myLeft;

UPDATE nested_category SET rgt = rgt - 1, lft = lft - 1 WHERE lft BETWEEN @myLeft AND @myRight;
UPDATE nested_category SET rgt = rgt - 2 WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - 2 WHERE lft > @myRight;

UNLOCK TABLES;

In this case we subtract two from all elements to the right of the node (since without children it would have a width of two), and one from the nodes that are its children (to close the gap created by the loss of the parent’s left value). Once again, we can confirm our elements have been promoted:

SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;

+---------------+
| name          |
+---------------+
| ELECTRONICS   |
|  TELEVISIONS  |
|   TUBE        |
|   LCD         |
|   PLASMA      |
|  CD PLAYERS   |
|  2 WAY RADIOS |
|   FRS         |
+---------------+

Other scenarios when deleting nodes would include promoting one of the children to the parent position and moving the child nodes under a sibling of the parent node, but for the sake of space these scenarios will not be covered in this article.

Final Thoughts

While I hope the information within this article will be of use to you, the concept of nested sets in SQL has been around for over a decade, and there is a lot of additional information available in books and on the Internet. In my opinion the most comprehensive source of information on managing hierarchical information is a book called Joe Celko’s Trees and Hierarchies in SQL for Smarties, written by a very respected author in the field of advanced SQL, Joe Celko. Joe Celko is often credited with the nested sets model and is by far the most prolific author on the subject. I have found Celko’s book to be an invaluable resource in my own studies and highly recommend it. The book covers advanced topics which I have not covered in this article, and provides additional methods for managing hierarchical data in addition to the Adjacency List and Nested Set models.

In the References / Resources section that follows I have listed some web resources that may be of use in your research of managing hierarchal data, including a pair of PHP related resources that include pre-built PHP libraries for handling nested sets in MySQL. Those of you who currently use the adjacency list model and would like to experiment with the nested set model will find sample code for converting between the two in the Storing Hierarchical Data in a Database resource listed below.

FltReadFile

十一月 27th, 2008

FltReadFile 从一个打开的文件、流或者设备中读取数据。

NTSTATUS
  FltReadFile(
    IN PFLT_INSTANCE  InitiatingInstance,
    IN PFILE_OBJECT  FileObject,
    IN PLARGE_INTEGER  ByteOffset OPTIONAL,
    IN ULONG  Length,
    OUT PVOID  Buffer,
    IN FLT_IO_OPERATION_FLAGS  Flags,
    OUT PULONG  BytesRead OPTIONAL,
    IN PFLT_COMPLETED_ASYNC_IO_CALLBACK  CallbackRoutine OPTIONAL,
    IN PVOID  CallbackContext OPTIONAL
    ); 

参数

InitiatingInstance
Opaque instance pointer for the minifilter driver instance that is initiating the read request. This parameter is required and cannot be NULL.
FileObject
Pointer to a file object for the file that the data is to be read from. This file object must be currently open. Calling FltReadFile when the file object is not yet open or is no longer open (for example, in a pre-create or post-cleanup callback routine) causes the system to ASSERT on a checked build. This parameter is required and cannot be NULL.
ByteOffset
Pointer to a caller-allocated variable that specifies the starting byte offset within the file where the read operation is to begin.If this offset is supplied, or if the FLTFL_IO_OPERATION_DO_NOT_UPDATE_BYTE_OFFSET flag is specified in the Flags parameter, FltReadFile does not update the file object’s CurrentByteOffset field.If the file object that FileObject points to was opened for synchronous I/O, the caller of FltReadFile can specify that the current file position offset be used instead of an explicit ByteOffset value by setting this parameter to NULL. If the current file position is used, FltReadFile updates the file object’s CurrentByteOffset field by adding the number of bytes read when it completes the read operation.

If the file object that FileObject points to was opened for asynchronous I/O, this parameter is required and cannot be NULL.

Length
Size, in bytes, of the buffer that the Buffer parameter points to.
Buffer
Pointer to a caller-allocated buffer that receives the data that is read from the file.
Flags
Bitmask of flags specifying the type of read operation to be performed.

标志 含义
FLTFL_IO_OPERATION_DO_NOT_UPDATE_BYTE_OFFSET Minifilter drivers can set this flag to specify that FltReadFile should not update the file object’s CurrentByteOffset field.
FLTFL_IO_OPERATION_NON_CACHED Minifilter drivers can set this flag to specify a noncached read, even if the file object was not opened with FILE_NO_INTERMEDIATE_BUFFERING.
FLTFL_IO_OPERATION_PAGING Minifilter drivers can set this flag to specify a paging read.

 

BytesRead
Pointer to a caller-allocated variable that receives the number of bytes read from the file. If CallbackRoutine is not NULL, this parameter is ignored. Otherwise, this parameter is optional and can be NULL.
CallbackRoutine
Pointer to a PFLT_COMPLETED_ASYNC_IO_CALLBACK-typed callback routine to call when the read operation is complete. This parameter is optional and can be NULL.
CallbackContext
Context pointer to be passed to the CallbackRoutine if one is present. This parameter is optional and can be NULL. If CallbackRoutine is NULL, this parameter is ignored. 

返回值

FltReadFile returns the NTSTATUS value that was returned by the file system.

注释

微过滤器调用 FltReadFile 从一个打开的文件中读取数据。

FltReadFile 创建一个读请求并发送到微过滤器驱动挂载的进程后到文件系统。指定的进程之上的挂载进程则收不到读请求。

FltReadFile 在下列条件成立时,准备非缓存I/O:

  • 调用者在参数 Flags 中设置了 FLTFL_IO_OPERATION_NON_CACHED 标志;
  • 文件对象使用非缓存I/O打开的。通常是在调用 FltCreateFile, FltCreateFileEx, 或 ZwCreateFile 时,在参数CreateOptions 标志中指定了FILE_NO_INTERMEDIATE_BUFFERING 。 

Noncached I/O imposes the following restrictions on the parameter values passed to FltReadFile:

  • The buffer that the Buffer parameter points to must be aligned in accordance with the alignment requirement of the underlying storage device. To allocate such an aligned buffer, call FltAllocatePoolAlignedWithTag.
  • The byte offset that the ByteOffset parameter points to must be a nonnegative multiple of the volume’s sector size.
  • The length specified in the Length parameter must be a nonnegative multiple of the volume’s sector size.

 

If an attempt is made to read beyond the end of the file, FltReadFile returns an error.

If the value of the CallbackRoutine parameter is not NULL, the read operation is performed asynchronously.

If the value of the CallbackRoutine parameter is NULL, the read operation is performed synchronously. That is, FltReadFile waits until the read operation is complete before returning. This is true even if the file object that FileObject points to was opened for asynchronous I/O.

If multiple threads call FltReadFile for the same file object, and the file object was opened for synchronous I/O, the Filter Manager does not attempt to serialize I/O on the file. In this respect, FltReadFile differs from ZwReadFile.

Requirements

IRQL: PASSIVE_LEVEL

Headers: Declared in fltkernel.h. Include fltkernel.h.

在miniFilter驱动里管理上下文

十一月 11th, 2008

上下文 是一个由微过滤器定义的结构,可以用于和一个过滤管理器对象关联。微过滤器可以微下列对象创建和设置上下文:

  • 文件(Files,Vista及后续操作系统)
  • 实例
  • 流句柄(File objects)
  • Transactions(Files,Vista及后续操作系统)

除了卷上下文必须在非分页的内存池上分配,其他的既可以在非分页也可以在分页内存池中分配。

过滤管理器在他们挂载的对象删除后、微过滤器驱动实例从卷上解挂载或微过滤器卸载,自动删除相关的上下文。

注册上下文类型

当微过滤器驱动在DriverEntry中调用 FsRegisterFilter时,必须注册好每个要使用的上下文类型。

要注册上下文类型,微过滤器驱动创建一个类型为 FLT_CONTEXT_REGISTRATION 的变长数组。保持在 FLT_REGISTRATION 结构的 ContextRegistration 域中,传递到 FltRegisterFilter 函数的 Registration 参数。数组中的成员的顺序没有关系,但最后一个成员必须是 {FLT_CONTEXT_END}。

对于微过滤器驱动使用到的每一个上下文类型,FLT_CONTEXT_REGISTRATION 结构必须至少提供一个上下文定义。每一个FLT_CONTEXT_REGISTRATION结构定义了上下文的类型、大小和其他信息。

微过滤器驱动调用FltAllocateContext创建一个新的上下文,过滤管理器使用size参数

对于固定大小的上下文,FLT_CONTEXT_REGISTRATION结构的Size成员指定了字节大小,上下文的大小最大为MAXUSHORT(64KB)。0也是一个有效的值。过滤管理器用旁视列表分配固定大小的上下文。

对于不定大小的上下文,Size成员必须设置为FLT_VARIABLE_SIZED_CONTEXTS。过滤管理器直接从分页或非分页的内存池中分配不定长的上下文。

FLT_CONTEXT_REGISTRATION结构的Flags成员,可以指定FLTFL_CONTEXT_REGISTRATION_NO_EXACT_SIZE_MATCH。如果微过滤器驱动使用定长的上下文,并且指定了这个标志,上下文的大小大于等于请求的长度的话,过滤管理器从旁视列表里分配内存。否则上下文大小必须等于请求大小。

对于一个给定的上下文类型,微过滤器驱动可以支持3种固定大小(每一个大小不同)的上下文定义和1种变长定义。【更多信息,参见FLT_CONTEXT_REGISTRATION】

微过滤器驱动支持可选上下文释放前的清理回调例程。【参见 PFLT_CONTEXT_CLEANUP_CALLBACK】

微过滤器驱动可以定义一个他自己的分配、释放回调例程。【参见PFLT_CONTEXT_ALLOCATE_CALLBACK 和 PFLT_CONTEXT_FREE_CALLBACK】。

下面是从CTX示例中的部分代码,展示了一个用于注册实例、文件、流和文件对象(流句柄)的FLT_CONTEXT_REGISTRATION结构数组。

vtiger CRM 安装问题总结

九月 4th, 2008
  1. IMAP webMail的设置
  2. webMail的中文正确显示

Read the rest of this entry »