Linux高性能服务器编程——ch10笔记

这篇具有很好参考价值的文章主要介绍了Linux高性能服务器编程——ch10笔记。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

第10章 信号

信号是由用户、系统或者进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常。

10.1 Linux信号概述

:::tips
int kill(pid_t pid, int sig);
:::
kill函数:一个进程给其他进程发送信号的API。
sig一般大于0,如果设为0则表示不发送信号,可以用来检测进程或进程组是否存在。由于进程PID的回绕(当进程被启动的时候,系统将按照顺序选择下一个没有被使用的数字作为它的PID(2~32768),当数字已经回绕一圈的时候,新的PID重新从2开始),可能导致被检测的PID不是我们期望的;另一方面,这种检测方法不是原子操作。
:::tips
typedef void (*__sighandler_t) (int);
:::
信号处理函数是可重入的,避免引发竞态条件。
如果程序在执行处于阻塞状态的系统调用时接收到信号,并且为该信号设置了信号处理函数,则默认情况下系统调用将被中断,并且errno被设置为EINTR。

10.2 信号函数

:::tips
_sighandler_t signal(int sig, _sighandler_t _handler);
:::
signal函数:为一个信号设置处理函数。
:::tips
int sigaction(int sig, const struct sigaction* act, struct sigaction* oact);
:::
sigaction函数:设置信号处理函数的更健壮的接口。可以为信号设置标志来自动重启被该信号中断的系统调用。
信号掩码:指定哪些信号不能发送给本进程。

10.3 信号集

sigset_t实际上是一个长整型数组,数组的每个元索的每个位表示一个信号。与文件描述符集fd_set类似。
:::tips
int sigprocmask(Int _how, _const sigset_t * _set, sigset_t* _oset);
:::
sigprocmask函数:设置或查看进程的信号掩码。
:::tips
int sigpending(sigset_t* set);
:::
sigpending函数:信号掩码使得被屏蔽的信号不能被进程接收,此时该信号被挂起,通过sigpending函数获得被挂起的信号集。此时通过sigprocmask函数可以使能被挂起的信号。

10.4 统一事件源

信号是一种异步事件:信号处理函数和程序的主循环是两条不同的执行路线。
为了尽快执行信号处理函数,可以把信号的主要处理逻辑放到程序的主循环中。信号处理函数往管道的写端写入信号值,主循环则从管道的读端读出该信号值。使用I/O复用来监听,信号事件就能和其他I/O事件一样被处理,即统一事件源。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER 1024

static int pipefd[2];

int setnonblocking(int fd)
{
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

void addfd(int epollfd, int fd)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    setnonblocking(fd);
}

/* 信号处理函数 */
void sig_handler(int sig)
{
    /* 保留原来的errno, 在函数最后恢复, 以保证函数的可重入性 */
    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_handler = sig_handler;
    sa.sa_flags |= SA_RESTART;
    sigfillset(&sa.sa_mask);
    assert(sigaction(sig, &sa, NULL) != -1);
}

int main(int argc, char *argv[])
{
    if (argc <= 2)
    {
        printf("usage: %s ip_address port_number\n", basename(argv[0]));
        return 1;
    }

    const char *ip = argv[1];
    int port = atoi(argv[2]);

    int ret = 0;
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenfd > 0);

    ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(listenfd, 5);
    assert(ret != -1);

    epoll_event events[MAX_EVENT_NUMBER];
    int epollfd = epoll_create(5);
    assert(epollfd != -1);
    addfd(epollfd, listenfd);

    /* 使用socketpair创建管道,注册pipefd[0]上的可读事件 */
    ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);
    assert(ret != -1);
    setnonblocking(pipefd[1]);
    addfd(epollfd, pipefd[0]);

    /* 设置一些信号的处理函数 */
    addsig(SIGHUP);
    addsig(SIGCHLD);
    addsig(SIGTERM);
    addsig(SIGINT);
    bool stop_server = false;

    while (!stop_server)
    {
        int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
        if ((number < 0) && (errno != EINTR))
        {
            printf("epoll failure\n");
            break;
        }

        for (int i = 0; i < number; ++i)
        {
            int sockfd = events[i].data.fd;
            /* 如果就绪的文件描述符是listenfd, 则处理新的连接 */
            if (sockfd == listenfd)
            {
                struct sockaddr_in client_address;
                socklen_t client_addrlength = sizeof(client_address);
                int sockfd = accept(listenfd, (struct sockaddr *)&client_address,
                        &client_addrlength);
                addfd(epollfd, sockfd);
            }
            /* 如果就绪的文件描述符是pipefd[0], 则处理信号 */
            else if ((sockfd == pipefd[0]) && (events[i].events & EPOLLIN))
            {
                char signals[1024];
                ret = recv(pipefd[0], signals, sizeof(signals), 0);
                if (ret == -1)
                {
                    continue;
                }
                else if (ret == 0)
                {
                    continue;
                }
                else
                {
                    /* 因为每个信号值占1字节,所以按字节来逐个接收信号,我们以SIGTREM
                     * 为例,来说明如何安全地终止服务器主循环 */
                    for (int i = 0; i < ret; ++i)
                    {
                        switch(signals[i])
                        {
                            case SIGCHLD:
                            case SIGHUP:
                                {
                                    continue;
                                }
                            case SIGTERM:
                            case SIGINT:
                                {
                                    stop_server = true;
                                }
                        }
                    }
                }
            }
            else
            {

            }
        }
    }

    printf("close fds\n");
    close(listenfd);
    close(pipefd[1]);
    close(pipefd[0]);

    return 0;
}

10.5 网络编程相关信号

SIGHUP:当挂起进程的控制终端时,SIGHUP信号将被触发。对于没有控制终端的网络后台程序而言,它们通常利用SIGHUP信号来强制服务器重读配置文件,例如xinetd超级服务程序。
xinetd处理SIGHUP的流程:
1)程序接收到SIGHUP信号时,信号处理函数便用管道通知主程序该信号的到来。信号处理函数往管道的写端写入SIGHUP信号,而主程序使用poll检测到管道的读端上有可读事件,就将管道上的数据读入;
2)xinetd重新读取一个子配置文件;3或4;
3)xinetd给子进程发送SIGTERM信号来终止该子进程,并调用waitpid来等待该子进程结束;(停止echo服务)
4)xinetd启动telnet服务的过程:创建一个流服务 socket 并将其绑定到端口上,然后监听该端口;(开启telnet服务)
SIGPIPE:往一个读端关闭的管道或socket连接中写数据将引发SIGPIPE信号,程序接收到后默认结束进程。以poll为例检测是否关闭:当管道的读端关闭时,写端文件描述符上的POLLHUP事件将被触发;当socket连接被对方关闭时,socket的POLLRDHUP事件将被触发。
SIGURG:内核通知应用程序带外数据到达。另一种方法是I/O复用技术,select等系统调用在接收到带外数据时将返回,并向应用程序报告socket上的异常事件。文章来源地址https://www.toymoban.com/news/detail-736365.html

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>

#define BUF_SIZE 1024

static int connfd;

/* SIGURG信号的处理函数 */
void sig_urg(int sig)
{
    int save_errno = errno;
    char buffer[BUF_SIZE];
    memset(buffer, '\0', BUF_SIZE);
    int ret = recv(connfd, buffer, BUF_SIZE - 1, MSG_OOB); /* 接收带外数据 */
    printf("got %d bytes os oob data '%s'\n", ret, buffer);
    errno = save_errno;
}

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

int main(int argc, char *argv[])
{

    if (argc <= 2)
    {
        printf("usage: %s ip_address port_number \n", basename(argv[0]));
        return 1;
    }

    const char *ip = argv[1];
    int port = atoi(argv[2]);

    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    int sockfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(sockfd > 0);

    int ret = bind(sockfd, (struct sockaddr *)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(sockfd, 5);
    assert(ret != -1);

    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof(client);
    connfd = accept(sockfd, (struct sockaddr *)&client, &client_addrlength);
    if (connfd < 0)
    {
        printf("errno is : %d\n", errno);
    }
    else
    {
        addsig(SIGURG, sig_urg);
        /* 使用SIGURG信号之前,我们必须设置socket的宿主进程或进程组 */
        fcntl(connfd, F_SETOWN, getpid());

        char buffer[BUF_SIZE];
        while (1)
        {
            /* 循环接收普通数据 */
            memset(buffer, '\0', BUF_SIZE);
            ret = recv(connfd, buffer, BUF_SIZE - 1, 0);
            if (ret <= 0)
            {
                break;
            }
            printf("get %d bytes of normal data '%s'\n", ret, buffer);
        }

        close(connfd);
    }

    close(sockfd);

    return 0;
}

到了这里,关于Linux高性能服务器编程——ch10笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux高性能服务器编程——学习笔记①

    第一章有一些概念讲的很好,值得好好关注一下!!! 1.1 主要的协议 1.1.1 数据链路层 ​ 数据链路层实现了网卡接口的网络驱动程序,以处理数据在物理媒介(以太网、令牌环)上的传输。 ​ 常用的协议有两种: ARP协议(Address Resolve Protocol,地址解析协议) RARP(Reverse

    2024年01月20日
    浏览(63)
  • Linux高性能服务器编程——ch10笔记

    信号是由用户、系统或者进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常。 :::tips int kill(pid_t pid, int sig); ::: kill函数:一个进程给其他进程发送信号的API。 sig一般大于0,如果设为0则表示不发送信号,可以用来检测进程或进程组是否存在。由于进程P

    2024年02月06日
    浏览(42)
  • Linux高性能服务器编程 学习笔记 第五章 Linux网络编程基础API

    我们将从以下3方面讨论Linux网络API: 1.socket地址API。socket最开始的含义是一个IP地址和端口对(ip,port),它唯一表示了使用TCP通信的一端,本书称其为socket地址。 2.socket基础API。socket的主要API都定义在sys/socket.h头文件中,包括创建socket、命名socket、监听socket、接受连接、发

    2024年02月07日
    浏览(56)
  • Linux高性能服务器编程 学习笔记 第二章 IP协议详解

    本章从两方面探讨IP协议: 1.IP头部信息。IP头部出现在每个IP数据报中,用于指定IP通信的源端IP地址、目的端IP地址,指导IP分片和重组,指定部分通信行为。 2.IP数据报的路由和转发。IP数据报的路由和转发发生在除目标机器外的所有主机和路由器上,它们决定数据报是否应

    2024年02月09日
    浏览(41)
  • Linux高性能服务器编程 学习笔记 第一章 TCP/IP协议族

    现在Internet使用的主流协议族是TCP/IP协议族,它是一个分层、多协议的通信体系。 TCP/IP协议族包含众多协议,我们只详细讨论IP协议和TCP协议,因为它们对编写网络应用程序有最直接的影响。如果想系统学习网络协议,RFC(Request For Comments,评论请求)是首选资料。 TCP/IP协议

    2024年02月09日
    浏览(65)
  • 【linux高性能服务器编程】项目实战——仿QQ聊天程序源码剖析

    hello !大家好呀! 欢迎大家来到我的Linux高性能服务器编程系列之项目实战——仿QQ聊天程序源码剖析,在这篇文章中, 你将会学习到如何利用Linux网络编程技术来实现一个简单的聊天程序,并且我会给出源码进行剖析,以及手绘UML图来帮助大家来理解,希望能让大家更能了

    2024年04月28日
    浏览(42)
  • Linux高性能服务器编程|阅读笔记:第1章 - TCP/IP协议族

    Hello! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出~   ଘ(੭ˊᵕˋ)੭ 昵称:海轰 标签:程序猿|C++选手|学生 简介:因C语言结识编程,随后转入计算机专业,获得过国家奖学金,有幸在竞赛中拿过一些国奖、省奖…已保研 学习经验:扎实基础 + 多做

    2024年02月01日
    浏览(58)
  • Linux高性能服务器编程|阅读笔记:第6章 - 高级I/O函数

    Hello! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出~   ଘ(੭ˊᵕˋ)੭ 昵称:海轰 标签:程序猿

    2024年02月03日
    浏览(41)
  • 强推Linux高性能服务器编程, 真的是后端开发技术提升, 沉淀自身不容错过的一本经典书籍

    目录 第1章 TCP/IP协议 1.1 TCP/IP协议族体系结构以及主要协议 1.1.1 数据链路层 1.1.2 网络层 1.1.3 传输层 1.1.4 应用层 1.2 封装 1.3 分用 1.5 ARP协议工作原理 1.5.1 以太网ARP请求/应答报文详解 1.5.2 ARP高速缓存的查看和修改 1.5.3 使用tcpdump观察ARP通信过程所得结果如下 本篇核心关键所在

    2024年02月07日
    浏览(50)
  • 【网络编程】高性能并发服务器源码剖析

      hello !大家好呀! 欢迎大家来到我的网络编程系列之洪水网络攻击,在这篇文章中, 你将会学习到在网络编程中如何搭建一个高性能的并发服务器,并且我会给出源码进行剖析,以及手绘UML图来帮助大家来理解,希望能让大家更能了解网络编程技术!!! 希望这篇文章能

    2024年04月15日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包