Linux篇:网络基础1

一、网络基础:网络本质就是在获取和传输数据,而系统的本质是在加工和处理数据。

1、应用问题:
①如何处理发来的数据?—https/http/ftp/smtp
②长距离传输的数据丢失的问题?——TCP协议
③如何定位的主机的问题?——IP协议
④怎么保证数据能准确到达下一个设备?——数据链路层

2、协议:
①协议是双方的约定,最终的表现形式就是具有特定字段的结构体对象。
②协议分层:软件分层最典型的表现是:在结构体或类的设计上,做数据结构层面的分离/用回调函数的方式,在软件的逻辑上进行分层。
③通过高内聚,低耦合,大大降低软件维护成本。

3、
①OSI七层模型(略)。
②TCP/IP五层(或四层)模型:(物理层、)数据链路层、网络层、传输层、应用层(像这样的网络协议栈命名为TCP/IP协议栈)。
③其中,传输层和网络层是Linux内核中的模块,也就是操作系统。
④网络通信的本质就是贯穿协议栈的过程。
⑤网络协议栈的层状结构中,每一层都有协议。

4、报文=报头+有效载荷
①通信的过程,本质就是不断的封装和解包的过程!
②扩展(为封装和解包提供前提,是大部分协议的共性,未来我们学习具体协议的时候都会围绕这两个问题):
a.几乎任何层的协议都要提供一种能将报头和有效载荷分离的能力。
b.几乎任何层的协议都要在报头中提供决定将自己的有效载荷交付给上层的哪一个协议的能力(报文的分用)。

5、以太网通信(每台主机在局域网上都要有一个自己唯一的编号):
①以太网(局域网)通信原理。
②Mac地址:能够标识特定一台主机在局域网中的唯一性。
③以太网发生数据碰撞问题,发送主机都要执行的碰撞避免算法(延迟发送,错峰处理)(碰撞域)。
④网卡的工作模式:正常模式、混杂模式(抓包原理)。
⑤交换机:划分碰撞域,防止报文在更大的局域网中扩散,有效降低数据在局域网中碰撞的概率。
⑥如何看待局域网:一个时刻只允许一个主机向局域网中投递数据。使用这个共享资源(临界资源)时,要保证该共享资源的互斥访问。

6、IP地址:
①IP地址存在的意义:能够标识特定一台主机在全网当中的唯一性。能够指导我们进行路径规划。
②网络可以根据目的IP中当前主机和目标主机是否在同一网段来决定是否将数据交给路由器(本质就是局域网通信)。
③IP报文在全球子网自由通信的原因:路由器会根据目标子网重新解包封装一个符合当前局域网的新报头。
④IP协议通过工作在IP层的路由器屏蔽了底层网络的差异化。
⑤IP实现了全球主机的软件虚拟层,一切皆是IP报文。
⑥IP地址和MAC地址的区别:
IP地址,尤其是目的IP,一般都是不会改变的,协助我们进行路径选择。
MAC地址,出局域网之后,源和目都要被丢弃,让路由器重新封装。
⑦在应用层一般把数据包叫做请求与响应,在传输层一般叫做数据段/数据包,在网络层一般叫做数据报,在链路层一般叫做数据帧。

7、网络通信:
①网络协议中的下三层主要解决的是数据安全可靠的送到远端机器。
②用户使用应用层软件,完成数据发送和接受。
③我们日常网络通信的本质就是进程间通信。
④通过网络协议栈,利用网络资源,让不同的进程看到同一份资源。

8、端口号:
①端口号:无论对于client和server,都能唯一的标识该主机上的一个网络应用层的进程。
②在公网上,IP地址能表示唯一的一台主机。
③所以,IP:Port能够标识全网唯一的一个进程。
④源IP:源端口   client_ip:client_port
目的IP:目的端口   server_ip:server_port
这种基于IP加端口的方式称为socket。
⑤端口号vs进程PID(PID已经能够标识一台主机上进程的唯一性了,为什么还要搞一个端口号?):
a.不是所有的进程都要网络通信,但是所有进程都要有PID。
b.系统网络功能解耦。
⑥我们的客户端如何知道服务器的端口号是多少?
每一个服务的端口号必须是众所周知,精心设计,被客户端知晓的。
⑦一个进程可以绑定多个端口号吗?可以。
一个端口号可以被多个进程绑定吗?不行。

9、
TCP协议(传输控制协议):
①传输层协议。
②有连接。
③可靠传输。
④面向字节流。

UDP协议(用户数据报协议):
①传输层协议。
②无连接。
③不可靠传输。
④面向数据报。

二、Socket编程接口:

套接字编程的种类:
①域间套接字编程:另一个主机内。
②原始套接字编程:网络工具。
③网络套接字编程:用户间的网络通信。
想将网络接口统一抽象化(参数的类型必须是统一的)。

三、UDP协议实现:

##Makefile

.PHONY:all
all:udpserver udpclient
udpserver:Main.cc
	g++ -o $@ $^ -std=c++11
udpclient:UdpClient.cc
	g++ -o $@ $^ -lpthread -std=c++11
.PHONY:clean
clean:
	rm -f udpserver udpclient
// Main.cc

#include "UdpServer.hpp"
#include <memory>
#include <cstdio>
#include <vector>

// "123.60.161.111" 点分十进制字符串风格的IP地址

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}

// std::string Handler(const std::string &info, const std::string &clientip, uint16_t clientport)
// {
//     std::cout << "[" << clientip << ":" << clientport << "]#" << info << std::endl;
//     std::string res = "Server get a message: ";
//     res += info;
//     std::cout << res << std::endl;

//     return res;
// }

// bool SafeCheck(const std::string &cmd)
// {
//     int safe = false;
//     std::vector<std::string> key_word = {
//         "rm",
//         "mv",
//         "cp",
//         "kill",
//         "sudo",
//         "unlink",
//         "uninstall",
//         "yum",
//         "top",
//         "while"
//     };

//     for(auto &word : key_word)
//     {
//         auto pos = cmd.find(word);
//         if(pos != std::string::npos) return false;
//     }
//     return true;
// }

// std::string ExcuteCommand(const std::string &cmd)
// {
//     std::cout << "get a request cmd: " << cmd << std::endl;
//     if(!SafeCheck(cmd)) return "Bad man";

//     FILE *fp = popen(cmd.c_str(), "r");
//     if(nullptr == fp)
//     {
//         perror("popen");
//         return "error";
//     }

//     std::string result;
//     char buffer[4096];
//     while(true)
//     {
//         char *ok = fgets(buffer, sizeof(buffer), fp);
//         if(ok == nullptr) break;
//         result += buffer;
//     }
//     pclose(fp);

//     return result;
// }

// ./udpserver port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }

    uint16_t port = std::stoi(argv[1]);

    // std::unique_ptr<UdpServer> svr(new UdpServer(8080, "123.60.161.111")); // 如果是虚拟机,这段代码是可以执行的。但是云服务器禁止bind公网IP。bind(IP:0),凡是发给我这台主机的数据,我们都要根据端口号向上交付(任意地址bind)。
    // std::unique_ptr<UdpServer> svr(new UdpServer(8080));
    std::unique_ptr<UdpServer> svr(new UdpServer(port, "127.0.0.1")); // 本地环回地址,只能用于本地进程间通信,通常用它来进行cs的测试。

    svr->Init();
    svr->Run();

    return 0;
}
// UdpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <unordered_map>
#include "Log.hpp"

// using func_t = std::function<std::string(const std::string&)>;
typedef std::function<std::string(const std::string &, const std::string &, uint16_t)> func_t;

Log lg;

enum{
    SOCKET_ERR=1,
    BIND_ERR
};

uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";
const int size = 1024;

class UdpServer
{
public:
    UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip):sockfd_(0), port_(port), ip_(ip), isrunning_(false)
    {}

    void Init()
    {
        // 1、创建udp socket(Udp的socket是全双工的,允许同时被读写)
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // PF_INET
        if(sockfd_ < 0)
        {
            lg(Fatal, "socket create error, sockfd: %d", sockfd_);
            exit(SOCKET_ERR);
        }
        lg(Info, "socket create success, sockfd: %d", sockfd_);
        // 2、bind socket
        struct sockaddr_in local; // 结构提可以用{}初始化,不能用{}赋值
        bzero(&local, sizeof(local)); // 将一段内存初始化为0
        local.sin_family = AF_INET; // 表明自身结构体类型
        local.sin_port = htons(port_); // 需要保证该端口号是网络字节序列,因为该端口号是要给对方发送的
        // local.sin_addr.s_addr = inet_addr(ip_.c_str()); // 1、string -> uint32_t 2、uint32_t必须是网络序列的
        local.sin_addr.s_addr = htonl(INADDR_ANY);

        if(bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind errno , error: %d, err string: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));
    }

    void CheckUser(const struct sockaddr_in &client, const std::string clientip, uint16_t port)
    {
        auto iter = online_user_.find(clientip);
        if(iter == online_user_.end())
        {
            online_user_.insert({clientip, client});
            std::cout << "[" << clientip << ":" << port << "] add to online user." << std::endl;

        }
    }


    void Broadcast(const std::string &info, const std::string clientip, uint16_t clientport)
    {
        for(const auto &user : online_user_)
        {
            std::string message = "[";
            message += clientip;
            message += ":";
            message += std::to_string(clientport);
            message += "]#";
            message += info;
            socklen_t len = sizeof(user.second);
            sendto(sockfd_, message.c_str(), message.size(), 0, (struct sockaddr*)(&user.second), len);
        }
    }

    // void Run(func_t func) // 对代码进行分层
    void Run()
    {
        isrunning_ = true;
        char inbuffer[size];
        while(isrunning_)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);

            if(n < 0)
            {
                lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
                continue;
            }
            uint16_t clientport = ntohs(client.sin_port);
            std::string clientip = inet_ntoa(client.sin_addr);

            CheckUser(client, clientip, clientport);
            
            std::string info = inbuffer;
            Broadcast(info, clientip, clientport);
        }

    }

    ~UdpServer()
    {
        if(sockfd_ > 0) close(sockfd_);
    }
private:
    int sockfd_; // 网络文件描述符
    std::string ip_; // 任意地址bind
    uint16_t port_; // 表明服务器进程的端口号
    bool isrunning_;
    std::unordered_map<std::string, struct sockaddr_in> online_user_;
};
// UdpClient.cc

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " serverip serverport\n" << std::endl;
}

struct ThreadData
{
    struct sockaddr_in server;
    int sockfd;
    std::string serverip;
};

void *recv_message(void *args)
{
    ThreadData *td = static_cast<ThreadData *>(args);
    char buffer[1024];
    while(true)
    {
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);

        ssize_t s = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cerr << buffer << std::endl;
        }
    }
}

void *send_message(void *args)
{
    ThreadData *td = static_cast<ThreadData *>(args);
    std::string message;
    socklen_t len = sizeof(td->server);

    std::string welcome = td->serverip;
    welcome += " coming...";
    sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&td->server, len);

    while(true)
    {
        std::cout << "Please Enter@ ";
        std::getline(std::cin, message);

        // std::cout << message << std::endl;
        // 1、数据 2、给谁发
        sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&td->server, len);
    }
}


// 多线程
// ./udpclient serverip serverport
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    struct ThreadData td;

    bzero(&td.server, sizeof(td.server)); // 将一段内存初始化为0
    td.server.sin_family = AF_INET;
    td.server.sin_port = htons(serverport); 
    td.server.sin_addr.s_addr = inet_addr(serverip.c_str());

    td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(td.sockfd < 0)
    {
        std::cout << "socket error" << std::endl;
        return 1;
    }

    td.serverip = serverip;

    pthread_t recvr, sender;
    pthread_create(&recvr, nullptr, recv_message, &td);
    pthread_create(&sender, nullptr, send_message, &td);

    // client也要bind,只不过不需要用户显示bind。一般由OS自由随机选择。
    // 一个端口号只能被一个进程bind,对server是如此,对于client,也是如此。
    // 其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以。
    // 系统在首次发送数据的时候bind。

    pthread_join(recvr, nullptr);
    pthread_join(sender, nullptr);

    close(td.sockfd);
    return 0;
}
// Log.hpp
 
#pragma once
 
#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
 
#define SIZE 1024
 
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
 
#define Screen 1
#define Onefile 2
#define Classfile 3
 
#define LogFile "log.txt"
 
class Log
{
public:
    Log()
    {
        printMethod = Screen;
        path = "./log/";
    }
 
    void Enable(int method)
    {
        printMethod = method;
    }
 
    std::string levelToString(int level)
    {
        switch(level)
        {
            case Info: return "Info";
            case Debug: return "Debug";
            case Warning: return "Warning";
            case Error: return "Error";
            case Fatal: return "Fatal";
            default: return "None";
        }
    }
 
    void printLog(int level, const std::string &logtxt)
    {
        switch(printMethod)
        {
            case Screen:
                std::cout << logtxt << std::endl;
                break;
            case Onefile:
                printOneFile(LogFile, logtxt);
                break;
            case Classfile:
                printClassFile(level, logtxt);
                break;
            default:
                break;
        }
    }
 
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY|O_CREAT|O_APPEND, 0666); // "log.txt"
        if(fd < 0) return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
 
    void printClassFile(int level, const std::string logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt);
    }
 
    ~Log()
    {}
 
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(), 
            ctime->tm_year+1900, ctime->tm_mon+1, ctime->tm_mday, 
            ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
 
        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);
 
        //格式:默认部分+自定义部分
        char logtxt[SIZE*2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);
 
        // printf("%s", logtxt);//暂时打印
        printLog(level, logtxt);
    }
private:
    int printMethod;
    std::string path;
};

 四、TCP协议实现:

// Makefile

.PHONY:all
all:tcpserverd tcpclient
tcpserverd:Main.cc
	g++ -o $@ $^ -std=c++11 -lpthread
tcpclient:TcpClient.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f tcpserverd tcpclient
// Main.cc

#include "TcpServer.hpp"
#include <iostream>
#include <memory>

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}

// ./tcpserver 8080
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(UsageError);
    }
    uint16_t port = std::stoi(argv[1]);
    lg.Enable(Classfile);
    std::unique_ptr<TcpServer> tcp_svr(new TcpServer(port));

    tcp_svr->InitServer();
    tcp_svr->Start();

    return 0;
}
// TcpServer.hpp

#pragma once

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include "Log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"
#include "Daemon.hpp"


const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 10; // 但是一般不要设置的太大
extern Log lg;

enum
{
    UsageError = 1,
    SocketError,
    BindError,
    ListenError,
};

class TcpServer;

class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &p, TcpServer *t): sockfd(fd), clientip(ip), clientport(p), tsvr(t)
    {}
public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer *tsvr;
};

class TcpServer
{
public:
    TcpServer(const uint16_t &port, const std::string &ip = defaultip):listensock_(defaultfd), port_(port), ip_(ip)
    {}

    void InitServer()
    {
        listensock_ = socket(AF_INET, SOCK_STREAM, 0);
        if(listensock_ < 0)
        {
            lg(Fatal, "socket create error, listensock: %d", listensock_);
            exit(SocketError);
        }
        lg(Info, "socket create success, : %d", listensock_);

        int opt = 1;
        setsockopt(listensock_, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt)); // 防止偶发性的服务器无法进行立即重启

        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        inet_aton(ip_.c_str(), &(local.sin_addr));
        // local.sin_addr.s_addr = INADDR_ANY;

        if(bind(listensock_, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind errno , error: %d, err string: %s", errno, strerror(errno));
            exit(BindError);
        }
        lg(Info, "bind socket success, listensock: %d", listensock_);

        // Tcp是面向连接的,服务器一般是比较“被动的”,服务器一直处于等待连接的状态
        if(listen(listensock_, backlog) < 0)
        {
            lg(Fatal, "listen errno , error: %d, err string: %s", errno, strerror(errno));
            exit(ListenError);
        }
        lg(Info, "listen socket success, listensock: %d", listensock_);
    }

    // static void *Routine(void *args)
    // {
    //     pthread_detach(pthread_self());
    //     ThreadData *td = static_cast<ThreadData *>(args);
    //     td->tsvr->Service(td->sockfd, td->clientip, td->clientport);
    //     delete td;
    //     return nullptr;
    // }

    void Start()
    {
        Daemon();
        // signal(SIGPIPE, SIG_IGN);
        ThreadPool<Task>::GetInstance()->Start();
        // signal(SIGCHLD, SIG_IGN);
        lg(Info, "tcpServer is running...");
        for(;;)
        {
            // 1. 获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listensock_, (struct sockaddr*)&client, &len);
            if(sockfd < 0)
            {
                lg(Warning, "accept errno , error: %d, err string: %s", errno, strerror(errno)); // ?
                continue;
            }
            uint16_t clientport = ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));

            // 2. 根据新连接来进行通信
            lg(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d\n", sockfd, clientip, clientport);
            std::cout << "hello world" << std::endl;
            
            // version 1 -- 单进程版
            // Service(sockfd, clientip, clientport);
            // close(sockfd);

            // version 2 -- 多进程版
            // pid_t id = fork();
            // if(id == 0)
            // {
            //     // child
            //     close(listensock_);
            //     if(fork() > 0) exit(0); // 子进程立刻退出
            //     Service(sockfd, clientip, clientport); // 孙子进程由系统领养
            //     close(sockfd);
            //     exit(0);
            // }
            // close(sockfd);
            // // father
            // pid_t rid = waitpid(id, nullptr, 0);
            // (void)rid;

            // version 3 -- 多线程版
            // ThreadData *td = new ThreadData(sockfd, clientip, clientport, this);
            // pthread_t tid;
            // pthread_create(&tid, nullptr, Routine, td);

            // version 4 -- 线程池版
            Task t(sockfd, clientip, clientport);
            ThreadPool<Task>::GetInstance()->Push(t);
        }
    }

    // void Service(int sockfd, const std::string &clientip, const uint16_t &clientport)
    // {
    //     // 测试代码
    //     char buffer[4096];
    //     while(true)
    //     {
    //         ssize_t n = read(sockfd, buffer, sizeof(buffer));
    //         if(n > 0)
    //         {
    //             buffer[n] = 0;
    //             std::cout << "client say# " << buffer << std::endl;
    //             std::string echo_string = "tcpserver echo# ";
    //             echo_string += buffer;

    //             write(sockfd, echo_string.c_str(), echo_string.size());
    //         }
    //         else if(n == 0)
    //         {
    //             lg(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), sockfd, clientport);
    //             break;
    //         }
    //         else
    //         {
    //             lg(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd, clientip.c_str(), clientport);
    //             break;
    //         }
    //     }
    // }
    ~TcpServer(){}
private:
    int listensock_; // 监听套接字
    std::string ip_;
    uint16_t port_; 
};
// TcpClient.cc

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

void Usage(const std::string &proc)
{
    std::cout << "\n\rUsage: " << proc << " serverip serverport\n" << std::endl;
}

// ./tcpclient serverip serport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));
    // local.sin_addr.s_addr = INADDR_ANY;
    
    while (true)
    {
        int cnt = 20;
        int isreconnect = false;
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            return 1;
        }
        do
        {
            // tcp客户端要bind,但不需要显示bind。
            // 客户端发起connect的时候,进行自动随机bind。
            int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
            if (n < 0)
            {
                isreconnect = true;
                cnt--;
                std::cerr << "connect error..., reconnect: " << cnt << std::endl;
                sleep(1);
            }
            else{
                break;
            }
        } while (cnt && isreconnect);

        if (cnt == 0)
        {
            std::cerr << "user offline..." << std::endl;
            break;
        }

        // while (true)
        // {
            std::string message;
            std::cout << "Please Enter# ";
            std::getline(std::cin, message);

            int n = write(sockfd, message.c_str(), message.size());

            if (n < 0)
            {
                std::cerr << "write error..." << std::endl;
                // break;
            }

            char inbuffer[4096];
            n = read(sockfd, inbuffer, sizeof(inbuffer));
            if (n > 0)
            {
                inbuffer[n] = 0;
                std::cout << inbuffer << std::endl;
            }
            // else{
            //     break;
            // }
        // }
        close(sockfd);
    }
    
    return 0;
}
// Task.hpp

#pragma once
#include <iostream>
#include <string>
#include "Log.hpp"
#include "Init.hpp"

extern Log lg;
Init init;

class Task
{
public:
    Task(int sockfd, const std::string &clientip, const uint16_t &clientport)
        : sockfd_(sockfd), clientip_(clientip), clientport_(clientport)
    {}

    Task()
    {}

    void run()
    {
        // 测试代码
        char buffer[4096];
        // Tcp是面向字节流的,你怎么保证,你读取上来的数据,是"一个" "完整" 的报文呢?
        ssize_t n = read(sockfd_, buffer, sizeof(buffer)); // BUG?
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client key# " << buffer << std::endl;
            std::string echo_string = init.translation(buffer);

            // sleep(5);
            // // close(sockfd_);
            // lg(Warning, "close sockfd %d done", sockfd_);

            // sleep(2);
            n = write(sockfd_, echo_string.c_str(), echo_string.size()); // 100 fd 不存在
            if(n < 0)
            {
                lg(Warning, "write error, errno : %d, errstring: %s", errno, strerror(errno));
            }
        }
        else if (n == 0)
        {
            lg(Info, "%s:%d quit, server close sockfd: %d", clientip_.c_str(), clientport_, sockfd_);
        }
        else
        {
            lg(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd_, clientip_.c_str(), clientport_);
        }
        close(sockfd_);
    }
    void operator()()
    {
        run();
    }
    ~Task()
    {
    }

private:
    int sockfd_;
    std::string clientip_;
    uint16_t clientport_;
};
// ThreadPool.hpp

#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>

struct ThreadInfo
{
    pthread_t tid;
    std::string name;
};

static const int defalutnum = 10;

template <class T>
class ThreadPool
{
public:
    void Lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }
    void Wakeup()
    {
        pthread_cond_signal(&cond_);
    }
    void ThreadSleep()
    {
        pthread_cond_wait(&cond_, &mutex_);
    }
    bool IsQueueEmpty()
    {
        return tasks_.empty();
    }
    std::string GetThreadName(pthread_t tid)
    {
        for (const auto &ti : threads_)
        {
            if (ti.tid == tid)
                return ti.name;
        }
        return "None";
    }

public:
    static void *HandlerTask(void *args)
    {
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        std::string name = tp->GetThreadName(pthread_self());
        while (true)
        {
            tp->Lock();

            while (tp->IsQueueEmpty())
            {
                tp->ThreadSleep();
            }
            T t = tp->Pop();
            tp->Unlock();

            t();
        }
    }
    void Start()
    {
        int num = threads_.size();
        for (int i = 0; i < num; i++)
        {
            threads_[i].name = "thread-" + std::to_string(i + 1);
            pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);
        }
    }
    T Pop()
    {
        T t = tasks_.front();
        tasks_.pop();
        return t;
    }
    void Push(const T &t)
    {
        Lock();
        tasks_.push(t);
        Wakeup();
        Unlock();
    }
    static ThreadPool<T> *GetInstance()
    {
        if (nullptr == tp_) // ???
        {
            pthread_mutex_lock(&lock_);
            if (nullptr == tp_)
            {
                std::cout << "log: singleton create done first!" << std::endl;
                tp_ = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&lock_);
        }

        return tp_;
    }

private:
    ThreadPool(int num = defalutnum) : threads_(num)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:
    std::vector<ThreadInfo> threads_;
    std::queue<T> tasks_;

    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    static ThreadPool<T> *tp_;
    static pthread_mutex_t lock_;
};

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

template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;
// Init.hpp

#pragma once

#include <iostream>
#include <string.h>
#include <fstream>
#include <unordered_map>
#include "Log.hpp"

const std::string dictname = "./dict.txt";
const std::string sep = ":";

static bool Split(std::string &s, std::string *part1, std::string *part2)
{
    auto pos = s.find(sep);
    if(pos == std::string::npos) return false;
    *part1 = s.substr(0, pos);
    *part2 = s.substr(pos+1);
    return true;
}

class Init
{
public:
    Init()
    {
        std::ifstream in(dictname);
        if(!in.is_open())
        {
            lg(Fatal, "ifstream open %s error", dictname.c_str());
            exit(1);
        }
        std::string line;
        while(std::getline(in, line))
        {
            std::string part1, part2;
            Split(line, &part1, &part2);
            dict.insert({part1, part2});
        }
        in.close();
    }
    std::string translation(const std::string &key)
    {
        auto iter = dict.find(key);
        if(iter == dict.end()) return "Unknown";
        else return iter->second;
    }
private:
    std::unordered_map<std::string, std::string> dict;
};

守护进程化:

// Daemon.hpp

#pragma once

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

const std::string nullfile = "/dev/null";

void Daemon(const std::string &cwd = "")
{
    // 1、忽略其他异常信号
    signal(SIGCLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);
    
    // 2、将自己变成独立的回话
    if(fork() > 0) exit(0);
    setsid();

    // 3、更改当前调用进程的工作目录
    if(!cwd.empty()) chdir(cwd.c_str());

    // 4、标准输入、标准输出、标准错误重定向至/dev/null
    int fd = open(nullfile.c_str(), O_RDWR);
    if(fd > 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        close(fd);
    }
}
// Log.hpp

#pragma once
 
#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
 
#define SIZE 1024
 
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
 
#define Screen 1
#define Onefile 2
#define Classfile 3
 
#define LogFile "log.txt"
 
class Log
{
public:
    Log()
    {
        printMethod = Screen;
        path = "./log/";
    }
 
    void Enable(int method)
    {
        printMethod = method;
    }
 
    std::string levelToString(int level)
    {
        switch(level)
        {
            case Info: return "Info";
            case Debug: return "Debug";
            case Warning: return "Warning";
            case Error: return "Error";
            case Fatal: return "Fatal";
            default: return "None";
        }
    }
 
    void printLog(int level, const std::string &logtxt)
    {
        switch(printMethod)
        {
            case Screen:
                std::cout << logtxt << std::endl;
                break;
            case Onefile:
                printOneFile(LogFile, logtxt);
                break;
            case Classfile:
                printClassFile(level, logtxt);
                break;
            default:
                break;
        }
    }
 
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY|O_CREAT|O_APPEND, 0666); // "log.txt"
        if(fd < 0) return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
 
    void printClassFile(int level, const std::string logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt);
    }
 
    ~Log()
    {}
 
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(), 
            ctime->tm_year+1900, ctime->tm_mon+1, ctime->tm_mday, 
            ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
 
        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);
 
        //格式:默认部分+自定义部分
        char logtxt[SIZE*2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);
 
        // printf("%s", logtxt);//暂时打印
        printLog(level, logtxt);
    }
private:
    int printMethod;
    std::string path;
};

Log lg;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值