lesson10网络基础2

模拟实现网络版计算器

  1. 序列化反序列化
  2. 定制自己的协议
  3. 将我们的服务守护进程化,让他变成一个网络服务

lesson10网络基础2_第1张图片

CalClient.cc

#include 
#include "Sock.hpp"
#include "Protocol.hpp"

using namespace ns_protocol;

static void Usage(const std::string &process)
{
    std::cout << "\nUsage: " << process << " serverIp serverPort\n"
              << std::endl;
}

// ./client server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string server_ip = argv[1];
    uint16_t server_port = atoi(argv[2]);
    Sock sock;
    int sockfd = sock.Socket();
    if (!sock.Connect(sockfd, server_ip, server_port))
    {
        std::cerr << "Connect error" << std::endl;
        exit(2);
    }
    bool quit = false;
    std::string buffer;
    while (!quit)
    {
        // 1. 获取需求
        Request req;
        std::cout << "Please Enter # ";
        std::cin >> req.x_ >> req.op_ >> req.y_;

        // 2. 序列化
        std::string s = req.Serialize();
        // std::string temp = s;
        // 3. 添加长度报头
        s = Encode(s);
        // 4. 发送给服务端
        Send(sockfd, s);

        // 5. 正常读取
        while (true)
        {
            bool res = Recv(sockfd, &buffer);
            if (!res)
            {
                quit = true;
                break;
            }
            std::string package = Decode(buffer);
            if (package.empty())
                continue;
            Response resp;
            resp.Deserialized(package);
            std::string err;
            switch (resp.code_)
            {
            case 1:
                err = "除0错误";
                break;
            case 2:
                err = "模0错误";
                break;
            case 3:
                err = "非法操作";
                break;
            default:
                std::cout << resp.x_ << resp.op_ << resp.y_ << " = " << resp.result_ << " [success]" << std::endl;
                break;
            }
            if(!err.empty()) std::cerr << err << std::endl;
            // sleep(1);
            break;
        }
    }
    close(sockfd);
    return 0;
}

CalServer.cc

#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Daemon.hpp"
#include 
#include 

using namespace ns_tcpserver;
using namespace ns_protocol;

static void Usage(const std::string &process)
{
    std::cout << "\nUsage: " << process << " port\n"
              << std::endl;
}

static Response calculatorHelper(const Request &req)
{
    Response resp(0, 0, req.x_, req.y_, req.op_);
    switch (req.op_)
    {
    case '+':
        resp.result_ = req.x_ + req.y_;
        break;
    case '-':
        resp.result_ = req.x_ - req.y_;
        break;
    case '*':
        resp.result_ = req.x_ * req.y_;
        break;
    case '/':
        if (0 == req.y_)
            resp.code_ = 1;
        else
            resp.result_ = req.x_ / req.y_;
        break;
    case '%':
        if (0 == req.y_)
            resp.code_ = 2;
        else
            resp.result_ = req.x_ % req.y_;
        break;
    default:
        resp.code_ = 3;
        break;
    }
    return resp;
}

void calculator(int sock)
{
    std::string inbuffer;
    while (true)
    {
        // 1. 读取成功
        bool res = Recv(sock, &inbuffer); // 在这里我们读到了一个请求?
        if (!res)
            break;
        // std::cout << "begin: inbuffer: " << inbuffer << std::endl;
        
        // 2. 协议解析,保证得到一个完整的报文
        std::string package = Decode(inbuffer);
        if (package.empty())
            continue;
        // std::cout << "end: inbuffer: " << inbuffer << std::endl;
        // std::cout << "packge: " << package << std::endl;
        logMessage(NORMAL, "%s", package.c_str());
        
        // 3. 保证该报文是一个完整的报文
        Request req;
        
        // 4. 反序列化,字节流 -> 结构化
        req.Deserialized(package); // 反序列化
        
        // 5. 业务逻辑
        Response resp = calculatorHelper(req);
        
        // 6. 序列化
        std::string respString = resp.Serialize(); // 对计算结果进行序列化
        
        // 7. 添加长度信息,形成一个完整的报文
        // "length\r\ncode result\r\n"
        // std::cout << "respString: " << respString << std::endl;
        respString = Encode(respString);
        // std::cout << "encode: respString: " << respString << std::endl;
        
        // 8. send这里我们暂时先这样写,多路转接的时候,我们再来谈发送的问题
        Send(sock, respString);
    }
}

// ./CalServer port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    // 一般经验:server在编写的时候,要有较为严谨性的判断逻辑
    // 一般服务器,都是要忽略SIGPIPE信号的,防止在运行中出现非法写入的问题!
    // signal(SIGPIPE, SIG_IGN);
    MyDaemon();
    std::unique_ptr server(new TcpServer(atoi(argv[1])));
    server->BindService(calculator);
    server->Start();
    // Request req(123, 456, '+');
    // std::string s = req.Serialize();
    // std::cout << s << std::endl;

    // Request temp;
    // temp.Deserialized(s);
    // std::cout << temp.x_ << std::endl;
    // std::cout << temp.op_ << std::endl;
    // std::cout << temp.y_ << std::endl;
    return 0;
}

Daemon.hpp

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 

// 设置守护进程
void MyDaemon()
{
    // 1. 忽略信号,SIGPIPE,SIGCHLD
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);

    // 2. 不要让自己成为组长
    if (fork() > 0)
        exit(0);
    // 3. 调用setsid
    setsid();
    // 4. 标准输入,标准输出,标准错误的重定向,守护进程不能直接向显示器打印消息
    int devnull = open("/dev/null", O_RDONLY | O_WRONLY);
    if(devnull > 0)
    {
        dup2(0, devnull);
        dup2(1, devnull);
        dup2(2, devnull);
        close(devnull);
    }
}

Log.hpp

#pragma once

#include 
#include 
#include 
#include 
#include 

// 日志是有日志级别的
#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

const char *gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"
};

#define LOGFILE "./calculator.log"

// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOW
    if(level== DEBUG) return;
#endif
    char stdBuffer[1024]; //标准部分
    time_t timestamp = time(nullptr);
    snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);

    char logBuffer[1024]; //自定义部分
    va_list args;
    va_start(args, format);
    vsnprintf(logBuffer, sizeof logBuffer, format, args);
    va_end(args);

    FILE *fp = fopen(LOGFILE, "a");
    fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
    fclose(fp);
}

Makefile

.PHONY:all
all:client CalServer

client:CalClient.cc
	g++ -o $@ $^ -std=c++11
CalServer:CalServer.cc
	g++ -o $@ $^ -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -f client CalServer

Protocol.hpp

#pragma once

#include 
#include 
#include 
// #include 

namespace ns_protocol
{
#define MYSELF 0

#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define SEP "\r\n"
#define SEP_LEN strlen(SEP) // 不能是sizeof!

    class Request
    {
    public:
        // 1. 自主实现 "length\r\nx_ op_ y_\r\n"
        // 2. 使用现成的方案
        // 序列化
        std::string Serialize()
        {
#ifdef MYSELF
            // 使用自己定义的方式
            std::string str;
            str = std::to_string(x_);
            str += SPACE;
            str += op_; // TODO
            str += SPACE;
            str += std::to_string(y_);
            return str;
#else
            Json::Value root;
            root["x"] = x_;
            root["y"] = y_;
            root["op"] = op_;
            Json::FastWriter writer;
            return writer.write(root);
#endif
        }
        // "x_ op_ y_"
        // "1234 + 5678"
        // 反序列化
        bool Deserialized(const std::string &str)
        {
#ifdef MYSELF
            std::size_t left = str.find(SPACE);
            if (left == std::string::npos)
                return false;
            std::size_t right = str.rfind(SPACE);
            if (right == std::string::npos)
                return false;
            x_ = atoi(str.substr(0, left).c_str());
            y_ = atoi(str.substr(right + SPACE_LEN).c_str());
            if (left + SPACE_LEN > str.size())
                return false;
            else
                op_ = str[left + SPACE_LEN];
            return true;
#else
            Json::Value root;
            Json::Reader reader;
            reader.parse(str, root);
            x_ = root["x"].asInt();
            y_ = root["y"].asInt();
            op_ = root["op"].asInt();
            return true;
#endif
        }

    public:
        Request()
        {
        }
        Request(int x, int y, char op) : x_(x), y_(y), op_(op)
        {
        }
        ~Request() {}

    public:
        int x_;   // 是什么?
        int y_;   // 是什么?
        char op_; // '+' '-' '*' '/' '%'
    };

    class Response
    {
    public:
        // "code_ result_"
        // 结果序列化
        std::string Serialize()
        {
#ifdef MYSELF
            std::string s;
            s = std::to_string(code_);
            s += SPACE;
            s += std::to_string(result_);
            return s;
#else
            Json::Value root;
            root["code"] = code_;
            root["result"] = result_;
            root["xx"] = x_;
            root["yy"] = y_;
            root["zz"] = op_;
            Json::FastWriter writer;
            return writer.write(root);
#endif
        }
        // "111 100"
        // 结果反序列化
        bool Deserialized(const std::string &s)
        {
#ifdef MYSELF
            std::size_t pos = s.find(SPACE);
            if (pos == std::string::npos)
                return false;
            code_ = atoi(s.substr(0, pos).c_str());
            result_ = atoi(s.substr(pos + SPACE_LEN).c_str());
            return true;
#else
            Json::Value root;
            Json::Reader reader;
            reader.parse(s, root);
            code_ = root["code"].asInt();
            result_ = root["result"].asInt();
            x_ =  root["xx"].asInt();
            y_ =  root["yy"].asInt();
            op_ =  root["zz"].asInt();
            return true;
#endif
        }

    public:
        Response()
        {
        }
        Response(int result, int code, int x, int y, char op) 
        : result_(result), code_(code), x_(x), y_(y), op_(op)
        {
        }
        ~Response() {}

    public:
        int result_; // 计算结果
        int code_;   // 计算结果的状态码

        int x_;
        int y_;
        char op_;
    };

    // 临时方案
    // 调整方案2: 我们期望,你必须给我返回一个完整的报文
    bool Recv(int sock, std::string *out)
    {
        // UDP是面向数据报:
        // TCP 面向字节流的:
        // recv : 你怎么保证,你读到的inbuffer,是一个完整完善的请求呢?不能保证
        // "1234 + 5678" : 1234 +
        // "1234 + 5678" : 1234 + 5678 123+99
        // "1234 "
        // 必须是:"1234 + 5678"
        // 单纯的recv是无法解决这个问题的,需要对协议进一步定制!
        char buffer[1024];
        ssize_t s = recv(sock, buffer, sizeof(buffer)-1, 0); // 9\r\n123+789\r\n
        if (s > 0)
        {
            buffer[s] = 0;
            *out += buffer;
        }
        else if (s == 0)
        {
            // std::cout << "client quit" << std::endl;
            return false;
        }
        else
        {
            // std::cout << "recv error" << std::endl;
            return false;
        }
        return true;
    }

    void Send(int sock, const std::string str)
    {
        // std::cout << "sent in" << std::endl;
        int n = send(sock, str.c_str(), str.size(), 0);
        if (n < 0)
            std::cout << "send error" << std::endl;
    }
    // "length\r\nx_ op_ y_\r\n..." // 10\r\nabc
    // "x_ op_ y_\r\n length\r\nXXX\r\n"
    // 反序列化(进一步)
    std::string Decode(std::string &buffer)
    {
        std::size_t pos = buffer.find(SEP);
        if(pos == std::string::npos) return "";
        int size = atoi(buffer.substr(0, pos).c_str());
        int surplus = buffer.size() - pos - 2*SEP_LEN;
        if(surplus >= size)
        {
            //至少具有一个合法完整的报文, 可以动手提取了
            buffer.erase(0, pos+SEP_LEN);
            std::string s = buffer.substr(0, size);
            buffer.erase(0, size + SEP_LEN);
            return s;
        }
        else
        {
            return "";
        }
    }
    // "XXXXXX"
    // "123\r\nXXXXXX\r\n"
    // 序列化(进一步)
    std::string Encode(std::string &s)
    {
        std::string new_package = std::to_string(s.size());
        new_package += SEP;
        new_package += s;
        new_package += SEP;
        return new_package;
    }

}

Sock.hpp

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "Log.hpp"

// 封装一下套接字 TCP
class Sock
{
private:
    const static int gbacklog = 20;
public:
    Sock() {}
    int Socket()
    {
        int listensock = socket(AF_INET, SOCK_STREAM, 0);// 监听套接字 
        if (listensock < 0)
        {
            logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "create socket success, listensock: %d", listensock);
        return listensock;
    }

    void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(port);// 端口转换
        inet_pton(AF_INET, ip.c_str(), &local.sin_addr);// 端口转换
        
        if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
            exit(3);
        }
    }

    void Listen(int sock)
    {
        if (listen(sock, gbacklog) < 0)
        {
            logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
            exit(4);
        }
        logMessage(NORMAL, "init server success");
    }
    // 一般经验
    // const std::string &: 输入型参数
    // std::string *: 输出型参数
    // std::string &: 输入输出型参数
    int Accept(int listensock, std::string *ip, uint16_t *port)
    {
        struct sockaddr_in src;
        socklen_t len = sizeof(src);
        int servicesock = accept(listensock, (struct sockaddr *)&src, &len);// 转移
        if (servicesock < 0)
        {
            logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
            return -1;
        }
        if(port) *port = ntohs(src.sin_port);
        if(ip) *ip = inet_ntoa(src.sin_addr);
        return servicesock;
    }

    bool Connect(int sock, const std::string &server_ip, const uint16_t &server_port)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());

        if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0) return true;
        else return false;
    }
    ~Sock() {}
};

TcpServer.hpp

#pragma once

#include "Sock.hpp"
#include 
#include 
#include 

namespace ns_tcpserver
{
    using func_t = std::function;// 包装器 - 包装的是一个函数

    class TcpServer;

    // 这个更像是一个结构体
    class ThreadData
    {
    public:
        ThreadData(int sock, TcpServer *server):sock_(sock), server_(server)
        {}
        ~ThreadData() {}
    public:
        int sock_;
        TcpServer *server_;
    };

    class TcpServer
    {
    private:
        static void *ThreadRoutine(void *args)
        {
            pthread_detach(pthread_self());
            ThreadData *td = static_cast(args);
            td->server_->Excute(td->sock_);
            close(td->sock_);
            return nullptr;
        }

    public:
        TcpServer(const uint16_t &port, const std::string &ip = "0.0.0.0")
        {
            // 初始化
            listensock_ = sock_.Socket();
            sock_.Bind(listensock_, port, ip);
            sock_.Listen(listensock_);
        }

        // 新增
        void BindService(func_t func) 
        { 
            func_.push_back(func);
        }

        // 执行
        void Excute(int sock)
        {
            for(auto &f : func_)
            {
                f(sock);
            }
        }

        void Start()
        {
            for (;;)
            {
                std::string clientip;
                uint16_t clientport;
                int sock = sock_.Accept(listensock_, &clientip, &clientport);
                if (sock == -1)
                    continue;
                logMessage(NORMAL, "create new link success, sock: %d", sock);
                // 创建进程
                pthread_t tid;
                ThreadData *td = new ThreadData(sock, this);
                pthread_create(&tid, nullptr, ThreadRoutine, td);
            }
        }
        
        ~TcpServer()
        {
            if (listensock_ >= 0)
                close(listensock_);// 就像关闭文件描述符一样
        }

    private:
        int listensock_;
        Sock sock_;
        std::vector func_;
    };
}

lesson10网络基础2_第2张图片

 lesson10网络基础2_第3张图片

 lesson10网络基础2_第4张图片

关于守护进程 

全都是在前台运行的 

lesson10网络基础2_第5张图片

  • 前台进程: 和终端关联的进程,前台进程
  • 任何xshell登录,只允许一个前台进程和多个后台进程
  • 进程除了有自己的pid,ppid,还有一个组ID
  • 在命令行中,同时有管道启动多个进程,多个进程是兄弟关系,父进程都是bash->可以用来匿名管道来通信
  • 而被创建的多个进程可一个成为一个进程组的概念,组长一般是第一个进程
  • 任何一次登陆,登陆的用户,需要有多个进程(组),来给这个用户提供服务的(bash),用户自己可以启动很多进程,或者进程组,
    • 我们把给用户提供服务的进程或者用户自己启动的所有的进程或者服务,整体都是要属于一个叫做会话的机制中的
  •  通过setsid()可以将自己变成自成会话
  • 通过fork()可以保证自己不是进程组的组长,
  • 守护进程不能直接向显示器打印消息,一旦打印,就会被暂停,终止

lesson10网络基础2_第6张图片 

 

你可能感兴趣的:(Linux,网络,c++,java)