8.第五章:
1.socket的主要API都定义在sys/socket.h头文件
实现主机名和IP地址之间的转换,以及服务名称和端口号之间的转换,定义在netdb.h头文件
字节序分为大端字节序(big endian)和小端字节序(little endian)。
现代PC大多采用小端字节序,因此小端字节序又被称为主机字节序。
一般大端字节序也称为网络字节序,即在网络中一般将字节序统一转化为大端进行传播,即接收方收到的第一个字节被当作高位看待
通过c语言的union结构实现机器的字节序的判断
将数据转化为同一种字节序进行传输的四个函数
2.通用socket地址结构体: sockaddr_storage和sockaddr,可以用于存放IPv4或IPv6地址等
地址族与协议族对应表。在bits/socket.h头文件中,地址族与协议族可以混用。
通用socket地址结构体设置与获取IP地址和端口号较烦琐,故一般使用专用socket地址结构体,如sockaddr_in和sockaddr_in6用于IPv4和IPv6。所有专用socket地址(以及sockaddr_storage)类型的变量在实际使用时都需要转化为通用socket地址类型sockaddr(强制转换即可),因为所有socket编程接口使用的地址参数的类型都是sockaddr(所有与socket编程相关的函数使用的地址参数的类型都是sockaddr)。
十进制字符串表示IPv4地址,以及用十六进制字符串表示IPv6地址。
(用点分十进制字符串表示的IPv4地址或用十六进制字符串表示的IPv6地址)和用网络字节序整数表示的IP地址之间的转换的函数
socket在linux中被当成文件描述符
socket函数:初始化socket,返回一个int类型
bind函数:给socket绑定IP和端口
listen函数:让socket处于监听状态
accept函数:从监听队列中获取连接
accept调用对于客户端网络断开毫不知情(这里与作者的实验和我的实验结果完全不一致???)
accept只是从监听队列中取出连接,而不论连接处于何种状态(如上面的ESTABLISHED状态和CLOSE_WAIT状态),更不关心任何网络状况的变化(这里与作者的实验和我的实验结果完全不一致???)
connnect:与服务器建立连接并返回一个socket供客户端读写,通过对socket的读写来与服务器进行通信。实际上只返回了一个int类型的变量,这就说明了connnect在系统内部完成了对真正的socket进行了一系列操作。
close:关闭指定的套接字。如果不止有一个进程引用了该套接字,则close的作用变为将套接字的引用计数减一。
shutdown:不论是否有其他进程引用了该套接字,都关闭此套接字。shutdown还可以只关socket的读或写。
linux下可以使用read和write或recv和send对数据进行读写。
recv成功时返回实际读取到的数据的长度,它可能小于我们期望的长度len。因此我们可能要多次调用recv,才能读取到完整的数据
send成功时返回实际写入的数据的长度
flags参数为数据收发提供了额外的控制,通常设置为0。flags参数只对send和recv的当前调用生效,而后面我们将看到如何通过setsockopt系统调用永久性地修改socket的某些属性。
发送和接收外带数据(紧急数据)。接收紧急数据会导致正常的数据不能通过一个recv调用全部读出
socket编程接口中用于UDP数据报读写的系统调用是recvfrom和sendto。recvfrom/sendto系统调用也可以用于面向连接(STREAM)的socket的数据读写,只需要把最后两个参数都设置为NULL
socket编程接口还提供了通用数据读写函数(recvmsg和sendmsg)
内核通知应用程序带外数据到达的两种常见方式是:I/O复用产生的异常事件和SIGURG信号。
利用sockatmark判断读取的数据是否为外带数据,然后利用带MSG_OOB标志的recv调用来接收带外数据
获取本端socket地址以及远端的socket地址:getsockname和getpeername
fcntl系统调用是控制文件描述符属性的通用POSIX方法。getsockopt和setsockopt是专门用来读取和设置socket文件描述符属性的方法。getsockopt和setsockopt通过设置socket选项的方法来读取和设置socket文件描述符属性
服务器端的有些soket选项必须在调用listen前进行设置才会有效,即三次握手前设置。客户端的有些soket选项必须在调用connnect前进行设置才会有效,即三次握手前设置。
listen监听队列中接受的连接至少已经完成了TCP三次握手的前两个步骤。connect调用后,TCP三次握手就已完成
对监听socket设置这些socket选项,那么accept返回的连接socket将自动继承这些选项。(这句话什么意思,什么叫监听socket选项)
SO_REUSEADDR选项:
服务器程序可以通过设置socket选项SO_REUSEADDR来强制使用被处于TIME_WAIT状态的连接所占用的socket地址。
我们也可以通过修改内核参数/proc/sys/net/ipv4/tcp_tw_recycle来快速回收被关闭的socket,从而使得TCP连接根本就不进入TIME_WAIT状态
SO_RCVBUF和SO_SNDBUF选项:
设置接受和发送缓存的大小,设置的大小不能低于系统设定的最小值。我们可以修改/proc/sys/net/ipv4/tcp_rmem和/proc/sys/net/ipv4/tcp_wmem来强制TCP接收缓冲区和发送缓冲区的大小没有最小值限制。
这里有一个通过tcpdump抓取的通信过程,很有意思可以看一看
SO_RCVLOWAT和SO_SNDLOWAT选项(一般设为1):大于SO_RCVLOWAT时可读,大于SO_SNDLOWAT可写
SO_LINGER选项:
用于控制close系统调用在关闭TCP连接时的行为。(如果通过close没有成功关闭,而是引用计数减一,此时这些行为还会发生吗??)
l_onoff等于0:代表此功能关闭
l_onoff不为0,l_linger等于0:丢弃TCP发送缓冲区中残留的数据,发送复位报文段,这种情况给服务器提供了异常终止一个连接的方法
l_onoff不为0,l_linger大于0:具体见书中内容
可以用主机明代替IP地址,用服务器名代替端口号。
gethostbyname和gethostbyaddr:
gethostbyname函数通常先在本地的/etc/hosts配置文件中查找主机,如果没有找到,再去访问DNS服务器。
gethostbyaddr函数根据IP地址获取主机的完整信息。
getservbyname和getservbyport:getservbyname函数根据名称获取某个服务的完整信息,getservbyport函数根据端口号获取某个服务的完整信息。它们实际上都是通过读取/etc/services文件来获取服务的信息的。
上面四个函数都是不可重入的,但有可重入的版本。
正如Linux下所有其他函数的可重入版本的命名规则那样,这些函数的函数名是在原函数名尾部加上_r(re-entrant)。
getaddrinfo:可通过主机名获取IP,通过服务器名获取端口号。
getnameinfo:通过socket地址同时获得以字符串表示的主机名和服务名
第六章 高级I/O函数:本章实际上本质是对文件描述符进行操作的函数进行介绍
用于创建文件描述符的函数,包括pipe、dup/dup2函数
用于读写数据的函数,包括readv/writev、sendfile、mmap/munmap、splice和tee函数。
用于控制I/O行为和属性的函数,包括fcntl函数
pipe函数创建的这两个文件描述符fd[0]和fd[1]分别构成管道的两端。用fd[1]写入的数据,用fd[0]读出。
写端文件描述符fd[1]的引用计数减少至0,则fd[0]的read操作将返回0,即读取到了文件结束标记(EOF)
读端文件描述符fd[0]的引用计数减少至0,则fd[1]的write操作将失败,并引发SIGPIPE信号
我们可以使用fcntl函数来修改管道容量
socketpair函数方便地创建双向管道。
dup和dup2:复制文件描述符。dup——取当前可用最小整数值作为文件描述符。dup——取第一个不小于file_descriptor_two的整数值作为文件描述符。
【注】通过dup和dup2创建的文件描述符并不继承原文件描述符的属性,比如close-on-exec和non-blocking等(这两个是啥??我怎么不记得见过??)
先关闭标准输出文件描述符STDOUT_FILENO(其值是1),然后通过dup()复制socket文件描述符connfd。此时由于1是最小可用的,c故onnfd就被复制到了值为1的文件描述符上。此时printf产生的输出会传送到客户端中。
readv函数将数据从文件描述符读到分散的内存块中,即分散读;writev函数则将多块分散的内存数据一并写入文件描述符中,即集中写。
Web服务器上实现集中写的程序
sendfile函数实现:两个文件描述符之间的传递数据完全在内核中操作,从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,这种数据传递也叫零拷贝。
用sendfile函数传输文件的一个程序。和“Web服务器上实现集中写的程序”实现相同功能,但是简单多了。
mmap函数和munmap函数:用于申请和释放内存。申请的内存可以作为进程间通信的共享内存
POSIX是IEEE为要在各种UNIX操作系统上运行的软件而定义的一系列API标准的总称
上面提到的大多数函数都是成功时返回0或操作东西的数量,失败则返回-1并设置errno。
splice函数:用于在两个文件描述符之间移动数据,也是零拷贝操作。fd_in和fd_out必须至少有一个是管道文件描述符。
将客户端发送的数据原样返回给客户端的程序
tee:tee函数在两个管道文件描述符之间复制数据,也是零拷贝操作,它不消耗数据(啥叫不消耗数据??)。fd_in和fd_out必须都是管道文件描述符
6-5代码标注:从STDIN_FILENO就可以看出来,只要使用相应的文件描述符,就可以对系统内已经定义好的文件描述符进行使用
fcntl函数:提供了对文件描述符的各种控制操作,ioctl比fcntl能够执行更多的控制。
将文件描述符设置为非阻塞的(这会产生什么效果呢???)
当被关联的文件描述符可读或可写时,系统将触发SIGIO信号;当被关联的文件描述符(而且必须是一个socket)上有带外数据可读时,系统将触发SIGURG信号。将信号和文件描述符关联的方法,就是使用fcntl函数为目标文件描述符指定宿主进程或进程组。使用SIGIO时,还需要利用fcntl设置其O_ASYNC标志。SIGIO和SIGURG这两个信号与其他Linux信号不同
第七章 Linux服务器程序规范
后台进程(守护进程)、日志系统、linux服务器程序拥有自己的运行账户、配置文件、linux服务器进程启动时会生成一个PID文件
日志这部分没有实例,我完全看不懂?????
Linux提供一个守护进程来处理系统日志——syslogd,现在一般用rsyslogd(syslogd升级版)
rsyslogd守护进程可以接收用户进程输出的日志和内核日志。
用户进程通过syslog函数生成系统日志并将日志放入到/dev/log中,rsyslogd则监听该文件以获取用户进程的输出
内核日志由printk等函数打印至内核的环状缓存(ring buffer)中。环状缓存的内容直接映射到/proc/kmsg文件中。rsyslogd则通过读取该文件获得内核日志。
rsyslogd守护进程的各种配置文件的说明
syslog函数:应用程序使用syslog函数与rsyslogd守护进程通信。
openlog应该是对输出日志内容的补充。对后续syslog调用的行为进行配置。
setlogmask:用于设置掩码,日志级别大于掩码的信息被忽略
closelog():关闭掩码
什么叫日志,保存日志干嘛,日志保存起来不就行。那处理系统日志——syslogd是用来干嘛的??举一个例子说明一下,这玩意能干嘛??找一个有关于rsyslogd和syslog函数的实例
一个进程拥有两个用户ID:UID和EUID
EUID:如果程序设置了set-user-id标志,那么当某用户使用该程序时,此用户的EUID变为该程序拥有者的ID,同时该用户暂时具有该程序拥有者的权限。如使用su程序时,EUID为0(root),用户暂时拥有了root用户的权限,
给程序test_uid设置set-user-id标志:sudo chomd +s test_uid
将以root身份启动的进程切换为以一个普通用户身份运行。
大部分服务器就必须以root身份启动,但不能以root身份运行
每个进程都具有GID和EGID吗??它们是干嘛的??和进程组是什么关系??
Linux下每个进程都隶属于一个进程组,因此它们除了PID信息外,还有进程组ID(PGID)
每个进程组都有一个首领进程,其PGID和PID相同。
getpgid:获得进程所在的进程组的ID
setpgid:设置当前进程到某个进程组
一些有关联的进程组将形成一个会话
setsid用于创建会话,该函数不能由进程组的首领进程调用。
调用setsid会产生的效果:1)调用进程称为会话首领 2)新建一个进程组,调用进程成为此进程组的首领 3)调用进程甩开终端
getsid获取会话的SID,SID就是首领所在的进程组的PGID。
ps命令可查看进程、进程组和会话之间的关系
用于系统资源限制的函数:getrlimit和setrlimit,如可以设置用户可以创建的进程数限制。
Web服务器的逻辑根目录并非文件系统的根目录“/”,而是站点的根目录(对于Linux的Web服务来说,该目录
一般是/var/www/)。所以需要改变进程的根目录。
获取进程当前工作目录和改变进程工作目录的函数分别是:getcwd和chdir
通过chroot可以改变函数的根目录,chroot并不改变进程的当前工作目录,所以调用chroot之后,我们仍然需要使用chdir(“/”)来将工作目录切换至新的根目录。改变进程的根目录之后,程序可能无法访问类似/dev的文件(和目录),因为这些文件(和目录)并非处于新的根目录之下。
daemon函数:服务器程序后台化
除了TIME_WAIT,去记录以下FIN_WAIT1等东西是啥,并解决accept那里运行与作者的结果不一致问题。
第八章 高性能服务器程序框架(后续章节的总览)
I/O处理单元、逻辑单元、提高服务器性能的其他建议
I/O处理单元:服务器和客户端之间的通信过程实际上对socket文件的读写过程,也就是一个输入输出的过程(I\O的过程)。所以I/O处理单元应该是用于同时处理多个客户端请求的方式。
逻辑单元:服务器给客户端分配的逻辑单元是由fork系统调用创建的子进程。也就是给每一个客户端的请求都分配一个子进程来进行处理
C/S模型非常适合资源相对集中的场合,并且它的实现也很简单,但其缺点也很明显:服务器是通信的中心,当访问量过大时,可能所有客户都将得到很慢的响应。
P2P模型可以看作C/S模型的扩展:每台主机既是客户端,又是服务器。
服务器程序种类繁多,但其基本框架都一样,不同之处在于逻辑处理
服务器编程框架:I/O处理单元、请求队列、逻辑单元、网络存储单元
I/O处理单元:
单个服务器程序:等待并接受新的客户连接。也有可能接收客户数据,将服务器响应数据返回给客户端
服务器机群:它实现负载均衡,从所有逻辑服务器中选取负荷最小的一台来为新客户服务。
逻辑单元:
单个服务器程序:它分析并处理客户数据,然后将结果传递给I/O处理单元或者直接发送给客户端。一个逻辑单元通常是一个进程或线程
服务器机群:一个逻辑单元本身就是一台逻辑服务器。
网络存储单元:可以是数据库、缓存和文件,甚至是一台独立的服务器。但它不是必须的。
请求队列:请求队列是各单元之间的通信方式的抽象(这是什么意思,我不理解???)。请求队列通常被实现为池的一部分。
I/O模型
socket在创建的时候默认是阻塞的。我们可以给socket系统调用的第2个参数传递SOCK_NONBLOCK标志,或者通过fcntl系统调用的F_SETFL命令,将其设置为非阻塞的
阻塞I/O:系统调用可能因为无法立即完成而被操作系统挂起,直到等待的事件发生为止。如调用recv后,需要等待对方send的数据送达。非阻塞I/O中,调用recv后,程序会继续执行下一条命令,在对方send的数据送达时,程序会接收到相应通知(通知一般是内核发送给程序的),并调用相应的处理函数。
socket的基础API中,可能被阻塞的系统调用包括accept、send、recv和connect。
通过I/O复用和SIGIO信号可以实现非阻塞I/O:如,非阻塞I/O中,调用recv后,程序会继续执行下一条命令,在对方send的数据送达时,程序会接收到SIGIO信号,SIGIO信号的信号处理函数将被触发。
I/O复用函数本身是阻塞的,它们能提高程序效率的原因在于它们具有同时监听多个I/O事件的能力,当监听到相应的事件就给相应的程序发送相应的通知。。
I/O复用函数是select、poll和epoll_wait
同步I/O与异步I/O
POSIX规范所定义的异步I/O模型:数据的读写处理完全由内在内核中
两种高效的事件处理模式:Reactor和Proactor。
服务器程序通常需要处理三类事件:I/O事件、信号及定时事件。
同步I/O模型通常用于实现Reactor模式,异步I/O模型则用于实现Proactor模式。不过,也可以使用同步I/O方式模拟出Proactor模式。
Reactor模式:要求主线程(I/O处理单元)只负责监听文件描述上是否有事件发生,有的话就立即将该事件通知工作线程(逻辑单元)。如epoll_wait——注册事件,事件发生后通知主线程,主线程将已经发生的事件放入请求队列,并唤醒请求队列上的某个线程去处理这个事件。
Proactor模式:与Reactor模式不同,Proactor模式将所有I/O操作都交给主线程和内核来处理,工作线程仅仅负责业务逻辑。以aio_read和aio_write为例——注册事件并告诉内核读(写)缓冲区的位置,当socket上的数据被读入用户缓冲区后 或 当用户缓冲区的数据被写入socket之后,向应用程序发送一个信号,以通知应用程序数据处理。
我理解的Proactor模式:以aio_read和aio_write为例——主线程中注册事件并定义一个变量buf用于接收客户发送过来的数据。事件经过上面的注册以后,当客户发送数据过来的时候,内核就会自动地将数据放入buf中并触发工作线程去处理buf中的数据。也就是说Reactor模式的工作线程处理的是一个一个套接字,然后从套接字中读取数据 或 使用套接字发送数据。
而Proactor模式的工作线程处理的是一个一个具体的数据,而所有的I/O操作都放在了主线程和内核中(即所有对套接字的读写都放在了主线程和内核中)
模拟Proactor模式:主线程执行数据读写操作,读写完成之后,主线程向工作线程通知这一“完成事件”。
服务器的两种高效的并发模式:半同步/半异步(half-sync/halfasync)模式和领导者/追随者(Leader/Followers)模式。
半同步/半异步模式:
这里的同步和异步和I/O模型中所说的不同。
“同步”指的是程序完全按照代码序列的顺序执行;异步:程序顺序执行的过程中,可能有有各种事件触发各种函数的执行
同步和异步的优缺点
半同步/半异步模式:结合同步和异步。异步线程监听客户请求、将请求插入请求队列中。请求队列将通知某个工作在同步模式的工作线程来读取并处理该请求对象。
为什么感觉I/O模型和半同步/半异步模式描述的是一个东西???答:确实好像就是一个东西
半同步/半异步模式的变种——半同步/半反应堆(half-sync/half-reactive)模式:半同步/半反应堆模式采用的事件处理模式是Reactor模式,它要求工作线程自己从socket上读取客户请求和往socket写入服务器应答。
半同步/半反应堆模式的缺点
高效的半同步/半异步模式:无请求队列,主线程只管理监听socket,连接socket由工作线程来管理。
领导者/追随者模式:轮流做领导去监听I/O事件,有事件时,领导就派追随者去处理或领导自己去处理(此时可调用promote_new_leader选出新的领导)
句柄Handle:一个文件描述符。领导将事件处理器绑定到Handle上,当Handle上检测到事件,领导调用事件处理器进行处理
有限状态机:
根据数据包类型对应不同状态,不同状态编写不同的处理逻辑。执行完一个逻辑后就改变状态,不同的状态就可以执行不同的逻辑。
没看完,看得我很烦!!!以后再说吧!草
提高服务器性能的其他建议:
池:从池中获取硬件资源,池中的资源是预先静态分配的,如果资源不够时,就再动态分配一些并加入池中。池可分为多种,常见的有内存池、进程池、线程池和连接池
-
内存池:用于socket的接收缓存和发送缓存
-
进程池和线程池:用于并发编程。拿一个进程或线程出来处理新来到的客户请求
-
连接池:很多建好的与数据库的连接,只需要从连接池中取出连接,就可以访问数据库。
数据复制
- 当应用程序不关心数据内容时,可以直接用零拷贝
- 当两个工作进程之间要传递大量的数据时,使用共享内存来在它们之间直接共享这些数据,而不是使用管道或者消息队列来传递
上下文切换和锁
第九章 I/O复用
I/O复用:监听多个文件描述符上面的事件
先看我对I/O复用的总结
EPOLLONESHOT事件:
select、poll和epoll都能同时监听多个文件描述符,通过某种结构体变量来告诉内核监听哪些文件描述符上的哪些事件,并使用该结构体类型的参数来获取内核处理的结果。
select:只能处理可读、可写及异常事件,需要用三个参数分别处理这三种事件
poll:任何事件都被统一处理,接口较为简洁。每次select和poll调用都返回整个用户注册的事件集合(其中包括就绪的和未就绪的)
epoll:它在内核中维护一个事件表,返回就绪的事件
select和poll都只能工作在相对低效的LT模式,而epoll则可以工作在ET高效模式。并且epoll还支EPOLLONESHOT事件。
EPOLLONESHOT事件:一个socket连接在任一时刻都只被一个线程处理,直到此线程处理完毕以后,其他线程才能处理此socket,实现过程如下:文件描述符fd注册了EPOLLONESHOT事件,则操作系统最多触发fd上注册的一个可读、可写或者异常事件,且只触发一次,触发以后,epoll就检测不到fd上面发生的事件。触发事件一般会开启一个线程去处理相应的事件。此线程最后要将fd上的EPOLLONESHOT事件重置,这样使得epoll有机会再次检测到fd上的事件,进而使得其他线程有机会为fd服务。
从上面可以看出,注册了EPOLLONESHOT事件以后,每一个时刻只有一个线程在对fd进行操作。EPOLLONESHOT事件解决的是同一个时刻,有多个线程操作同一个套接字的情况。
EPOLLONESHOT事件的示例代码、其他的示例代码。
linux内部实现了一个超级服务xinetd,用于处理telnet、echo等操作。
第十章 信号
信号:通知进程的信息
kill函数:一个进程给其他进程发送信号的API是kill函数
可以自定义一个处理信号的函数,也可以使用SIG_IGN(忽略信号)和SIG_DFL(使用信号的默认处理方式)
Linux信号
使用sigaction函数可以重启被信号中断的系统调用
信号掩码指定哪些信号不能发送给本进程
我有时看到复杂的数据类型的名字的时候我会有点懵,但是没有关系,我就把它理解成一个类(事实上也有可能是一个结构体等)
signal函数:传入信号处理函数的指针和要捕获的信号
sigaction比较高级的signal函数
Linux使用数据结构sigset_t来表示一组信号,以及对sigset_t的一些操作
sigprocmask用于设置或查看进程的信号掩码
被挂起的信号:收到被屏蔽的信号,信号会被挂起。等到屏蔽被取消以后,进程会立即获取该信号。多次获取到同一被屏蔽的信号,屏蔽在被取消以后,该信号的处理函数也只被触发一次
sigpending获得进程当前被挂起的信号集
fork调用产生的子进程将继承父进程的信号掩码,但具有一个空的挂起信号集。
信号处理函数往管道的写端写入信号值,主循环监听管道的读端文件描述符上的可读事件。信号事件就能和其他I/O事件一样被处理,即统一事件源
代码没看
网络编程相关信号
SIGHUP信号可以用来强制服务器重读配置文件
xinetd处理SIGHUP的流程
往一个读端关闭的管道或socket连接中写数据将引发SIGPIPE信号
用SIGURG检测带外数据是否到达。代码还没看
黑马程序员笔记:https://www.cnblogs.com/codingbigdog/p/16246557.html
第11章 定时器
定时器:用于计时,时间到了,就去处理相应的任务。如过一定时间就去检测一个客户连接的活动状态。
Linux提供了三种定时方法:socket选项SO_RCVTIMEO和SO_SNDTIMEO、SIGALRM信号、I/O复用系统调用的超时参数。
1.socket选项SO_RCVTIMEO和SO_SNDTIMEO:使用setsockopt函数和选项SO_RCVTIMEO/SO_SNDTIMEO给socket设置接收数据超时时间和发送数据超时时间。函数send、sendmsg、recv、recvmsg、accept和connect中使用此socket会产生不同的效果。
send、sendmsg、connect:在一定时间没有将数据发送成功,返回错误。
recv、recvmsg、accept:在一定时间没有接收到数据,返回错误。
代码11-1connect_timeout.cpp:connect中使用设置定时的套接字——十秒之内没有连接成功,返回错误。
2.SIGALRM信号:由alarm和setitimer函数设置的实时闹钟一旦超时,将触发SIGALRM信号(见C++ 信号处理)。因此,我们可以利用该信号的信号处理函数来处理定时任务。本书中介绍了两个具体的定时器实现代码:
- 基于升序链表:将定时器都放入一个链表中,链表按照超时时间做升序排序。
代码:util_timer结构体代表一个定时器,其中包含一个时间,包含一个回调函数
sort_timer_lst类:用于生成一个升序的定时器链表,并通过成员函数添加、删除链表中的定时器 - 升序定时器链表的实际应用——关闭非活动连接:将超过一定时间没有使用的套接字从epoll树上删除。
代码:每隔一段时间alarm发送一个SIGALRM信号,信号处理函数通过通道将此信号通知给主循环,主循环中通过tick()遍历定时器链表并删除超时套接字。
3.I/O复用系统调用的超时参数:将epoll_wait函数的形参timeout设为相应的值。经过timeout的时间后,epoll_wait会被触发并epoll_wait的返回值为0。
管理定时器的容器:时间轮和时间堆。容器指的是装一个一个定时器的地方。
-
时间轮:一个轮子上有N个槽,每个槽代表一个链表。每一个链表代表一个时间。比如一个槽代表一秒,一圈有六十槽,那么一圈就代表六十秒。当指针指向了第n个槽的时候,可能代表已经过了n秒(假设一个槽代表一秒),也可能代表过了N+n、2N+n、3N+n...秒。处理该槽上超时的节点,并删除此节点。
代码:使用数组tw_timer *slots[N]表示时间轮,slots中每个元素指向一个定时器链表,链表无序。
tw_timer类用于表示定时器,成员变量rotation表示圈数,time_slot表示定时器属于时间轮上哪个槽。在本代码中,一个槽代表一秒,一圈六十秒,所以rotation=x,time_slot=y代表x分t秒。 -
时间堆:使用最小堆来存储定时器,每一次都将堆顶的定时器的超时时间作为依据,当超时时间到了就执行堆顶定时器的任务,并删除堆顶元素。
代码:还没看。
问题:
1.wan和lan的区别??
2.如何根据IP数据报中的8位服务类型TOS,实现相应服务??
3.在5.1.2中的内存对齐是什么意思??
4.在5.4中的程序想要说明什么???我没看懂?、
5.“3.9 TCP超时重传”有个问题未解决???
6."P172内存对齐"是什么??
7.“p175void指针”
8."宏有什么用?直接定一个变量不就行了"
9.什么叫不可重入?
答:如果一个函数的执行期间被中断后,到重新恢复到断点进行执行的过程中,函数所依赖的环境没
有发生改变,那么这个函数就是可重入的
我们知道中断时确实保存一些上下文,但是仅限于返回地址,cpu寄存器等之类的少量上下文,而函
数内部使用的诸如全局或静态变量,buffer等并不在保护之列,所以如果这些值在函数被中断期间发
生了改变,那么当函数回到断点继续执行时,其结果就不可预料了。
10.CGI服务器啥??
11.人手一个的web服务器项目,我该如何脱颖而出?
答:C++基础基础较为牢固、算法题做得比较好、还有其他别的项目、我学的正是进去工作以后要用的。。。。。。。。