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

目录

一.应用层

1.再谈 "协议"

2.序列化,反序列化

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

(2)自描述长度的协议

3.网络版计算器

细节(1):报头方案

(2)netCal函数步骤

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

serverTcp.cc 中的 netCal函数

 易错点:

Protocol.hpp(半成品)

二.json 序列化和反序列化

1.安装json库

json的头文件:#include

2.request中的json

(1)request的jason序列化:

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

(2)request的jason反序列化:

3.response中的json

4.makefile中json的操作

(1)makefile中定义变量-D

(2) -ljsoncpp 包json对应的第三方库

5.代码

(1)snprintf:string—>char类型数组

(2)流程概述

Protocol.hpp

clientTcp.cc

serverTcp.cc

Lock.hpp

Makefile

Task.hpp

ThreadPool.hpp

daemonize.hpp

log.hpp

util.hpp


一.应用层

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:

lib64中放的是json对应的静态库

json的头文件:#include <jsoncpp/json/json.h>

2.request中的json

(1)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);      将结构体写成字符串存入out中
#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.response中的json

// 定制的响应
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
    }
  

4.makefile中json的操作

(1)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的序列化代码;

 

(2) -ljsoncpp 包json对应的第三方库

-ljsoncpp :包第三方库,去掉 libjsoncpp.so 前缀和后缀

 

5.代码

(1)snprintf:string—>char类型数组

int snprintf ( char * s, size_t n, const char * format, ... );

以C字符串的形式存储在s指向的缓冲区中。如果产生的字符串长于n-1个字符,剩余的字符将被丢弃而不被存储,但会被计入函数返回的值。在写入的内容之后,会自动附加一个终止的空字符 \0。

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());

把str内容写入strtmp数组中。

(2)流程概述

客户端:①客户输入1+1存入字符串。②字符串"1+1"通过makeReuquest函数—>req结构体。③req结构体 通过serialize—>字符串"1 + 1"。④字符串"1 + 1"encode加码后成为“5\r\n1 + 1\r\n”写字符串入套接字。(①②③是为了把不规范的字符串转成可以加码的规范字符串)

服务器:①服务端读取 “5\r\n1 + 1\r\n” 。②字符串 “5\r\n1 + 1\r\n” decode解码得到字符串"1 + 1"。③字符串"1 + 1" deserialize反序列化 存入req结构体。④Response resp = calculator(req); 计算rep后生成结果resp:exitCode_=0,result_=2。⑤resp serialize序列化后encode加码,返回给客户端。

完整版:

 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);      将结构体写成字符串存入out中
#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);    把in反序列化进root中,这里的root是输出型参数(引用传参了)
        x_ = root["x"].asInt();    asInt:把root["x"]对应的KV的V值转为整数存入x_
        y_ = root["y"].asInt(); (上面已经设置了json的K值,这里要对应提取)
        op_ = root["op"].asInt();    没有aschar这一说,就是存入ASCII值,因为
        return true;                 op_是char类型,所以读取时会以char类型读取
#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;
}

Json:结果 

其他文件:

Lock.hpp

#pragma once

#include <iostream>
#include <pthread.h>

class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&lock_, nullptr);
    }
    void lock()
    {
        pthread_mutex_lock(&lock_);
    }
    void unlock()
    {
        pthread_mutex_unlock(&lock_);
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&lock_);
    }

private:
    pthread_mutex_t lock_;
};

class LockGuard
{
public:
    LockGuard(Mutex *mutex) : mutex_(mutex)
    {
        mutex_->lock();
        std::cout << "加锁成功..." << std::endl;
    }

    ~LockGuard()
    {
        mutex_->unlock();
        std::cout << "解锁成功...." << std::endl;
    }

private:
    Mutex *mutex_;
};

Makefile

.PHONY:all
all:clientTcp serverTcpd
Method=#-DMY_SELF

clientTcp: clientTcp.cc
	g++ -o $@ $^ $(Method) -std=c++11 -ljsoncpp
serverTcpd:serverTcp.cc
	g++ -o $@ $^ $(Method) -std=c++11 -ljsoncpp -lpthread

.PHONY:clean
clean:
	rm -f serverTcpd clientTcp

Task.hpp

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
#include "log.hpp"

class Task
{
public:
    //等价于
    // typedef std::function<void (int, std::string, uint16_t)> callback_t;
    using callback_t = std::function<void (int, std::string, uint16_t)>;
private:
    int sock_; // 给用户提供IO服务的sock
    uint16_t port_;  // client port
    std::string ip_; // client ip
    callback_t func_;  // 回调方法
public:
    Task():sock_(-1), port_(-1)
    {}
    Task(int sock, std::string ip, uint16_t port, callback_t func)
    : sock_(sock), ip_(ip), port_(port), func_(func)
    {}
    void operator () ()
    {
        logMessage(DEBUG, "线程ID[%p]处理%s:%d的请求 开始啦...",\
            pthread_self(), ip_.c_str(), port_);

        func_(sock_, ip_, port_);

        logMessage(DEBUG, "线程ID[%p]处理%s:%d的请求 结束啦...",\
            pthread_self(), ip_.c_str(), port_);
    }
    ~Task()
    {}
};

ThreadPool.hpp

#pragma once

#include <iostream>
#include <cassert>
#include <queue>
#include <memory>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
#include <sys/prctl.h>
#include "Lock.hpp"

using namespace std;

int gThreadNum = 15;

template <class T>
class ThreadPool
{
private:
    ThreadPool(int threadNum = gThreadNum) : threadNum_(threadNum), isStart_(false)
    {
        assert(threadNum_ > 0);
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    void operator=(const ThreadPool<T>&) = delete;

public:
    static ThreadPool<T> *getInstance()
    {
        static Mutex mutex;
        if (nullptr == instance) //仅仅是过滤重复的判断
        {
            LockGuard lockguard(&mutex); //进入代码块,加锁。退出代码块,自动解锁
            if (nullptr == instance)
            {
                instance = new ThreadPool<T>();
            }
        }

        return instance;
    }
    //类内成员, 成员函数,都有默认参数this
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        // prctl(PR_SET_NAME, "follower"); // 更改线程名称
        while (1)
        {
            tp->lockQueue();
            while (!tp->haveTask())
            {
                tp->waitForTask();
            }
            //这个任务就被拿到了线程的上下文中
            T t = tp->pop();
            tp->unlockQueue();
            t(); // 让指定的先处理这个任务
        }
    }
    void start()
    {
        assert(!isStart_);
        for (int i = 0; i < threadNum_; i++)
        {
            pthread_t temp;
            pthread_create(&temp, nullptr, threadRoutine, this);
        }
        isStart_ = true;
    }
    void push(const T &in)
    {
        lockQueue();
        taskQueue_.push(in);
        choiceThreadForHandler();
        unlockQueue();
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    int threadNum()
    {
        return threadNum_;
    }

private:
    void lockQueue() { pthread_mutex_lock(&mutex_); }
    void unlockQueue() { pthread_mutex_unlock(&mutex_); }
    bool haveTask() { return !taskQueue_.empty(); }
    void waitForTask() { pthread_cond_wait(&cond_, &mutex_); }
    void choiceThreadForHandler() { pthread_cond_signal(&cond_); }
    T pop()
    {
        T temp = taskQueue_.front();
        taskQueue_.pop();
        return temp;
    }

private:
    bool isStart_;
    int threadNum_;
    queue<T> taskQueue_;
    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    static ThreadPool<T> *instance;
    // const static int a = 100;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;

daemonize.hpp

#pragma once

#include <cstdio>
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void daemonize()
{
    int fd = 0;
    // 1. 忽略SIGPIPE
    signal(SIGPIPE, SIG_IGN);
    // 2. 更改进程的工作目录
    // chdir();
    // 3. 让自己不要成为进程组组长
    if (fork() > 0)
        exit(0);
    // 4. 设置自己是一个独立的会话
    setsid();
    // 5. 重定向0,1,2
    if ((fd = open("/dev/null", O_RDWR)) != -1) // fd == 3
    {
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        // 6. 关闭掉不需要的fd
        if(fd > STDERR_FILENO) close(fd);
    }
    // 6. close(0,1,2)// 严重不推荐
}

log.hpp
 

#pragma once

#include <cstdio>
#include <ctime>
#include <cstdarg>
#include <cassert>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3

const char *log_level[] = {"DEBUG", "NOTICE", "WARINING", "FATAL"};

#define LOGFILE "serverTcp.log"

class Log
{
public:
    Log():logFd(-1)
    {}
    void enable()
    {
        umask(0);
        logFd = open(LOGFILE, O_WRONLY | O_CREAT | O_APPEND, 0666);
        assert(logFd != -1);
        dup2(logFd, 1);
        dup2(logFd, 2);
    }
    ~Log()
    {
        if(logFd != -1) 
        {
            fsync(logFd);
            close(logFd);
        }
    }
private:
    int logFd;
};

// logMessage(DEBUG, "%d", 10);
void logMessage(int level, const char *format, ...)
{
    assert(level >= DEBUG);
    assert(level <= FATAL);

    char *name = getenv("USER");

    char logInfo[1024];
    va_list ap; // ap -> char*
    va_start(ap, format);

    vsnprintf(logInfo, sizeof(logInfo) - 1, format, ap);

    va_end(ap); // ap = NULL

    // 每次打开太麻烦
    // umask(0);
    // int fd = open(LOGFILE, O_WRONLY | O_CREAT | O_APPEND, 0666);
    // assert(fd >= 0);

    FILE *out = (level == FATAL) ? stderr : stdout;
    fprintf(out, "%s | %u | %s | %s\n",
            log_level[level],
            (unsigned int)time(nullptr),
            name == nullptr ? "unknow" : name,
            logInfo);

    fflush(out); // 将C缓冲区中的数据刷新到OS
    fsync(fileno(out));   // 将OS中的数据尽快刷盘

    // close(fd);
    // char *s = format;
    // while(s){
    //     case '%':
    //         if(*(s+1) == 'd')  int x = va_arg(ap, int);
    //     break;
    // }
}

util.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cassert>
#include <ctype.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"

#define SOCKET_ERR 1
#define BIND_ERR   2
#define LISTEN_ERR 3
#define USAGE_ERR  4
#define CONN_ERR   5

#define BUFFER_SIZE 1024

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值