Andy Niu

作为一名菜鸟,记录个人的心得体会,欢迎大家指正。
posts - 52, comments - 3, trackbacks - 0, articles - 0

2012年3月7日

IP地址是一个规定,现在使用的是IPv4,既由4个0-255之间的数字组成,在计算机内部存储时只需要4个字节即可。在计算机中,IP地址是分配给网卡的,每个网卡有一个唯一的IP地址,如果一个计算机有多个网卡,则该台计算机则拥有多个不同的IP地址,在同一个网络内部,IP地址不能相同。IP地址的概念类似于电话号码、身份证这样的概念。由于IP地址不方便记忆,所以有专门创造了域名(Domain Name)的概念,其实就是给IP取一个字符的名字,例如163.com、sina.com等。IP和域名之间存在一定的对应关系。如果把IP地址类比成身份证号的话,那么域名就是你的姓名。 

一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区分不同的服务的。        

其实在网络中只能使用IP地址进行数据传输,所以在传输以前,需要把域名转换为IP,这个由称作DNS的服务器专门来完成。 所以在网络编程中,可以使用IP或域名来标识网络上的一台设备。      为了在一台设备上可以运行多个程序,人为的设计了端口(Port)的概念,类似的例子是公司内部的分机号码。规定一个设备有216个,也就是65536个端口,每个端口对应一个唯一的程序。每个网络程序,无论是客户端还是服务器端,都对应一个或多个特定的端口号。由于0-1024之间多被操作系统占用,所以实际编程时一般采用1024以后的端口号。 下面是一些常见的服务对应的端口:

ftp:23,telnet:23,smtp:25,dns:53,http:80,https:443

使用端口号,可以找到一台设备上唯一的一个程序。  所以如果需要和某台计算机建立连接的话,只需要知道IP地址或域名即可,但是如果想和该台计算机上的某个程序交换数据的话,还必须知道该程序使用的端口号。


 

数据传输方式 在网络上,不管是有线传输还是无线传输,数据传输的方式有两种:

TCP(Transfer Control Protocol) 传输控制协议方式,该传输方式是一种稳定可靠的传送方式,类似于显示中的打电话。只需要建立一次连接,就可以多次传输数据。就像电话只需要拨一次号,就可以实现一直通话一样,如果你说的话不清楚,对方会要求你重复,保证传输的数据可靠。 使用该种方式的优点是稳定可靠,缺点是建立连接和维持连接的代价高,传输速度不快。

UDP(User Datagram Protocol) 用户数据报协议方式,该传输方式不建立稳定的连接,类似于发短信息。每次发送数据都直接发送。发送多条短信,就需要多次输入对方的号码。该传输方式不可靠,数据有可能收不到,系统只保证尽力发送。 使用该种方式的优点是开销小,传输速度快,缺点是数据有可能会丢失。          在实际的网络编程中,大家可以根据需要选择任何一种传输方式,或组合使用这两种方式实现数据的传递。

posted @ 2012-03-07 16:24 Andy Niu 阅读(19) 评论(0) 编辑

2012年3月6日

MSDN的解释:阻塞调用线程,直到某个线程终止时为止。首先明确几个问题:

1、一个进程由一个或者多个线程组成,线程之间有可能会存在一定的先后关系和互斥关系。多线程编程,首先就是要想办法划分线程,减少线程之间的先后关系和互斥关系,这样才能保证线程之间的独立性,各自工作,不受影响。Google的MapReduce核心思想就是尽量减少线程之间的先后关系和互斥关系。

2、无论如何地想办法,线程之间还是会存在一定的先后关系和互斥关系,这时候可以使用Thread.Join方法。

3、一个线程在执行的过程中,可能调用另一个线程,前者可以称为调用线程,后者成为被调用线程。

4、Thread.Join方法的使用场景:调用线程挂起,等待被调用线程执行完毕后,继续执行。

5、被调用线程执行Join方法,告诉调用线程,你先暂停,我执行完了,你再执行。从而保证了先后关系。

6、考虑一种有意思的情况:在当前线程内调用Thread.CurrentThread.Join() 会出现什么情况?分析:假设当前线程为A,此时调用线程为A,被调用线程也为A,由于调用线程A暂停,被调用线程A(也就是调用线程A)永远不会执行完毕,造成死锁。

posted @ 2012-03-06 11:12 Andy Niu 阅读(22) 评论(0) 编辑

我们可能经常会用到 Thread.Sleep 函数来使线程挂起一段时间。那么你有没有正确的理解这个函数的用法呢?思考下面这两个问题:
1、假设现在是 2008-4-7 12:00:00.000,如果我调用一下 Thread.Sleep(1000) ,在 2008-4-7 12:00:01.000 的时候,这个线程会 不会被唤醒?
2、某人的代码中用了一句看似莫明其妙的话:Thread.Sleep(0) 。既然是 Sleep 0 毫秒,那么他跟去掉这句代码相比,有啥区别么?


 我们先回顾一下操作系统原理。
操作系统中,CPU竞争有很多种策略。Unix系统使用的是时间片算法,而Windows则属于抢占式的。

在时间片算法中,所有的进程排成一个队列。操作系统按照他们的顺序,给每个进程分配一段时间,即该进程允许运行的时间。如果在 时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。调度程 序所要做的就是维护一张就绪进程列表,,当进程用完它的时间片后,它被移到队列的末尾。

所谓抢占式操作系统,就是说如果一个进程得到了 CPU 时间,除非它自己放弃使用 CPU ,否则将完全霸占 CPU 。因此可以看出,在抢占式操作系统中,操作系统假设所有的进程都是“人品很好”的,会主动退出 CPU 。在抢占式操作系统中,假设有若干进程,操作系统会根据他们的优先级、饥饿时间(已经多长时间没有使用过 CPU 了),给他们算出一 个总的优先级来。操作系统就会把 CPU 交给总优先级最高的这个进程。当进程执行完毕或者自己主动挂起后,操作系统就会重新计算一次所有进程的总优先级,然后再挑一个优先级最高的把 CPU 控制权交给他。


 我们用分蛋糕的场景来描述这两种算法。假设有源源不断的蛋糕(源源不断的时间),一副刀叉(一个CPU),10个等待吃蛋糕的人(10 个进程)。

如果是 Unix操作系统来负责分蛋糕,那么他会这样定规矩:每个人上来吃 1 分钟,时间到了换下一个。最后一个人吃完了就再从头开始。于是,不管这10个人是不是优先级不同、饥饿程度不同、饭量不同,每个人上来的时候都可以吃 1 分钟。当然,如果有人本来不太饿,或者饭量小,吃了30秒钟之后就吃饱了,那么他可以跟操作系统说:我已经吃饱了(挂起)。于是操作系统就会让下一个人接着来。

如果是 Windows 操作系统来负责分蛋糕的,那么场面就很有意思了。他会这样定规矩:我会根据你们的优先级、饥饿程度去给你们每个人计算一个优先级。优先级最高的那个人,可以上来吃蛋糕——吃到你不想吃为止。等这个人吃完了,我再重新根据优先级、饥饿程度来计算每个人的优先级,然后再分给优先级最高的那个人。
这样看来,这个场面就有意思了——可能有些人是PPMM,因此具有高优先级,于是她就可以经常来吃蛋糕。可能另外一个人是个丑男,而去很ws,所以优先级特别低,于是好半天了才轮到他一次(因为随着时间的推移,他会越来越饥饿,因此算出来的总优先级就会越来越高,因此总有一天会轮到他的)。而且,如果一不小心让一个大胖子得到了刀叉,因为他饭量大,可能他会霸占着蛋糕连续吃很久很久,导致旁边的人在那里咽口水。。。
而且,还可能会有这种情况出现:操作系统现在计算出来的结果,5号PPMM总优先级最高,而且高出别人一大截。因此就叫5号来吃蛋糕。5号吃了一小会儿,觉得没那么饿了,于是说“我不吃了”(挂起)。因此操作系统就会重新计算所有人的优先级。因为5号刚刚吃过,因此她的饥饿程度变小了,于是总优先级变小了;而其他人因为多等了一会儿,饥饿程度都变大了,所以总优先级也变大了。不过这时候仍然有可能5号的优先级比别的都高,只不过现在只比其他的高一点点——但她仍然是总优先级最高的啊。因此操作系统就会说:5号mm上来吃蛋糕……(5号mm心里郁闷,这不刚吃过嘛……人家要减肥……谁叫你长那么漂亮,获得了那么高的优先级)。

那么,Thread.Sleep 函数是干吗的呢?还用刚才的分蛋糕的场景来描述。上面的场景里面,5号MM在吃了一次蛋糕之后,觉得已经有8分饱了,她觉得在未来的半个小时之内都不想再来吃蛋糕了,那么她就会跟操作系统说:在未来的半个小时之内不要再叫我上来吃蛋糕了。这样,操作系统在随后的半个小时里面重新计算所有人总优先级的时候,就会忽略5号mm。Sleep函数就是干这事的,他告诉操作系统“在未来的多少毫秒内我不参与CPU竞争”。


 看完了 Thread.Sleep 的作用,我们再来想想文章开头的两个问题。

对于第一个问题,答案是:不一定。因为你只是告诉操作系统:在未来的1000毫秒内我不想再参与到CPU竞争。那么1000毫秒过去之后,这时候也许另外一个线程正在使用CPU,那么这时候操作系统是不会重新分配CPU的,直到那个线程挂起或结束;况且,即使这个时候恰巧轮到操作系统进行CPU 分配,那么当前线程也不一定就是总优先级最高的那个,CPU还是可能被其他线程抢占去。

与此相似的,Thread有个Resume函数,是用来唤醒挂起的线程的。好像上面所说的一样,这个函数只是“告诉操作系统我从现在起开始参与CPU竞争了”,这个函数的调用并不能马上使得这个线程获得CPU控制权。(注意这个函数在net2.0以后标注为“过时”并在后续版本不提供支持了)

对于第二个问题,答案是:有,而且区别很明显。假设我们刚才的分蛋糕场景里面,有另外一个PPMM 7号,她的优先级也非常非常高(因为非常非常漂亮),所以操作系统总是会叫道她来吃蛋糕。而且,7号也非常喜欢吃蛋糕,而且饭量也很大。不过,7号人品很好,她很善良,她没吃几口就会想:如果现在有别人比我更需要吃蛋糕,那么我就让给他。因此,她可以每吃几口就跟操作系统说:我们来重新计算一下所有人的总优先级吧。不过,操作系统不接受这个建议——因为操作系统不提供这个接口。于是7号mm就换了个说法:“在未来的0毫秒之内不要再叫我上来吃蛋糕了”。这个指令操作系统是接受的,于是此时操作系统就会重新计算大家的总优先级——注意这个时候是连7号一起计算的,因为“0毫秒已经过去了”嘛。因此如果没有比7号更需要吃蛋糕的人出现,那么下一次7号还是会被叫上来吃蛋糕。

因此,Thread.Sleep(0)的作用,就是“触发操作系统立刻重新进行一次CPU竞争”。竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。这也是我们在大循环里面经常会写一句Thread.Sleep(0) ,因为这样就给了其他线程比如Paint线程获得CPU控制权的权力,这样界面就不会假死在那里。


 末了说明一下,虽然上面提到说“除非它自己放弃使用 CPU ,否则将完全霸占 CPU”,但这个行为仍然是受到制约的——操作系统会监控你霸占CPU的情况,如果发现某个线程长时间霸占CPU,会强制使这个线程挂起,因此在实际上不会出现“一个线程一直霸占着 CPU 不放”的情况。至于我们的大循环造成程序假死,并不是因为这个线程一直在霸占着CPU。实际上在这段时间操作系统已经进行过多次CPU竞争了,只不过其他线程在获得CPU控制权之后很短时间内马上就退出了,于是就又轮到了这个线程继续执行循环,于是就又用了很久才被操作系统强制挂起。。。因此反应到界面上,看起来就好像这个线程一直在霸占着CPU一样。

末了再说明一下,文中线程、进程有点混乱,其实在Windows原理层面,CPU竞争都是线程级的,本文中把这里的进程、线程看成同一个东西就好了。

posted @ 2012-03-06 09:56 Andy Niu 阅读(52) 评论(0) 编辑

2012年3月5日

1、首先确定一点:同步安全,异步不安全。

2、结合实例,针对下面的使用场景:妈妈做蛋糕给儿子吃。

3、需求分析:

(a)蛋糕的数量是共享数据,应该放入临界区。

(b)妈妈做蛋糕的时候,儿子不能吃;儿子吃蛋糕的时候,妈妈不能做。

(c)假设儿子很饿,妈妈做一个,儿子吃一个,妈妈做好一个后,等待儿子去吃,吃完后再去做,但是,如果儿子贪玩,很长时间都没吃完一个蛋糕,妈妈就不管了。继续做蛋糕。

(d)有一点是肯定的:必须要蛋糕,儿子才能吃,也就是说,如果蛋糕没了,儿子必须等待妈妈做好蛋糕,并且必须一直等下去,知道妈妈做出蛋糕。

(e)妈妈做好蛋糕后,通知儿子吃蛋糕;儿子吃完蛋糕后,通知妈妈继续做蛋糕。

4、代码如下:

View Code
 1     class ThreadSynchronize
2 {
3 private int n = 1; //生产者和消费者共同处理的数据
4 private int max = 100;

5
6 private object sync = new object();
7
8 public void Produce()
9 {
10 lock (sync)
11 {
12 for (; n <= max; n++)
13 {
14 Console.WriteLine("妈妈:第" + n.ToString() + "块蛋糕做好了");
15 // 妈妈通知儿子,蛋糕做好了,赶快来吃
16 Monitor.Pulse(sync);

17 // 妈妈等待儿子吃蛋糕,儿子5秒内吃不完的话,妈妈不管了,继续做蛋糕
18 Monitor.Wait(sync,5000);

19 }
20 }
21 }
22
23 public void Consume()
24 {
25 lock (sync)
26 {
27 while (true)
28 {
29 // 儿子通知妈妈,蛋糕吃完了,赶快做蛋糕
30 Monitor.Pulse(sync);

31 // 儿子等待妈妈做蛋糕,一直等下去,知道妈妈做出蛋糕
32 Monitor.Wait(sync);

33 Console.WriteLine("孩子:开始吃第" + n.ToString() + "块蛋糕");
34 }
35 }
36 }
37
38 static void Main(string[] args)
39 {
40 ThreadSynchronize obj = new ThreadSynchronize();
41 Thread tProduce = new Thread(new ThreadStart(obj.Produce));
42 Thread tConsume = new Thread(new ThreadStart(obj.Consume));
43 tProduce.Start();
44 tConsume.Start();
45
46 Console.ReadLine();
47 }
48 }

注意:为了避免当前线程一直等下去,也就是出现死锁,有两个办法:

1、当前线程调用Monitor.Wait(sync,5000); 加上时间限制,等待超时,就不等了,进入就绪状态,准备执行。

2、互斥的线程调用Monitor.Pulse(sync); 通知当前线程。

posted @ 2012-03-05 18:23 Andy Niu 阅读(20) 评论(0) 编辑

举例来说:程序员为女儿做蛋糕。

1、程序员需要食谱,食谱上写出了需要哪些原料,如何做。食谱就是程序,原料就是数据,程序员就是CPU。程序员按照食谱、对原料进行加工,做蛋糕的过程就相当于CPU执行代码、操作数据的过程。做蛋糕的过程就是进程。

2、做蛋糕可以分为几个小的步骤,这些步骤共同加工原料。每个小的步骤就是一个线程。比如做蛋糕进程可以分为以下几个线程:取原料(包括鸡蛋、面粉、奶油、糖、水等),混合原料,烘烤混合物。这些步骤之间存在一定的先后关系和互斥关系,比如:混合原料必须在取原料之后,二者不能同时进行。

3、假设程序员做蛋糕的时候,淘气的儿子跑进来,对爸爸说,被蜜蜂蛰了(相当于进来一个优先级更高的进程)。这时候,程序员就会暂停做蛋糕,同时记录做到了哪一步。然后拿出急救说明书(另一段程序),急救箱(另一端程序的数据),处理儿子的蛰伤(另一个进程)。处理完毕后,程序员接着原来的步骤继续做蛋糕。

posted @ 2012-03-05 16:54 Andy Niu 阅读(13) 评论(0) 编辑

2012年2月10日

软件测试的粒度分为:单元测试、软件集成测试、系统集成测试、系统测试。

单元测试:可以理解为测试函数。

软件集成测试:软件包含多个模块,软件集成测试就是 模块集成软件的过程中是否有错误,可以理解为测试函数的调用。

系统集成测试:系统包含多个子系统,系统集成测试就是 子系统集成系统的过程中是否有错误,可以理解为测试子系统之间的调用。

系统测试:测试整个系统。

posted @ 2012-02-10 17:20 Andy Niu 阅读(17) 评论(0) 编辑

1、定义与操作

View Code
1     int i =5;
2 int *p = // 这里的*表示p是指针,&是取地址操作符,取出i的地址。
3 std::cout<<*p;// 这里的*是取内容操作符,取出指针p的内容。
4 int &r = i; // 这里的&表示r是引用。

   指针通过 -> 调用方法,引用通过 .  调用方法

2、引用是弱化了的指针,体现在以下方面:

(1)引用不能对地址操作;

(2)引用不能为空,只能在定义时初始化一次,不能改变。(注:这一点和C#中的引用不同,C#中的引用可以为null,也可以指向另一个对象)。

posted @ 2012-02-10 16:42 Andy Niu 阅读(7) 评论(0) 编辑

2012年2月6日

覆盖率测试分为:语句覆盖、判定覆盖、条件覆盖、判定/条件覆盖、条件组合覆盖、路径覆盖。

1、语句覆盖是指选择足够的测试用例,使得运行这些测试用例时,被测程序的每一个语句至少执行一次,其覆盖标准无法发现判定中逻辑运算的错误。

2、判定覆盖是指选择足够的测试用例,使得运行这些测试用例时,每个判定的所有可能结果至少出现一次,但若程序中的判定是有几个条件联合构成时,它未必能发现每个条件的错误。

3、条件覆盖是指选择足够的测试用例,使得运行这些测试用例时,判定中每个条件的所有可能结果至少出现一次,但未必能覆盖全部分支。

4、判定/条件覆盖是使判定中每个条件的所有可能结果至少出现一次,并且每个判定本身的所有可能结果也至少出现一次。

5、条件组合覆盖是使每个判定中条件结果的所有可能组合至少出现一次,因此判定本身的所有可能解说也至少出现一次,同时也是每个条件的所有可能结果至少出现一次。

6、路径覆盖是每条可能执行到的路径至少执行一次。


结合具体程序说明:

View Code
1 if(a>0 || b>0)
2 {
3 //...
4 }
5 else
6 {
7 //...
8 }

1、测试用例设计为 (1,-1)、(-1,-1),即可满足语句覆盖率为100%

2、同样(1,-1)、(-1,-1),也能满足判定覆盖率为100%,但是不满足条件覆盖率为100%

3、要想让条件覆盖率为100%,还需要增加b>0的情况,即(1,-1)、(-1,1),但是不满足判定覆盖率为100%

4、要想判定/条件覆盖率为100%,需要设计(1,1)、(-1,-1)

5、要想条件组合覆盖率为100%,需要设计(1,1)、(1,-1)、(-1,1)、(-1,-1)


 总结:语句覆盖是一种最弱的覆盖,判定覆盖和条件覆盖比语句覆盖强,满足判定/条件覆盖标准的测试用例一定也满足判定覆盖、条件覆盖和语句覆盖,条件组合覆盖是除路径覆盖外最强的,路径覆盖也是一种比较强的覆盖,但未必考虑判定条件结果的组合,并不能代替条件覆盖和条件组合覆盖。

posted @ 2012-02-06 15:31 Andy Niu 阅读(15) 评论(0) 编辑

1、首先不要想接口与抽象类的区别,而要采用逆向思维,即是什么原因导致了他们的区别。

2、接口和抽象类的使用场景不同,也就是说,接口和抽象类是针对不同的使用场景而设计的。我们站在接口和抽象类的设计者的角度来看待这个问题。

3、接口的使用场景是:完全抽象,仅仅声明特征(能完成某些任务),不给出任何实现。

     抽象类的使用场景是:部分抽象,给出部分实现。

4、接口的使用场景是:接口代表了子类能做什么,是 Can-Do关系,

     抽象类的使用场景是:抽象类代表了子类是什么,是 IsA关系。

     因为子类只能是一个实体,但可以做多件事。所以,接口可以多继承,抽象类单继承。

5、接口:接口代表了子类的次要功能,因此可以多继承,重构的时候特别有用,提取出一部分公有的而不影响。

     抽象类:抽象类代表了子类的主要功能,因此单继承。

     因为:根据常识,一个类肯定是具备一个主要功能,多个次要功能。

6、既然接口和抽象类都是表现抽象,抽象的东西当然不能实例化。

7、接口:完全抽象,因此必须实现全部的方法(这个很好理解,既然你说能完成某些任务,当然要给出是如何完成这些任务的)。为了避免子类继承不需要的方法,即接口污染,要把具备多功能的大接口分解成单功能的小接口。

     抽象类:部分抽象,只需要实现抽象的方法,其他的可以直接继承过来。

8、接口和抽象类就是为了让别人来继承的,因此不能密封。

9、接口的命名规则:微软 前缀I 后缀able,比如 ICloneable ;Sun 后缀able,比如 Cloneable

     抽象类的命名规则:微软 和具体类一样,比如 StringComparer;Sun 前缀 Abstract,比如AbstractMap

10、区别于接口和抽象类,具体类不是用来继承的,因此尽量避免继承具体类。

posted @ 2012-02-06 14:53 Andy Niu 阅读(16) 评论(0) 编辑

声明:对外声明说,我能完成某项任务。抽象的说明。

定义:按照步骤,详细说明我是如何完成这项任务的。具体的行为。

posted @ 2012-02-06 14:40 Andy Niu 阅读(10) 评论(0) 编辑