《Linux高性能服务器编程》阅读笔记

  • bind成功时返回0,失败时返回-1并设置errno。其中,两种常见的errno是EACCES和EADDRINUSE,他们的含义分别是:
    • EACCES:被绑定的地址是受保护的地址,仅有超级用户能够访问
    • EADDRINUSE:被绑定的地址正在使用中。
  • listenbacklog参数表示:处于彻底链接状态的socket的上限。
  • accept只是从监听队列中取出链接,而无论链接处于何种状态。(ESSTABLISHED或者CLOSE_WAIT
  • connect失败时返回-1并设置errno。其中,最多见的两种errno是ECONNREFUSEDETIMEDOUT
    • ECONNREFUSED:目标端口不存在,链接被拒绝。
    • ETIMEDOUT:链接超时。
  • close并不是当即关闭一个链接,而是将fd的引用数减1。算法

    重点来了:

  • 对于监听Socket来讲,在listen调用前设置这些经常使用的Socket选项,那么Accept返回的链接Socket将继承这些选项:SO_KEEPALIVESO_LINGERSO_RCVBUFSO_REVLOWATSO_SNDBUFSO_SNDLOWATTCP_NODELAY
  • 对于客户端Socket来讲,上述的选项应该在调用Connect函数以前设置,由于Connect调用成功返回以后,TCP三次握手已经完成。
  • 下面我将详细介绍这些套接字选项:
    • SO_REUSEADDR:服务器程序能够经过设置Socket选项来强制使用陷入TIME_WAIT状态的链接占用的Socket Address
    • SO_REUSPORT:容许在一个端口上启动一个服务的多个实例,只要每一个实例绑定不一样的本地IP地址便可。
    • SO_RCVBUFSO_SNDBUF:表示TCP接受缓冲区与发送缓冲区的大小。TCP接受缓冲区的大小最好设置为MSS(1460)的偶数倍.
    • SO_RCVLOWATSO_SNDLOWAT:表示接受缓冲区与发送缓冲区的低水平位标记。默认状况下,均为1。
    • SO_LINGER:能够经过该选项控制Close系统调用的不一样的行为。默认状况下,当咱们使用Close系统调用来关闭一个Socket时,Slose当即返回,TCP模块负责将该Socket发送缓冲区中的数据发送给对方。
    • SO_KEPPALIVE:发送周期性保活报文维持链接。
    • TCP_NODELAY:禁止Nagle算法,即在链接上会出现不少的小的数据块,在局域网环境下能够开启。
  • 零拷贝函数:
#include<sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);
  • 零拷贝函数:
#include<fcntl.h>
ssize_t splice(int in_fd, loff_t* off_in, int out_fd, loff_t* off_out, size_t len, unsigned int flags);

//使用splice实现零拷贝反射服务器
int pipefd[2];
int ret = pipe(pipefd);
ret = splice(connectfd, NULL, pipefd[1], NULL, 65536, SPLICE_F_MORE | SPLICE_F_MOVE);
ret = splice(pipefd[0], NULL, connectfd, NULL, 65536, SPLICE_F_MORE | SPLICE_F_MOVE);
close(connectfd);
  • 针对非阻塞IO执行的系统调用老是当即返回,而无论事情是否已经发生。若是事情没有当即发生,这些系统调用就当即返回-1,就和出错返回同样,此时咱们必须根据返回时设置的errno来判断具体发生了什么状况。对Accept,Send,Recv而言,事件发生未发生时errno一般被设置为EAGAIN(意味着“再来一次”)或者EWOULDBLOCK(意味着“指望阻塞”);对于Connect而言,errno则被设置为EINPEOCESS(意味着“在处理中”)。
  • IO复用函数自己是阻塞的,他们能够提升程序效率的缘由在于他们具备同事监听多个IO事件的能力。
  • 服务器程序一般须要处理三类事件:IO事件定时器事件信号事件
  • 两种事件处理模式:ReactorProator
    • Reactor
      • 主线程往Epoll内核事件表中注册可读事件
      • 主线程等待可读事件发生
      • 可读事件发生时,主线程将可读事件放入请求队列
      • 主线程将睡眠在请求队列中的工做线程唤醒,处理可读事件。处理完毕以后,向Epoll注册可写事件
      • 主线程等待可写事件发生
      • 可写事件发生时,主线程将可写事件放入请求队列
      • 如此往复循环
    • Proator
      • 主线程向内核注册读完成事件,完成时,经过信号通知应用程序
      • 主线程继续处理其余逻辑
      • 主线程收到读完成事件后,将读到的数据封装成一个事件对象送入工做线程。工做线程处理完毕以后,向内核注册读完成事件,并告诉内核用户缓冲区的位置
      • 主线程继续处理其余逻辑
      • 主线程收到读完成事件,作善后处理
      • 如此往复循环
  • 三个IO复用机制
    • select:每次事件发生以后,以前注册事件都会被修改,须要用FD_ISSET进行判断,而后从新注册。
    • poll:同select,使用轮询机制
    • epollepoll对文件描述符的操做有两种模式:LT(电平触发)和ET(边沿触发)。LT模式是默认的工做模式。当设置EPOLLET时,epoll会以ET模式来操做文件描述符,ET是高效工做模式。
      • ET:当epoll_wait检测到其上有事件发生并将这次的事件通知给应用程序后,应用程序必须马上处理该事件,由于后续的epoll_wait不会再次向应用程序通知这一事件。下降了epoll事件被触发的次数,所以效率高于LT。
      • LT:epoll_wait检测到有事件发生并将这次的事件通知应用程序后,应用程序能够不当即处理该事件。这样,当应用程序下一次调用epoll_wait的时候,epoll_wait还会再次向应用通知该事件,直到该事件被处理。
      • EPOLLONESHOT:对于注册EPOLLONESHOT事件的文件描述符,操做系统最多触发其上注册的一个事件(可读,可写或者异常),而且只触发一次,除非咱们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。
        ```编程

        include<sys/select.h>

        int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);服务器

FD_ZERO(fd_set* fdset);
FD_SET(int fd, fd_set* fdset);
FD_CLR(int fd,fd_set* fdset);
int FD_ISSET(int fd,fd_set* fdset);网络

struct timeval
{
long tv_sec; //秒数
long tv_usec; //微秒数
}socket

 

include<poll.h>

int poll(struct pollfd* fds, nfds_t nfds, int timeout);函数

struct pollfd
{
int fd;
short event; //注册的事件
short revent; //实际发生的事件,内核填充,记得每次判断后归零
}ui

POLLIN:数据可读
POLLOUT:数据可写
POLLREHUP:TCP链接被对方关闭,或者对方关闭了写操做
POLLERR:错误
POLLHUP:挂起
POLLNVAL:文件描述符没有打开操作系统

 

include<sys/epoll.h>

int epoll_create(int size);
int epoll_ctl(int epollfd, int op, int fd, struct epoll_event* event);
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);线程

op参数的类型:
EPOLL_CTL_ADD:向事件注册表中注册fd事件
EPOLL_CTL_MOD:修改fd上注册的事件
EPOLL_CTL_DEL:删除fd上注册的事件ssr

struct epoll_event
{
_uint32_t events;
epoll_data_t data;
};

union epoll_data
{
void* ptr;
int fd; //指定事件所从属的描述符
uint32_t u32;
uint64_t u64;
} epoll_data_t;

- 服务器端处理信号的方式:

void sig_handler(int sig)
{
int save_errno = errno;
int msg = sig;
send(pipefd[1], (char*)&msg, 1, 0);
errno = save_errno;
}

void addsig(int sig)
{
struct sigaction sa;
memset(&sa, '\0', sizeof(sa));
sa.sa_handleer = sig_handler;
sa.sa_flags |= SA_RESTART;
sigfillset(&sa.sa_mask);
assrt(sigaction(sig, &sa, NULL) != -1);
}
```

  • 与网络编程相关的信号:
    • SIGHUP:挂起进程的控制终端时,SIGHUP信号会被触发。
    • SIGPIPE:往一个关闭的管道或者Socket中写入数据。默认状态是结束进程。
    • SIGURG:带外数据到达。
    • SIGCHLD:子进程状态发生变化,使用wait进行处理
    • SIGTERM:终止进程。kill命令默认发送该信号。
    • SIGINT:键盘输入以中断进程。
相关文章
相关标签/搜索