Linux高性能服务器编程

Linux高性能服务器编程

一、TCP/IP协议族

1.TCP/IP体系结构图

在这里插入图片描述

2.ARP协议

2.1 ARP协议工作原理

ARP协议能实现任意网络层地址到任意物理地址的转换。即知道对方IP地址,通过ARP协议就可以知道对方的物理地址(MAC地址)。

工作原理是:主机向自己所在的网络广播一个ARP请求,该请求包含目标机器的网络地址。此网络上的其他机器都将收到这个请求,但只有被请求的目标机器会回应一个ARP应答,其中包含自己的物理地址。

2.2 以太网ARP请求/应答报文格式

在这里插入图片描述

  • 硬件类型:定义物理地址的类型,它的值为1表示MAC地址。
  • 协议类型:表示要映射的协议地址类型,0x800,表示IP地址。
  • 操作:指出4种操作类型,ARP请求(1),ARP应答(2),RARP请求(3),RARP应答(4)
2.3 ARP高速缓存的查看与修改

通常,ARP维护一个高速缓存,其中包含经常访问(比如网关地址)或最近访问的机器的 IP地址到物理地址的映射。这样就避免了重复的 ARP 请求,提高了发送数据包的速度。

1.查看ARP缓存

arp -a 
# ? (169.254.169.254) at <incomplete> on ens33
# ? (192.168.111.50) at 00:50:56:c0:00:08 [ether] on ens33
#gateway (192.168.111.2) at 00:50:56:f1:3a:68 [ether] on ens33
arp -d 192.168.1.109   //删除 ARP缓存项
arp -s 192.168.1.109 08:00:27:53:10:67 //添加 ARP缓存项

3. DNS协议

3.1 DNS 查询和应答报文

在这里插入图片描述

  • 16位标识字段用于标记一对DNS查询和应答。

在这里插入图片描述

  • QR:查询/应答标志。0:查询,1:应答。
  • opcode :定义查询和应答的类型。0:标准查询,1:反向查询(由IP地址获得主机域名),2:表示请求服务器状态。
  • AA:授权应答标志,仅由应答报文使用。1表示域名服务器是授权服务器。
  • TC:截断标志,仅当DNS报文使用UDP服务时使用。1:表示DNS报文超过512字节,并被截断。
  • RD:递归查询标志。1表示执行递归查询,0表示执行迭代查询。
  • RA:允许递归标志,仅由应答报文使用,1表示DNS服务器支持递归查询。
  • zero:这3位未用,必须都设置0.
  • rcode:4位返回码,表示应答的状态。0:无错误,3:域名不存在。

在这里插入图片描述

  • 类型A,值是1,表示获取目标主机的IP地址。
  • 类型CNAME 值是5,表示获取目标主机的别名。
  • 类型PTR,值是12,表示反向查询。
  • 查询类通常为1,表示获取因特网地址(IP地址)

应答字段、授权字段和额外信息字段都使用资源记录(Resource Rccord,RR)格式。

在这里插入图片描述

  • 类型和类,同上。
  • 生存时间:表示该查询记录结果可被本地客户端程序缓存多长时间,单位是秒。
  • 16位资源数据长度和资源数据的内容取决于类型字段。类型A,资源数据是32位的IPv4地址,资源数据长度为4(以字节为单位)。

二、IP协议详解

1.路由表更新

route add  xxxxxxxxxxxxxxx #添加路由信息
route del xxxxxxxxxx #删除路由信息

三、TCP

1.特点

当接收端收到一个或多个TCP 报文段后,TCP模块将它们携带的应用程序数据按照TCP报文段的序号(见后文)依次放入TCP接收缓冲区中,并通知应用程序读取数据。接收端应用程序可以一次性将 TCP接收缓冲区中的数据全部读出,也可以分多次读取,这取决于用户指定的应用程序读缓冲区的大小。因此,应用程序执行的读操作次数和TCP 模块接收到的 TCP 报文段个数之间也没有固定的数量关系。

2.字节流

综上所述,发送端执行的写操作次数和接收端执行的读操作次数之间没有任何数量关系,这就是字节流的概念:==应用程序对数据的发送和接收是没有边界限制的。==UDP则不然发送端应用程序每执行一次写操作,UDP模块就将其封装成一个UDP 数据报并发送之。接收端必须及时针对每一个 UDP数据报执行读操作(通过recvfrom 系统调用),否则就会丢包(这经常发生在较慢的服务器上)。并且,如果用户没有指定足够的应用程序缓冲区来读取UDP 数据,则 UDP 数据将被截断。

3.TCP头部结构

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.三次握手与四次握手

在这里插入图片描述

5.半关闭状态

TCP连接是全双工的,所以它允许两个方向的数据传输被独立关闭。换言之,通信的一端可以发送结束报文段给对方,告诉它本端已经完成了数据的发送,但允许继续接收来自对方的数据,直到对方也发送结束报文段以关闭连接。TCP连接的这种状态称为半关闭(halfclose)状态。

四、HTTP

1.HTTP请求

在这里插入图片描述

在这里插入图片描述

"http://www.baidu.com/index.html”是目标资源的 URL。其中“http”是所谓的 scheme,表示获取目标资源需要使用的应用层协议。其他常见的scheme还有ftp、rtsp和file等。www.baidu.com”指定资源所在的目标主机。“index.html”指定资源文件的名称,这里指的是服务器根目录(站点的根目录,而不是服务器的文件系统根目录“/”)中的索引文件.

  • HTTP/1.0”表示客户端(wget 程序)使用的 HTTP 的版本号是 1.0。目前的主流 HTTP版本是 1.1。

HTTP 请求内容中的第 2~4行都是HTTP 请求的头部字段。一个 HTTP 请求可以包含多个头部字段。一个头部字段用一行表示,包含字段名称、冒号、空格和字段的值。HTTP 请求中的头部字段可按任意顺序排列。

  • “User-Agent: Wget/l.12 (linux-gnu)”表示客户端使用的程序是 wget。

  • “Host: www.baidu.com”表示目标主机名是 www.baidu.com。HTTP 协议规定 HTTP 请求中必须包含的头部字段就是目标主机名。

  • “Conncction: close”是我们执行 wget命令时传人的(见代码清单 4-1),用以告诉服务器处理完这个 HTTP请求之后就关闭连接。

在所有头部字段之后,HTTP请求必须包含一个空行,以标识头部字段的结束。请求行和每个头部字段都必须以结束(回车符和换行符):而空行则必须只包含一个,不能有其他字符,甚至是空白字符。在空行之后,HTTP请求可以包含可选的消息体。如果消息体非空,则HTTP请求的头部字段中必须包含描述该消息体长度的字段“Content-Length”。

2.HTTP应答

在这里插入图片描述

第一行是状态行。“HTTP/1.0”是服务器使用的 HTTP 协议的版本号。通常,服务器需要使用和客户端相同的 HTTP 协议版本。“200 OK”是状态码和状态信息。常见的状态码和状态信息及其含义如表 4-2 所示。

在这里插入图片描述

第 2~7行是 HTTP 应答的头部字段。其表示方法与 HTTP 请求中的头部字段相同。

  • “Server: BWS/1.0”表示目标 Web 服务器程序的名字是 BWS(Baidu Web Server)。
  • “Content-Length: 8024”表示目标文档的长度为 8024 字节。这个值和 wget 输出的文档长度一致。
  • “Content-Type: text/html;charset =gbk”表示目标文档的MIME类型。其中“text”是主文档类型,“htm!”是子文档类型。“texthtml”表示目标文档 index.html是text类型中的html 文档。“charset”是 text 文档类型的一个参数,用于指定文档的字符编码。
  • “Set-Cookie: BAIDUID=A5B6C72D68CF639CE8896FD79A03FBD8:FG=1; expires=Wed.04 -Ju1-42 00:10:47 GMT; path=/; domain=.baidu.com”表示服务器传送一个 Cookie 给客户端。其中,“BAIDUID”指定 Cookie 的名字,“expires”指定 Cookic 的生存时间,“domain”和“path”指定该 Cookie 生效的域名和路径。
  • “Via: 1.0 localhost (squid/3.0 STABLE18)”表示 HTTP应答在返回过程中经历过的所有代理服务器的地址和名称。这里的1ocalhost实际上指的是“192.168.1.108”。这个头部字段的功能有点类似于IP协议的记录路由功能。

在所有头部字段之后,HTTP 应答必须包含一个空行,以标识头部字段的结束。状态行和每个头部字段都必须以结束:而空行则必须只包含一个,不能有其他字符,甚至是空白字符。空行之后是被请求文档 index.html的内容(当然,我们并不关心它),其长度是 8024字节。

五、Linux网络编程API

1.带外标记

#include <sys/socket.h>

int sockatmark(int sockfd);

sockatmark 判断 sockfd 是否处于带外标记,即下一个被读取到的数据是否是带外数据。如果是,sockatmark返回 1,此时我们就可以利用带 MSG OOB 标志的 recv 调用来接收带外数据。如果不是,则 sockatmark 返回 0。

2.地址信息

#include <sys/socket.h>

int getsockname(int sockfd, struct sockaddr *addr, 						socklen_t *addrlen);


int getpeername(int sockfd, struct sockaddr *addr, 						socklen_t *addrlen);
  • getsockname 获取 sockfd对应的本端socket地址,并将其存储于 address 参数指定的内存中,该 socket地址的长度则存储于address len参数指向的变量中。如果实际 socket 地址的长度大于 address 所指内存区的大小,那么该 socket地址将被截断。getsockname 成功时返回 0,失败返回-1并设置 errno。
  • getpeermame 获取 sockid 对应的远端 socket地址,其参数及返回值的含义与 getsockname的参数及返回值相同。

六、高级IO函数

1.sendfile

sendhle 函数在两个文件描述符之间直接传递数据。

#include <sys/sendfile.h>

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, 					size_t count);

in_fd必须是一个支持类似 mmap 函数的文件描述符,即它必须指向真实的文件,不能是 socket 和管道;而 out_fd则必须是一个 socket。由此可见,sendfle 几乎是专门为在网络上传输文件而设计的。

2.splice

splice 函数用于在两个文件描述符之间移动数据,也是零拷贝操作。

#define _GNU_SOURCE       /* See feature_test_macros(7) */
#include <fcntl.h>

ssize_t splice(int fd_in, loff_t *off_in, int fd_out,
                      loff_t *off_out, size_t len, 						unsigned int flags);

fd in 参数是待输人数据的文件描述符。如果fd in是一个管道文件描述符,那么 off in参数必须被设置为 NULL。如果d in不是一个管道文件描述符(比如socket),那么 of in表示从输入数据流的何处开始读取数据。此时,若of in被设置为 NULL,则表示从输入数据流的当前偏移位置读入;若of in不为NULL,则它将指出具体的偏移位置。fdoutoffout 参数的含义与 fd in/of in相同,不过用于输出数据流。len参数指定移动数据的长度;ags 参数则控制数据如何移动,它可以被设置为表 6-2中的某些值的按位或。

在这里插入图片描述

3.tee

tee函数在两个管道文件描述符之间复制数据,也是零拷贝操作。它不消耗数据.

#include <fcntl.h>

ssize_t tee(int fd_in, int fd_out, size_t len, unsigned 			int flags);

七、Linux服务器程序规范

1.日志

1.1syslog
#include <syslog.h>
/*改变syslog的默认输出方式*/
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
  • ident参数指定的字符串被添加到日志消息的日期和时间之后,它通常被设置为程序的名字。
  • option参数对后续syslog调用的行为进行配置。

在这里插入图片描述

  • facility参数可用来修改syslog函数中的默认设施值。

此外,日志的过滤也很重要。程序在开发阶段可能需要输出很多调试信息,而发布之后我们又需要将这些调试信息关闭。解决这个问题的方法并不是在程序发布之后删除调试代码(因为日后可能还需要用到),而是简单地设置日志掩码,使日志级别大于日志掩码的日志信息被系统忽略。下面这个函数用于设置 syslog 的日志掩码:

#include <syslog.h>
int setlogmask(int mask);

2.系统资源限制

#include <sys/time.h>
#include <sys/resource.h>

int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);

struct rlimit 
{
    rlim_t rlim_cur;  /* Soft limit */
    rlim_t rlim_max;  /* Hard limit (ceiling for rlim_cur) */
};

3.改变工作目录/根目录

#include <unistd.h>
/*获取进程当前工作目录*/
char *getcwd(char *buf, size_t size);
/*改变进程工作目录*/
int chdir(const char *path);

/*改变进程根目录*/
int chroot(const char *path);

4.变为后台进程

#include <unistd.h>

int daemon(int nochdir, int noclose);

其中,nochdir 参数用于指定是否改变工作目录,如果给它传递0,则工作目录将被设置为“/”(根目录),否则继续使用当前工作目录。noclose参数为0时,标准输入、标准输出和标准错误输出都被重定向到 /dev/nul 文件,否则依然使用原来的设备。该函数成功时返回0,失败则返回-1并设置 errno。

八、高性能服务器程序框架

1.Reactor模式

Reactor 是这样一种模式,它要求主线程(I/O处理单元,下同)只负责监听文件描述上是否有事件发生,有的话就立即将该事件通知工作线程(逻辑单元,下同)。除此之外,主线程不做任何其他实质性的工作。读写数据,接受新的连接,以及处理客户请求均在工作线程中完成。

2.Proactor模式

Proactor 模式将所有 I/O 操作都交给主线程和内核来处理,工作线程仅仅负责业务逻辑。

使用异步I/O模型.

九、I/O复用

1.select

1.1 概念

在指定的文件描述符准备好I/O之前或超过一定的时间限制,select()调用就会阻塞。

监测的文件描述符可以分为三类,分别等待不同的事件。监测readfds 集合中的文件描述符,确认其中是否有可读数据(也就是说,读操作可以无阻塞的完成)。监测 writefds 集合中的文件描述符,确认其中是否有一个写操作可以不阻塞地完成。监测 exceptefds中的文件描述符,确认其中是否有出现异常发生或者出现带外(out-of-band)数据(这种情况只适用于套接字)。指定的集合可能为空 (NULL),相应的, select()则不对此类时间进行监视。

成功返回时,每个集合只包含对应类型的IO 就绪的文件描述符。

版本比较古老,可移植性比较好。

/* According to POSIX.1-2001 */
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

/*
nfds : 三个集合中最大的文件描述符 +1
timeval:超时设置
*/
int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set);//删掉某个描述符
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);//把某个描述符放入某集合
void FD_ZERO(fd_set *set);//清空描述符集合
struct timeval 
{
    long    tv_sec;         /* seconds */
    long    tv_usec;        /* microseconds */        
};
1.2 案例
//先布置监视任务
rfdset,wfdset,exceptfds
FD_SET(rfdset);
//监视
int ret = select()
//查看监视结果
if(ret < 0)
{
}
.....

2.poll

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd 
{ 
    int   fd;         /* file descriptor */   
    short events;     /* requested events */
    short revents;    /* returned events */
};

3.epoll

3.1 基本语法

linux的方言,不可以移植。

#include <sys/epoll.h>
/*
创建一个epoll描述符。
size 可以随便填,给个正数即可
*/
int epoll_create(int size);
int epoll_create1(int flags);

epoll_ctl()可以向指定的 epoll 上下文中加入或删除文件描述符:

#include <sys/epoll.h>
/* 管理一个epoll描述符 */
int epoll_ctl(int epfd, int op, int fd, 
              struct epoll_event *event);

op 的选项

名称描述
EPOLL_CTL_ADD把fd指定的文件添加到epfd指定的epoll实例监听集中
EPOLL_CTL_DEL把fd指定的文件从epfd指定的epoll监听集中删掉
EPOLL_CTL_MOD使用event改变在已有fd上的监听行为
typedef union epoll_data
{
      void        *ptr;
      int          fd;
      uint32_t     u32;
      uint64_t     u64;
} epoll_data_t;

struct epoll_event 
{ 
   uint32_t     events;      /* Epoll events */
   epoll_data_t data;        /* User data variable */
};

events参数

名称描述
EPOLLERR文件出错。即使没有设置,这个事件也是被监听的
EPOLLET在监听文件上开启边沿触发,默认行为是水平触发
EPOLLHUP文件被挂起,即使没有设置,这个事件也是被监听的
EPOLLIN文件未阻塞,可读
EPOLLONESHOT在一次事件产生并处理后,文件不再被监听。(必须指定新事件)
EPOLLOUT文件未阻塞,可写
EPOLLPRI高优先级的带外数据可读
#include <sys/epoll.h>

/* wait for an I/O event on an epoll file descriptor */
int epoll_wait(int epfd, struct epoll_event *events,
               int maxevents, int timeout);

如果 timeout 为0,即使没有事件发生,调用也立即返回,此时调用返回0。如果 timeout 为 -1,调用将一直等待到有事件发生。
当调用返回,epoll_event 结构体中的 events 字段描述了发生的事件。 data字段包含了所有用户在调用 epoll_ctl()前的设置。

3.2 案例
/* 在epfd实例中加入一个fd指定的监听文件*/

struct epoll_event event;
event.data.fd = fd;

event.events = EPOLLIN | EPOLLOUT;
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
if(ret)
    perror("epoll_ctl()");
3.3 ET 和 LT

ET模式 边沿触发模式,LT模式:水平触发模式(默认)。

水平触发方式中,只要输入缓冲有数据就会一直通知该事件。

边缘触发中输入缓冲收到数据时仅注册1次该事件。即使输入缓冲中还留有数据,也不会再注册。边缘触发方式下,以阻塞方式工作的read和write函数有可能引起服务器端的长时间停顿。因此,边缘触发方式中一定要采用非阻塞read和write函数。

将套接字改为非阻塞方式的方法

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );
名称含义
F_GETFL获得fd的文件描述符属性
F_SETFL更改文件描述符属性

将文件改为非阻塞模式

int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);

非堵塞读的时候,需要放入循环之后

4.总结

在这里插入图片描述

十、信号

1.发送信号 kill

一个进程给其他进程发送信号的API,

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

2.信号处理方式

2.1 用户自定义信号处理函数
void xxxx(int);
2.2 文件定义的信号处理函数
#include <bits/signum.h>

#define SIG_DFL((_sighandler_t) 0); //忽略目标信号
#define SIG_IGN((_sighandler_t) 1);//使用信号的默认处理方式
/**
默认处理方式如下:
	结束进程(Term)
	忽略信号(Ign)
    结束进程并生成核心转储文件(Core)
    暂停进程(Stop)
    继续进程(Cont)
*/

3. signal

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

自定义一个信号。

4.sigaction

自定义一个信号,健壮性比signal更好。

#include <signal.h>

int sigaction(int signum, const struct sigaction *act,
              struct sigaction *oldact);

struct sigaction
{
     void     (*sa_handler)(int);
     void     (*sa_sigaction)(int, siginfo_t *, void *);
     sigset_t   sa_mask;
     int        sa_flags;
     void     (*sa_restorer)(void);
};

在这里插入图片描述

5.信号集

#include <signal.h>

//把某个信号集清空
int sigemptyset(sigset_t *set);

//填充某个信号集
int sigfillset(sigset_t *set);

//向信号集中添加信号
int sigaddset(sigset_t *set, int signum);

//删除信号集中某信号
int sigdelset(sigset_t *set, int signum);

//判断是否是某信号集中的一个
int sigismember(const sigset_t *set, int signum);

6.进程信号掩码

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, 
                sigset_t *oldset);

how 选项

/*  
SIG_BLOCK
The set of blocked signals is the union of the current set and the set argument.

SIG_UNBLOCK
The signals in set are removed from the current set of blocked signals.  It is permissible to attempt  to
unblock a signal which is not blocked.

SIG_SETMASK
The set of blocked signals is set to the argument set.
          
*/

7.被挂起的信号

设置进程信号掩码后,被屏蔽的信号将不能被进程接收。如果给进程发送一个被屏蔽的信号,则操作系统将该信号设置为进程的一个被挂起的信号。如果我们取消对被挂起信号的屏蔽,则它能立即被进程接收到。如下函数可以获得进程当前被挂起的信号集

#include <signal.h>

int sigpending(sigset_t *set);

set参数用于保存被挂起的信号集。显然,进程即使多次接收到同一个被挂起的信号,sigpending 函数也只能反映一次。并且,当我们再次使用 sigprocmask 使能该挂起的信号时,该信号的处理函数也只被触发一次。

十一、定时器

1.Linux三种定时方式

  • socket选项SO_RCVTIMEO和SO_SNDTIMEO
  • SIGALRM信号
  • I/O复用系统调用的超时参数

2.socket设置

第5章中我们介绍过 socket选项 SO RCVTIMEO和 SO SNDTIMEO,它们分别用来设置 socket 接收数据超时时间和发送数据超时时间。因此,这两个选项仅对与数据接收和发送相关的 socket专用系统调用有效,这些系统调用包括send、sendmsg、recv、recvmsg、accept和connect。

在这里插入图片描述

由表 11-1可见,在程序中,我们可以根据系统调用(send、sendmsg、recv、recvmsg、accept和 connect)的返回值以及 erno 来判断超时时间是否已到,进而决定是否开始处理定时任务。

3.SIGALRM信号

通过SIGALRM信号的信号处理函数,进行处理定时任务。

4.I/O复用系统调用的超时参数

5.时间轮

在这里插入图片描述

6.时间堆

在这里插入图片描述

7.升序时间链表

双向链表。

十二、高性能I/O框架库 Libevent

1.句柄

IO 框架库要处理的对象,即 IO 事件、信号和定时事件,统一称为事件源。一个事件源通常和一个句柄绑定在一起。句柄的作用是,当内核检测到就绪事件时,它将通过句柄来通知应用程序这一事件。在 Linux环境下,I/O 事件对应的句柄是文件描述符,信号事件对应的句柄就是信号值。

2.事件多路分发器

事件的到来是随机的、异步的。我们无法预知程序何时收到一个客户连接请求,又亦或收到一个暂停信号。所以程序需要循环地等待并处理事件,这就是事件循环。在事件循环中,等待事件一般使用 IO 复用技术来实现。I/0 框架库一般将系统支持的各种 JO 复用系统调用封装成统一的接口,称为事件多路分发器。事件多路分发器的demuliplcx 方法是等待事件的核心函数,其内部调用的是 select、poll、cpoll wait 等函数。此外,事件多路分发器还需要实现register_event和remove_event方法,以供调用者往事件多路分发器中添加事件和从事件多路分发器中删除事件。

3.事件处理器和具体事件处理器

事件处理器执行事件对应的业务逻辑。它通常包含一个或多个handle_event 回调函数,这些回调函数在事件循环中被执行。I/O框架库提供的事件处理器通常是一个接口,用户需要继承它来实现自己的事件处理器,即具体事件处理器。因此,事件处理器中的回调函数一般被声明为虚函数,以支持用户的扩展。
此外,事件处理器一般还提供一个get_handle方法,它返回与该事件处理器关联的句柄。那么,事件处理器和句柄有什么关系?当事件多路分发器检测到有事件发生时,它是通过句柄来通知应用程序的。因此,我们必须将事件处理器和句柄绑定,才能在事件发生时获取到正确的事件处理器

4.Reactor

提供的主要方法:

  • handle_events。该方法执行事件循环。它重复如下过程:等待事件,然后依次处理所有就绪事件对应的事件处理器。
  • register_event。该方法调用事件多路分发器的register_event方法来向事件多路分发器中注册一个事件。
  • remove_event。该方法调用事件多路分发器的remove_event方法来删除事件多路分发器中的一个事件。

在这里插入图片描述

5.Libevent的特点

  • 跨平台支持。支持UNIX,LInux,Windows
  • 统一事件源。对IO事件、信号和定时事件提供统一的处理。
  • 线程安全。
  • 基于Reactor模式的实现。

十三、进程间通信

1.管道pipe

2.信号量

3.共享内存

#include <sys/ipc.h>
#include <sys/shm.h>

/*
shmget 系统调用创建一段新的共享内存,或者获取一段已经存在的共享内存。
*/
int shmget(key_t key, size_t size, int shmflg);

#include <sys/types.h>
#include <sys/shm.h>

/*共享内存被创建/获取之后,我们不能立即访问它,而是需要先将它关联到进程的地址空间中。使用完共享内存之后,我们也需要将它从进程地址空间中分离。这两项任务分别由如下两个系统调用实现:*/
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);

#include <sys/ipc.h>
#include <sys/shm.h>

/* shmctl 系统调用控制共享内存的某些属性。*/
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

struct shmid_ds
{
    struct ipc_perm shm_perm;    
    /* Ownership and permissions */
    size_t          shm_segsz;   
    /* Size of segment (bytes) */
    time_t          shm_atime;   
    /* Last attach time */
    time_t          shm_dtime;   
    /* Last detach time */
    time_t          shm_ctime;   
    /* Last change time */
    pid_t           shm_cpid;    
    /* PID of creator */
    pid_t           shm_lpid;   
    /* PID of last shmat(2)/shmdt(2) */
    shmatt_t        shm_nattch;  
    /* No. of current attaches */
     ...
};

4.消息队列

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

/*创建消息队列*/
int msgget(key_t key, int msgflg);
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

/* 把一条消息添加到消息队列中*/
int msgsnd(int msqid, const void *msgptr, size_t msgsz, 
           int msgflg);

在这里插入图片描述

/*从消息队列中获取消息*/
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, 
                long msgtyp,int msgflg);

在这里插入图片描述

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
/*控制消息队列的某些属性*/
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

 struct msqid_ds 
 {
     struct ipc_perm msg_perm;     /* Ownership and permissions */
     time_t   msg_stime;    /* Time of last msgsnd(2) */
     time_t   msg_rtime;    /* Time of last msgrcv(2) */
     time_t   msg_ctime;    /* Time of last change */
     unsigned long   __msg_cbytes;
     /* Current number of bytes in queue (nonstandard) */
      msgqnum_t  msg_qnum;     /* Current number of messages in queue */
      msglen_t   msg_qbytes;   /* Maximum number of bytes
                                                allowed in queue */
     pid_t     msg_lspid;    /* PID of last msgsnd(2) */
     pid_t     msg_lrpid;    /* PID of last msgrcv(2) */
};

cmd:

在这里插入图片描述

十四、多进程编程

1.使用信号,通知父进程

==当一个进程结束时,它将给其父进程发送一个SIGCHLD信号。==因此,我们可以在父进程中捕获SIGCHLD信号,并在信号处理函数中调用waitpid函数以“彻底结束”一个子进程。

static void handle_child(int sig)
{
	pid_t pid;
    int stat;
    
    while((pid = waitpid(-1, &stat, WNOHANG)) > 0)
    {
		/* 对结束的子进程进行善后*/
    }

}

waitpid 只等待由 pid 参数指定的子进程。如果 pid 取值为 -1,那么它就和 wait 函数相同,即等待任意一个子进程结束。stat loc参数的含义和 wait 兩数的 stat loc 参数相同。options参数可以控制 waitpid 函数的行为。该参数最常用的取值是 WNOHANG。==当 options 的取值是WNOHANG 时,waitpid 调用将是非阻塞的:==如果pid指定的目标子进程还没有结束或意外终止,则 waitpid 立即返回0;如果目标子进程确实正常退出了,则 waitpid 返回该子进程的PID。waitpid 调用失败时返回-1并设置errno。

十五、多线程编程

十六、进程池,线程池

1.进程池模型

在这里插入图片描述

同一个客户请求可以让多个子进程进行处理。

在这里插入图片描述

同一个客户请求,由同一个子进程处理

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值