viggox

Linux系统编程温故知新系列 --- 01

1.大端法与小端法

大端法:按照从最高有效字节到最低有效字节的顺序存储,称为大端法

小端法:按照从最低有效字节到最高有效字节的顺序存储,称为小端法

网际协议使用大端字节序来传送TCP分节中的多字节整数(比如16位端口号,32位IPv4地址)。 

 

2.time_wait状态

客户端(执行主动关闭的那一端)连接在收到服务器的结束报文段(FIN, ACK),并没有直接进入CLOSED状态,而是转移到TIME_WAIT状态。在这个状态,客户端连接要等待一段长为2MSL(MSL: 报文段最大生存时间)的时间,才能完全关闭。

time_wait的存在原因:

01.可靠地实现TCP全双工连接的终止

假设最终的报文段(ACK)丢失了,服务器将重新发送(FIN,ACK), 因此客户端必须维护状态信息,以允许它重新发送最终那个ACK。要是客户不维护状态信息,它将响应以一个RST(另外一种类型的TCP分节), 该分节将被服务器解释为一个错误。

 

02.保证让迟来的TCP报文段有足够的时间被识别并丢弃,允许老的重复分节在网络中消逝

TIME_WAIT状态的持续时间是MSL的2倍,这就足以让某个方向的分组最多存活MSL秒被丢弃,另一个方向上的应答最多存活MSL秒被丢弃,即确保网络上两个传输方向上尚未被接收到的、迟到的TCP报文段都已经消失(被中转路由器丢弃)。

 

time_wait的危害:

如果是服务器主动关闭连接后异常终止,则因为它总是使用同一个知名服务端口号,所以连接的TIME_WAIT状态将会导致服务器程序退出后不能立即重启

如何避免time_wait状态:

可以通过打开SO_REUSEADDR套接字选项来强制进程立即使用处于TIME_WAIT状态的连接占用的端口,即使以前建立的将该端口用作它们的本地端口的连接仍存在。

 

3. select/poll/epoll/kqueue对比

3.1 select .vs. poll:

01.实现细节上的区别:

select()和poll()都使用了相同的内核poll例程集合,这些poll例程有别于系统调用poll()集合本身。每个例程都返回有关单个文件描述符就绪的信息。这个就绪信息以位掩码的形式返回,其值同poll()系统调用中返回的revents字段中的比特值相关。

poll()系统调用的实现:为每个文件描述符调用内核poll例程,并将结果信息填到对应的revents字段中去

select()系统调用的实现:内核使用一组宏将内核poll例程返回的信息转化为由select()返回的与之对应的事件类型

02.API之间的区别:

select()能同时检测的文件描述符数量有上限限制(FD_SETSIZE,linux下值为1024),而poll()在理论上无此限制。

由于select()的参数fd_set同时也是保存调用结果的地方,如果要在循环中重复调用select()的话,我们必须每次都要重新初始化fd_set。而poll()因为是通过独立的两个字段events(针对输入)和revents(针对输出)来处理,从而避免每次都要重新初始化参数。

select()提供的超时精度(微秒)比poll()提供的超时精度(毫秒)高。

如果其中一个被检查的文件描述符被关闭了,通过在对应的revents字段中设定POLLNVAL标记,poll()会准确告诉我们是哪一个文件描述符被关闭了。与之相反,select()只会返回-1,并设置错误码为EBADF。

 

3.2 epoll .vs. select & poll

01. select & poll在监视大量的文件描述符时,性能远差于epoll。

原因: select & poll采用的都是轮询的方式,每次调用都要扫描并返回整个注册文件描述符集合,因此其检测就绪事件的算法复杂度为O(N)。epoll则在内核中维护一个事件表,epoll_wait()每次调用,都直接从该内核事件表中取得用户注册的事件,而无需反复地从用户空间读入这些事件。epoll_wait()采用回调的方式,内核检测到就绪的文件描述符时,将触发回调函数,回调函数就将该文件描述符上对应的事件插入内核就绪事件队列,内核将在适当的时机将该就绪队列中的内容拷贝到用户空间。因此epoll_wait无需轮询整个文件描述符集合来检测哪些事件已经就绪,其算法复杂度为O(1)。

02. select & poll只能工作在相对低效的LT模式,而epoll则可以工作在ET高效模式。并且epoll还支持EPOLLONESHOT事件,该事件能进一步减少可读、可写和异常等事件被触发的次数。

03. select内部是数组实现,poll内部是链表实现,所以select有最大fd限制,二者均有用户态到内核态的拷贝过程,两者的切换和数据拷贝都很消耗性能;而epoll内部是一棵红黑树,且因为epoll采取了共享内存机制,从而避免了用户态和内核态之间的切换。

 

3.2 kqueue .vs. epoll 

性能上:epoll每次只能更新一个兴趣列表中的文件描述符状态,换句话说,如果要在兴趣列表中更新100个文件描述符状态,就需要调用100次epoll_ctl()函数。大量的系统调用会显著地降低性能;但是,你可以通过多次使用EV_SET宏,在一次kevent调用中,更新多个感兴趣的文件描述符状态。

使用上:kevent所允许过滤的事件,除了select/poll/epoll所关注的文件I/O和超时外,还有异步I/O,文件修改通知,进程跟踪和信号处理。与此同时,epoll不支持监听普通文件或目录的文件描述符。

 

4.多进程 VS 多线程:

多进程——缺点

01. 创建及维护进程对系统来说都有开销——启动进程需要时间,而操作系统必须投入内部资源来管理进程

02. 一般来说子进程需要使用某种IPC机制来通知父进程有关I/O操作的状态,而进程之间的通信通常设置复杂,或是速度较慢,或者兼而有之——因为操作系统通常在进程间提供了大量保护,以避免一个进程不小心修改了另一个进程的数据

多进程——优点

01.操作系统在进程间提供的附加保护操作和更高级别的通信机制,意味着可以比线程更容易地编写安全的并发代码

02.你可以通过网络连接的不同的机器上运行独立的进程,在某些情况下,这是一个提高并行可用性和提高性能的低成本方法

多线程——缺点

如果数据要被多个线程访问,那么开发者就必须确保当每个线程访问时所看到的数据是一致的(即线程同步),这将使编程工作变得复杂,尤其是如果我们使用线程池技术来最小化需要处理的大量并发客户的线程数量时

多线程——优点

共享的地址空间,以及缺少线程间的数据保护,使得使用多线程相关的开销远小于使用多进程——操作系统有更少的簿记要做

对于C++来说,C++标准并没有为进程间通信提供任何原生支持,所以使用多进程的应用程序将不得不依赖平台相关的API来实现

 

5.epoll 水平触发通知. vs .边缘触发通知 (两种文件描述符准备就绪的通知模式)

水平触发通知(LT notification):如果文件描述符上可以非阻塞地执行IO系统调用,此时认为它已经就绪

边缘触发通知(ET notification):如果文件描述符自上次状态检查以来有了新的IO活动(比如新的输入),此时需要触发通知

当采用水平触发通知时,我们可以在任意时刻检查文件描述符的就绪状态。由于水平触发模式允许我们在任意时刻重复检查IO状态,没有必要每次当文件描述符就绪后就需要尽可能多地执行IO。

当采用边缘触发通知时,只有当IO事件发生时我们才会收到通知。在另一个IO事件到来之前我们不会收到任何新的通知。所以对于采用边缘触发通知的程序,在接受到一个IO事件通知后,程序在某个时刻应该在相应的文件描述符上尽可能多地执行IO;如果程序采用循环来对文件描述符执行尽可能多地IO,则应当将文件描述符设置为非阻塞的,以防止最终当没有更多IO可执行时,IO系统调用会阻塞。

总的来看,ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,因此效率比LT模式要高。 

 

6.EPOLLONESHOT标志

对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次,除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。

一个用途就是确保一个socket连接在任一时刻只被一个线程处理。

 

References:

UNIX 网络编程,卷一

LINUX系统编程手册,下册

Linux高性能服务器编程

posted on 2016-03-15 17:48  viggox  阅读(201)  评论(0编辑  收藏  举报

导航