Programming

怀念Richard Stevens

今天读W. Richard Stevens的《TCP/IP详解》第一卷,越发感觉此书颇有意味。

Richard Stevens老先生文笔流畅不说,其严谨的态度也是让吾等佩服不已。他为了写这本书,硬是专门组建了一个Unix计算机网络(打开书的第一页便是),里面计算机用的操作系统涵盖很多知名Unix变种,像BSD,SVR,Solaris,里面的每一个例子作者也都是亲自在这些机器上测试过的。例子虽小,确很能说明问题,图例虽不生动华丽,但详尽清晰,文字着墨不多,却又字字珠玑。让人读来浑然不知身外之事。读此书真乃享受也。不愧是流传多年的经典之作!

手头上还有另一本 Richard Stevens老先生的经典之作——《Unix网络编程,卷I》,此书虽刚开始翻,但其“启发性”已经扑面而来,相遇恨晚啊!

Richard Stevens留下的Unix编程和计算机网络的书籍大大造福后人,其人也被誉为Unix导师的导师。一生能有其中一部力作可谓无憾矣!而况七部乎?而且,其写作风格也可谓技术写作的典范,其严谨的态度更是任何写书或者打算写书的人都应该好好学习的!

(对比国内……算了,咱就别对比了,一对比咱们都可以去死了。什么叫严谨,什么叫负责,读一读那书便知。啥也不说了,继续向人家学习吧!)

他不清楚的,他下决心要弄明白。他知道的,他要努力传授给所有感兴趣的人们!这就是真正的Richard Stevens!不幸的是 Richard Stevens老先生已经于1999年9月1日离我们而去,他的离去是计算机界的重大损失!想必这时他还坐在上帝身边,继续从事他的写作。

读技术类的书从来没感动过,Richard的书是第一个例外,但不会是最后一个!以此文祭奠尊敬的Richard Stevens!

如何获得本机IP

如何在程序中通过函数直接获取本机IP一直是讨论比较多的话题。我在做XylFTP项目的时候,碰巧编写了客户端和服务器端两边的获取本地IP的函数(方法)。

服务器端在处理PASV命令时,需要回复自己的IP和进行数据连接的端口。由于服务器端是用C语言编写,所以可以使用底层的操作来实现。具体代码如下:

它使用了ioctl这个系统调用,主要是针对网卡驱动来使用。它的优点是即使本机有多个网卡,也可以正常工作。缺点是如果网卡驱动不支持ioctl操作,此函数则会失败。

客户端在发送PORT命令时,也需要带上自己的IP。由于客户端是用Java编写,所以不方便使用底层的方法,幸运的是Java的Socket类为我们提供了一个getLocalAddress()方法。具体代码如下:

用这个方法获得IP的优点是简单,代码只有数行,但是它要求必须先建立Socket,否则得到的只是127.0.0.1。如果我们想不建立连接就获取IP,此法就不适用了。

后来,在和刘洋同学商量此问题时,见到了另一种在Java中获取本机IP的方法,用的是Java包装的NetworkInterface类,具体代码如下:

这种方法相对比较麻烦,和最上面的用C实现的方法有相似之处。但在合适的条件下,仍然可以选择使用。

还有没有其它方法?当然有!不过这些方法只能针对具体环境,比如,在局域网的UDP通信环境中如何不用NetworkInterface获得本机IP?一种可能的解决方法(仅仅理论,未实践)是:先在局域网内进行广播,让接受者接到广播包后获取源IP,并把此IP当作数据发送到广播主机,这样可以获取广播主机自己的IP。另一种方法是通过RARP,不过前提是此局域网里有一台RARP服务器。

Java课程设计

这两周做Java课程设计。虽然说是两周的时间,可是真正做的时间很少,不光忙着准备期末考试,而且其它事也比较多。做的过程中对Java的Swing有了进一步的认识,也学会了NetBeans的使用。感觉它真是个好东西,可以大幅度提高生产力啊!

我和卢洪伟同学花两天的时间做了一个Java扫描器,非常简单,仅支持简单端口扫描,ping和finger,简单插件功能。有兴趣的同学可以在我们的基础上进一步完善,源代码可以在这里下载:
http://wangcong.org/src/JavaScanner.rar

它在GPLv3许可证下发布,请自由使用。

跟我一起复习数据库(三)

简单地说,如果把关系看作一张表,那么一个元组就是这张表的一行,一个属性就是一列;元组的数目称为势,属性的数目称作度;域是值的集合,关系中属性的值取自域。

关系变量分两种类型:基本关系变量和视图(也分别称为实的和虚的关系变量)。SQL中的域不是类型;SQL中的表(基本的或其他的)不是关系,因为( a)它们允许重复的行;( b)它们的 列有从左到右的次序。实际上,它们甚至可以有两个或多个相同名字的列(对基本表或视图名,SQL中是不允许重名的) 。

基本关系代数的8个操作符:
选择:返回一个关系,其中的元组来自指定关系中所有满足指定条件的元组。
投影:返回一个关系,由去掉若干属性列后的指定关系中剩余的所有(子)元组组成。
积:返回一个关系,包含任意两个分别来自两个指定关系的元组组合的所有可能的元组。
并:返回的关系由两个指定关系中所有的元组构成。
交:返回关系由同时出现在两个指定关系中的元组构成。
差:返回的关系由那些属于第一个关系却不属于第二个关系的元组构成。
连接: 返回关系中的元组是两个元组的结合,这两个元组分别来自两个指定的关系,需
满足的条件是此两个关系存在相同的属性,且在相同属性上有相同的值(在结果元组中,共同的值只出现一次,而不是两次) 。
除:此操作是在两个单目关系和一个双目关系上,返回关系的元组满足以下条件:这些
元组来自一个单目关系,其在双目关系中的对应元组能与另一个单目关系中的所有元组相匹配。

A根据C除以B(其中A是被除数, B是除数,C是中间数)
也可以粗略地说,结果关系包含A中满足如下条件的 X值:在C中对应的Y值包含B中的所有Y值。

跟我一起复习数据库(二)

下面是一些重要的概念和解释,均摘选自原书

数据库:

数据库系统是指一个计算机存储记录的系统,即,它是一个计算机系统,该系统的目标是存储信息并支持用户检索和更新所需要的信息。

数据模型:
数据模型 是对对象、操作等的一个抽象的、自包含的逻辑定义,这些定义合起来构成了一个面对用户的抽象机。

数据库方法的优点:

(1) 数据共享( 2) 减少冗余 (3) 避免不一致 (4) 提供事务支持 (5) 保持完整性 (6) 增强安全性 (7) 平衡相互冲突的请求 (8) 加强标准化

ANSI/SPARC体系结构分为三层:即内模式、概念模式和外模式。广义地讲:

• 内模式(存储模式)是最接近物理存储的—也就是,数据的物理存储方式;
• 外模式(用户模式)是最接近用户的—也就是,用户所看到的数据视图;
• 概念模式(公共逻辑模式,或有时称逻辑模式)是介于前两者之间的间接的层次。

“外部视图”(即外模式)会有许许多多,每一个都或多或少地抽象表示整个数据库的某一部分,而“概念视图” (概念模式)只有一个,它包含对现实世界数据库的抽象表示。 大多数用户只对整个数据库的某一部分感兴趣)。同样,“内部视图”(即内模式)也只有一个,表示数据库的物理存储。

直观地说,关系系统是这样的系统:
1) 结构化方面:数据库中的数据对用户来说是表,并且只是表;
2) 完整性方面:数据库中的这些表满足一定的完整性约束(在本节最后讨论);
3) 操纵性方面:用户可以使用用于表操作的操作符。

从一个表导出另一个表的操作符。其中,选择、投影和连接这三种尤为重要。
• 选择操作是从表中提取特定的几行。
• 投影操作是从表中提取特定的几列。
• 连接操作是根据某一列的值将两个表连接起来。

事务:事务是一个逻辑工作单元,通常包括几个数据库操作,并指出当不同的操作处于同一事务中时,用户要通知系统。 BEGIN TRANSACTION、COMMIT 和ROLLBACK操作就是基于这一考虑提出的。

注意以下几点问题:
1) 要保证事务的原子性 ,也就是说,即使系统在处理中发生故障,也要保证(从逻辑的
观点)事务中的操作要么都做,要么都不做。
2) 要保证事务的持续性 ,一旦事务成功地执行了 COMMIT,即使随后系统发生故障,也
要确保它的更新写入数据库中。注意:本质上,是事务的持续性保证了数据库中数据
的持久性。
3) 要保证事务的隔离性,事务 T1对数据库的更新操作对任何不同的事务 T2来说是不可见
的,直到或除非 T1成功执行 COMMIT。COMMIT使事务的更新对其他事务来说是可见
的;这些更新已经提交,并且要保证不能取消。若事务执行了ROLLBACK,所有事务
的更新操作都要取消(回滚) 。对后一种情况,其结果应该就像事务一开始就没有执行
一样。
4) 要保证一组并发事务的交叉执行(通常)是可串行的,即其结果与按某一未指明的次
序串行地执行时的结果相同。

基本关系变量和视图的不同在于:
• 基本关系变量是“真实存在”,意味着它们所表示的数据真正存在于数据库中。
• 相反,视图并不“真实存在”,只是提供了观察“真实数据”的各种方式。

跟我一起复习数据库(一)

本文基于Christopher J. Date所著的《An Introduction to Database Systems》的中文翻译版。这本书是数据库领域的经典书籍,现在都出到第8版了,可见作者是多么认真负责。本文也可以看作是此书的读书笔记。

在正式开始之前,我们先来八卦一下传说中的E.F.Codd——关系模型的发明者,关系型数据库的奠基人。E.F.Codd的全名是Edgar Frank “Ted” Codd。1970年,当时E.F.Codd还是加利福尼亚的圣何塞研究中心的年轻的IBM程序员,他就在ACM上发了一篇paper,名为A Relational Model of Data for Large Shared Data Banks,里面提出了数据应该按照基于关系原理的不同种类来组织。也就是这篇论文,成了关系型数据库的开山之作,成了计算机科学界的经典论文之一。随后,Codd又连续发了几篇论文,为关系型数据库奠定了坚实的理论基础。Codd也因此获得了1981年的图灵奖。你可能不知道的是,Codd其实是英国人,而不是美国人,他1923年生于英格兰的Portland。2003年4月18日,他在佛罗里达州的威廉姆斯岛的家中离开了人世,但Codd的理论长留你我心中了。

(Edgar F. Codd)

八卦一下编程语言

TIOBE上看到上个月的编程语言排行榜,咱也来八卦一把。

Java依旧笑春风,占据了老大的位置,看来粉丝仍然不减。我本人对Java语言并不怎么喜欢,但我承认,Java的Swing确实是个好东西。

虽然我本人是C的粉丝,但C排到第二的位置也确实让我吃惊了一把,没想到它会排这么靠前。很早以前就一些人叫嚣C语言要完蛋了,我想他们看了这个结果会闭嘴的。连C++之父都说:“C是而且将会保持是一门有生存力的语言。”

C++排第三,这没什么奇怪的。据说包括Knuth在内的一些人都喜欢C++。我对C++的感觉比Java要好些,就是它太大了……

VB居然紧随C++之后,让我大跌眼镜。M$造的这东西也就哄哄年轻人吧。Python居然沦落到第7,而且还有下滑的趋势, 实在让我感到担心。其它值得一提的是,Lisp排第15,感觉可以。D语言虽然很早就听说过了,可没想到会爬升这么快。Lua离前20名只差0.003%,可见WOW的影响力。

对Java的几点思考

1. 符号带来的问题

我们知道,Java里的整数类型——byte,short,int——全部是有符号的。这有时会带来麻烦,如果我们并不想要最高位作为符号位的话。

下面是两个例子:

a) 我们不得不这样做来正确显示IP地址:

byte[] ipa;
InetAddress ina = CmdSocket.getLocalAddress();
ipa = ina.getAddress();
for (int i=0; i< 4; i++) {
System.out.println(((int)ipa[i]) & 0xff);
}

b) 记住,char是无符号的:[1]

public class Multicast {
public static void main(String[] args) {
System.out.println((int) (char) (byte) -1);
}
}

2. 类型问题

Java在处理类型之间转化时有着自己的规则,这些规则有时并不好理解。

a. 转化[2]

short x = 0;
int i = 123456;
x += i;
x = x + i; //<== fails here

b. 溢出[3]

public class LongDivision {
public static void main(String[] args) {
final long MICROS_PER_DAY = 24 60 60 1000 1000;
final long MILLIS_PER_DAY = 24 60 60 * 1000;
System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY);
}
}

3. 读文件的问题

Java中有各种各样的流,这使得选取恰当的流并不容易。有时,我们为了得到一个理想的流,不得不转化多次:

BufferedOutputStream FTPOutStream;
FTPOutStream = new BufferedOutputStream((OutputStream)System.out);
BufferedInputStream FTPInStream;
FTPInStream = new BufferedInputStream(new FileInputStream(LocalFile));

而且,Java中的DataInputStream流并不像想象中那么好,根本原因是:readInt()并不认识int,它只管读下面4个字节,并不在乎它这四个到底是什么,即使在文本中它们看起来可能只是几个非数字的字符!同样,readLong不认识long,readDouble()也不认识double。而这一点,C++和C做得明显要更好。

Scanner或许可以帮上一些忙,不过很可惜,你不能用它读一个char!


参考资料:

[1][2][3]均摘自《Java Puzzlers》
[4]《Java Language Specification》

goto惹了谁?

最近我是和goto卯上了,先是向内核邮件列表提交了关于goto标签排版的补丁,现在又准备在自己的书里详细论述一下goto的坏处和好处。

发现这真不是一个聪明的决定,几个简单的搜索就已经把自己拉下goto的泥潭中了。 历史上,关于goto的争论似乎就没怎么停过。众所周知,最早论述goto的还是大牛Edsger Dijkstra,他早在1968年(那时候还没C语言呢)写了一篇流传颇广的paper——Go To Statement Considered Harmful,正是这篇短小的文章,向人们第一次揭示了goto的坏处,于是goto的名声就不怎么好了。直至今天,在我们的大学里,教授们也忠告我们不要使用goto,它会让你的程序变得很难读,云云~

这当然还没完,自从有了Go To Statement Considered Harmful,XX Considered Harmful似乎就成了计算机界很流行的用语了,于是乎也就有了“GOTO Considered Harmful” Considered Harmful“‘GOTO Considered Harmful’ Considered Harmful” Considered Harmful?。再到后来(1974年),Knuth老大爷看不惯了,他觉得该为goto平平反了,于是出手写了一篇Structured Programming with go to Statements ,牛人不愧是牛人,从来不盲从别人不说,端的还是理论和实践两把手。

另一个时髦的而且Considered Harmful的话题是Threads Considered Harmful

BTW:这篇文章也Considered Harmful?;)