小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
linux系统编程专栏<—请点击
linux网络编程专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
在这里插入图片描述



前言

【linux】网络套接字编程(四)TCP服务器与客户端的实现(单进程/单线程的TCP服务器),setsockopt,listen,accept,telnet,connect,inet_pton——书接上文 详情请点击<——,本文会在上文的基础上进行讲解,所以对上文不了解的读者友友请点击前方的蓝字链接进行学习
本文由小编为大家介绍——【linux】网络套接字编程(五)TCP服务器与客户端的实现——多进程版,多线程版


一、多进程版

  1. 由于是多进程,所以对于创建子进程不熟悉的读者友友,可以点击后方蓝字链接进行学习
    (一)fork创建子进程,exit终止子进程,详情请点击<——
    (二)waitpid进程等待,详情请点击<——
    (三)孤儿进程,详情请点击<——

逻辑一

  1. 在之前的文章中,小编讲解了TCP服务器与客户端的实现单进程/单线程版,本文小编会在上文的基础上继续进行扩展四个版本,所以本文会在上文的基础上进行讲解,所以对上文不了解的读者友友请点击后方蓝字链接进行学习 详情请点击<——
  2. 这里的多进程版是指的服务器,即当服务器收到accept连接成功后,当前的服务器进程fork创建子进程,让子进程执行服务器和客户端的通信服务,那么父进程等待子进程即可
    在这里插入图片描述
  3. 父进程fork创建子进程后,子进程的文件描述符是拷贝的父进程的文件描述符表,所以父进程的资源,子进程同样也可以进行访问,那么对于子进程来讲,子进程可以看到listensock_这个网络文件描述符对应的文件打开对象,但是我们并不期望子进程对这个文件打开对象进行访问或者修改等操作,因为我服务器fork创建出子进程的目的,就是让子进程拿着sockfd然后作为服务器和客户端进行通信服务的,所以开始进入子进程,那么我们就close将listensock_这个网络文件描述符进行关闭
  4. 那么对于当前的服务器父进程来讲,我父进程都已经将网络文件描述符sockfd交给子进程了让子进程和客户端进行通信服务,所以我父进程已经不需要sockfd这个网络文件描述符,所以我父进程直接close将sockfd这个网络文件描述符关闭即可,可是父进程总要管子进程吧,所以父进程waitpid就要等待子进程即可
    在这里插入图片描述
  5. 小编,小编,可是你直接close关闭对应的网络文件描述符listensock_和sockfd之后,不会对对方进程有什么影响吗?即子进程close关闭网络文件描述符listensock_会不会直接将对应的文件打开对象释放掉,然后造成父进程无法继续监听,父进程close关闭网络文件描述符sockfd会不会直接将对应的文件打开对象释放掉,然后造成子进程无法使用sockfd与客户端进行通信?
  6. 不会的,因为每一个文件打开对象都会维护一个引用计数,用于表征当前有几个进程正在使用当前的文件打开对象,只有最后一个使用文件打开对象的进程close关闭文件打开对象的时候,文件打开对象才会被释放
  7. 所以当父进程fork创建子进程后,listensock_和sockfd对应的文件打开对象的引用计数会变成2,子进程close关闭网络文件描述符listensock_以及父进程close关闭网络文件描述符sockfd之后,listensock_和sockfd对应的文件打开对象都只会将引用计数从2减成1,所以文件打开对象不会释放,不会对进程的正常使用文件打开对象造成影响
  8. 所以服务器进程fork创建子进程,当fork的返回值id等于0的时候,代表是子进程,那么我们让子进程执行close关闭listensock_即可,然后子进程执行和客户端的通信服务Service,当执行完毕后close关闭文件描述符socket即可,最后子进程已经完成了任务,我们不期望子进程继续向后执行,所以exit终止子进程
  9. 那么当fork的返回值id不等于0的时候,那么走到下面就是父进程,那么我们先让父进程close关闭socket即可,然后父进程总要管并且等待子进程,所以接下来就让父进程waitpid等待子进程即可,当父进程等待子进程成功之后,然后父进程就会继续循环然后继续监听连接
void StartServer()
{
    lg(Info, "tcpserver is running...");

    for (;;)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        
        int sockfd = accept(listensock_, (struct sockaddr*)&client, &len);
        if (sockfd < 0)
        {
            lg(Warning, "accept error, errno: %d, errstring: %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));

        lg(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d", 
            sockfd, clientip, clientport);
        //version 1 单进程/单线程版
        // Service(sockfd, clientip, clientport);
        // close(sockfd);

        //version 2 多进程版
        pid_t id = fork();
        if(id == 0)
        {
            //child
            close(listensock_);

            Service(sockfd, clientip, clientport);
            close(sockfd);
            
            exit(0);
        }
        close(sockfd);
        pid_t rid = waitpid(id, nullptr, 0);
        if(rid == id)
            std::cout << "father wait success" << std::endl;
    }
}
测试
  1. 那么我们接下来依次运行服务器,客户端
    在这里插入图片描述
  2. 如上,可见一个服务器,一个客户端的场景还是没有问题的,可是小编若是在添加一个客户端呢?
    在这里插入图片描述
  3. 如上,当我们启动第二个客户端然后想要让第二个客户端和服务器进行连接通信的时候,服务器没有响应,我们想要的是服务器创建出子进程之后,服务器让子进程去执行与客户端通信,然后服务器继续去监听连接,如果还有其他客户端也来连接了,那么就再创建子进程,当新创建的子进程与其它客户端进行通信,所以为什么,我们的服务器不响应了呢?
  4. 因为在我们的代码逻辑中,当前服务器作为子进程的父进程正在waitpid等待子进程,所以由于此时子进程并且没有结束和客户端的通信,即子进程并没有终止,所以服务器就会一直等待子进程,所以服务器压根没有执行accept的代码,所以也就自然不会对第二个客户端的连接进行响应了
  5. 那么照你的意思,我把子进程的服务终止了之后,服务器就会等待子进程成功,那么服务器就可以响应第二个客户端的连接请求了,对的,没错,可是如何终止子进程呢?很简单
  6. 我们使用ctrl+c终止掉第一个客户端即可,所以此时第一个客户端正在与子进程进行通信的写端就会关闭,那么子进程的读端就会读到0,并且返回0,并且我们在Service函数中也对这个返回值0进行了break退出死循环处理,所以子进程就会结束服务,然后退出终止了
    在这里插入图片描述
  7. 一瞬间,当小编使用ctrl+c终止第一个客户端的时候,第二个客户端就会和服务器连接成功,并且进行服务,无误
  8. 可是这个样让服务器去等待子进程退出,有点太挫了,能不能让服务器一直处于监听请求,然后如果监听成功之后,将sockfd交给子进程,子进程去和客户端进行通信,此时作为父进程的服务器很短的时间内就可以继续进入监听请求的状态,完全可以,那么就是下面小编的逻辑二的多线程版本

逻辑二

  1. 既然子进程不行,那么子进程的子进程呢?即我们尝试让孙子进程执行与客户端通信
  2. 所以当服务器创建子进程之后,让这个子进程再次创建子进程,然后让这个服务器的子进程立即exit退出,所以服务器几乎在一瞬间就可以等待到服务器的子进程退出
  3. 不是,小编,你这有问题呀,那么服务器的子进程的子进程不就没人管了,即服务器的子进程再次创建的子进程和服务器的关系就是子孙关系,即当前服务器的孙子进程就没人管了,进程如果fork子进程之后,只需要管好自己的子进程即可,至于孙子进程,我服务器不关心,谁关心?
  4. 操作系统关心呀,因为服务器虽然将它的子进程回收了,但是服务器的子进程可是服务器的孙子进程的父进程呀,所以孙子进程就成为了孤儿进程,此时孙子进程就会被操作系统领养,领养就领养吧,没事,也不会造成资源泄露,并且孙子进程还会执行Service和服务端进行通信,一举两得,所以此时服务端就会立即等待子进程成功,那么服务器就可以继续循环然后执行accept监听请求了
  5. 所以从此以后,服务器面对再多的客户端的连接请求都可以从容不迫的进行连接了,并且将与客户端进行通信的任务交给孙子进程来做,完美
void StartServer()
{
    lg(Info, "tcpserver is running...");

    for (;;)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        
        int sockfd = accept(listensock_, (struct sockaddr*)&client, &len);
        if (sockfd < 0)
        {
            lg(Warning, "accept error, errno: %d, errstring: %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));

        lg(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d", 
            sockfd, clientip, clientport);
        //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);
            //grandchild
            Service(sockfd, clientip, clientport);
            close(sockfd);

            exit(0);
        }
        close(sockfd);
        pid_t rid = waitpid(id, nullptr, 0);
        if(rid == id)
            std::cout << "father wait success" << std::endl;
    }
}
测试
  1. 所以此时我们编译,然后运行服务器,客户端,此时第一个客户端运行起来之后,立马服务器就等待子进程成功了,此时与客户端进行通信的是服务器的孙子进程,所以服务器就继续循环,然后监听其它客户端的连接请求
    在这里插入图片描述
  2. 那么我们在第一个客户端进行输入通信,无误
    在这里插入图片描述
  3. 那么关键来了,第二个客户端可以通信吗?所以此时我们开始启动第二个客户端
    在这里插入图片描述
  4. 无误,第二个客户端一启动,那么服务器就等待子进程成功了,此时与客户端进行通信的是服务器的孙子进程,所以服务器就继续循环,然后监听其它客户端的连接请求,那么如下我们在第二个客户端输入可以进行正常通信,无误
    在这里插入图片描述
  5. 小编,小编,可是各自与这两个客户端进行通信的两个孙子进程真的成孤儿进程,然后被操作系统领养了吗?即孙子进程的父进程变成1操作系统了吗?所以我们使用如下指令查看一下
ps axj | head -1 && ps axj | grep tcp | grep -v grep
  1. 结果如下,孙子进程的父进程变成1操作系统了,无误,至此多线程版的TCP服务器与客户端的实现完成,TCP服务器再也不用怕多个客户端的连接请求了
    在这里插入图片描述

二、多线程版

  1. 由于是多线程,所以对于创建多线程不熟悉的读者友友,可以点击后方蓝字链接进行学习
    (一)pthread_create,详情请点击<——
    (二)pthread_datech,详情请点击<——
  2. 那么下面我们就pthread_create创建线程即可,那么线程在执行线程函数Routine的时候应该有什么呢?线程要调用Service函数,那么首先要有网络文件描述符sockfd,其次是客户端IP地址,客户端端口号port
  3. 我们期望将这个线程函数Routine放在类中,这样具有一定的封装性,其实更严格点来讲可以将这个线程函数设置成私有的,我们不期望外部可以直接调用,而是在类内使用这个线程函数Routine,但是这里小编便捷点就不设置了
  4. 那么对于这个线程函数Routine来讲,由于小编将其放在了类内,所以第一个参数会有一个this指针,所以这样就和pthread_create中要求的线程函数类型不匹配了,所以我们期望第一个参数是void*类型的变量,所以我们就使用static修改这个线程函数Routine,这样的话这个线程函数Routine就没有this指针了,并且类型匹配,也可以传参pthread_create了
  5. 但是当前的新线程要执行和客户端进行通信的服务就要执行Service,Service这个函数是类内的成员函数,而新线程执行的Routine线程函数是static静态的成员函数,静态的成员函数没有this指针,所以不能够调用普通的成员函数Service,所以呢?我们还要给Routine传参this指针,可是这里Routine传参的不仅仅是this指针,还需要有网络文件描述符sockfd,其次是客户端IP地址,客户端端口号port,那么怎么办呢?类
  6. 所以我们将这些变量封装一个类ThreadData不就好了,那么pthread_create给线程函数传参的时候,直接传参类实例化的对象的指针即可,这样新线程在Routine函数的内部就可以拿到这些变量了,所以下面封装这个类ThreadData,并且将这些变量,this指针,网络文件描述符sockfd,其次是客户端IP地址,客户端端口号port,设置为类ThreadData的成员变量,并且在构造函数中进行初始化即可
  7. 由于小编将ThreadData这个类放在了TcpServer类的前面,所以这里如果在ThreadData的成员变量中直接使用TcpServer*定义类的指针,那么由于编译器编译是从向前找,所以此时就会找不到TcpServer这个类,自然也就无法使用TcpServer这个类定义指针了,所以也就会编译报错了,那么我们使用class TcpServer;提前声明一下即可解决这个问题
  8. 我们知道线程也是需要被等待pthread_join的,但是这里的主线程很明显不想等待,这里的主线程作为服务器想要继续向后执行,然后accept监听其它客户端的连接请求,所以主线程不想等待,那么该如何办呢?很简单线程分离即可
  9. 很明显主线程这里认为等待新线程是一种负担,主线程并不想等待新线程,所以让新线程执行线程函数Routine的时候,上来先pthread_join线程分离即可,这样当新线程退出的时候,系统会自动将新线程回收释放新线程对应的资源,这样主线程就不需要pthread_join新线程了,所以主线程就可以继续accept监听其它客户端的连接请求,一旦收到了连接请求,那么将网络文件描述符sockfd交给新线程去给客户端进行通信即可
  10. 那么此时我们继续看线程函数,线程分离之后,然后进行参数转换为ThreadData的指针td,然后就可以通过td找到this指针然后传入对应的参数调用Service函数让新线程去和客户端通信了
  11. 当Service函数执行完毕,那么此时就可以close关闭网络文件描述符sockfd了,注意这里的td指针也是new出来的,所以为了避免内存泄漏,这里我们要及时的对td指针delete释放,最后线程函数需要一个返回值,这里我们返回nullptr即可
  12. 这里注意,由于我们引入了线程,所以在服务器进行编译的时候要添加-lpthread选项,以便编译器找到pthread库所在的位置进行链接
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;
};
static void* Routine(void* args)
{
    pthread_detach(pthread_self());

    ThreadData* td = static_cast<ThreadData*>(args);
    td->tsvr->Service(td->sockfd, td->clientip, td->clientport);

    close(td->sockfd);
    delete td;

    return nullptr;
}
void StartServer()
{
    lg(Info, "tcpserver is running...");

    for (;;)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        
        int sockfd = accept(listensock_, (struct sockaddr*)&client, &len);
        if (sockfd < 0)
        {
            lg(Warning, "accept error, errno: %d, errstring: %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));

        lg(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d", 
            sockfd, clientip, clientport);
        //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);
        //     //grandchild
        //     Service(sockfd, clientip, clientport);
        //     close(sockfd);

        //     exit(0);
        // }
        // close(sockfd);
        // pid_t rid = waitpid(id, nullptr, 0);
        // if(rid == id)
        //     std::cout << "father wait success" << std::endl;

        //version 3 多线程版
        ThreadData* td = new ThreadData(sockfd, clientip, clientport, this);

        pthread_t tid;
        pthread_create(&tid, nullptr, Routine, td);

    }
}
测试
  1. 所以我们接下来进行编译服务器与客户端的代码,然后运行服务器
    在这里插入图片描述
  2. 接下来运行第一个客户端,并且进行通信,无误
    在这里插入图片描述
  3. 接下来运行第二个客户端,然后进行通信,无误
    在这里插入图片描述
  4. 然后ctrl+c退出第一个客户端,退出第二个客户端,无误
    在这里插入图片描述
  5. 最后ctrl+c退出服务端,无误
    在这里插入图片描述
  6. 忘记观察系统中的轻量级线程了,那么小编将服务器和客户端重新运行起来,所以我们使用如下执行查看一下系统中的线程,可以看到tcpserver对应的的确有三个线程正在运行,一个主线程,两个新线程,其中这两个新线程对应负责两个客户端进程,无误
ps -aL | head -1 && ps -aL | grep tcp

在这里插入图片描述

三、源代码

makefile

all:tcpserver tcpclient

tcpserver:Main.cc
	g++ -o $@ $^ -std=c++11 -lpthread
tcpclient:TcpClient.cc
	g++ -o $@ $^ -std=c++11

.PHONT:clean
clean:
	rm -f tcpserver tcpclient

TcpServer.hpp

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include "Log.hpp"

const int defaultfd = -1;
const uint16_t defaultport = 8080;
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 = defaultport, 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, "create socket error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(SocketError);
        }
        lg(Info, "create socket success, listensock_: %d", listensock_);

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

        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(port_);
        inet_aton(ip_.c_str(), &(server.sin_addr));
        socklen_t len = sizeof(server);

        if (bind(listensock_, (struct sockaddr *)&server, len) < 0)
        {
            lg(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(BindError);
        }
        lg(Info, "bind socket success, listensock_: %d", listensock_);

        if (listen(listensock_, backlog) < 0)
        {
            lg(Fatal, "listen error, errno: %d, errstring: %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);

        close(td->sockfd);
        delete td;

        return nullptr;
    }

    void StartServer()
    {
        lg(Info, "tcpserver is running...");

        for (;;)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            
            int sockfd = accept(listensock_, (struct sockaddr*)&client, &len);
            if (sockfd < 0)
            {
                lg(Warning, "accept error, errno: %d, errstring: %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));

            lg(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d", 
                sockfd, clientip, clientport);
            //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);
            //     //grandchild
            //     Service(sockfd, clientip, clientport);
            //     close(sockfd);

            //     exit(0);
            // }
            // close(sockfd);
            // pid_t rid = waitpid(id, nullptr, 0);
            // if(rid == id)
            //     std::cout << "father wait success" << std::endl;

            //version 3 多线程版
            ThreadData* td = new ThreadData(sockfd, clientip, clientport, this);

            pthread_t tid;
            pthread_create(&tid, nullptr, Routine, td);

        }
    }

    void Service(int sockfd, const std::string& clientip, const uint16_t& clientport)
    {
        while (true)
        {
            char inbuffer[4096];
            ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
            if (n > 0)
            {
                inbuffer[n] = 0;
                std::cout << "client say# " << inbuffer << std::endl;

                std::string echo_string = "tcpserver echo# ";
                echo_string += inbuffer;

                write(sockfd, echo_string.c_str(), echo_string.size());                
            }
            else if(n == 0)
            {
                lg(Info, "%s:%d quit, server colse sockfd: %d", clientip.c_str(), clientport, sockfd);
                break;
            }
            else  
            {
                //异常
                lg(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", 
                    sockfd, clientip.c_str(), clientport);
                break;
            }
        }
    }

    ~TcpServer()
    {
        if (listensock_ > 0)
            close(listensock_);
    }

private:
    int listensock_;
    uint16_t port_;
    std::string ip_;
};

Main.cc

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


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


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

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

    std::unique_ptr<TcpServer> server(new TcpServer(port));
    server->InitServer();
    server->StartServer();


    return 0;
}

TcpCilent.cc

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

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

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

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

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        std::cerr << "socket create err" << std::endl;
        return 1;
    }

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));
    socklen_t len = sizeof(server);
    
    //客户端发起connect请求的时候,操作系统会自动进行端口号的随机bind绑定
    int n = connect(sockfd, (struct sockaddr*)&server, len);
    if(n < 0)
    {
        std::cerr << "connect err..." << std::endl;
        return 2;
    }

    std::string message;
    char inbuffer[4096];
    while(true)
    {
        std::cout << "Please Enter# ";
        std::getline(std::cin, message);

        int n = write(sockfd, message.c_str(), message.size());
        if(n < 0)
        {
            std::cerr << "write err" << std::endl;
            break;
        }

        n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
        if(n > 0)
        {
            inbuffer[n] = 0;
            std::cout << inbuffer << std::endl;
        }
        else
        {
            break;
        }
    }

    close(sockfd);


    return 0;
}

Log.hpp

#pragma once

#include <iostream>
#include <string>
#include <ctime>
#include <cstdio>
#include <cstdarg>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.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;
    }

    ~Log()
    {}

    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 "Fata";
            default:
                return "";
        }
    }

    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[2 * SIZE];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);

        printLog(level, logtxt);
    }

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

        printOneFile(filename, logtxt);
    }


private:
    int printMethod;
    std::string path;
};

Log lg;


总结

以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐