linux篇【13】:网络应用层—网络版计算器,序列化

news2025/1/19 23:05:51

目录

一.应用层

1.再谈 "协议"

2.序列化,反序列化

(1)序列化,反序列化的实例:

(2)自描述长度的协议

3.网络版计算器

细节(1):报头方案

(2)netCal函数步骤

(3)client.hpp中如果是quit就是直接continue检测退出。

serverTcp.cc 中的 netCal函数

 易错点:

Protocol.hpp(半成品)

二.json 序列化和反序列化

1.安装json库

2.request的jason序列化:

 Json::FastWriter与Json::StyledWriter两种显示风格

2.request的jason反序列化:

3.makefile中定义变量-D

 4.代码

Protocol.hpp

clientTcp.cc

serverTcp.cc


一.应用层

1.再谈 "协议"

协议是一种 " 约定 ". socket api 的接口 , 在读写数据时, 都是按 "字符串" 的方式来发送接收的 . 如果我们要传输一些 "结构化的数据 " 怎么办呢 ?
 

2.序列化,反序列化

直接发送同样的结构体对象,是不可取的,虽然在某些情况下,它确实行,但是我们需要进行序列化和反序列化

定义:定义结构体来表示我们需要交互的信息 ; 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体; 这个过程叫做 " 序列化 " " 反序列化 "

序列化,反序列化的操作是在用户发送和网络发送中间加了一层软件层,

(1)序列化,反序列化的实例:

把客户端的结构体通过软件层变成一个字符串叫做序列化,发送给网络服务器前把字符串再转化回结构体叫做反序列化。约定这个字符串有一共三个区域,用特殊字符"\3"分割,三部分都是char类型。

(2)自描述长度的协议

这一个字符串每个部分的字符串整体的长度对方怎么知道?
——我们定制协议的时候,序列化之后,需要将长度(长度类型我们设置为4字节),将长度放入序列化之后的字符串的开始之前——自描述长度的协议! !
encode:涉及加密
decode:解密

3.网络版计算器

例如, 我们需要实现一个服务器版的加法器 . 我们需要客户端把要计算的两个加数发过去 , 然后由服务器进行计算 ,
后再把结果返回给客户端 .
约定方案一 :
客户端发送一个形如 "1+1" 的字符串 ;
这个字符串中有两个操作数 , 都是整形 ;
两个数字之间会有一个字符是运算符 , 运算符只能是 + ;
数字和运算符之间没有空格 ;
...
约定方案二 :
定义结构体来表示我们需要交互的信息 ;
发送数据时将这个结构体按照一个规则转换成字符串 , 接收到数据的时候再按照相同的规则把字符串转
化回结构体 ;
这个过程叫做 " 序列化 " " 反序列化 "

细节(1):报头方案

encode,整个序列化之后的字符串进行添加长度(\r\n是我们规定的特殊字符)
定长报头方案:strlen XXXXXXXXXX 前面长度是四字节,后面是字符串。这种方案也可以,但是都是二进制,打印不方便,可读性不好
"strlen\r\n"XXXXXXXXX\r\n --采用这种方案 

解释:那直接""XXXXXXXXX\r\n"读一行不行吗?——答:我们不知道字符串内是否包含\r\n,如果内部包含就会影响读取(比如XXXXXXXXX是123\r\n,总的是123\r\n\r\n,那我们会读到第一个\r\n结束,但实际上第一个\r\n是属于字符串内容的,就出错了),但是"strlen\r\n" 中的长度描述字符串一定不包含 \r\n ,读了字符串长度后,再从后面读这么长的字符串即可

(2)netCal函数步骤

string的rfind是从右往左找

netCal函数步骤:先创建好收集处理请求的对象Request req; 。从套接字中读数据。①检查是否已经具有了一个完整报文strPackage,不具有就continue,具有才继续执行。②读到的报文是字符串(序列化的),然后利用decode——对整个序列化之后的字符串进行提取长度。③deserialize(const std::string &in)反序列化 -- 字符串 -> 结构化的数据。④(3.)处理请求逻辑,Response resp = calculator(req);输入的是一个Request req,得到一个Response resp。⑤(4.)对resp进行序列化。⑥(5.)对报文进行encode。⑦简单进行发送

(3)client.hpp中如果是quit就是直接continue检测退出。

因为有了协议,如果是quit再往下继续处理数据会出错。

serverTcp.cc 中的 netCal函数

// 1. 全部手写
// 2. 部分采用别人的方案--序列化和反序列化的问题
void netCal(int sock, const std::string &clientIp, uint16_t clientPort)
{
    assert(sock >= 0);
    assert(!clientIp.empty());
    assert(clientPort >= 1024);    //因为一般1-1023端口属于系统保留端口,这些端口已经分配给一些应用了,所以我们只能使用1024及以上的端口

    // 9\r\n100 + 200\r\n    9\r\n112 / 200\r\n
    std::string inbuffer;
    while (true)
    {
        Request req;
        char buff[128];
        ssize_t s = read(sock, buff, sizeof(buff) - 1);
        if (s == 0)
        {
            logMessage(NOTICE, "client[%s:%d] close sock, service done", clientIp.c_str(), clientPort);
            break;
        }
        else if (s < 0)
        {
            logMessage(WARINING, "read client[%s:%d] error, errorcode: %d, errormessage: %s",
                       clientIp.c_str(), clientPort, errno, strerror(errno));
            break;
        }

        // read success
        buff[s] = 0;
        inbuffer += buff;
        // 1. 检查inbuffer是不是已经具有了一个strPackage
        uint32_t packageLen = 0;
        std::string package = decode(inbuffer, &packageLen); //TODO
        if (packageLen == 0) continue; // 无法提取一个完整的报文,继续努力读取吧
        // 2. 已经获得一个完整的package
        if (req.deserialize(package))
        {
            req.debug();
            // 3. 处理逻辑, 输入的是一个req,得到一个resp
            Response resp = calculator(req); //resp是一个结构化的数据
            // 4. 对resp进行序列化
            std::string respPackage;
            resp.serialize(&respPackage);
            // 5. 对报文进行encode -- //TODO
            respPackage = encode(respPackage, respPackage.size());
            // 6. 简单进行发送 -- 后续处理
            write(sock, respPackage.c_str(), respPackage.size());
        }
    }
}

 易错点:

(1)buff[s] = 0; inbuffer += buff; 遗漏

(2)decode中 in.find(CRLF) 查找失败错误遗漏

(3) decode中5. 将当前报文完整的从in中全部移除掉 遗漏

(4)这些全漏了!!!!

Protocol.hpp(半成品)

#pragma once

#include <iostream>
#include <string>
#include <cassert>
// 我们要在这里进行我们自己的协议定制!
// 网络版本的计算器

#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF) // 坑:sizeof(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)

// decode,整个序列化之后的字符串进行提取长度
// 1. 必须具有完整的长度
// 2. 必须具有和len相符合的有效载荷
// 我们才返回有效载荷和len
// 否则,我们就是一个检测函数!
// 9\r\n100 + 200\r\n    9\r\n112 / 200\r\n
std::string decode(std::string &in, uint32_t *len)
{
    assert(len);
    // 1. 确认是否是一个包含len的有效字符串
    *len = 0;
    std::size_t pos = in.find(CRLF);
    if (pos == std::string::npos)
        return ""; // 1234\r\nYYYYY for(int i = 3; i < 9 ;i++) [)
    // 2. 提取长度
    std::string inLen = in.substr(0, pos);
    int intLen = atoi(inLen.c_str());
    // 3. 确认有效载荷也是符合要求的
    int surplus = in.size() - 2 * CRLF_LEN - pos;
    if (surplus < intLen)
        return "";
    // 4. 确认有完整的报文结构
    std::string package = in.substr(pos + CRLF_LEN, intLen);
    *len = intLen;
    // 5. 将当前报文完整的从in中全部移除掉
    int removeLen = inLen.size() + package.size() + 2 * CRLF_LEN;
    in.erase(0, removeLen);
    // 6. 正常返回
    return package;
}

// encode, 整个序列化之后的字符串进行添加长度
std::string encode(const std::string &in, uint32_t len)
{
    // "exitCode_ result_"
    // "len\r\n""exitCode_ result_\r\n"
    std::string encodein = std::to_string(len);
    encodein += CRLF;
    encodein += in;
    encodein += CRLF;
    return encodein;
}

// 定制的请求 x_ op y_
class Request
{
public:
    Request()
    {
    }
    ~Request()
    {
    }
    // 序列化 -- 结构化的数据 -> 字符串
    void serialize(std::string *out)
    {
        
    }

    // 反序列化 -- 字符串 -> 结构化的数据
    bool deserialize(const std::string &in)
    {
        // 100 + 200
        std::size_t spaceOne = in.find(SPACE);
        if (std::string::npos == spaceOne)
            return false;
        std::size_t spaceTwo = in.rfind(SPACE);
        if (std::string::npos == spaceTwo)
            return false;

        std::string dataOne = in.substr(0, spaceOne);
        std::string dataTwo = in.substr(spaceTwo + SPACE_LEN);
        std::string oper = in.substr(spaceOne + SPACE_LEN, spaceTwo - (spaceOne + SPACE_LEN));
        if (oper.size() != 1)
            return false;

        // 转成内部成员
        x_ = atoi(dataOne.c_str());
        y_ = atoi(dataTwo.c_str());
        op_ = oper[0];
        return true;
    }

    void debug()
    {
        std::cout << "#################################" << std::endl;
        std::cout << "x_: " << x_ << std::endl;
        std::cout << "op_: " << op_ << std::endl;
        std::cout << "y_: " << y_ << std::endl;
        std::cout << "#################################" << std::endl;
    }

public:
    // 需要计算的数据
    int x_;
    int y_;
    // 需要进行的计算种类
    char op_; // + - * / %
};

// 定制的响应
class Response
{
public:
    Response() : exitCode_(0), result_(0)
    {
    }
    ~Response()
    {
    }
    // 序列化
    void serialize(std::string *out)
    {
        // "exitCode_ result_"
        std::string ec = std::to_string(exitCode_);
        std::string res = std::to_string(result_);

        *out = ec;
        *out += SPACE;
        *out += res;
    }
    // 反序列化
    void deserialize(std::string &in)
    {
    }
public:
    // 退出状态,0标识运算结果合法,非0标识运行结果是非法的,!0是几就表示是什么原因错了!
    int exitCode_;
    // 运算结果
    int result_;
};

二.json 序列化和反序列化

1.安装json库

sudo yum install -y jsoncpp-devel

查看已安装的jsoncpp:

2.request的jason序列化:

// 序列化 -- 结构化的数据 -> 字符串
    // 认为结构化字段中的内容已经被填充
    void serialize(std::string *out)
    {
#ifdef MY_SELF
        std::string xstr = std::to_string(x_);
        std::string ystr = std::to_string(y_);
        // std::string opstr = std::to_string(op_); // op_ -> char -> int -> 43 ->

        *out = xstr;
        *out += SPACE;
        *out += op_;
        *out += SPACE;
        *out += ystr;
#else
        //json
        // 1. Value对象,万能对象
        // 2. json是基于KV
        // 3. json有两套操作方法
        // 4. 序列化的时候,会将所有的数据内容,转换成为字符串
        Json::Value root;
        root["x"] = x_;
        root["y"] = y_;
        root["op"] = op_;

        Json::FastWriter fw;
        // Json::StyledWriter fw;
        *out = fw.write(root);
#endif
    }

 Json::FastWriter与Json::StyledWriter两种显示风格

通常 Json::FastWriter 传输数据量较少,使用Json::StyledWriter较多

 

2.request的jason反序列化:

 // 反序列化 -- 字符串 -> 结构化的数据
    bool deserialize(std::string &in)
    {
#ifdef MY_SELF
        // 100 + 200
        std::size_t spaceOne = in.find(SPACE);
        if (std::string::npos == spaceOne)
            return false;
        std::size_t spaceTwo = in.rfind(SPACE);
        if (std::string::npos == spaceTwo)
            return false;

        std::string dataOne = in.substr(0, spaceOne);
        std::string dataTwo = in.substr(spaceTwo + SPACE_LEN);
        std::string oper = in.substr(spaceOne + SPACE_LEN, spaceTwo - (spaceOne + SPACE_LEN));
        if (oper.size() != 1)
            return false;

        // 转成内部成员
        x_ = atoi(dataOne.c_str());
        y_ = atoi(dataTwo.c_str());
        op_ = oper[0];
        return true;
#else
        //json
        Json::Value root;
        Json::Reader rd;
        rd.parse(in, root);    把in反序列化进root中,这里的root是输出型参数
        x_ = root["x"].asInt();    asInt:把root["x"]对应的KV的V值转为整数存入x_
        y_ = root["y"].asInt();
        op_ = root["op"].asInt();    没有aschar这一说,就是存入ASCII值,因为
        return true;                 op_是char类型,所以读取时会以char类型读取
#endif
    }

3.makefile中定义变量-D

-D:命令行定义宏。目的:这样就不用把宏定义在源代码中(不用动源代码了),某种宏的定义会决定条件编译对相应代码进行裁剪。 

在makefile中定义变量Method=-DMY_SELF,编译就会加上这个变量(类似于Protocol.hpp
中定义的 #define MY_SELF 1),代码中的 #ifdef MY_SELF 条件编译会起作用,此时就会执行我们自己的序列化代码;

 

 如果利用#进行注释:Method=#-DMY_SELF —> 此时Method是无内容的,此时代码中的 #ifdef MY_SELF 条件编译不起作用而会执行#else,此时就会执行jason的序列化代码;

 

 4.代码

完整版:

 lesson43/calServer · whb-helloworld/104期 - 码云 - 开源中国 (gitee.com)

Protocol.hpp

#pragma once

#include <iostream>
#include <string>
#include <cassert>
#include <jsoncpp/json/json.h>
#include "util.hpp"

// 我们要在这里进行我们自己的协议定制!
// 网络版本的计算器

#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF) // 坑:sizeof(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)

#define OPS "+-*/%"

// #define MY_SELF 1

// decode,整个序列化之后的字符串进行提取长度
// 1. 必须具有完整的长度
// 2. 必须具有和len相符合的有效载荷
// 我们才返回有效载荷和len
// 否则,我们就是一个检测函数!
// 9\r\n100 + 200\r\n    9\r\n112 / 200\r\n
std::string decode(std::string &in, uint32_t *len)
{
    assert(len);
    // 1. 确认是否是一个包含len的有效字符串
    *len = 0;
    std::size_t pos = in.find(CRLF);
    if (pos == std::string::npos)
        return ""; // 1234\r\nYYYYY for(int i = 3; i < 9 ;i++) [)
    // 2. 提取长度
    std::string inLen = in.substr(0, pos);
    int intLen = atoi(inLen.c_str());
    // 3. 确认有效载荷也是符合要求的
    int surplus = in.size() - 2 * CRLF_LEN - pos;
    if (surplus < intLen)
        return "";
    // 4. 确认有完整的报文结构
    std::string package = in.substr(pos + CRLF_LEN, intLen);
    *len = intLen;
    // 5. 将当前报文完整的从in中全部移除掉
    int removeLen = inLen.size() + package.size() + 2 * CRLF_LEN;
    in.erase(0, removeLen);
    // 6. 正常返回
    return package;
}

// encode, 整个序列化之后的字符串进行添加长度
std::string encode(const std::string &in, uint32_t len)
{
    // "exitCode_ result_"
    // "len\r\n""exitCode_ result_\r\n"
    std::string encodein = std::to_string(len);
    encodein += CRLF;
    encodein += in;
    encodein += CRLF;
    return encodein;
}

// 定制的请求 x_ op y_
class Request
{
public:
    Request()
    {
    }
    ~Request()
    {
    }
    // 序列化 -- 结构化的数据 -> 字符串
    // 认为结构化字段中的内容已经被填充
    void serialize(std::string *out)
    {
#ifdef MY_SELF
        std::string xstr = std::to_string(x_);
        std::string ystr = std::to_string(y_);
        // std::string opstr = std::to_string(op_); // op_ -> char -> int -> 43 ->

        *out = xstr;
        *out += SPACE;
        *out += op_;
        *out += SPACE;
        *out += ystr;
#else
        //json
        // 1. Value对象,万能对象
        // 2. json是基于KV
        // 3. json有两套操作方法
        // 4. 序列化的时候,会将所有的数据内容,转换成为字符串
        Json::Value root;
        root["x"] = x_;
        root["y"] = y_;
        root["op"] = op_;

        Json::FastWriter fw;
        // Json::StyledWriter fw;
        *out = fw.write(root);
#endif
    }

    // 反序列化 -- 字符串 -> 结构化的数据
    bool deserialize(std::string &in)
    {
#ifdef MY_SELF
        // 100 + 200
        std::size_t spaceOne = in.find(SPACE);
        if (std::string::npos == spaceOne)
            return false;
        std::size_t spaceTwo = in.rfind(SPACE);
        if (std::string::npos == spaceTwo)
            return false;

        std::string dataOne = in.substr(0, spaceOne);
        std::string dataTwo = in.substr(spaceTwo + SPACE_LEN);
        std::string oper = in.substr(spaceOne + SPACE_LEN, spaceTwo - (spaceOne + SPACE_LEN));
        if (oper.size() != 1)
            return false;

        // 转成内部成员
        x_ = atoi(dataOne.c_str());
        y_ = atoi(dataTwo.c_str());
        op_ = oper[0];
        return true;
#else
        //json
        Json::Value root;
        Json::Reader rd;
        rd.parse(in, root);
        x_ = root["x"].asInt();
        y_ = root["y"].asInt();
        op_ = root["op"].asInt();
        return true;
#endif
    }

    void debug()
    {
        std::cout << "#################################" << std::endl;
        std::cout << "x_: " << x_ << std::endl;
        std::cout << "op_: " << op_ << std::endl;
        std::cout << "y_: " << y_ << std::endl;
        std::cout << "#################################" << std::endl;
    }

public:
    // 需要计算的数据
    int x_;
    int y_;
    // 需要进行的计算种类
    char op_; // + - * / %
};

// 定制的响应
class Response
{
public:
    Response() : exitCode_(0), result_(0)
    {
    }
    ~Response()
    {
    }
    // 序列化 -- 不仅仅是在网络中应用,本地也是可以直接使用的!
    void serialize(std::string *out)
    {
#ifdef MY_SELF
        // "exitCode_ result_"
        std::string ec = std::to_string(exitCode_);
        std::string res = std::to_string(result_);

        *out = ec;
        *out += SPACE;
        *out += res;
#else
        //json
        Json::Value root;
        root["exitcode"] = exitCode_;
        root["result"] = result_;
        Json::FastWriter fw;
        // Json::StyledWriter fw;
        *out = fw.write(root);
#endif
    }
    // 反序列化
    bool deserialize(std::string &in)
    {
#ifdef MY_SELF
        // "0 100"
        std::size_t pos = in.find(SPACE);
        if (std::string::npos == pos)
            return false;
        std::string codestr = in.substr(0, pos);
        std::string reststr = in.substr(pos + SPACE_LEN);

        // 将反序列化的结果写入到内部成员中,形成结构化数据
        exitCode_ = atoi(codestr.c_str());
        result_ = atoi(reststr.c_str());
        return true;
#else
        //json
        Json::Value root;
        Json::Reader rd;
        rd.parse(in, root);
        exitCode_ = root["exitcode"].asInt();
        result_ = root["result"].asInt();
        return true;
#endif
    }
    void debug()
    {
        std::cout << "#################################" << std::endl;
        std::cout << "exitCode_: " << exitCode_ << std::endl;
        std::cout << "result_: " << result_ << std::endl;
        std::cout << "#################################" << std::endl;
    }

public:
    // 退出状态,0标识运算结果合法,非0标识运行结果是非法的,!0是几就表示是什么原因错了!
    int exitCode_;
    // 运算结果
    int result_;
};

bool makeReuquest(const std::string &str, Request *req)
{
    // 123+1  1*1 1/1
    char strtmp[BUFFER_SIZE];
    snprintf(strtmp, sizeof strtmp, "%s", str.c_str());
    char *left = strtok(strtmp, OPS);
    if (!left)
        return false;
    char *right = strtok(nullptr, OPS);
    if (!right)
        return false;
    char mid = str[strlen(left)];

    req->x_ = atoi(left);
    req->y_ = atoi(right);
    req->op_ = mid;
    return true;
}

clientTcp.cc

#include "util.hpp"
#include "Protocol.hpp"
#include <cstdio>
// 2. 需要bind吗??需要,但是不需要自己显示的bind! 不要自己bind!!!!
// 3. 需要listen吗?不需要的!
// 4. 需要accept吗?不需要的!

volatile bool quit = false;

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;
    std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8081\n"
              << std::endl;
}
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);

    // 1. 创建socket SOCK_STREAM
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        std::cerr << "socket: " << strerror(errno) << std::endl;
        exit(SOCKET_ERR);
    }

    // 2. connect,发起链接请求,你想谁发起请求呢??当然是向服务器发起请求喽
    // 2.1 先填充需要连接的远端主机的基本信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverPort);
    inet_aton(serverIp.c_str(), &server.sin_addr);
    // 2.2 发起请求,connect 会自动帮我们进行bind!
    if (connect(sock, (const struct sockaddr *)&server, sizeof(server)) != 0)
    {
        std::cerr << "connect: " << strerror(errno) << std::endl;
        exit(CONN_ERR);
    }
    std::cout << "info : connect success: " << sock << std::endl;

    std::string message;
    while (!quit)
    {
        message.clear();
        std::cout << "请输入表达式>>> "; // 1 + 1 
        std::getline(std::cin, message); // 结尾不会有\n
        if (strcasecmp(message.c_str(), "quit") == 0){
            quit = true;
            continue;
        }
        // message = trimStr(message); // 1+1 1 +1 1+ 1 1+     1 1      +1 => 1+1 -- 不处理
        Request req;
        if(!makeReuquest(message, &req)) continue;
        // req.debug();
        std::string package;
        req.serialize(&package); // done
        std::cout << "debug->serialize-> " << package << std::endl;

        package = encode(package, package.size()); // done
        std::cout << "debug->encode-> \n" << package << std::endl;

        ssize_t s = write(sock, package.c_str(), package.size());
        if (s > 0)
        {
            char buff[1024];
            size_t s = read(sock, buff, sizeof(buff)-1);
            if(s > 0) buff[s] = 0;
            std::string echoPackage = buff;
            Response resp;
            uint32_t len = 0;

            // std::cout << "debug->get response->\n" << echoPackage << std::endl;

            std::string tmp = decode(echoPackage, &len); // done
            if(len > 0)
            {
                echoPackage = tmp;
                // std::cout << "debug->decode-> " << echoPackage << std::endl;

                resp.deserialize(echoPackage);
                printf("[exitcode: %d] %d\n", resp.exitCode_, resp.result_);
            }
        }
        else if (s <= 0)
        {
            break;
        }
    }
    close(sock);
    return 0;
}

serverTcp.cc

#include "Protocol.hpp"
#include "util.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"
#include "daemonize.hpp"

#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <cerrno>

class ServerTcp; // 申明一下ServerTcp

// 大小写转化服务
// TCP && UDP: 支持全双工
void transService(int sock, const std::string &clientIp, uint16_t clientPort)
{
    assert(sock >= 0);
    assert(!clientIp.empty());
    assert(clientPort >= 1024);

    char inbuffer[BUFFER_SIZE];
    while (true)
    {
        ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1); // 我们认为我们读到的都是字符串
        if (s > 0)
        {
            // read success
            inbuffer[s] = '\0';
            if (strcasecmp(inbuffer, "quit") == 0)
            {
                logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
                break;
            }
            logMessage(DEBUG, "trans before: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);
            // 可以进行大小写转化了
            for (int i = 0; i < s; i++)
            {
                if (isalpha(inbuffer[i]) && islower(inbuffer[i]))
                    inbuffer[i] = toupper(inbuffer[i]);
            }
            logMessage(DEBUG, "trans after: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);

            write(sock, inbuffer, strlen(inbuffer));
        }
        else if (s == 0)
        {
            // pipe: 读端一直在读,写端不写了,并且关闭了写端,读端会如何?s == 0,代表对端关闭
            // s == 0: 代表对方关闭,client 退出
            logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
            break;
        }
        else
        {
            logMessage(DEBUG, "%s[%d] - read: %s", clientIp.c_str(), clientPort, strerror(errno));
            break;
        }
    }

    // 只要走到这里,一定是client退出了,服务到此结束
    close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!
    logMessage(DEBUG, "server close %d done", sock);
}

void execCommand(int sock, const std::string &clientIp, uint16_t clientPort)
{
    assert(sock >= 0);
    assert(!clientIp.empty());
    assert(clientPort >= 1024);

    char command[BUFFER_SIZE];
    while (true)
    {
        ssize_t s = read(sock, command, sizeof(command) - 1); // 我们认为我们读到的都是字符串
        if (s > 0)
        {
            command[s] = '\0';
            logMessage(DEBUG, "[%s:%d] exec [%s]", clientIp.c_str(), clientPort, command);
            // 考虑安全
            std::string safe = command;
            if ((std::string::npos != safe.find("rm")) || (std::string::npos != safe.find("unlink")))
            {
                break;
            }
            // 我们是以r方式打开的文件,没有写入
            // 所以我们无法通过dup的方式得到对应的结果
            FILE *fp = popen(command, "r");
            if (fp == nullptr)
            {
                logMessage(WARINING, "exec %s failed, beacuse: %s", command, strerror(errno));
                break;
            }
            char line[1024];
            while (fgets(line, sizeof(line) - 1, fp) != nullptr)
            {
                write(sock, line, strlen(line));
            }
            // dup2(fd, 1);
            // dup2(sock, fp->_fileno);
            // fflush(fp);
            pclose(fp);
            logMessage(DEBUG, "[%s:%d] exec [%s] ... done", clientIp.c_str(), clientPort, command);
        }
        else if (s == 0)
        {
            // pipe: 读端一直在读,写端不写了,并且关闭了写端,读端会如何?s == 0,代表对端关闭
            // s == 0: 代表对方关闭,client 退出
            logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
            break;
        }
        else
        {
            logMessage(DEBUG, "%s[%d] - read: %s", clientIp.c_str(), clientPort, strerror(errno));
            break;
        }
    }

    // 只要走到这里,一定是client退出了,服务到此结束
    close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!
    logMessage(DEBUG, "server close %d done", sock);
}

static Response calculator(const Request &req)
{
    Response resp;
    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 '/':
        { // x_ / y_
            if (req.y_ == 0) resp.exitCode_ = -1; // -1. 除0
            else resp.result_ = req.x_ / req.y_;
        }
    break;
    case '%':
        { // x_ / y_
            if (req.y_ == 0) resp.exitCode_ = -2; // -2. 模0
            else resp.result_ = req.x_ % req.y_;
        }
    break;
    default:
        resp.exitCode_ = -3; // -3: 非法操作符
        break;
    }

    return resp;
} 

// 1. 全部手写 -- done
// 2. 部分采用别人的方案--序列化和反序列化的问题 -- xml,json,protobuf
void netCal(int sock, const std::string &clientIp, uint16_t clientPort)
{
    assert(sock >= 0);
    assert(!clientIp.empty());
    assert(clientPort >= 1024);

    // 9\r\n100 + 200\r\n    9\r\n112 / 200\r\n
    std::string inbuffer;
    while (true)
    {
        Request req;
        char buff[128];
        ssize_t s = read(sock, buff, sizeof(buff) - 1);
        if (s == 0)
        {
            logMessage(NOTICE, "client[%s:%d] close sock, service done", clientIp.c_str(), clientPort);
            break;
        }
        else if (s < 0)
        {
            logMessage(WARINING, "read client[%s:%d] error, errorcode: %d, errormessage: %s",
                       clientIp.c_str(), clientPort, errno, strerror(errno));
            break;
        }

        // read success
        buff[s] = 0;
        inbuffer += buff;
        std::cout << "inbuffer: " << inbuffer << std::endl;
        // 1. 检查inbuffer是不是已经具有了一个strPackage
        uint32_t packageLen = 0;
        std::string package = decode(inbuffer, &packageLen); 
        if (packageLen == 0) continue; // 无法提取一个完整的报文,继续努力读取吧
        std::cout << "package: " << package << std::endl;
        // 2. 已经获得一个完整的package
        if (req.deserialize(package))
        {
            req.debug();
            // 3. 处理逻辑, 输入的是一个req,得到一个resp
            Response resp = calculator(req); //resp是一个结构化的数据
            // 4. 对resp进行序列化
            std::string respPackage;
            resp.serialize(&respPackage);
            // 5. 对报文进行encode -- 
            respPackage = encode(respPackage, respPackage.size());
            // 6. 简单进行发送 -- 后续处理
            write(sock, respPackage.c_str(), respPackage.size());
        }
    }
}

class ThreadData
{
public:
    uint16_t clientPort_;
    std::string clinetIp_;
    int sock_;
    ServerTcp *this_;

public:
    ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts)
        : clientPort_(port), clinetIp_(ip), sock_(sock), this_(ts)
    {
    }
};

class ServerTcp
{
public:
    ServerTcp(uint16_t port, const std::string &ip = "")
        : port_(port),
          ip_(ip),
          listenSock_(-1),
          tp_(nullptr)
    {
        quit_ = false;
    }
    ~ServerTcp()
    {
        if (listenSock_ >= 0)
            close(listenSock_);
    }

public:
    void init()
    {
        // 1. 创建socket
        listenSock_ = socket(PF_INET, SOCK_STREAM, 0);
        if (listenSock_ < 0)
        {
            logMessage(FATAL, "socket: %s", strerror(errno));
            exit(SOCKET_ERR);
        }
        logMessage(DEBUG, "socket: %s, %d", strerror(errno), listenSock_);

        // 2. bind绑定
        // 2.1 填充服务器信息
        struct sockaddr_in local; // 用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = PF_INET;
        local.sin_port = htons(port_);
        ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));
        // 2.2 本地socket信息,写入sock_对应的内核区域
        if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind: %s", strerror(errno));
            exit(BIND_ERR);
        }
        logMessage(DEBUG, "bind: %s, %d", strerror(errno), listenSock_);

        // 3. 监听socket,为何要监听呢?tcp是面向连接的!
        if (listen(listenSock_, 5 /*后面再说*/) < 0)
        {
            logMessage(FATAL, "listen: %s", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMessage(DEBUG, "listen: %s, %d", strerror(errno), listenSock_);
        // 运行别人来连接你了

        // 4. 加载线程池
        tp_ = ThreadPool<Task>::getInstance();
    }
    // static void *threadRoutine(void *args)
    // {
    //     pthread_detach(pthread_self()); //设置线程分离
    //     ThreadData *td = static_cast<ThreadData *>(args);
    //     td->this_->transService(td->sock_, td->clinetIp_, td->clientPort_);
    //     delete td;
    //     return nullptr;
    // }
    void loop()
    {
        // signal(SIGCHLD, SIG_IGN); // only Linux
        tp_->start();
        logMessage(DEBUG, "thread pool start success, thread num: %d", tp_->threadNum());
        while (!quit_)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4. 获取连接, accept 的返回值是一个新的socket fd ??
            // 4.1 listenSock_: 监听 && 获取新的链接-> sock
            // 4.2 serviceSock: 给用户提供新的socket服务
            int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
            if (quit_)
                break;
            if (serviceSock < 0)
            {
                // 获取链接失败
                logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
                continue;
            }
            // 4.1 获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            std::string peerIp = inet_ntoa(peer.sin_addr);

            logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);
            // 5 提供服务, echo -> 小写 -> 大写
            // 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
            // transService(serviceSock, peerIp, peerPort);

            // 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗?会的
            // pid_t id = fork();
            // assert(id != -1);
            // if(id == 0)
            // {
            //     close(listenSock_); //建议
            //     //子进程
            //     transService(serviceSock, peerIp, peerPort);
            //     exit(0); // 进入僵尸
            // }
            // // 父进程
            // close(serviceSock); //这一步是一定要做的!

            // 5.1 v1.1 版本 -- 多进程版本  -- 也是可以的
            // 爷爷进程
            // pid_t id = fork();
            // if(id == 0)
            // {
            //     // 爸爸进程
            //     close(listenSock_);//建议
            //     // 又进行了一次fork,让 爸爸进程
            //     if(fork() > 0) exit(0);
            //     // 孙子进程 -- 就没有爸爸 -- 孤儿进程 -- 被系统领养 -- 回收问题就交给了系统来回收
            //     transService(serviceSock, peerIp, peerPort);
            //     exit(0);
            // }
            // // 父进程
            // close(serviceSock); //这一步是一定要做的!
            // // 爸爸进程直接终止,立马得到退出码,释放僵尸进程状态
            // pid_t ret = waitpid(id, nullptr, 0); //就用阻塞式
            // assert(ret > 0);
            // (void)ret;

            // 5.2 v2 版本 -- 多线程
            // 这里不需要进行关闭文件描述符吗??不需要啦
            // 多线程是会共享文件描述符表的!
            // ThreadData *td = new ThreadData(peerPort, peerIp, serviceSock, this);
            // pthread_t tid;
            // pthread_create(&tid, nullptr, threadRoutine, (void*)td);

            // 5.3 v3 版本 --- 线程池版本
            // 5.3.1 构建任务
            // 5.3 v3.1
            // Task t(serviceSock, peerIp, peerPort, std::bind(&ServerTcp::transService, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
            // tp_->push(t);

            // 5.3 v3.2
            // Task t(serviceSock, peerIp, peerPort, transService);
            // tp_->push(t);
            // 5.3 v3.3
            // Task t(serviceSock, peerIp, peerPort, execCommand);
            // tp_->push(t);

            // 5.4 v3.3
            Task t(serviceSock, peerIp, peerPort, netCal);
            tp_->push(t);

            // waitpid(); 默认是阻塞等待!WNOHANG
            // 方案1

            // logMessage(DEBUG, "server 提供 service start ...");
            // sleep(1);
        }
    }

    bool quitServer()
    {
        quit_ = true;
        return true;
    }

private:
    // sock
    int listenSock_;
    // port
    uint16_t port_;
    // ip
    std::string ip_;
    // 引入线程池
    ThreadPool<Task> *tp_;
    // 安全退出
    bool quit_;
};

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " port ip" << std::endl;
    std::cerr << "example:\n\t" << proc << " 8080 127.0.0.1\n"
              << std::endl;
}

ServerTcp *svrp = nullptr;

void sigHandler(int signo)
{
    if (signo == 3 && svrp != nullptr)
        svrp->quitServer();
    logMessage(DEBUG, "server quit save!");
}

// ./ServerTcp local_port local_ip
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
        ip = argv[2];

    // daemonize(); // 我们的进程就会成为守护进程
    signal(3, sigHandler);
    // Log log;
    // log.enable();
    ServerTcp svr(port, ip);
    svr.init();
    svrp = &svr;
    svr.loop();
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/155249.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2.13Epd 中景园2.13寸电子墨水屏 显示原理(局部刷新指令)

显示有两种模式&#xff1a; 1、MCU RAM 操作之后一起写入显示屏的RAM 在MCU上分配一个成员大小为8位的数组 长度是像素点数的1/8(一位控制一个像素点) 如图&#xff1a;数组是Image_BW 之后是用 显示的操作算法将每个点由对应的数字或字母 汉字的字模写入数组中 最后将数组一起…

如何尽早发现潜在开发风险,降低项目风险?

1、提前规划风险预防和控制策略 针对开发流程中各环节可能存在的风险&#xff0c;项目经理需进行科学的项目风险分析&#xff0c;提前进行科学地规划和部署&#xff0c;制定符合项目特点的风险评估和监督机制&#xff0c;实行岗位负责制&#xff0c;提前制定控制策略&#xff0…

mac配置MySQL环境

对于开发人员来说&#xff0c;只需要安装MySQL Server 和 MySQL Workbench这两个软件&#xff0c;这样就可以满足开发需求 MySQL Server&#xff1a;专门用来提供数据存储和服务的软件 MySQL Workbench:&#xff1a;可视化的MySQL管理工具&#xff0c;通过他&#xff0c;可以方…

ArcGIS中栅格计算器常用函数的使用

先看下目录 一、 ArcGlS中栅格计算器简介 栅格计算器位置及界面布局 二、 ArcGIS棚格计算器中Con&#xff08;&#xff09;函数的使用 &#xff08;一&#xff09; Con&#xff08;&#xff09;函数的用法 &#xff08;二&#xff09; ArcGIS 案例操作 三、 ArcGIS栅格计算…

Linux 上面安装 RocektMQ 安装

下载 RocketMQ 我下载的版本是&#xff1a;4.9.3 下载地址 Apache Downloads 环境说明 Linux64 位系统JDK1.8 (64位) 安装步骤 第一步&#xff1a;传入文件到 Linux 服务器 先将下载好的包传到 linux 服务器上面去 这里我传入的路径是在 /usr/local 第二步&#xff1a…

【Pan-sharpening:主要是调节参数】

Pan-sharpening based on multi-objective decision for multi-band remote sensing images &#xff08;Pan-sharpening基于多目标决策的多波段遥感图像&#xff09; Pan-sharpening细节注射适用于融合多光谱图像(MS)与其相应的全色(PAN)图像产生合成图像。理论上&#xff0c…

怎么图片压缩?这几个压缩小技巧值得一试

我们在朋友圈或者其它的社交平台上&#xff0c;发布一些照片或者图片的时候&#xff0c;经常会遇到由于图片过大而无法发送的情况&#xff0c;这时候&#xff0c;我们只需要将图片进行压缩就可以解决这个问题了&#xff0c;那么你们知道图片如何压缩大小吗&#xff1f;下面我整…

ANSYS apdl软件学习指令(建立三维模型)

目的&#xff1a;记录常用的三维建模指令 软件版本&#xff1a;ANSYS2022 以角点坐标建立长方体指令 BLC4, XCORNER, YCORNER, WIDTH, HEIGHT, DEPTHblc4指令是建立长方体最常用的指令之一&#xff0c;其中XCORNER, YCORNER是指当前坐标系中需要绘制长方体的起点&#xff0c;…

ThinkPHP6 配置使用Redis

安装Redis 如果未安装redis&#xff0c;请先安装redis&#xff0c;文档可参考&#xff1a; - windows安装redis - linux安装redis 安装predis 项目中需要使用predis来连接和操作redis&#xff0c;我们用composer来安装和加载它。 项目目录命令行执行&#xff1a; compose…

北大硕士LeetCode算法专题课--链表相关问题

算法面试相关专题&#xff1a; 北大硕士LeetCode算法专题课-字符串相关问题_ 北大硕士LeetCode算法专题课-数组相关问题_ 北大硕士LeetCode算法专题课-基础算法查找_ 北大硕士LeetCode算法专题课-基础算法之排序_ 北大硕士LeetCode算法专题课---算法复杂度介绍_ 北大硕士…

荧光定量PCR检测法的原理和应用领域

一、荧光定量PCR原理 在PCR扩增反应体系中加入荧光基团就是荧光定量PCR&#xff08;Real-time PCR&#xff09;&#xff0c;通过对扩增反应中每一个循环产物荧光信号的实时检测,最后通过标准曲线对未知模板进行定量分析的方法。 以探针法荧光定量PCR为例&#xff1a;PCR扩增时…

STM32G0开发笔记-Platformio+libopencm3-EEPROM M24C02使用

使用Platformio平台的libopencm3开发框架来开发STM32G0&#xff0c;以下为EEPROM M24C02的使用方法。 1 新建项目 建立eeprom项目 在PIO的Home页面新建项目&#xff0c;项目名称eeprom&#xff0c;选择开发板为 MonkeyPi_STM32_G070RB&#xff0c;开发框架选择libopencm3&am…

loss盘点: GIoU loss (拆 PaddleDetection 轮子)

1. GIoU 计算 关于 GIoU 的实现&#xff0c;可直接参看原文给出的网站&#xff1a; https://giou.stanford.edu/ GIoU∣A∩B∣∣A∪B∣−∣C∖(A∪B)∣∣C∣IoU−∣C∖(A∪B)∣∣C∣GIoU \frac { |A \ \cap \ B | } { |A \ \cup \ B | } - \frac { | C \setminus (A \ \cup \…

Spring MVC 总结

Spring MVC Spring MVC 是 javaWeb 的开发模块&#xff0c;是一个架构模式&#xff0c;是整个 javaWeb 开发领域 最重要的一个技术。 Spring MVC 文档 Spring MVC 是 Spring 体系的 轻量级Web MVC框架。Spring MVC 的核心 Controller 控制器&#xff0c;用于处理请求&#xff…

C语言入门(五)——深入理解函数

return 语句 增量式开发 递归 return 语句 之前我们一直在main函数中使用return语句&#xff0c;现在是时候全面深入地学习一下。在有返回值地函数中&#xff0c;return语句的作用是提供整个函数的返回值&#xff0c;并结束当前函数返回到调用它的地方。在没有返回值的函数中…

vue修改el-input样式,样式穿透

vue–element的form表单中修改el-input样式 在使用 element ui 组件过程中&#xff0c;我最近碰到了新的问题。 初始的界面效果是这样的&#xff1a; 这个效果真的不怎么好看&#xff0c;我想把输入框的背景色变浅&#xff0c;变得透明。于是我在代码中添加 .el-input{backgro…

Polygon生态

1. 引言 当前区块链存在不可能三角问题&#xff1a; 1&#xff09;去中心化2&#xff09;可扩展性3&#xff09;安全性 当前单一链仅能兼顾不可能三角中的2个维度&#xff0c;而无法做到三者兼顾。 区块链中的主要角色有&#xff1a; 1&#xff09;共识层——负责对包含在…

python基础篇之数字类型(上)

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a;lqj_本人的博客_CSDN博客-微信小程序,前端,vue领域博主lqj_本人擅长微信小程序,前端,vue,等方面的知识https://blog.csdn.net/lbcyllqj?spm1000.2115.3001.5343 哔哩哔哩欢迎关注&…

【NI Multisim 14.0原理图设计基础——元器件属性设置】

目录 序言 一、属性编辑 二、元器件属性设置 &#x1f349; 1.“标签”选项卡 &#x1f349;2.“显示”选项卡 &#x1f349; 3.“值”选项卡 &#x1f349; 4.“故障”选项卡 &#x1f349;5.“管脚”选项卡 &#x1f349; 6.“变体”选项卡 &#x1f349; 7.“用户字…

正则表达式查漏补缺(包括es9的新方法)

一、创建正则 1、方法创建正则 // let regnew RegExp("a","ig")//检查字符串中是否包含a // let str"a" // console.log(reg.test("As"))i是忽略大小写 g是全局匹配&#xff0c;不会只返回一个 2、字面量创建正则 // let reg/a|c/…