datetime:2022/12/29 14:37
author:nzb

多进程网络服务端

需要有信号多进程相关知识

利用信号防止产生僵尸进程

  • 僵尸进程: 进程使用fork 创建子进程,如果子进程退出,而父进程并没有调用 wait 获 waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中的这些进程是僵尸进程。 避免僵尸进程的方法:
    • 1.fork 两次用孙子进程去完成子进程的任务
    • 2.用 wait() 函数使父进程阻塞
    • 3.使用信号量,在 signal handler 中调用 waitpid , 这样父进程不用阻塞

server.cpp

#include "_freecplus.h"

CLogFile logfile;       // 服务程序的运行日志。
CTcpServer TcpServer;   // 创建服务端对象。

// 程序退出时调用的函数
void FathEXIT(int sig);   // 父进程退出函数。
void ChldEXIT(int sig);   // 子进程退出函数。

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("Using:./mpserver port logfile\nExample:./mpserver 5005 /tmp/mpserver.log\n\n");
        return -1;
    }

    // 关闭全部的信号
    for (int ii = 0; ii < 100; ii++) signal(ii, SIG_IGN);

    // 打开日志文件。
    if (logfile.Open(argv[2], "a+") == false) {
        printf("logfile.Open(%s) failed.\n", argv[2]);
        return -1;
    }

    // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程
    // 但请不要用 "kill -9 +进程号" 强行终止
    signal(SIGINT, FathEXIT);
    signal(SIGTERM, FathEXIT);

    if (TcpServer.InitServer(atoi(argv[1])) == false) // 初始化TcpServer的通信端口。
    {
        logfile.Write("TcpServer.InitServer(%s) failed.\n", argv[1]);
        FathEXIT(-1);
    }

    while (true) {
        if (TcpServer.Accept() == false)   // 等待客户端连接。
        {
            logfile.Write("TcpServer.Accept() failed.\n");
            continue;
        }

        if (fork() > 0) {
            TcpServer.CloseClient();
            continue;
        } // 父进程返回到循环首部。

        // 子进程重新设置退出信号。
        signal(SIGINT, ChldEXIT);
        signal(SIGTERM, ChldEXIT);

        TcpServer.CloseListen();

        // 以下是子进程,负责与客户端通信。
        logfile.Write("客户端(%s)已连接。\n", TcpServer.GetIP());

        char strbuffer[1024];  // 存放数据的缓冲区。

        while (true) {
            memset(strbuffer, 0, sizeof(strbuffer));
            if (TcpServer.Read(strbuffer, 50) == false) break; // 接收客户端发过来的请求报文。
            logfile.Write("接收:%s\n", strbuffer);

            strcat(strbuffer, "ok");      // 在客户端的报文后加上"ok"。
            logfile.Write("发送:%s\n", strbuffer);
            if (TcpServer.Write(strbuffer) == false) break;     // 向客户端回应报文。
        }

        logfile.Write("客户端已断开。\n");    // 程序直接退出,析构函数会释放资源。

        ChldEXIT(-1);  // 通信完成后,子进程退出。
    }
}

// 父进程退出时调用的函数
void FathEXIT(int sig) {
    if (sig > 0) {
        signal(sig, SIG_IGN);
        signal(SIGINT, SIG_IGN);
        signal(SIGTERM, SIG_IGN);
        logfile.Write("catching the signal(%d).\n", sig);
    }

    kill(0, 15);  // 通知其它的子进程退出。

    logfile.Write("父进程退出。\n");

    // 编写善后代码(释放资源、提交或回滚事务)
    TcpServer.CloseClient();

    exit(0);
}

// 子进程退出时调用的函数
void ChldEXIT(int sig) {
    // 为什么大于0,因为可以这样使用 ChldEXIT(-1);  // 通信完成后,子进程退出。
    if (sig > 0) {
        signal(sig, SIG_IGN);
        signal(SIGINT, SIG_IGN);
        signal(SIGTERM, SIG_IGN);
    }

    logfile.Write("子进程退出。\n");

    // 编写善后代码(释放资源、提交或回滚事务)
    TcpServer.CloseClient();

    exit(0);
}

client.cpp

#include "_freecplus.h"

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("Using:./demo47 ip port\nExample:./demo47 172.21.0.3 5005\n\n");
        return -1;
    }

    CTcpClient TcpClient;   // 创建客户端的对象。

    if (TcpClient.ConnectToServer(argv[1], atoi(argv[2])) == false) // 向服务端发起连接请求。
    {
        printf("TcpClient.ConnectToServer(\"%s\",%s) failed.\n", argv[1], argv[2]);
        return -1;
    }

    char strbuffer[1024];    // 存放数据的缓冲区。

    for (int ii = 0; ii < 30; ii++)   // 利用循环,与服务端进行5次交互。
    {
        memset(strbuffer, 0, sizeof(strbuffer));
        snprintf(strbuffer, 50, "%d:这是第%d个超级女生,编号%03d。", getpid(), ii + 1, ii + 1);
        printf("发送:%s\n", strbuffer);
        if (TcpClient.Write(strbuffer) == false) break;    // 向服务端发送请求报文。

        memset(strbuffer, 0, sizeof(strbuffer));
        if (TcpClient.Read(strbuffer, 20) == false) break;  // 接收服务端的回应报文。
        printf("接收:%s\n", strbuffer);

        sleep(1);
    }

    // 程序直接退出,析构函数会释放资源。
}

增加业务逻辑

server.cpp

#include "_freecplus.h"

CLogFile logfile;
CTcpServer TcpServer;   // 创建服务端对象。

// 程序退出时调用的函数
void FathEXIT(int sig);   // 父进程退出函数。
void ChldEXIT(int sig);   // 子进程退出函数。

// 处理业务的主函数。
bool _main(const char *strrecvbuffer, char *strsendbuffer);

// 心跳报文。
bool biz000(const char *strrecvbuffer, char *strsendbuffer);

// 身份验证业务处理函数。
bool biz001(const char *strrecvbuffer, char *strsendbuffer);

// 查询余客业务处理函数。
bool biz002(const char *strrecvbuffer, char *strsendbuffer);

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("Using:./mpserver_biz port logfile\nExample:./mpserver_biz 5005 /tmp/mpserver_biz.log\n\n");
        return -1;
    }

    // 关闭全部的信号
    for (int ii = 0; ii < 100; ii++) signal(ii, SIG_IGN);

    // 打开日志文件。
    if (logfile.Open(argv[2], "a+") == false) {
        printf("logfile.Open(%s) failed.\n", argv[2]);
        return -1;
    }

    // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程
    // 但请不要用 "kill -9 +进程号" 强行终止
    signal(SIGINT, FathEXIT);
    signal(SIGTERM, FathEXIT);

    if (TcpServer.InitServer(atoi(argv[1])) == false) // 初始化TcpServer的通信端口。
    {
        logfile.Write("TcpServer.InitServer(%s) failed.\n", argv[1]);
        FathEXIT(-1);
    }

    while (true) {
        if (TcpServer.Accept() == false)   // 等待客户端连接。
        {
            logfile.Write("TcpServer.Accept() failed.\n");
            continue;
        }

        if (fork() > 0) {
            TcpServer.CloseClient();
            continue;
        } // 父进程返回到循环首部。

        // 子进程重新设置退出信号。
        signal(SIGINT, ChldEXIT);
        signal(SIGTERM, ChldEXIT);

        TcpServer.CloseListen();

        // 以下是子进程,负责与客户端通信。
        logfile.Write("客户端(%s)已连接。\n", TcpServer.GetIP());

        char strrecvbuffer[1024], strsendbuffer[1024];  // 存放数据的缓冲区。

        while (true) {
            memset(strrecvbuffer, 0, sizeof(strrecvbuffer));
            memset(strsendbuffer, 0, sizeof(strsendbuffer));

            if (TcpServer.Read(strrecvbuffer, 30) == false) break; // 接收客户端发过来的请求报文。
            logfile.Write("接收:%s\n", strrecvbuffer);

            // 处理业务的主函数。
            if (_main(strrecvbuffer, strsendbuffer) == false) ChldEXIT(-1);

            logfile.Write("发送:%s\n", strsendbuffer);
            if (TcpServer.Write(strsendbuffer) == false) break;     // 向客户端回应报文。
        }

        logfile.Write("客户端已断开。\n");    // 程序直接退出,析构函数会释放资源。

        ChldEXIT(-1);  // 通信完成后,子进程退出。
    }
}

// 父进程退出时调用的函数
void FathEXIT(int sig) {
    if (sig > 0) {
        signal(sig, SIG_IGN);
        signal(SIGINT, SIG_IGN);
        signal(SIGTERM, SIG_IGN);
        logfile.Write("catching the signal(%d).\n", sig);
    }

    kill(0, 15);  // 通知其它的子进程退出。

    logfile.Write("父进程退出。\n");

    // 编写善后代码(释放资源、提交或回滚事务)
    TcpServer.CloseClient();

    exit(0);
}

// 子进程退出时调用的函数
void ChldEXIT(int sig) {
    if (sig > 0) {
        signal(sig, SIG_IGN);
        signal(SIGINT, SIG_IGN);
        signal(SIGTERM, SIG_IGN);
    }

    logfile.Write("子进程退出。\n");

    // 编写善后代码(释放资源、提交或回滚事务)
    TcpServer.CloseClient();

    exit(0);
}

bool _main(const char *strrecvbuffer, char *strsendbuffer)  // 处理业务的主函数。
{
    int ibizcode = -1;
    GetXMLBuffer(strrecvbuffer, "bizcode", &ibizcode);

    switch (ibizcode) {
        case 0:  // 心跳
            biz000(strrecvbuffer, strsendbuffer);
            break;
        case 1:  // 身份验证。
            biz001(strrecvbuffer, strsendbuffer);
            break;
        case 2:  // 查询余额。
            biz002(strrecvbuffer, strsendbuffer);
            break;

        default:
            logfile.Write("非法报文:%s\n", strrecvbuffer);
            return false;
    }

    return true;
}

// 身份验证业务处理函数。
bool biz001(const char *strrecvbuffer, char *strsendbuffer) {
    char username[51], password[51];
    memset(username, 0, sizeof(username));
    memset(password, 0, sizeof(password));

    GetXMLBuffer(strrecvbuffer, "username", username, 50);
    GetXMLBuffer(strrecvbuffer, "password", password, 50);

    if ((strcmp(username, "wucz") == 0) && (strcmp(password, "p@ssw0rd") == 0))
        sprintf(strsendbuffer, "<retcode>0</retcode><message>成功。</message>");
    else
        sprintf(strsendbuffer, "<retcode>-1</retcode><message>用户名或密码不正确。</message>");

    return true;
}

// 查询余额业务处理函数。
bool biz002(const char *strrecvbuffer, char *strsendbuffer) {
    char cardid[51];
    memset(cardid, 0, sizeof(cardid));

    GetXMLBuffer(strrecvbuffer, "cardid", cardid, 50);

    if (strcmp(cardid, "62620000000001") == 0)
        sprintf(strsendbuffer, "<retcode>0</retcode><message>成功。</message><ye>100.50</ye>");
    else
        sprintf(strsendbuffer, "<retcode>-1</retcode><message>卡号不存在。</message>");

    return true;
}

// 心跳报文
bool biz000(const char *strrecvbuffer, char *strsendbuffer) {
    sprintf(strsendbuffer, "<retcode>0</retcode><message>成功。</message>");

    return true;
}

client.cpp

#include "_freecplus.h"

CTcpClient TcpClient;   // 创建客户端的对象。

bool biz000();  // 发送心跳报文。
bool biz001();  // 身份验证
bool biz002();  // 余额查询

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("Using:./demo47_biz ip port\nExample:./demo47_biz 172.21.0.3 5005\n\n");
        return -1;
    }

    if (TcpClient.ConnectToServer(argv[1], atoi(argv[2])) == false) // 向服务端发起连接请求。
    {
        printf("TcpClient.ConnectToServer(\"%s\",%s) failed.\n", argv[1], argv[2]);
        return -1;
    }

    /*
    // 身份验证
    if (biz001()==false)
    {
      printf("biz001() failed.\n"); return -1;
    }

    sleep(10);

    biz002(); // 余额查询

    sleep(5);

    biz002(); // 余额查询
    */

    for (int ii = 0; ii < 10; ii++) {
        if (biz000() == false) break;

        sleep(10);
    }

    // 程序直接退出,析构函数会释放资源。
}

// 身份验证。
bool biz001() {
    char strbuffer[1024];    // 存放数据的缓冲区。

    memset(strbuffer, 0, sizeof(strbuffer));
    snprintf(strbuffer, 1000, "<bizcode>1</bizcode><username>wucz</username><password>p@ssw0rd</password>");
    printf("发送:%s\n", strbuffer);
    if (TcpClient.Write(strbuffer) == false) return false;    // 向服务端发送请求报文。

    memset(strbuffer, 0, sizeof(strbuffer));
    if (TcpClient.Read(strbuffer, 20) == false) return false;  // 接收服务端的回应报文。
    printf("接收:%s\n", strbuffer);

    int iretcode = -1;
    GetXMLBuffer(strbuffer, "retcode", &iretcode);

    if (iretcode == 0) {
        printf("身份验证成功。\n");
        return true;
    }

    printf("身份验证失败。\n");

    return false;
}

// 余额查询
bool biz002() {
    char strbuffer[1024];    // 存放数据的缓冲区。

    memset(strbuffer, 0, sizeof(strbuffer));
    snprintf(strbuffer, 1000, "<bizcode>2</bizcode><cardid>62620000000001</cardid>");
    printf("发送:%s\n", strbuffer);
    if (TcpClient.Write(strbuffer) == false) return false;    // 向服务端发送请求报文。

    memset(strbuffer, 0, sizeof(strbuffer));
    if (TcpClient.Read(strbuffer, 20) == false) return false;  // 接收服务端的回应报文。
    printf("接收:%s\n", strbuffer);

    int iretcode = -1;
    GetXMLBuffer(strbuffer, "retcode", &iretcode);

    if (iretcode == 0) {
        printf("查询余额成功。\n");
        return true;
    }

    printf("查询余额失败。\n");

    return false;
}

bool biz000()  // 发送心跳报文。
{
    char strbuffer[1024];    // 存放数据的缓冲区。

    memset(strbuffer, 0, sizeof(strbuffer));
    snprintf(strbuffer, 1000, "<bizcode>0</bizcode>");
    //printf("发送:%s\n",strbuffer);
    if (TcpClient.Write(strbuffer) == false) return false;    // 向服务端发送请求报文。

    memset(strbuffer, 0, sizeof(strbuffer));
    if (TcpClient.Read(strbuffer, 20) == false) return false;  // 接收服务端的回应报文。
    //printf("接收:%s\n",strbuffer);

    return true;
}

results matching ""

    No results matching ""