1. 程式人生 > >網路程式設計常用訊號與統一事件源

網路程式設計常用訊號與統一事件源

首先給出基本所有的訊號,方便以後查詢.

*1	    SIGHUP	                終止          	終止控制終端或程序

2	    SIGINT	                終止        	鍵盤產生的中斷(Ctrl-C)
3	    SIGQUIT	                dump        	鍵盤產生的退出
4	    SIGILL	                dump        	非法指令
5	    SIGTRAP	                dump        	debug中斷
6	    SIGABRT/SIGIOT	        dump        	異常中止
7
SIGBUS/SIGEMT dump 匯流排異常/EMT指令 8 SIGFPE dump 浮點運算溢位 9 SIGKILL 終止 強制程序終止 10 SIGUSR1 終止 使用者訊號,程序可自定義用途 11 SIGSEGV dump 非法記憶體地址引用 12 SIGUSR2 終止 使用者訊號,程序可自定義用途 *
13 SIGPIPE 終止 向某個沒有讀取的管道中寫入資料 14 SIGALRM 終止 時鐘中斷(鬧鐘) 15 SIGTERM 終止 程序終止 16 SIGSTKFLT 終止 協處理器棧錯誤 17 SIGCHLD 忽略 子程序退出或中斷 18 SIGCONT 繼續 如程序停止狀態則開始執行 19
SIGSTOP 停止 停止程序執行 20 SIGSTP 停止 鍵盤產生的停止 21 SIGTTIN 停止 後臺程序請求輸入 22 SIGTTOU 停止 後臺程序請求輸出 *23 SIGURG 忽略 socket發生緊急情況 24 SIGXCPU dump CPU時間限制被打破 25 SIGXFSZ dump 檔案大小限制被打破 26 SIGVTALRM 終止 虛擬定時時鐘 27 SIGPROF 終止 profile timer clock 28 SIGWINCH 忽略   視窗尺寸調整 29 SIGIO/SIGPOLL 終止   I/O可用 30 SIGPWR 終止    電源異常 31 SIGSYS/SYSUNUSED dump 系統呼叫異常

與網路程式設計有關的訊號主要有:SIGHUP,SIGPIPESIGURG,以下分別來介紹:

  • SIGHUP : SIGHUP訊號的觸發時機是在程序的控制終端被掛起,對於沒有控制終端的網路後臺程式來說,通常是利用SIGHUP訊號來強制伺服器程式重讀相關的配置檔案,一個典型的例子就是xinetd超級伺服器程式。(說實話,目前還沒有遇到和處理過這個訊號)

  • SIGPIPE: 往讀端關閉的管道或socket連線中寫資料將會觸發SIGPIPE訊號,伺服器程式需要在程式碼中捕獲並處理該訊號,或者至少忽略它,因為SIGPIPE訊號的預設處理行為是結束所在程序.

    • 具體socket中產生SIGPIPE訊號的情況是:對端close()通訊連線後,本端繼續發資料,本端將會收到一個RST復位報文提示本端對端已經關閉連線、需重新建立連線,此時再繼續往對端發資料,本端系統將會產生SIFPIPE訊號
    • 產生的結果是:引起SIGPIPE訊號的寫操作將設定errnoEPIPE
    • 如何處理:
      • send函式的情況:send()系統呼叫的MSG_NOSIGNAL可選項可禁止寫操作觸發SPGPIPE訊號,所以在這種情況下可以使用send()函式反饋的errno值來判斷管道或者socket連線的對端是否關閉(errno == EPIPE)。
      • I/O複用:IO複用也可以用來檢測管道和socket連線的讀(對)端是否已經關閉,如poll,當讀對端關閉時,本(寫)端描述符的POLLHUP事件將被觸發,當socket連線被對端關閉時,socket上的POLLRDHUP事件將被觸發。
  • SIGURG: 核心通知應用程式帶外資料到達的訊號.

   訊號是一種非同步事件,訊號處理函式和程式的主迴圈是兩條不同的執行路線(訊號處理函式需要儘可能快地執行完畢,以確保訊號不被遮蔽太久(因為為了避免一些競態條件,訊號在處理期間,系統不會再次觸發它))

網路程式設計處理的事件主要有I/O,訊號和定時器!!!而其中定時器說到底還是與訊號有關,所以處理的主要有兩種事件:訊號和I/O.而訊號又是一種非同步事件,頻繁地直接處理訊號不利於程式的效能和可靠性,我們需要將他們統一起來處理.

  • 如何統一:把訊號的主要處理邏輯放到主迴圈中,而不是在訊號處理函式中進行主要的邏輯處理。訊號處理函式被觸發時,只是簡單地通知主迴圈程式接收到訊號,並把訊號值傳遞給主迴圈,主迴圈程式根據接收到的訊號值執行目標訊號對應的邏輯程式碼。
  • 訊號處理函式怎麼把接收到的訊號通知傳遞給主迴圈程式呢?利用管道:訊號處理函式往管道寫端寫入訊號值,主迴圈程式從管道的讀端讀出該訊號
  • 那麼就剩下最後一個問題了-主迴圈如何知道管道上何時有資料可讀吶?so easy!!! 使用I/O複用監聽管道讀端即可

上面把訊號和I/O事件統一出來處理的方法就是所謂的統一事件源了.

很多優秀的I/O框架庫和後臺服務程式都統一處理事件和I/O事件,比如Libenent I/O框架庫和xinetd超級服務。

實現程式碼:
base.hpp

#ifndef _BASE_H
#define _BASE_H

#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <pthread.h>
#include <netdb.h>
#include <math.h>
#include <stdexcept>
#include <iostream>
#include <assert.h>

class CallFailed
{
  public:
    explicit CallFailed(const std::string &s, const int &line) : ErrString(s), LineNo(line)
    {
        std::cout << ErrString << LineNo << std::endl;
        std::cout << "errno == " << errno << std::endl;
        perror("The reason is :");
        exit(0);
    }
    std::string ErrString;
    int LineNo;
};

class other_error : public std::logic_error
{
    /*等待填充*/
};
#endif

socket.h

#ifndef _SERVSOCKET_H
#define _SERVSOCKET_H

#include "base.hpp"

class BaseSocket
{
  public:
    BaseSocket() //預設建構函式
    {
        base_socket_ = socket(AF_INET, SOCK_STREAM, 0);
        if (base_socket_ < 0)
            throw CallFailed(" socket.h 檔案: socket create failed !!! at line  ", __LINE__);
    }
    BaseSocket(const int fd) { base_socket_ = fd; }
    //拷貝建構函式
    BaseSocket(const BaseSocket &rfs) { base_socket_ = rfs.base_socket_; }

    BaseSocket(BaseSocket &&) = delete;                 //移動建構函式
    BaseSocket &operator=(BaseSocket &&) = delete;      //移動賦值操作符
    BaseSocket &operator=(const BaseSocket &) = delete; //賦值操作符
    ~BaseSocket()
    {
        close(base_socket_);
    }
    inline void SetBaseSocket(const int &fd) { base_socket_ = fd; }
    inline int GetBaseSocket() { return base_socket_; }

    inline void Close()
    {
        if (close(base_socket_) == -1)
            throw CallFailed(" socket.h 檔案: close function failed !!! at line  ", __LINE__);
    }
    inline int Send(const void *buf, size_t len, int flags = SOCK_NONBLOCK)
    {
    }

    inline int Recv(void *buffer, size_t length, int &index, int flags = SOCK_NONBLOCK)
    {
    }

  protected:
    int base_socket_ = -1;
};

class ServSocket : public BaseSocket
{
    /*繼承了 base_socket_ */
  public:
    ServSocket() = delete;
    ServSocket(const ServSocket &) = delete;
    ServSocket &operator=(const ServSocket &) = delete;
    ServSocket(ServSocket &&) = delete;            //移動建構函式
    ServSocket &operator=(ServSocket &&) = delete; //移動賦值操作符

    explicit ServSocket(const char *ip, const int port)
    {
        bzero(&serv_addr, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        inet_pton(AF_INET, ip, &serv_addr.sin_addr);
        serv_addr.sin_port = htons(port);
    }
    inline void SetReuse()
    {
        int optval = 1;
        if (setsockopt(base_socket_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int)) < 0)
            throw CallFailed(" socket.h 檔案: setsockopt function failed !!! at line  ", __LINE__);
    }
    inline int Bind()
    {
        /* Bind(listenfd, (struct sockaddr *)&address, sizeof(address));**/
        if (bind(base_socket_, reinterpret_cast<struct sockaddr *>(&serv_addr), sizeof(serv_addr)) < 0)
            throw CallFailed(" socket.h 檔案: bind function failed !!! at line  ", __LINE__);
        return (base_socket_);
    }
    inline int Listen()
    {
        int backlog = 1024;
        char *ptr = NULL;
        if ((ptr = getenv("LISTENQ")) != NULL)
            backlog = atoi(ptr);

        if (listen(base_socket_, backlog) < 0)
            throw CallFailed(" socket.h 檔案: listen function failed !!! at line  ", __LINE__);
        return (base_socket_);
    }
    inline int Accept(struct sockaddr *sa, socklen_t *salenptr)
    {
        int n;
    again:
        if ((n = accept(base_socket_, sa, salenptr)) < 0)
        {
            /*連線在listen後建立,在accept前夭折,此時accept可能會返回錯誤,對此必須處理,*/
#ifdef EPROTO
            if (errno == EPROTO || errno == ECONNABORTED || errno == EWOULDBLOCK)
#else
            if (errno == ECONNABORTED)
#endif
                goto again;
            else
                throw CallFailed(" socket.h 檔案: accept function failed !!! at line  ", __LINE__);
        }
        return (n);
    }

  private:
    struct sockaddr_in serv_addr;
};
#endif

epoll.h

#ifndef _EPOLL_H
#define _EPOLL_H

#include "base.hpp"

const int MAX_EVENTS_NUMBER = 10000;

class Epoll
{
  public:
    Epoll()
    {
        this->epollfd_ = epoll_create(5); /*引數大於0即可*/
        if (this->epollfd_ < 0)
            throw CallFailed("Epoll.hpp 檔案:epoll_create function failed !!! at line  ", __LINE__);
    }
    Epoll(const Epoll &) = delete;
    Epoll &operator=(const Epoll &) = delete;

    ~Epoll() /*不拋異常*/
    {
        close(epollfd_);
    }

    inline int Add(const int &fd, bool oneshot = false)
    {
        epoll_event event;
        event.data.fd = fd;
        event.events = EPOLLIN | EPOLLET;

        if (oneshot)
            event.events |= EPOLLONESHOT;

        int ret = epoll_ctl(epollfd_, EPOLL_CTL_ADD, fd, &event);
        if (ret < 0)
            throw CallFailed("Epoll.hpp 檔案:epoll_ctl function failed !!! at line  ", __LINE__);
        return (ret);
    }
    inline int RemoveEvent(struct epoll_event &ev, int &fd)
    {
        int ret = epoll_ctl(epollfd_, EPOLL_CTL_DEL, fd, &ev);
        if (ret < 0)
            throw CallFailed("Epoll.hpp 檔案:epoll_ctl function failed !!! at line  ", __LINE__);
        return (ret);
    }
    inline int RemoveFd(int &fd)
    {
        int ret = epoll_ctl(epollfd_, EPOLL_CTL_DEL, fd, 0);
        if (ret < 0)
            throw CallFailed("Epoll.hpp 檔案:epoll_ctl function failed !!! at line  ", __LINE__);
        return (ret);
    }
    inline int ModifyEvent(struct epoll_event &ev, int &fd)
    {
        int ret = epoll_ctl(epollfd_, EPOLL_CTL_MOD, fd, &ev);
        if (ret < 0)
            throw CallFailed("Epoll.hpp 檔案:epoll_ctl function failed !!! at line  ", __LINE__);
        return (ret);
    }
    inline int Wait()
    {
        /* -1: 代表阻塞 */
        int ret = epoll_wait(epollfd_, events, MAX_EVENTS_NUMBER, -1);
        /*epoll_wait 出錯 */
        if ((ret < 0) && (errno != EINTR))
            throw CallFailed("Epoll.hpp 檔案:epoll_wait function failed !!! at line  ", __LINE__);
        return (ret);
    }
    inline int GetEpollFd() { return epollfd_; }

    inline int GetFdByIndex(const int &index)
    {
        return events[index].data.fd;
    }
    inline struct epoll_event *GetEventAddressByIndex(const int &index)
    {
        return &events[index];
    }
    inline uint32_t GetEventsByIndex(const int &index)
    {
        return events[index].events;
    }

  private:
    int epollfd_ = -1;
    struct epoll_event events[MAX_EVENTS_NUMBER];
};
#endif

main.cpp

/*************************************************************************
	> File Name: main.cpp
	> Author: Liu Shengxi 
	> Mail: [email protected]
	> Created Time: 2018年12月31日 星期一 21時35分13秒
 ************************************************************************/

#include <iostream>
#include "socket.h"
#include "epoll.h"
using namespace std;

int listenfd = -1;
static int pipefd[2];

void SetNonBlock(const int &fd)
{
	int old_option = fcntl(fd, F_GETFL);
	int new_option = old_option | O_NONBLOCK;
	fcntl(fd, F_SETFL, new_option);
}
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_handler = sig_handler;
	/*由此訊號中斷的系統呼叫是否要再啟動*/
	sa.sa_flags |= SA_RESTART;

	/*sigfillset()用來將引數set 訊號集初始化, 然後把所有的訊號加入到此訊號集裡*/
	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];
	const int port = atoi(argv[2]);

	ServSocket server(ip, port);
	server.Bind();
	server.Listen();
	Epoll epoll;

	listenfd = server.GetBaseSocket();
	epoll.Add(listenfd);
	SetNonBlock(listenfd);
	auto ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);
	assert(ret != -1);
	//pipefd[0] 用來讀 pipefd[1] 用來寫
	// 將訊號註冊進pipe管道的讀端pipefd[0],通過對讀端的監聽,來實現統一事件源。
	SetNonBlock(pipefd[1]);
	epoll.Add(pipefd[0]);

	addsig(SIGHUP);
	addsig(SIGCHLD);
	addsig(SIGTERM);
	addsig(SIGINT);
	while (true)
	{
		int number = epoll.Wait();
		for (int i = 0; i < number; i++)
		{
			int sockfd = epoll.GetFdByIndex(i);
			if (sockfd == listenfd)
			{
				struct sockaddr_in cli_addr;
				socklen_t len = sizeof(cli_addr);
				int connfd = server.Accept(reinterpret_cast<struct sockaddr *>(&