TCP编程简单回显服务

1.认识接口

listen

  • 监测套接字,让套接字随时等待新链接到来。
  • 参数1:被监听的套接字
  • 参数2:链接队列长度

accept

  • 从套接字中获取链接
  • 参数1:绑定监听的套接字
  • 参数2,3:输出型参数,获取客户端套接字信息,
  • 返回值:返回一个文件描述符,该文件描述符是转属于服务端和该链接的套接字文件,服务端和该客户端通信时使用该返回的文件描述符。

connect

  • 客户端建立连接的请求
  • 参数2,3:向谁发起连接的请求->服务端的套接字结构体。
  • 参数1:客户端自己建立的套接字
  • 返回值:0表示成功,-1失败。
  • 该函数作用:1.bind本地socket地址;2.向server发起请求。

2.TCP简单回显服务器代码

cpp 复制代码
#include<iostream>
#include<sys/socket.h>
#include<cstring>
#include<arpa/inet.h>
#include<unistd.h>
#include<netinet/in.h>
#include<string>
#include"Logger.hpp"
#include"InetAddr.hpp"

using namespace NS_LOG_MODULE;

enum
{
    SUCCESS=0,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    USAGE_ERR

};

static const int gbacklog=16;
static const uint16_t gport=8080;


class TcpServer
{
public:
    TcpServer(uint16_t port = gport):
    _port(gport)
    {}
    ~TcpServer(){}
    void  InitServer()
    {
        //TCP:面向字节流
        _listensockfd = socket(AF_INET,SOCK_STREAM,0);//TCP
        if(_listensockfd<0)
        {
            LOG(LogLevel::FATAL)<<"create socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::DEBUG)<<"create socket success: "<<_listensockfd;

        //2.填充本地套接字信息
        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;

        //3.bind
        int n=bind(_listensockfd,(struct sockaddr*)&local,sizeof(local));
        if(n<0)
        {
            LOG(LogLevel::FATAL)<<"socket bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::DEBUG)<<"bind success";

        
        //4.tcp是面向连接的,tcp服务器要处于监听状态
        n=listen(_listensockfd,gbacklog);
        if(n<0)
        {
            LOG(LogLevel::FATAL)<<"listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::DEBUG)<<"listen success";


    }

    void serviceIO(int sockfd,InetAddr &address)
    {
        //Tcp 和udp一样是全双工的
        LOG(LogLevel::DEBUG)<<"client info is:"<<address.ToString();

        while(true)
        {
            char inbuffer[1024] = {0};
            //面向字节流:read write
            //读
            ssize_t n = read(sockfd,inbuffer,sizeof(inbuffer)-1);
            if(n>0)//读取成功
            {
                inbuffer[n]=0;
                LOG(LogLevel::INFO)<<address.ToString()<<" say#"<<inbuffer;
            }
            else if(n==0)//链接断开
            {
                //断开连接,就是客户端把文件描述符关闭
                LOG(LogLevel::INFO)<<"client quit,address: "<<address.ToString();
                break;
            }
            else
            {
                LOG(LogLevel::ERROR)<<"client read error,address: "<<address.ToString();
                break;
            }//n<0读取失败
            //写
            std::string echo_string = "server echo#" + std::string(inbuffer);

            write(sockfd,echo_string.c_str(),echo_string.size());


        }

    }


    void Start()
    {
        while(true)
        {
            //tcp不能直接读数据,要先获取链接
            struct sockaddr_in clientaddr;
            socklen_t len = sizeof(clientaddr);
            int sockfd = accept(_listensockfd,(struct sockaddr*)&clientaddr,&len);
            if(sockfd <0)
            {
                LOG(LogLevel::WARNING)<<"accept error";
                continue;
            }
            LOG(LogLevel::DEBUG)<<"accept success: "<<sockfd;
            
            //6.处理新sockfd
            //version 0 单线程
            InetAddr clientaddress(clientaddr); 
            serviceIO(sockfd,clientaddress);


            
            close(sockfd);
        }
    }
private:
    uint16_t _port;
    //套接字文件描述符
    int _listensockfd;
};
cpp 复制代码
#include"EchoTcpServer.hpp"
#include<memory>

static void Usage(const std::string &proc)
{
    std::cout<<"Usage:\n\t";
    std::cout<<proc<<" Local_port"<<std::endl;
}

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

    ENABLE_CONSOLE_LOG_STRATEGY();
    uint16_t server_port = std::stoi(argv[1]);
    std::unique_ptr<TcpServer> tsvr =std::make_unique<TcpServer>();
    tsvr->InitServer();
    tsvr->Start();

    return 0;
}
cpp 复制代码
#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<cstring>
#include<cstdlib>
#include<string>
#include<unistd.h>
#include"InetAddr.hpp"

static void Usage(const std::string &proc)
{
    std::cout<<"Usage:\n\t";
    std::cout<<proc<<"server_ip server_port"<<std::endl;
}

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

    std::string server_ip =argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    //如果连接失败,重新连接,使用while
    //创建TCP套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd<0)
    {
        std::cerr<<"socket error" <<std::endl;
        exit(2);
    }

    //不能显示bind,会引起端口号冲突,OS会自动bind。
    //OS帮助进行随机端口,防止端口冲突,客户端只需要保证唯一性即可。
    //TCP面向连接,发起建立连接

    InetAddr serveraddress(server_port,server_ip);
    int n=connect(sockfd,(struct sockaddr*)serveraddress.GetNetAddress(),serveraddress.Len());
    if(n<0)
    {
        std::cerr<<"connect to" <<serveraddress.ToString()<<"false";
        exit(3);
    }

    std::cerr<<"connect to" <<serveraddress.ToString()<<"success";
    

    //通信
    while(true)
    {
        std::string line;
        std::cout<<"please enter# ";
        std::getline(std::cin,line);

        write(sockfd,line.c_str(),line.size());

        char inbuffer[1024];
        ssize_t n = read(sockfd,inbuffer,sizeof(inbuffer));
        if(n>0)
        {
            inbuffer[n]=0;
            std::cout<<inbuffer<<std::endl;
        }
        else if(n==0)
        {
            std::cout<<"read enf of file"<<std::endl;
            break;
        }
        else
        {
            std::cerr<<"read error"<<std::endl;
            break;
        }

    }


    return 0;
}
cpp 复制代码
#pragma once

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

class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&_lock, nullptr);
    }
    void Lock()
    {
        pthread_mutex_lock(&_lock);
    }
    pthread_mutex_t *Ptr()
    {
        return &_lock;
    }
    void Unlock()
    {
        pthread_mutex_unlock(&_lock);
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&_lock);
    }
private:
    pthread_mutex_t _lock;
};

class LockGuard // RAII风格代码
{
public:
    LockGuard(Mutex &lock):_lockref(lock)
    {
        _lockref.Lock();
    }
    ~LockGuard()
    {
        _lockref.Unlock();
    }
private:
    Mutex &_lockref;
};
cpp 复制代码
#ifndef __LOGGER_HPP
#define __LOGGER_HPP

#include <iostream>
#include <cstdio>
#include <string>
#include <memory>
#include <sstream>
#include <ctime>
#include <sys/time.h>
#include <unistd.h>
#include <filesystem> // C++17
#include <fstream>
#include "Mutex.hpp"

namespace NS_LOG_MODULE
{
    enum class LogLevel
    {
        INFO,
        WARNING,
        ERROR,
        FATAL,
        DEBUG
    };
    std::string LogLevel2Message(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        case LogLevel::DEBUG:
            return "DEBUG";
        default:
            return "UNKNOWN";
        }
    }

    // 1. 时间戳 2. 日期+时间
    std::string GetCurrentTime()
    {
        struct timeval current_time;
        int n = gettimeofday(&current_time, nullptr);
        (void)n;

        // current_time.tv_sec; current_time.tv_usec;
        struct tm struct_time;
        localtime_r(&(current_time.tv_sec), &struct_time); // r: 可重入函数
        char timestr[128];
        snprintf(timestr, sizeof(timestr), "%04d-%02d-%02d %02d:%02d:%02d.%ld",
                 struct_time.tm_year + 1900,
                 struct_time.tm_mon + 1,
                 struct_time.tm_mday,
                 struct_time.tm_hour,
                 struct_time.tm_min,
                 struct_time.tm_sec,
                 current_time.tv_usec);
        return timestr;
    }

    // 输出角度 -- 刷新策略
    // 1. 显示器打印
    // 2. 文件写入
    // 策略模式,策略接口
    class LogStrategy
    {
    public:
        virtual ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };
    // 控制台日志刷新策略, 日志将来要向显示器打印
    class ConsoleStrategy : public LogStrategy
    {
    public:
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cerr << message << std::endl; // ??
        }
        ~ConsoleStrategy()
        {
        }

    private:
        Mutex _mutex;
    };

    const std::string defaultpath = "./log";
    const std::string defaultfilename = "log.txt";


    // 文件策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &name = defaultfilename)
            : _logpath(path),
              _logfilename(name)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_logpath))
                return;
            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }

        void SyncLog(const std::string &message) override
        {
            {
                LockGuard lockguard(_mutex);
                if (!_logpath.empty() && _logpath.back() != '/')
                {
                    _logpath += "/";
                }
                std::string targetlog = _logpath + _logfilename; // "./log/log.txt"
                std::ofstream out(targetlog, std::ios::app);     // 追加方式写入
                if (!out.is_open())
                {
                    std::cerr << "open " << targetlog << "failed" << std::endl;
                    return;
                }
                out << message << "\n";
                out.close();
            }
        }

        ~FileLogStrategy()
        {
        }

    private:
        std::string _logpath;
        std::string _logfilename;
        Mutex _mutex;
    };

    // 交给大家
    // const std::string defaultfilename = "log.info";
    // const std::string defaultfilename = "log.warning";
    // const std::string defaultfilename = "log.fatal";
    // const std::string defaultfilename = "log.error";
    // const std::string defaultfilename = "log.debug";
     // 文件策略&&分日志等级来进行保存
    // class FileLogLevelStrategy : public LogStrategy
    // {
    // public:
    // private:
    // };


    // 日志类:
    // 1. 日志的生成
    // 2. 根据不同的策略,进行刷新
    class Logger
    {
        // 日志的生成:
        // 构建日志字符串
    public:
        Logger()
        {
            UseConsoleStrategy();
        }
        void UseConsoleStrategy()
        {
            _strategy = std::make_unique<ConsoleStrategy>();
        }
        void UseFileStrategy()
        {
            _strategy = std::make_unique<FileLogStrategy>();
        }
        // 内部类, 标识一条完整的日志信息
        //  一条完整的日志信息 = 做半部分固定部分 + 右半部分不固定部分
        //  LogMessage RAII风格的方式,进行刷新
        class LogMessage
        {
        public:
            LogMessage(LogLevel level, std::string &filename, int line, Logger &logger)
                : _level(level),
                  _curr_time(GetCurrentTime()),
                  _pid(getpid()),
                  _filename(filename),
                  _line(line),
                  _logger(logger)
            {
                // 先构建出来左半部分
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << LogLevel2Message(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _filename << "] "
                   << "[" << _line << "] "
                   << " - ";

                _loginfo = ss.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this; // 返回当前LogMessage对象,方便下次继续进行<<
            }

            ~LogMessage()
            {
                if (_logger._strategy)
                {
                    _logger._strategy->SyncLog(_loginfo);
                }
            }

        private:
            LogLevel _level;
            std::string _curr_time;
            pid_t _pid;
            std::string _filename;
            int _line;
            std::string _loginfo; // 一条完整的日志信息

            // 一个引用,引用外部的Logger类对象
            Logger &_logger; // 方便我们后续进行策略式刷新
        };

        // 这里已经不是内部类了
        // 故意采用拷贝LogMessage
        LogMessage operator()(LogLevel level, std::string filename, int line)
        {
            return LogMessage(level, filename, line, *this);
        }

        ~Logger()
        {
        }

    private:
        std::unique_ptr<LogStrategy> _strategy; // 刷新策略
    };

    // 日志对象,全局使用
    Logger logger;

#define ENABLE_CONSOLE_LOG_STRATEGY() logger.UseConsoleStrategy();
#define ENABLE_FILE_LOG_STRATEGY() logger.UseFileStrategy();

#define LOG(level) logger(level, __FILE__, __LINE__)

}

#endif
cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>


// 对客户端进行先描述
class InetAddr
{
public:
    InetAddr(const struct sockaddr_in &address):_address(address), _len(sizeof(address))
    {
        _ip = inet_ntoa(_address.sin_addr);
        _port = ntohs(_address.sin_port);
    }
    InetAddr(uint16_t port, const std::string &ip = "0.0.0.0"):_ip(ip), _port(port)
    {
        bzero(&_address, sizeof(_address));
        _address.sin_family = AF_INET;
        _address.sin_port = htons(_port);                  // h->n
        _address.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1. 字符串ip->4字节IP 2. hton
        _len = sizeof(_address);
    }
    bool operator == (const InetAddr &addr)
    {
        return (this->_ip == addr._ip) && (this->_port == addr._port);
    }
    std::string ToString()
    {
        return "[" + _ip + ":" + std::to_string(_port) + "]";
    }
    InetAddr()
    {}
    struct sockaddr_in *GetNetAddress()
    {
        return &_address;
    }
    socklen_t Len()
    {
        return _len;
    }
    ~InetAddr()
    {}
private:
    // net address
    struct sockaddr_in _address;
    socklen_t _len;
    // host address
    std::string _ip;
    uint16_t _port;
};
  • 当前代码的服务端通信是单线程的,所以只有第一个客户端连接时可以通信,第二个客户端来连接时就会被阻塞。
  • 只有第一个连接断开时,服务器的主线程才会与第二个客户端进行连接通信。

3.回显服务器多进程版本

cpp 复制代码
#include<iostream>
#include<sys/socket.h>
#include<cstring>
#include<arpa/inet.h>
#include<unistd.h>
#include<netinet/in.h>
#include<string>
#include<sys/wait.h>
#include<signal.h>
#include"Logger.hpp"
#include"InetAddr.hpp"

using namespace NS_LOG_MODULE;

enum
{
    SUCCESS=0,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    USAGE_ERR,
    FORK_ERR

};

static const int gbacklog=16;
static const uint16_t gport=8080;


class TcpServer
{
public:
    TcpServer(uint16_t port = gport):
    _port(gport)
    {}
    ~TcpServer(){}
    void  InitServer()
    {
        //TCP:面向字节流
        _listensockfd = socket(AF_INET,SOCK_STREAM,0);//TCP
        if(_listensockfd<0)
        {
            LOG(LogLevel::FATAL)<<"create socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::DEBUG)<<"create socket success: "<<_listensockfd;

        //2.填充本地套接字信息
        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;

        //3.bind
        int n=bind(_listensockfd,(struct sockaddr*)&local,sizeof(local));
        if(n<0)
        {
            LOG(LogLevel::FATAL)<<"socket bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::DEBUG)<<"bind success";

        
        //4.tcp是面向连接的,tcp服务器要处于监听状态
        n=listen(_listensockfd,gbacklog);
        if(n<0)
        {
            LOG(LogLevel::FATAL)<<"listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::DEBUG)<<"listen success";


    }

    void serviceIO(int sockfd,InetAddr &address)
    {
        //Tcp 和udp一样是全双工的
        LOG(LogLevel::DEBUG)<<"client info is:"<<address.ToString();

        while(true)
        {
            char inbuffer[1024] = {0};
            //面向字节流:read write
            //读
            ssize_t n = read(sockfd,inbuffer,sizeof(inbuffer)-1);
            if(n>0)//读取成功
            {
                inbuffer[n]=0;
                LOG(LogLevel::INFO)<<address.ToString()<<" say#"<<inbuffer;
            }
            else if(n==0)//链接断开
            {
                //断开连接,就是客户端把文件描述符关闭
                LOG(LogLevel::INFO)<<"client quit,address: "<<address.ToString();
                break;
            }
            else
            {
                LOG(LogLevel::ERROR)<<"client read error,address: "<<address.ToString();
                break;
            }//n<0读取失败
            //写
            std::string echo_string = "server echo#" + std::string(inbuffer);

            write(sockfd,echo_string.c_str(),echo_string.size());


        }

    }


    void Start()
    {
        //忽略子进程的退出信号,告诉OS,父进程不关心子进程的退出状态,OS会自动清理子进程的资源
        //不会产生僵尸进程,最佳实践。
        // signal(SIGCHLD,SIG_IGN);
        while(true)
        {
            //tcp不能直接读数据,要先获取链接
            struct sockaddr_in clientaddr;
            socklen_t len = sizeof(clientaddr);
            int sockfd = accept(_listensockfd,(struct sockaddr*)&clientaddr,&len);
            if(sockfd <0)
            {
                LOG(LogLevel::WARNING)<<"accept error";
                continue;
            }
            LOG(LogLevel::DEBUG)<<"accept success: "<<sockfd;
            
            //6.处理新sockfd
            //version 0 单线程
            // InetAddr clientaddress(clientaddr); 
            // serviceIO(sockfd,clientaddress);

            //version 1 :多进程版本
            //获取新连接时,创建子进程
            pid_t id =fork();
            if(id<0)
            {
                LOG(LogLevel::FATAL)<<"fork error!";
                exit(FORK_ERR);
            }
            else if(id == 0)
            {
                //子进程
                //子进程不需要listensockfd,所以子进程要关闭
                close(_listensockfd);

                //解决方法二
                //子进程再创建一个子进程,让孙子进程去执行通信任务,
                //子进程直接退出,孙子进程变成孤儿进程,被bash接管,
                //退出后自动被操作系统回收
                if(fork()>0) exit(0);
                InetAddr clientaddress(clientaddr);

                serviceIO(sockfd,clientaddress);
                close(sockfd);//通信结束关闭套接字文件描述符
                exit(0);
                //子进程终止后会处于僵尸状态,父进程还需要wait,父进程被阻塞
                //逻辑又变成串行的(单进程)。
            }
            else
            {
                //父进程
                //父进程只负责监听建立建立连接,不需要去做通信工作
                //文件描述符是有限的且有用的,是一种资源,不用的资源就要尽快释放
                close(sockfd);

                //父进程也不能非阻塞等待,如果非阻塞,父进程识别一次,没有子进程退出
                //,父进程继续执行到accept函数,如果之后没有新连接,子进程退出后还是会一直僵尸状态
                //最佳实践:重定义信号处理,将子进程退出信号忽略
                //子进程创建新进程后直接退出,不会等太长时间
                pid_t rid =waitpid(id,nullptr,0);
                (void)rid;

            }

            close(sockfd);
        }
    }
private:
    uint16_t _port;
    //套接字文件描述符
    int _listensockfd;
};

4.回显服务器多线程版本

cpp 复制代码
#include<iostream>
#include<sys/socket.h>
#include<cstring>
#include<arpa/inet.h>
#include<unistd.h>
#include<netinet/in.h>
#include<string>
#include<sys/wait.h>
#include<signal.h>
#include<pthread.h>
#include<errno.h>
#include"Logger.hpp"
#include"InetAddr.hpp"

using namespace NS_LOG_MODULE;

enum
{
    SUCCESS=0,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    USAGE_ERR,
    FORK_ERR

};

static const int gbacklog=16;
static const uint16_t gport=8080;


class TcpServer
{
public:
    TcpServer(uint16_t port = gport):
    _port(gport)
    {}
    ~TcpServer(){}
    void  InitServer()
    {
        //TCP:面向字节流
        _listensockfd = socket(AF_INET,SOCK_STREAM,0);//TCP
        if(_listensockfd<0)
        {
            LOG(LogLevel::FATAL)<<"create socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::DEBUG)<<"create socket success: "<<_listensockfd;

        //2.填充本地套接字信息
        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;

        //3.bind
        int n=bind(_listensockfd,(struct sockaddr*)&local,sizeof(local));
        if(n<0)
        {
            LOG(LogLevel::FATAL)<<"socket bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::DEBUG)<<"bind success";

        
        //4.tcp是面向连接的,tcp服务器要处于监听状态
        n=listen(_listensockfd,gbacklog);
        if(n<0)
        {
            LOG(LogLevel::FATAL)<<"listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::DEBUG)<<"listen success";


    }

    void serviceIO(int sockfd,InetAddr &address)
    {
        //Tcp 和udp一样是全双工的
        LOG(LogLevel::DEBUG)<<"client info is:"<<address.ToString();

        while(true)
        {
            char inbuffer[1024] = {0};
            //面向字节流:read write
            //读
            ssize_t n = read(sockfd,inbuffer,sizeof(inbuffer)-1);
            if(n>0)//读取成功
            {
                inbuffer[n]=0;
                LOG(LogLevel::INFO)<<address.ToString()<<" say#"<<inbuffer;
            }
            else if(n==0)//链接断开
            {
                //断开连接,就是客户端把文件描述符关闭
                LOG(LogLevel::INFO)<<"client quit,address: "<<address.ToString();
                break;
            }
            else
            {
                LOG(LogLevel::ERROR)<<"client read error,address: "<<address.ToString();
                perror("info:");
                break;
            }//n<0读取失败
            //写
            std::string echo_string = "server echo#" + std::string(inbuffer);

            write(sockfd,echo_string.c_str(),echo_string.size());


        }

    }

    class ThreadData
    {
    public:
        ThreadData(TcpServer* ts,int sockfd,InetAddr addr)
        :_this(ts),
        _sockfd(sockfd),
        _addr(addr)
        {
        }
        ~ThreadData()
        {}
    public:
        TcpServer* _this;
        int _sockfd;
        InetAddr _addr;
    };

    //线程的回调函数返回值void*,参数void*.
    //静态的就不会有this指针,但是没有this指针,没办法调用成员函数
    //解决:结构体传参,定义内部类
    static void* thread_routine(void* args)
    {
        ThreadData* td = static_cast<ThreadData*>(args);
        pthread_detach(pthread_self());
        td->_this->serviceIO(td->_sockfd,td->_addr);
        delete td;
        return nullptr;
    }

    void Start()
    {
        //忽略子进程的退出信号,告诉OS,父进程不关心子进程的退出状态,OS会自动清理子进程的资源
        //不会产生僵尸进程,最佳实践。
        // signal(SIGCHLD,SIG_IGN);
        while(true)
        {
            //tcp不能直接读数据,要先获取链接
            struct sockaddr_in clientaddr;
            socklen_t len = sizeof(clientaddr);
            int sockfd = accept(_listensockfd,(struct sockaddr*)&clientaddr,&len);
            if(sockfd <0)
            {
                LOG(LogLevel::WARNING)<<"accept error";
                continue;
            }
            LOG(LogLevel::DEBUG)<<"accept success: "<<sockfd;
            
            //6.处理新sockfd
            //version 0 单线程
            // InetAddr clientaddress(clientaddr); 
            // serviceIO(sockfd,clientaddress);

            //version 1 :多进程版本
            //获取新连接时,创建子进程
            // pid_t id =fork();
            // if(id<0)
            // {
            //     LOG(LogLevel::FATAL)<<"fork error!";
            //     exit(FORK_ERR);
            // }
            // else if(id == 0)
            // {
            //     //子进程
            //     //子进程不需要listensockfd,所以子进程要关闭
            //     close(_listensockfd);

            //     //解决方法二
            //     //子进程再创建一个子进程,让孙子进程去执行通信任务,
            //     //子进程直接退出,孙子进程变成孤儿进程,被bash接管,
            //     //退出后自动被操作系统回收
            //     if(fork()>0) exit(0);
            //     InetAddr clientaddress(clientaddr);

            //     serviceIO(sockfd,clientaddress);
            //     close(sockfd);//通信结束关闭套接字文件描述符
            //     exit(0);
            //     //子进程终止后会处于僵尸状态,父进程还需要wait,父进程被阻塞
            //     //逻辑又变成串行的(单进程)。
            // }
            // else
            // {
            //     //父进程
            //     //父进程只负责监听建立建立连接,不需要去做通信工作
            //     //文件描述符是有限的且有用的,是一种资源,不用的资源就要尽快释放
            //     close(sockfd);

            //     //父进程也不能非阻塞等待,如果非阻塞,父进程识别一次,没有子进程退出
            //     //,父进程继续执行到accept函数,如果之后没有新连接,子进程退出后还是会一直僵尸状态
            //     //最佳实践:重定义信号处理,将子进程退出信号忽略
            //     //子进程创建新进程后直接退出,不会等太长时间
            //     pid_t rid =waitpid(id,nullptr,0);
            //     (void)rid;

            // }
            //多进程成本太高,地址空间,页表都需要拷贝


            //version2:多线程版本
            pthread_t tid;
            InetAddr clientaddress(clientaddr);
            ThreadData* td=new ThreadData(this,sockfd,clientaddress);
            pthread_create(&tid,nullptr,thread_routine,(void*)td);
            //线程把自己设置为分离状态,主线程就不用join了


            // close(sockfd);
        }
    }
private:
    uint16_t _port;
    //套接字文件描述符
    int _listensockfd;
};

4.1短服务

cpp 复制代码
#include <iostream>
#include <sys/socket.h>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#include "Logger.hpp"
#include "InetAddr.hpp"

using namespace NS_LOG_MODULE;

enum
{
    SUCCESS = 0,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    USAGE_ERR,
    FORK_ERR

};

static const int gbacklog = 16;
static const uint16_t gport = 8080;

class TcpServer
{
public:
    TcpServer(uint16_t port = gport) : _port(gport)
    {
    }
    ~TcpServer() {}
    void InitServer()
    {
        // TCP:面向字节流
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0); // TCP
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "create socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::DEBUG) << "create socket success: " << _listensockfd;

        // 2.填充本地套接字信息
        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;

        // 3.bind
        int n = bind(_listensockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "socket bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::DEBUG) << "bind success";

        // 4.tcp是面向连接的,tcp服务器要处于监听状态
        n = listen(_listensockfd, gbacklog);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::DEBUG) << "listen success";
    }

    void serviceIO(int sockfd, InetAddr &address)
    {
        // Tcp 和udp一样是全双工的
        // 长服务,长连接,小型应用
        //  LOG(LogLevel::DEBUG)<<"client info is:"<<address.ToString();

        // while(true)
        // {
        //     char inbuffer[1024] = {0};
        //     //面向字节流:read write
        //     //读
        //     ssize_t n = read(sockfd,inbuffer,sizeof(inbuffer)-1);
        //     if(n>0)//读取成功
        //     {
        //         inbuffer[n]=0;
        //         LOG(LogLevel::INFO)<<address.ToString()<<" say#"<<inbuffer;
        //     }
        //     else if(n==0)//链接断开
        //     {
        //         //断开连接,就是客户端把文件描述符关闭
        //         LOG(LogLevel::INFO)<<"client quit,address: "<<address.ToString();
        //         break;
        //     }
        //     else
        //     {
        //         LOG(LogLevel::ERROR)<<"client read error,address: "<<address.ToString();
        //         perror("info:");
        //         break;
        //     }//n<0读取失败
        //     //写
        //     std::string echo_string = "server echo#" + std::string(inbuffer);

        //     write(sockfd,echo_string.c_str(),echo_string.size());

        // }

        // 短服务
        char inbuffer[1024] = {0};
        // 面向字节流:read write
        // 读
        ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
        if (n > 0) // 读取成功
        {
            inbuffer[n] = 0;
            LOG(LogLevel::INFO) << address.ToString() << " say#" << inbuffer;
            std::string echo_string = "server echo#" + std::string(inbuffer);
            write(sockfd,echo_string.c_str(),echo_string.size());
        }
        else if (n == 0) // 链接断开
        {
            // 断开连接,就是客户端把文件描述符关闭
            LOG(LogLevel::INFO) << "client quit,address: " << address.ToString();
        }
        else
        {
            LOG(LogLevel::ERROR) << "client read error,address: " << address.ToString();
            perror("info:");
           
        } // n<0读取失败

       
        
    }

    class ThreadData
    {
    public:
        ThreadData(TcpServer *ts, int sockfd, InetAddr addr)
            : _this(ts),
              _sockfd(sockfd),
              _addr(addr)
        {
        }
        ~ThreadData()
        {
        }

    public:
        TcpServer *_this;
        int _sockfd;
        InetAddr _addr;
    };

    // 线程的回调函数返回值void*,参数void*.
    // 静态的就不会有this指针,但是没有this指针,没办法调用成员函数
    // 解决:结构体传参,定义内部类
    static void *thread_routine(void *args)
    {
        ThreadData *td = static_cast<ThreadData *>(args);
        pthread_detach(pthread_self());
        td->_this->serviceIO(td->_sockfd, td->_addr);
        close(td->_sockfd);
        delete td;
        return nullptr;
    }

    void Start()
    {
        // 忽略子进程的退出信号,告诉OS,父进程不关心子进程的退出状态,OS会自动清理子进程的资源
        // 不会产生僵尸进程,最佳实践。
        //  signal(SIGCHLD,SIG_IGN);
        while (true)
        {
            // tcp不能直接读数据,要先获取链接
            struct sockaddr_in clientaddr;
            socklen_t len = sizeof(clientaddr);
            int sockfd = accept(_listensockfd, (struct sockaddr *)&clientaddr, &len);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }
            LOG(LogLevel::DEBUG) << "accept success: " << sockfd;

            // 6.处理新sockfd
            // version 0 单线程
            //  InetAddr clientaddress(clientaddr);
            //  serviceIO(sockfd,clientaddress);

            // version 1 :多进程版本
            // 获取新连接时,创建子进程
            //  pid_t id =fork();
            //  if(id<0)
            //  {
            //      LOG(LogLevel::FATAL)<<"fork error!";
            //      exit(FORK_ERR);
            //  }
            //  else if(id == 0)
            //  {
            //      //子进程
            //      //子进程不需要listensockfd,所以子进程要关闭
            //      close(_listensockfd);

            //     //解决方法二
            //     //子进程再创建一个子进程,让孙子进程去执行通信任务,
            //     //子进程直接退出,孙子进程变成孤儿进程,被bash接管,
            //     //退出后自动被操作系统回收
            //     if(fork()>0) exit(0);
            //     InetAddr clientaddress(clientaddr);

            //     serviceIO(sockfd,clientaddress);
            //     close(sockfd);//通信结束关闭套接字文件描述符
            //     exit(0);
            //     //子进程终止后会处于僵尸状态,父进程还需要wait,父进程被阻塞
            //     //逻辑又变成串行的(单进程)。
            // }
            // else
            // {
            //     //父进程
            //     //父进程只负责监听建立建立连接,不需要去做通信工作
            //     //文件描述符是有限的且有用的,是一种资源,不用的资源就要尽快释放
            //     close(sockfd);

            //     //父进程也不能非阻塞等待,如果非阻塞,父进程识别一次,没有子进程退出
            //     //,父进程继续执行到accept函数,如果之后没有新连接,子进程退出后还是会一直僵尸状态
            //     //最佳实践:重定义信号处理,将子进程退出信号忽略
            //     //子进程创建新进程后直接退出,不会等太长时间
            //     pid_t rid =waitpid(id,nullptr,0);
            //     (void)rid;

            // }
            // 多进程成本太高,地址空间,页表都需要拷贝

            // version2:多线程版本
            pthread_t tid;
            InetAddr clientaddress(clientaddr);
            ThreadData *td = new ThreadData(this, sockfd, clientaddress);
            pthread_create(&tid, nullptr, thread_routine, (void *)td);
            // 线程把自己设置为分离状态,主线程就不用join了

            // close(sockfd);
        }
    }

private:
    uint16_t _port;
    // 套接字文件描述符
    int _listensockfd;
};

4.2接入线程池

cpp 复制代码
#include <iostream>
#include <sys/socket.h>
#include <cstring>
#include<functional>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#include"ThreadPool.hpp"
#include "Logger.hpp"
#include "InetAddr.hpp"

using namespace NS_LOG_MODULE;
using namespace NS_THREAD_POOL_MODULE;

using task_t = std::function<void()>; 

enum
{
    SUCCESS = 0,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    USAGE_ERR,
    FORK_ERR

};

static const int gbacklog = 16;
static const uint16_t gport = 8080;

class TcpServer
{
public:
    TcpServer(uint16_t port = gport) : _port(gport)
    {
    }
    ~TcpServer() {}
    void InitServer()
    {
        // TCP:面向字节流
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0); // TCP
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "create socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::DEBUG) << "create socket success: " << _listensockfd;

        // 2.填充本地套接字信息
        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;

        // 3.bind
        int n = bind(_listensockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "socket bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::DEBUG) << "bind success";

        // 4.tcp是面向连接的,tcp服务器要处于监听状态
        n = listen(_listensockfd, gbacklog);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::DEBUG) << "listen success";
    }

    void serviceIO(int sockfd, InetAddr address)
    {
        // 短服务
        char inbuffer[1024] = {0};
        // 面向字节流:read write
        // 读
        ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
        if (n > 0) // 读取成功
        {
            inbuffer[n] = 0;
            LOG(LogLevel::INFO) << address.ToString() << " say#" << inbuffer;
            std::string echo_string = "server echo#" + std::string(inbuffer);
            write(sockfd,echo_string.c_str(),echo_string.size());
        }
        else if (n == 0) // 链接断开
        {
            // 断开连接,就是客户端把文件描述符关闭
            LOG(LogLevel::INFO) << "client quit,address: " << address.ToString();
        }
        else
        {
            LOG(LogLevel::ERROR) << "client read error,address: " << address.ToString();
            perror("info:");
           
        } // n<0读取失败

       close(sockfd);
        
    }

    class ThreadData
    {
    public:
        ThreadData(TcpServer *ts, int sockfd, InetAddr addr)
            : _this(ts),
              _sockfd(sockfd),
              _addr(addr)
        {
        }
        ~ThreadData()
        {
        }

    public:
        TcpServer *_this;
        int _sockfd;
        InetAddr _addr;
    };

    // 线程的回调函数返回值void*,参数void*.
    // 静态的就不会有this指针,但是没有this指针,没办法调用成员函数
    // 解决:结构体传参,定义内部类
    static void *thread_routine(void *args)
    {
        ThreadData *td = static_cast<ThreadData *>(args);
        pthread_detach(pthread_self());
        td->_this->serviceIO(td->_sockfd, td->_addr);
        close(td->_sockfd);
        delete td;
        return nullptr;
    }

    void Start()
    {
        // 忽略子进程的退出信号,告诉OS,父进程不关心子进程的退出状态,OS会自动清理子进程的资源
        // 不会产生僵尸进程,最佳实践。
        //  signal(SIGCHLD,SIG_IGN);
        while (true)
        {
            // tcp不能直接读数据,要先获取链接
            struct sockaddr_in clientaddr;
            socklen_t len = sizeof(clientaddr);
            int sockfd = accept(_listensockfd, (struct sockaddr *)&clientaddr, &len);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }
            LOG(LogLevel::DEBUG) << "accept success: " << sockfd;
            
            //version3:线程池
            InetAddr clientaddress(clientaddr);
            ThreadPool<task_t>::Instance()->Enqueue([this,sockfd,clientaddress]()->void{
                this->serviceIO(sockfd,clientaddress);
            });

        }
    }

private:
    uint16_t _port;
    // 套接字文件描述符
    int _listensockfd;
};

​​​​​​​linux线程-CSDN博客

线程池代码位置

相关推荐
姜行运2 小时前
【Linux】基础指令2
android·linux·服务器
比奇堡派星星2 小时前
sed命令
linux·运维·服务器·开发语言
程序员zgh3 小时前
Linux 内存管理单元 MMU
linux·运维·服务器·c语言·开发语言·c++
Trouvaille ~3 小时前
【Linux】TCP协议基础与连接管理详解:从三次握手到四次挥手
linux·运维·服务器·网络·c++·网络协议·tcp/ip
njmanong4 小时前
Google点名处置IPIDEA及子品牌:代理IP行业进入强治理期
网络·网络协议·tcp/ip
终生成长者4 小时前
Kubernetes常用操作与概念总结--从服务器导出mongo数据,并下载到本地
服务器·容器·kubernetes
UP_Continue4 小时前
Linux--动静态库
linux·运维·服务器
CheungChunChiu4 小时前
Linux 音频系统全景解析:PipeWire、PulseAudio 与 ALSA 的层次关系
linux·运维·服务器·audio
君陌社区·网络安全防护中心4 小时前
通过OVSDB管理交换机
网络