1.类与类之间的关系:依赖,实现,泛化(继承),关联,组合,聚合。
1)依赖(虚线):一个类是 另一个类的函数参数 或者 函数返回值。
2)实现(实线加小圆):对纯虚函数类(抽象类)的实现。
3)继承 即 泛化(实线加空心三角形,从子类指向父类):表示一个类与另一个类之间的继承关系;
4)关联(实线):一个类是另一个类的成员变量。
5)聚合(菱形加实线):整体和部分的关系。不太紧密的(如:汽车与发动机,汽车可以选择各种型号的发动机)
6)组合(实心菱形加实线):生命体整体与部分的关系。紧密的关系(人和五脏六腑)
2.时序图:三个要素(对象,消息,生命线)。
3.RAM和ROM的区别:ROM和RAM都是一种存储技术,只是两者原理不同。
1.RAM为随机存储,掉电不会保存数据。RAM则可以随机读写。
2.ROM可以在掉电的情况下,依然保存原有的数据。这种存储器只能读,不能写
4.通俗的说,比如在电脑中,有内存和硬盘之说;其实内存就是一种RAM技术,而ROM则类似于硬盘技术;
两者都是存储器,只是RAM的速度要远远高于ROM的速度。
5.在手机中,RAM是指手机内存,对于手机性能来说,除了看手机处理器就是看内存了,内存越大手机配置越好.
手机ROM是指手机存储器,比如手机内存SD卡。ROM越大手机可以存储的东西就越多。
"++++++++++++++++++++++++++++++阻塞IO/非阻塞IO; 同步IO/异步IO +++++++++++++++++++++++++++++++++++++++++ "
一。IO请求的两个阶段:
1.等待资源阶段:IO请求一般需要请求特殊的资源(如磁盘、RAM、文件),当资源被上一个使用者使用没有被释放时,IO请求就会被阻塞,直到能够使用这个资源。
2.使用资源阶段:真正进行数据接收和发生。
二。在等待数据阶段,IO分为阻塞IO和非阻塞IO。
1.阻塞IO: 资源不可用时,IO请求一直阻塞,直到反馈结果(有数据或超时)。
2.非阻塞IO:资源不可用时,IO请求离开返回,返回数据标识资源不可用
三。在使用资源阶段,IO分为同步IO和异步IO。
1.同步IO:应用阻塞在发送或接收数据的状态,直到数据成功传输或返回失败。
2.异步IO:应用发送或接收数据后立刻返回,数据写入OS缓存,由OS完成数据发送或接收,并返回成功或失败的信息给应用。
四。IOPS,即每秒钟处理的IO请求数量。IOPS是随机访问类型业务(OLTP类)很重要的一个参考指标。
"+++++++++++++++++++++++++++++++阻塞IO/非阻塞IO; 同步IO/异步IO +++++++++++++++++++++++++++++++++++++++++ "
1 此文章为转载,如有侵权,请联系本人。转载出处,http://blog.chinaunix.net/uid-28458801-id-4464639.html
2
3 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个问题其实不同的人给出的答案都可能不同,比如wiki,就认为asynchronous IO和non-blocking IO是一个东西。这其实是因为不同的人的知识背景不同,并且在讨论这个问题的时候上下文(context)也不相同。所以,为了更好的回答这个问题,我先限定一下本文的上下文。
4
5 本文讨论的背景是Linux环境下的network IO。本文最重要的参考文献是Richard Stevens的“UNIX? Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2节“I/O Models ”,Stevens在这节中详细说明了各种IO的特点和区别,如果英文够好的话,推荐直接阅读。Stevens的文风是有名的深入浅出,所以不用担心看不懂。本文中的流程图也是截取自参考文献。
6
7 Stevens在文章中一共比较了五种IO Model:
8 * blocking IO
9 * nonblocking IO
10 * IO multiplexing
11 * signal driven IO
12 * asynchronous IO
13 由signal driven IO在实际中并不常用,所以主要介绍其余四种IO Model。
14 再说一下IO发生时涉及的对象和步骤。对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:
15 1)等待数据准备 (Waiting for the data to be ready)
16 2)将数据从内核拷贝到进程中(Copying the data from the kernel to the process)
17 记住这两点很重要,因为这些IO模型的区别就是在两个阶段上各有不同的情况。
18 1、阻塞IO(blocking IO)
19
20 在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:
21
22
23
24 图1 阻塞IO
25
26 当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
27 所以,blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。
28
29 几乎所有的程序员第一次接触到的网络编程都是从listen()、send()、recv() 等接口开始的,这些接口都是阻塞型的。使用这些接口可以很方便的构建服务器/客户机的模型。下面是一个简单地“一问一答”的服务器。
30
31
32
33 图2 简单的一问一答的服务器/客户机模型
34
35 我们注意到,大部分的socket接口都是阻塞型的。所谓阻塞型接口是指系统调用(一般是IO接口)不返回调用结果并让当前线程一直阻塞,只有当该系统调用获得结果或者超时出错时才返回。
36 实际上,除非特别指定,几乎所有的IO接口 ( 包括socket接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题,如在调用send()的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求。
37
38 一个简单的改进方案是在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。具体使用多进程还是多线程,并没有一个特定的模式。传统意义上,进程的开销要远远大于线程,所以如果需要同时为较多的客户机提供服务,则不推荐使用多进程;如果单个服务执行体需要消耗较多的CPU资源,譬如需要进行大规模或长时间的数据运算或文件访问,则进程较为安全。通常,使用pthread_create ()创建新线程,fork()创建新进程。
39 我们假设对上述的服务器 / 客户机模型,提出更高的要求,即让服务器同时为多个客户机提供一问一答的服务。于是有了如下的模型。
40
41
42
43 图3 多线程的服务器模型
44
45 在上述的线程 / 时间图例中,主线程持续等待客户端的连接请求,如果有连接,则创建新线程,并在新线程中提供为前例同样的问答服务。
46 很多初学者可能不明白为何一个socket可以accept多次。实际上socket的设计者可能特意为多客户机的情况留下了伏笔,让accept()能够返回一个新的socket。下面是 accept 接口的原型:
47 int accept(int s, struct sockaddr addr, socklen_t addrlen);
48 输入参数s是从socket(),bind()和listen()中沿用下来的socket句柄值。执行完bind()和listen()后,操作系统已经开始在指定的端口处监听所有的连接请求,如果有请求,则将该连接请求加入请求队列。调用accept()接口正是从 socket s 的请求队列抽取第一个连接信息,创建一个与s同类的新的socket返回句柄。新的socket句柄即是后续read()和recv()的输入参数。如果请求队列当前没有请求,则accept() 将进入阻塞状态直到有请求进入队列。
49 上述多线程的服务器模型似乎完美的解决了为多个客户机提供问答服务的要求,但其实并不尽然。如果要同时响应成百上千路的连接请求,则无论多线程还是多进程都会严重占据系统资源,降低系统对外界响应效率,而线程与进程本身也更容易进入假死状态。
50 很多程序员可能会考虑使用“线程池”或“连接池”。“线程池”旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。“连接池”维持连接的缓存池,尽量重用已有的连接、减少创建和关闭连接的频率。这两种技术都可以很好的降低系统开销,都被广泛应用很多大型系统,如websphere、tomcat和各种数据库等。但是,“线程池”和“连接池”技术也只是在一定程度上缓解了频繁调用IO接口带来的资源占用。而且,所谓“池”始终有其上限,当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。所以使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。
51 对应上例中的所面临的可能同时出现的上千甚至上万次的客户端请求,“线程池”或“连接池”或许可以缓解部分压力,但是不能解决所有问题。总之,多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题。
52
53 2、非阻塞IO(non-blocking IO)
54 Linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:
55
56
57
58 图4 非阻塞IO
59
60 从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
61 所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。
62
63 非阻塞的接口相比于阻塞型接口的显著差异在于,在被调用之后立即返回。使用如下的函数可以将某句柄fd设为非阻塞状态。
64 fcntl( fd, F_SETFL, O_NONBLOCK );
65 下面将给出只用一个线程,但能够同时从多个连接中检测数据是否送达,并且接受数据的模型。
66
67
68
69 图5 使用非阻塞的接收数据模型
70
71 在非阻塞状态下,recv() 接口在被调用后立即返回,返回值代表了不同的含义。如在本例中,
72 * recv() 返回值大于 0,表示接受数据完毕,返回值即是接受到的字节数;
73 * recv() 返回 0,表示连接已经正常断开;
74 * recv() 返回 -1,且 errno 等于 EAGAIN,表示 recv 操作还没执行完成;
75 * recv() 返回 -1,且 errno 不等于 EAGAIN,表示 recv 操作遇到系统错误 errno。
76 可以看到服务器线程可以通过循环调用recv()接口,可以在单个线程内实现对所有连接的数据接收工作。但是上述模型绝不被推荐。因为,循环调用recv()将大幅度推高CPU 占用率;此外,在这个方案中recv()更多的是起到检测“操作是否完成”的作用,实际操作系统提供了更为高效的检测“操作是否完成“作用的接口,例如select()多路复用模式,可以一次检测多个连接是否活跃。
77 3、多路复用IO(IO multiplexing)
78 IO multiplexing这个词可能有点陌生,但是如果我说select/epoll,大概就都能明白了。有些地方也称这种IO方式为事件驱动IO(event driven IO)。我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:
79
80
81
82 图6 多路复用IO
83
84 当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
85 这个图和blocking IO的图其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select和recvfrom),而blocking IO只调用了一个系统调用(recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句:所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)
86 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。因此select()与非阻塞IO类似。
87
88 大部分Unix/Linux都支持select函数,该函数用于探测多个文件句柄的状态变化。下面给出select接口的原型:
89 FD_ZERO(int fd, fd_set fds)
90 FD_SET(int fd, fd_set fds)
91 FD_ISSET(int fd, fd_set* fds)
92 FD_CLR(int fd, fd_set* fds)
93 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
94 struct timeval *timeout)
95 这里,fd_set 类型可以简单的理解为按 bit 位标记句柄的队列,例如要在某 fd_set 中标记一个值为16的句柄,则该fd_set的第16个bit位被标记为1。具体的置位、验证可使用 FD_SET、FD_ISSET等宏实现。在select()函数中,readfds、writefds和exceptfds同时作为输入参数和输出参数。如果输入的readfds标记了16号句柄,则select()将检测16号句柄是否可读。在select()返回后,可以通过检查readfds有否标记16号句柄,来判断该“可读”事件是否发生。另外,用户可以设置timeout时间。
96 下面将重新模拟上例中从多个客户端接收数据的模型。
97
99
100 图7 使用select()的接收数据模型
101
102 述模型只是描述了使用select()接口同时从多个客户端接收数据的过程;由于select()接口可以同时对多个句柄进行读状态、写状态和错误状态的探测,所以可以很容易构建为多个客户端提供独立问答服务的服务器系统。如下图。
103
104