NetCal1.0

CalculatorClient.cc

//telnet是一个命令,和接口要区分,可以向服务其发起tcp登录入telnet 127.0.0.1 8888
#include "TcpClient.hpp"
int main()
{
    std::cout << "cal client" << std::endl;
    return 0;
}

CalculatorServer.cc

#include "TcpServer.hpp"
#include <memory>
using namespace tcpserver_ns;

Response calculate(const Request &req)//服务器对于客户端发送的request的处理逻辑,也就是服务器对于客户端提供的一个具体的服务,以后只要将该函数以及Protocol.h文件一改,就当与是一个替换协议的过程。
{
    // 走到这里,一定保证req是有具体数据的!
    // _result(result), _code(code)
    Response resp(0, 0);
    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 (req._y == 0)
            resp._code = 1;
        else
            resp._result = req._x / req._y;
        break;
    case '%':
        if (req._y == 0)
            resp._code = 2;
        else
            resp._result = req._x % req._y;
        break;
    default:
        resp._code = 3;//操作符对于我们当前的服务器并不支持
        break;
    }

    return resp;
}
// ./calserver 8888,服务器运行的时候给端口号参数,这边省略了直接在函数内部定义了。
int main()
{
    uint16_t port = 8888;
    std::unique_ptr<TcpServer> tsvr(new TcpServer(calculate, port)); //服务器创建起来,该服务器只进行网络的io读取,并不进行数据的处理,数据处理逻辑时外部通过回调函数操作的。
    tsvr->InitServer();//初始化服务器
    tsvr->Start();//让服务器跑起来

    return 0;
}

Err.hpp

#pragma once
enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
    SETSID_ERR,
    OPEN_ERR
};

Log.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cstdarg>
#include <sys/types.h>
#include <unistd.h>

// 日志是有日志等级的

const std::string filename = "log/tcpserver.log";

enum
{
    Debug = 0,
    Info,
    Warning,
    Error,
    Fatal,
    Uknown
};

static std::string toLevelString(int level)
{
    switch (level)
    {
    case Debug:
        return "Debug";
    case Info:
        return "Info";
    case Warning:
        return "Warning";
    case Error:
        return "Error";
    case Fatal:
        return "Fatal";
    default:
        return "Uknown";
    }
}

static std::string getTime()
{
    time_t curr = time(nullptr);
    struct tm *tmp = localtime(&curr);
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d", tmp->tm_year + 1900, tmp->tm_mon+1, tmp->tm_mday,
             tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
    return buffer;
}

// 日志格式: 日志等级 时间 pid 消息体
// logMessage(DEBUG, "hello: %d, %s", 12, s.c_str()); // DEBUG hello:12, world
void logMessage(int level, const char *format, ...)
{
    char logLeft[1024];
    std::string level_string = toLevelString(level);
    std::string curr_time = getTime();
    snprintf(logLeft, sizeof(logLeft), "[%s] [%s] [%d] ", level_string.c_str(), curr_time.c_str(), getpid());

    char logRight[1024];
    va_list p;
    va_start(p, format);
    vsnprintf(logRight, sizeof(logRight), format, p);
    va_end(p);

    // 打印
    printf("%s%s\n", logLeft, logRight);

    // 保存到文件中
    // FILE *fp = fopen(filename.c_str(), "a");
    // if(fp == nullptr)return;
    // fprintf(fp,"%s%s\n", logLeft, logRight);
    // fflush(fp); //可写也可以不写
    // fclose(fp);


    // 预备
    //  va_list p; // char *
    //  int a = va_arg(p, int);  // 根据类型提取参数
    //  va_start(p, format); //p指向可变参数部分的起始地址
    //  va_end(p); // p = NULL;
}

Sock.hpp

//对tcpsocket做一下封装
#pragma once
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include "Log.hpp"
#include "Err.hpp"
static const int gbacklog = 32;
static const int defaultfd = -1;
class Sock
{
public:
    Sock() : _sock(defaultfd)
    {
    }
    void Socket()//创建套接字
    {
        _sock = socket(AF_INET, SOCK_STREAM, 0);//创建成功返回文件描述符
        if (_sock < 0)
        {
            logMessage(Fatal, "socket error, code: %d, errstring: %s", errno, strerror(errno));
            exit(SOCKET_ERR);
        }
    }
    void Bind(const uint16_t &port)//绑定套接字,只有服务器需要主动绑定,所以这个是专门给服务器写的接口。之所以参数里面没有ip地址是因为
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));//对结构做清空
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;

        if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)//开始绑定
        {
            logMessage(Fatal, "bind error, code: %d, errstring: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
    }
    void Listen()//将套接字设置成为listen状态
    {
        if (listen(_sock, gbacklog) < 0)
        {
            logMessage(Fatal, "listen error, code: %d, errstring: %s", errno, strerror(errno));
            exit(LISTEN_ERR);
        }
    }
    int Accept(std::string *clientip, uint16_t *clientport)//为tcp服务端准备的接口,这两个参数是输出型参数,获得客户端的信息。
    {
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        int sock = accept(_sock, (struct sockaddr *)&temp, &len);//tcp既然面向连接,就一定有人连我,现在就先把连接获取上来。
        if (sock < 0)
        {
            logMessage(Warning, "accept error, code: %d, errstring: %s", errno, strerror(errno));
        }
        else
        {
            *clientip = inet_ntoa(temp.sin_addr);//网络转主机序列,成为点分十进制的ip地址
            *clientport = ntohs(temp.sin_port);//网络转主机序列
        }
        return sock;
    }
    int Connect(const std::string &serverip, const uint16_t &serverport)//为tcp客户端准备的接口,传进来的参数告诉我们应该像谁进行连接,也就是服务端的ip和端口号。
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(serverport);
        server.sin_addr.s_addr = inet_addr(serverip.c_str());

        return connect(_sock, (struct sockaddr *)&server, sizeof(server));//发起请求
    }
    int Fd()//用来获取套接字
    {
        return _sock;
    }
    ~Sock()
    {
        if (_sock != defaultfd)
            close(_sock);
    }

private:
    int _sock;//这个套接字是用来监听、connect、还是获取连接等等由用户自己来决定
};

TcpClient.hpp

#pragma once
#include <iostream>

TcpServer.hpp

//tcpserver和tcpclient这两个hpp文件是用来存放实现网络应用的函数接口
#pragma once

#include <iostream>
#include <pthread.h>
#include <functional>
#include "Sock.hpp"
#include "Protocol.hpp"

namespace tcpserver_ns
{
    using namespace protocol_ns;
    class TcpServer;

    using func_t = std::function<Response(const Request&)>;//func_t是一个函数类型,函数返回类型是Response,参数是Request

    class ThreadData//pthread_create传参时需要很多的数据,比如说客户端的ip地址端口号,服务器创建的listen套接字等等,以及服务器对象,里面有服务器的业务逻辑
    {
    public:
        ThreadData(int sock, std::string ip, uint16_t port, TcpServer *tsvrp)
            : _sock(sock), _ip(ip), _port(port), _tsvrp(tsvrp)
        {
        }
        ~ThreadData() {}

    public:
        int _sock;
        std::string _ip;
        uint16_t _port;
        TcpServer *_tsvrp;
    };

    class TcpServer
    {
    public:
        TcpServer(func_t func, uint16_t port) : _func(func), _port(port)//func是对于Request的处理逻辑函数,也就是Response
        {
        }
        void InitServer()
        {
            // 1. 初始化服务器,首先得有套接字
            _listensock.Socket();//创建套接字
            _listensock.Bind(_port);//绑定套接字
            _listensock.Listen();//将套接字设置成为listen状态
            logMessage(Info, "init server done, listensock: %d", _listensock.Fd());
        }

        void Start()
        {
            for (;;)//服务器启动之后就需要周而复始的循环
            {
                std::string clientip;
                uint16_t clientport;
                int sock = _listensock.Accept(&clientip, &clientport);//获得连接,并得到客户端的ip和地址
                if (sock < 0)
                    continue;
                logMessage(Debug, "get a new client, client info : [%s:%d]", clientip.c_str(), clientport);//走到这边说明accept成功了
                //采用多线程的版本
                pthread_t tid;
                ThreadData *td = new ThreadData(sock, clientip, clientport, this);//
                pthread_create(&tid, nullptr, ThreadRoutine, td);//td做为线程执行函数的参数,里面由很多信息,比如说this指针、套接字sock、客户端的ip和端口号等等,this指针里面还有ServiceIO函数用来处理来自客户端的数据
            }
        }
        static void *ThreadRoutine(void *args)
        {
            pthread_detach(pthread_self());//省略了主线程的join操作 
            ThreadData *td = static_cast<ThreadData *>(args);
            td->_tsvrp->ServiceIO(td->_sock, td->_ip, td->_port);//该服务器的线程要开始执行处理客户端的任务了
            // logMessage(Debug, "thread running ...");
            delete td;
            return nullptr;
        }
        // 我们这个函数是被多线程调用的,负责提供具体的服务内容。将sock文件描述符的创建工作和sock的读取工作让不同的线程进行。,
        void ServiceIO(int sock, const std::string &ip, const uint16_t &port)//负责从sock读客户端的数据以及将处理号的数据write到sock,第二三个参数是让函数知道我是在和哪个客户端进行通信
        {
            // 1. read/recv - 如何正确的读,继续设计协议,该协议的目的是为了保证用户正确的获得一个完整的报文string!
            // 你怎么保证你读到的string 就是一个完整的string风格的请求呢??不能!
            

            // 我们进行一直循环读取,边读取,边检测,测试
            char buffer[1024];
            ssize_t s = recv(sock, buffer, sizeof(buffer), 0); // ssize_t recv(int sockfd, void *buf, size_t len, int flags)第四个参数flag代表读取方式,默认为零即可。

            // 2. 假设已经读到了一个完整的string
            Request req;
            req.Deserialize(str); // 对读到的request字符串要进行反序列化。将字符串str反序列化成为结构化的数据存储到req的x、y、op成员变量里面。

            // 3. 直接提取用户的请求数据x、y、op啦,处理逻辑是客户端发个我一个request,服务器返回一个response
            Response resp = _func(req); // 业务逻辑!这里是计算,未来可以是登录、注册等等服务

            // 4. 给用户返回响应 - 序列化
            std::string send_string;
            resp.Serialize(&send_string); // 对计算完毕的response结构要进行序列化,形成可发送字符串 

            // 5. 发送到网络
        }
        ~TcpServer()
        {
        }

    private:
        uint16_t _port;//任何一个服务器都需要一个端口号
        Sock _listensock;//创建listen套接字,虽然Class Sock里面并没有就sock进行具体的类型划分,但是在服务器里面我们要的就是listrn套接字
        func_t _func;
    };
}

Util.hpp

//存放工具方法
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
using namespace std;
class Util
{
public:
    // 纯输入型参数,在函数内部不会做修改: const &
    // 输出型参数: *
    // 输入输出型参数: &
    static bool StringSplit(const string &str, const string &sep, vector<std::string> *result)//sep是分隔符,将str按照分隔符sep分割并且将结果放到result里面
    {
        size_t start = 0;
        // 10 + 20
 
        while (start < str.size())//循环切割
        {
            auto pos = str.find(sep, start);
            if (pos == string::npos) break;
            result->push_back(str.substr(start, pos-start));
            // 位置的重新reload
            start = pos + sep.size();
        }
        if(start < str.size())  result->push_back(str.substr(start));//将最后一个结构化的变量放入vector当中
        return true;
    }

    static int toInt(const std::string &s)//将字符串转整数
    {
        return atoi(s.c_str());//字符串转整数
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值