【Linux网络编程】3. Socket编程 TCP

文章目录

一、函数介绍

1、TCP 收发数据

1)send 函数

2)recv 函数

2、进程管道操作

1)popen 函数

2)pclose 函数

二、EchoServer

1、多进程

代码结构:

c++ 复制代码
Mutex.hpp
Cond.hpp 
Log.hpp
Thread.hpp  
ThreadPool.hpp 
Common.hpp
InetAddr.hpp
TcpServer.hpp  
TcpServer.cc
TcpClient.cc

1)Mutex.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <pthread.h>

namespace MutexModule
{
    // 互斥锁封装类
    class Mutex
    {
    public:
        // 初始化锁
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }

        // 加锁
        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex);
        }

        // 解锁
        void Unlock()
        {
            int n = pthread_mutex_unlock(&_mutex);
        }

        // 获取锁指针
        pthread_mutex_t *Get()
        {
            return &_mutex;
        }

        // 销毁锁
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }

    private:
        pthread_mutex_t _mutex; // 原生互斥锁对象
    };

    // RAII 自动锁
    class LockGuard
    {
    public:
        LockGuard(Mutex &mutex)
            : _mutex(mutex)
        {
            _mutex.Lock();
        }

        ~LockGuard()
        {
            _mutex.Unlock();
        }

    private:
        Mutex &_mutex;
    };
}

2)Cond.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <pthread.h>
#include "Mutex.hpp"

using namespace MutexModule;

namespace CondModule
{
    // 条件变量封装
    class Cond
    {
    public:
        Cond()
        {
            pthread_cond_init(&_cond, nullptr);
        }

        // 等待: 阻塞并释放锁
        void Wait(Mutex &mutex)
        {
            int n = pthread_cond_wait(&_cond, mutex.Get());
        }

        // 唤醒一个等待线程
        void Signal() 
        {
            int n = pthread_cond_signal(&_cond);
        }

        // 唤醒所有等待线程
        void Broadcast() 
        {
            int n = pthread_cond_broadcast(&_cond);
        }

        ~Cond()
        {
            pthread_cond_destroy(&_cond);
        }
    private:
        pthread_cond_t _cond; // 底层条件变量
    };
}

3)Log.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <memory>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "Mutex.hpp"

namespace LogModule
{
    using namespace MutexModule;

    const std::string gsep = "\r\n";

    // 日志策略基类(接口)
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 控制台日志输出(线程安全)
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cout << message << gsep;
        }

        ~ConsoleLogStrategy() {}

    private:
        Mutex _mutex;
    };

    const std::string defaultpath = "./log/"; //   /var/log
    const std::string defaultfile = "my.log";

    // 文件日志输出(自动建目录、线程安全)
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path), _file(file)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_path))
                return;

            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e) 
            {
                std::cerr << e.what() << std::endl;
            }
        }

        // 追加写入日志文件
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            std::ofstream out(filename, std::ios::app);
            if (!out.is_open())
                return;

            out << message << gsep;
            out.close();
        }

    private:
        std::string _path;
        std::string _file;
        Mutex _mutex;
    };

    // 日志等级类
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    // 日志等级转字符串
    std::string LevelStr(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }

    // 获取格式化时间字符串(线程安全)
    std::string GetTimeStamp()
    {
        time_t curr = time(nullptr);
        struct tm curr_tm; // 出参
        localtime_r(&curr, &curr_tm);
        char buf[128]; // 出参
        snprintf(buf, sizeof(buf), "%4d-%02d-%02d %02d:%02d:%02d",
                 curr_tm.tm_year + 1900,
                 curr_tm.tm_mon + 1,
                 curr_tm.tm_mday,
                 curr_tm.tm_hour,
                 curr_tm.tm_min,
                 curr_tm.tm_sec);
        return buf;
    }

    // 日志核心管理类
    class Logger 
    {
    public:
        Logger()
        {
            EnableConsoleLogStrategy();
        }

        // 切换为文件输出
        void EnableFileLogStrategy()
        {
            _fflush_strategy = std::make_unique<FileLogStrategy>();
        }

        // 切换为控制台输出
        void EnableConsoleLogStrategy()
        {
            _fflush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        // 日志消息构造: 负责拼接内容, 析构时自动输出
        class LogMessage
        {
        public:
            // 构造日志头部(时间、等级、进程ID、文件名、行号)
            LogMessage(LogLevel level, std::string src_name, int line_number, Logger &logger)
                :  _logger(logger) 
            {
                std::stringstream ss;
                ss << "[" << GetTimeStamp() << "] "
                   << "[" << LevelStr(level) << "] "
                   << "[" << getpid() << "] "
                   << "[" << src_name << "] "
                   << "[" << line_number << "] - ";
                _loginfo = ss.str(); 
            }

            // 流方式拼接日志内容
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                std::stringstream ss;
                ss << info;           
                _loginfo += ss.str(); 
                return *this;         
            }

            // 析构自动输出日志
            ~LogMessage()
            {
                if (_logger._fflush_strategy) 
                {
                    _logger._fflush_strategy->SyncLog(_loginfo);
                }
            }

        private:     
            std::string _loginfo;   
            Logger &_logger;        
        };

        // 仿函数接口, 创建日志消息
        LogMessage operator()(LogLevel level, std::string name, int line)
        {
            return LogMessage(level, name, line, *this);
        }

    private:
        std::unique_ptr<LogStrategy> _fflush_strategy;
    };

    Logger logger; 

// 简化调用宏
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}

4)Thread.hpp

cpp 复制代码
#pragma once

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

namespace ThreadModule
{
    // 全局线程编号(自动命名)
    static uint32_t number = 1;

    class Thread
    {
        using func_t = std::function<void()>; // 线程执行函数类型

    private:
        // 标记线程已分离
        void EnableDetach()
        {
            _isdetach = true;
        }

        // 标记线程运行中
        void EnableRunning()
        {
            _isrunning = true;
        }

        // 线程入口(必须static)
        static void *Routine(void *args)
        {
            Thread *self = static_cast<Thread *>(args);
            self->EnableRunning();

            // 分离线程
            if (self->_isdetach)
                self->Detach();

            // 设置线程名
            pthread_setname_np(self->_tid, self->_name.c_str());

            // 执行用户任务
            self->_func();
            return nullptr;
        }

    public:
        // 构造:传入回调,自动生成线程名
        Thread(func_t func)
            : _tid(0),
              _isdetach(false),
              _isrunning(false),
              res(nullptr),
              _func(func)
        {
            _name = "thread-" + std::to_string(number++);
        }

        // 分离线程(自动回收)
        void Detach()
        {
            if (_isdetach || !_isrunning) return;
            pthread_detach(_tid);
            _isdetach = true;
        }

        // 创建并启动线程
        bool Start()
        {
            if (_isrunning) return false;

            int n = pthread_create(&_tid, nullptr, Routine, this);
            if (n == 0)
            {
                std::cout << _name << " create success\n";
                return true;
            }
            std::cerr << "create error: " << strerror(n) << "\n";
            return false;
        }

        // 取消线程
        bool Stop()
        {
            if (!_isrunning) return false;

            int n = pthread_cancel(_tid);
            if (n == 0)
            {
                _isrunning = false;
                std::cout << _name << " stop\n";
                return true;
            }
            std::cerr << "cancel error: " << strerror(n) << "\n";
            return false;
        }

        // 等待线程结束
        void Join()
        {
            if (_isdetach)
            {
                std::cout << "线程已分离,无法join\n";
                return;
            }
            pthread_join(_tid, &res);
            std::cout << "join success\n";
        }

        // 获取线程名
        std::string Name()
        {
            return _name;
        }

        ~Thread() {}

    private:
        pthread_t _tid;      // 线程ID
        std::string _name;   // 线程名
        bool _isdetach;      // 是否分离
        bool _isrunning;     // 是否运行
        void *res;           // 线程返回值
        func_t _func;        // 线程执行函数
    };
}

5)ThreadPool.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <vector>
#include <queue>
#include "Mutex.hpp"
#include "Cond.hpp"
#include "Log.hpp"
#include "Thread.hpp"

namespace ThreadPoolModule
{
    using namespace MutexModule;
    using namespace LogModule;
    using namespace CondModule;
    using namespace ThreadModule;

    const int gnum = 5;

    template <typename T>
    class ThreadPool
    {
    private:
        // 唤醒所有休眠线程
        void WakeUpAllThread()
        {
            {
                LockGuard lockguard(_mutex);
                if (_sleepernum)
                    _cond.Broadcast();
                LOG(LogLevel::INFO) << "唤醒所有休眠线程";
            }
        }

        // 唤醒一个休眠线程
        void WakeUpOne()
        {
            _cond.Signal();
            LOG(LogLevel::INFO) << "唤醒一个休眠线程";
        }

        // 构造: 创建指定数量的线程
        ThreadPool(int num = gnum)
            : _num(num),
              _isrunning(false),
              _sleepernum(0)
        {
            for (int i = 0; i < num; i++)
            {
                _threads.emplace_back([this]()
                                      { HandlerTask(); });
            }
        }

        // 启动线程池
        void Start()
        {
            if (_isrunning)
                return;

            _isrunning = true;
            for (auto &thread : _threads)
            {
                thread.Start();
                LOG(LogLevel::INFO) << "start new thread success: " << thread.Name();
            }
        }

    public:
        // 禁用拷贝与赋值
        ThreadPool(const ThreadPool<T> &) = delete;
        ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

        // 获取单例实例
        static ThreadPool<T> *GetInstance()
        {
            if (inc == nullptr)
            {
                LockGuard lockguard(_lock);
                LOG(LogLevel::DEBUG) << "获取单例...";
                if (inc == nullptr)
                {
                    LOG(LogLevel::DEBUG) << "首次使用单例,创建之...";
                    inc = new ThreadPool<T>();
                    inc->Start();
                }
            }
            return inc;
        }

        // 停止线程池
        void Stop()
        {
            if (!_isrunning)
                return;

            _isrunning = false;
            WakeUpAllThread();
        }

        // 等待所有线程退出
        void Join()
        {
            for (auto &thread : _threads)
            {
                thread.Join();
            }
        }

        // 线程处理任务
        void HandlerTask()
        {
            char name[128]; // 出参
            pthread_getname_np(pthread_self(), name, sizeof(name));

            while (true)
            {
                T t;
                {
                    LockGuard lockguard(_mutex);

                    // 任务队列为空, 等待任务
                    while (_taskq.empty() && _isrunning)
                    {
                        _sleepernum++;
                        _cond.Wait(_mutex); // 释放锁并休眠,被唤醒后重新获取锁
                        _sleepernum--;
                    }

                     // 线程退出条件
                    if (!_isrunning && _taskq.empty())
                    {
                        LOG(LogLevel::INFO) << name << " 退出了, 线程池退出&&任务队列为空";
                        break;
                    }

                    // 取出任务
                    t = _taskq.front();
                    _taskq.pop();
                }

                // 执行任务
                t();
            }
        }

        // 任务入队
        bool Enqueue(const T &in)
        {
            if (_isrunning)
            {
                {
                    LockGuard lockguard(_mutex);
                    _taskq.push(in);
                }

                // 所有线程都在休眠, 唤醒一个
                if (_threads.size() == _sleepernum)
                    WakeUpOne();

                return true;
            }
            return false;
        }

        ~ThreadPool() = default;

    private:
        std::vector<Thread> _threads; // 线程数组
        int _num;                     // 线程数量
        std::queue<T> _taskq;         // 任务队列
        Cond _cond;                   // 条件变量
        Mutex _mutex;                 // 互斥锁

        bool _isrunning; // 运行状态
        int _sleepernum; // 休眠线程数

        static ThreadPool<T> *inc; // 单例指针
        static Mutex _lock;        // 单例创建锁
    };

    // 静态成员初始化
    template <typename T>
    ThreadPool<T> *ThreadPool<T>::inc = nullptr;

    template <typename T>
    Mutex ThreadPool<T>::_lock;
}

6)Common.hpp

cpp 复制代码
#pragma once

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

// 程序退出状态码枚举
enum ExitCode
{
    OK = 0,      // 正常退出
    USAGE_ERR,   // 参数错误
    SOCKET_ERR,  // 创建socket失败
    BIND_ERR,    // 绑定失败
    LISTEN_ERR,  // 监听失败
    CONNECT_ERR, // 连接服务器失败
    FORK_ERR,    // 创建进程失败
    OPEN_ERR     // 打开文件失败
};


// 禁止拷贝基类: 继承后无法拷贝/赋值
class NoCopy
{
public:
    NoCopy() {}
    ~NoCopy() {}

    NoCopy(const NoCopy &) = delete;                  
    NoCopy &operator=(const NoCopy &) = delete;
};

// 地址类型转换宏: sockaddr_in → sockaddr*
#define CONV(addr) ((struct sockaddr *)&addr)

7)InetAddr.hpp

cpp 复制代码
#pragma once
#include "Common.hpp"

// IPv4地址封装: 主机格式 <-> 网络格式
class InetAddr
{
public:
    // 构造: 网络地址 -> 主机格式
    InetAddr(struct sockaddr_in &addr)
        : _addr(addr)
    {
        _port = ntohs(_addr.sin_port); // port
        char ipbuffer[64];             // ip
        inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
        _ip = ipbuffer;
    }

    // 构造:指定IP+端口 → 网络格式
    InetAddr(const std::string &ip, uint16_t port)
        : _ip(ip), _port(port)
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr); // ip
        _addr.sin_port = htons(_port);                    // port
    }

    // 构造:仅端口, 绑定本机所有IP
    InetAddr(uint16_t port)
        : _port(port), _ip()
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        _addr.sin_addr.s_addr = INADDR_ANY; // ip
        _addr.sin_port = htons(_port);      // port
    }
		
	// 设置网络地址信息
    void SetAddr(struct sockaddr_in &addr)
    {
        _addr = addr; 
        _port = ntohs(_addr.sin_port); 

        char ipbuffer[64];
        inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
        _ip = ipbuffer;
    }
    
    // 获取点分十进制IP
    std::string Ip() { return _ip; }

    // 获取主机字节序端口
    uint16_t Port() { return _port; }

    // 获取原生网络地址结构体
    const struct sockaddr_in &NetAddr() { return _addr; }

    // 获取通用地址指针(用于系统调用)
    const struct sockaddr *NetAddrPtr() { return CONV(_addr); }

    // 获取地址长度
    socklen_t NetAddrLen() { return sizeof(_addr); }

    // 比较两个地址是否相同
    bool operator==(const InetAddr &addr)
    {
        return addr._ip == _ip && addr._port == _port;
    }

    // 转为 ip:port 格式字符串
    std::string StringAddr()
    {
        return _ip + ":" + std::to_string(_port);
    }

    ~InetAddr() {}

private:
    struct sockaddr_in _addr; // 网络字节序地址
    std::string _ip;          // 点分十进制IP
    uint16_t _port;           // 主机字节序端口
};

8)TcpServer.hpp(多进程版)

cpp 复制代码
#pragma once
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

using namespace LogModule;
using namespace ThreadPoolModule;

// 业务回调函数: 处理客户端信息
using func_t = std::function<std::string(const std::string &, InetAddr &)>;

const static int defaultsockfd = -1;
const static int backlog = 8;

// TCP服务器(禁止拷贝)
class TcpServer : public NoCopy
{
public:
    // 构造: 初始化端口 + 业务回调
    TcpServer(uint16_t port, func_t func)
        : _port(port),
          _listensockfd(defaultsockfd),
          _isrunning(false),
          _func(func)
    {
    }

    // 初始化服务器: 创建socket + 绑定 + 监听
    void Init()
    {
        // 1. 创建TCP套接字
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            exit(SOCKET_ERR);
        }

        LOG(LogLevel::INFO) << "socket success: " << _listensockfd;

        // 2. 绑定端口
        InetAddr local(_port);
        int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success: " << _listensockfd;

        // 3. 监听端口
        n = listen(_listensockfd, backlog);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success: " << _listensockfd;
    }

    // 服务处理:读取消息 -> 回调处理 -> 回发响应
    void Service(int sockfd, InetAddr &peer)
    {
        char buffer[1024];
        while (true)
        {
            // 1. 读取客户端数据
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                buffer[n] = 0;
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;

                // 2. 执行业务回调
                std::string echo_str = _func(buffer, peer);

                // 3. 回写响应
                write(sockfd, echo_str.c_str(), echo_str.size());
            }
            else if (n == 0) // 客户端断开
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
                close(sockfd);
                break;
            }
            else // 读取异常
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
                close(sockfd);
                break;
            }
        }
    }
    
    // 运行服务器: 循环accept -> 创建线程处理
    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 1. 阻塞等待客户端连接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensockfd, CONV(peer), &len);

            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }

            InetAddr addr(peer); // 客户端地址
            LOG(LogLevel::INFO) << "accept success, peer addr: " << addr.StringAddr();

            // 多进程版本(双fork创建孤儿进程,避免僵尸进程)
            pid_t id = fork();
            if (id < 0)
            {
                LOG(LogLevel::FATAL) << "fork error";
                exit(FORK_ERR);
            }
            else if (id == 0)
            {
                // 子进程
                close(_listensockfd);

                // 再次fork, 生成孙子进程
                if (fork() > 0)
                    exit(OK); // 子进程退出,孙子进程变为孤儿进程(由init接管)

                // 孙子进程: 真正处理客户端请求
                Service(sockfd, addr);
                exit(OK);
            }
            else
            {
                // 父进程: 只负责监听+accept,不处理业务
                close(sockfd);                       // 关闭通信fd
                pid_t rid = waitpid(id, nullptr, 0); // 等待子进程退出,防止僵尸进程
            }
        }
        _isrunning = false; 
    }

    ~TcpServer()
    {
    }

private:
    uint16_t _port;    // 服务器端口
    int _listensockfd; // 监听套接字
    bool _isrunning;   // 运行状态
    func_t _func;      // 业务处理回调
};

9)TcpServer.cc

cpp 复制代码
#include "TcpServer.hpp"
#include "Dict.hpp"

// 默认回调: 回复 haha + 消息
std::string defaulthandler(const std::string &msg, InetAddr &)
{
    return "haha, " + msg;
}

// 打印帮助提示
void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " port" << std::endl;
}

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

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

    // 1. 创建TCP服务器, 绑定默认回调
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, defaulthandler);

    // 2. 初始化服务器
    tsvr->Init(); 

    // 3. 启动服务器
    tsvr->Run();  

    return 0;
}

10)TcpClient.cc

cpp 复制代码
#include <iostream>
#include "Common.hpp"
#include "InetAddr.hpp"

// 打印帮助提示
void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " server_ip server_port" << std::endl;
}

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

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

    // 1. 创建TCP客户端套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        exit(SOCKET_ERR);
    }

    // 2. 封装服务器地址并发起连接
    InetAddr serveraddr(serverip, serverport); 
    int n = connect(sockfd, serveraddr.NetAddrPtr(), serveraddr.NetAddrLen());
    if (n < 0)
    {
        std::cerr << "connect error" << std::endl;
        exit(CONNECT_ERR);
    }

    // 3. 循环: 发送消息 + 接收回显
    while (true)
    {
        // 输入消息
        std::string line;
        std::cout << "Please Enter# ";
        std::getline(std::cin, line);  

        // 发送给服务端
        write(sockfd, line.c_str(), line.size()); 

        // 接收服务端响应
        char buffer[1024];
        ssize_t size = read(sockfd, buffer, sizeof(buffer) - 1); 
        if (size > 0)
        {
            buffer[size] = 0;
            std::cout << "server echo# " << buffer << std::endl; 
        }
    }

    // 关闭套接字
    close(sockfd); 
    return 0;
}

客户端:

服务端:

2、多线程

1)TcpServer.hpp(多线程版)

cpp 复制代码
#pragma once
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

using namespace LogModule;
using namespace ThreadPoolModule;

// 业务回调函数: 处理客户端信息
using func_t = std::function<std::string(const std::string &, InetAddr &)>;

const static int defaultsockfd = -1;
const static int backlog = 8;

// TCP服务器(禁止拷贝)
class TcpServer : public NoCopy
{
public:
    // 构造: 初始化端口 + 业务回调
    TcpServer(uint16_t port, func_t func)
        : _port(port),
          _listensockfd(defaultsockfd),
          _isrunning(false),
          _func(func)
    {
    }

    // 初始化服务器: 创建socket + 绑定 + 监听
    void Init()
    {
        // 1. 创建TCP套接字
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            exit(SOCKET_ERR);
        }

        LOG(LogLevel::INFO) << "socket success: " << _listensockfd;

        // 2. 绑定端口
        InetAddr local(_port);
        int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success: " << _listensockfd;

        // 3. 监听端口
        n = listen(_listensockfd, backlog);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success: " << _listensockfd;
    }

    // 线程数据封装:传递给工作线程
    class ThreadData
    {
    public:
        ThreadData(int fd, InetAddr &ar, TcpServer *s)
            : sockfd(fd),
              addr(ar),
              tsvr(s)
        {
        }

    public:
        int sockfd;      // 通信套接字
        InetAddr addr;   // 客户端地址
        TcpServer *tsvr; // 服务器对象指针
    };

    // 服务处理:读取消息 -> 回调处理 -> 回发响应
    void Service(int sockfd, InetAddr &peer)
    {
        char buffer[1024];
        while (true)
        {
            // 1. 读取客户端数据
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                buffer[n] = 0;
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;

                // 2. 执行业务回调
                std::string echo_str = _func(buffer, peer);

                // 3. 回写响应
                write(sockfd, echo_str.c_str(), echo_str.size());
            }
            else if (n == 0) // 客户端断开
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
                close(sockfd);
                break;
            }
            else // 读取异常
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
                close(sockfd);
                break;
            }
        }
    }

    // 线程入口函数(分离线程)
    static void *Routine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        td->tsvr->Service(td->sockfd, td->addr);
        delete td;
        return nullptr;
    }

    // 运行服务器: 循环accept -> 创建线程处理
    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 阻塞等待客户端连接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensockfd, CONV(peer), &len);

            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }

            InetAddr addr(peer); // 客户端地址
            LOG(LogLevel::INFO) << "accept success, peer addr: " << addr.StringAddr();

            // 多线程版本
            // 创建线程处理新连接
            ThreadData *td = new ThreadData(sockfd, addr, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, Routine, td);
        }
        _isrunning = false; 
    }

    ~TcpServer()
    {
    }

private:
    uint16_t _port;    // 服务器端口
    int _listensockfd; // 监听套接字
    bool _isrunning;   // 运行状态
    func_t _func;      // 业务处理回调
};

客户端:

服务端:

3、线程池

1)TcpServer.hpp(线程池版)

cpp 复制代码
#pragma once
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

using namespace LogModule;
using namespace ThreadPoolModule;

// 业务回调函数: 处理客户端信息
using func_t = std::function<std::string(const std::string &, InetAddr &)>;
// 线程池任务类型: 无参无返回值
using task_t = std::function<void()>;

const static int defaultsockfd = -1;
const static int backlog = 8;

// TCP服务器(禁止拷贝)
class TcpServer : public NoCopy
{
public:
    // 构造: 初始化端口 + 业务回调
    TcpServer(uint16_t port, func_t func)
        : _port(port),
          _listensockfd(defaultsockfd),
          _isrunning(false),
          _func(func)
    {
    }

    // 初始化服务器: 创建socket + 绑定 + 监听
    void Init()
    {
        // 1. 创建TCP套接字
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            exit(SOCKET_ERR);
        }

        LOG(LogLevel::INFO) << "socket success: " << _listensockfd;

        // 2. 绑定端口
        InetAddr local(_port);
        int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success: " << _listensockfd;

        // 3. 监听端口
        n = listen(_listensockfd, backlog);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success: " << _listensockfd;
    }

    // 服务处理:读取消息 -> 回调处理 -> 回发响应
    void Service(int sockfd, InetAddr &peer)
    {
        char buffer[1024];
        while (true)
        {
            // 1. 读取客户端数据
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                buffer[n] = 0;
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;

                // 2. 执行业务回调
                std::string echo_str = _func(buffer, peer);

                // 3. 回写响应
                write(sockfd, echo_str.c_str(), echo_str.size());
            }
            else if (n == 0) // 客户端断开
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
                close(sockfd);
                break;
            }
            else // 读取异常
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
                close(sockfd);
                break;
            }
        }
    }

    // 运行服务器: 循环accept -> 创建线程处理
    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 阻塞等待客户端连接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensockfd, CONV(peer), &len);

            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }

            InetAddr addr(peer); // 客户端地址
            LOG(LogLevel::INFO) << "accept success, peer addr: " << addr.StringAddr();

            // 线程池版本(适合短服务/高并发)
            ThreadPool<task_t>::GetInstance()->Enqueue([this, sockfd, &addr]()
            {              
                // 让线程池处理客户端请求                                       
                this->Service(sockfd, addr);

                // 任务处理完毕, 关闭文件描述符
                close(sockfd); 
            });
        }
        _isrunning = false; 
    }

    ~TcpServer()
    {
    }

private:
    uint16_t _port;    // 服务器端口
    int _listensockfd; // 监听套接字
    bool _isrunning;   // 运行状态
    func_t _func;      // 业务处理回调
};

客户端:

服务端:

三、DictServer

1、线程池

代码结构:

c++ 复制代码
Mutex.hpp
Cond.hpp 
Log.hpp
Thread.hpp  
ThreadPool.hpp
Common.hpp
InetAddr.hpp
TcpServer.hpp // 线程池版
TcpServer.cc // 改动
TcpClient.cc
dictionary.txt 
Dict.hpp 

1)dictionary.txt

cpp 复制代码
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
hello: 
: 你好



run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天

2)Dict.hpp

cpp 复制代码
#pragma once

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

const std::string defaultdict = "./dictionary.txt";
const std::string sep = ": ";

using namespace LogModule;

// 翻译字典类: 加载文件+单词翻译
class Dict
{
public:
    Dict(const std::string& path=defaultdict)
    :_dict_path(path)
    {}

    // 加载字典文件
    bool LoadDict()
    {
        std::ifstream in(_dict_path);
        if(!in.is_open())
        {
            LOG(LogLevel::DEBUG) << "打开字典失败: " << _dict_path;
            return false;
        }

        std::string line; // 出参
        while(std::getline(in,line)) // 一行一行读取
        {
            auto pos = line.find(sep);
            if(pos==std::string::npos)
            {
                LOG(LogLevel::WARNING) << "解析失败: " << line;
                continue;
            }

            std::string english = line.substr(0, pos);
            std::string chinese = line.substr(pos + sep.size());
            if(english.empty()||chinese.empty())
            {
                LOG(LogLevel::WARNING) << "无效内容: " << line;
                continue;
            }

            _dict[english] = chinese;
            LOG(LogLevel::DEBUG) << "加载: " << line;
        }

        in.close();
        return true;
    }

    // 翻译单词
    std::string Translate(const std::string& word,InetAddr& client)
    {
        auto it = _dict.find(word);
        if(it==_dict.end())
        {
            LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << ":" << client.Port() << "]# " << word << "->None";
            return "None";
        }
        
        LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << ":" << client.Port() << "]# " << word << "->" << it->second;
        return it->second;
    }

    ~Dict()
    {}

private:
    std::string _dict_path; // 字典文件路径
    std::unordered_map<std::string, std::string> _dict; // 英汉对照词典
};

3)TcpServer.cc

cpp 复制代码
#include "TcpServer.hpp"
#include "Dict.hpp"

// 打印帮助提示
void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " port" << std::endl;
}

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

    uint16_t port = std::stoi(argv[1]);
    Enable_Console_Log_Strategy(); 
    
    // 1. 创建字典对象
    Dict d;

    // 2. 加载字典文件
    d.LoadDict(); 

    // 3. 创建TCP服务器, 绑定翻译回调
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, 
        [&d](const std::string &word, InetAddr &client)
        { 
            // 收到单词->翻译并返回结果
            return d.Translate(word, client); 
        });

    // 4. 初始化服务器
    tsvr->Init(); 

    // 5. 启动服务器
    tsvr->Run();  

    return 0;
}

客户端:

服务端:

四、ChatServer

1、线程池

代码结构:

c++ 复制代码
Mutex.hpp
Cond.hpp 
Log.hpp
Thread.hpp  
ThreadPool.hpp
Common.hpp
InetAddr.hpp
TcpServer.hpp // 线程池版
TcpServer.cc // 改动
TcpClient.cc // 改动
Route.hpp 

1)Route.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <vector>
#include "Log.hpp"
#include "Mutex.hpp"
#include "InetAddr.hpp"

using namespace LogModule;
using namespace MutexModule;

// 路由模块: UDP群聊转发
class Route
{
private:
    // 判断用户是否在线
    bool IsExist(InetAddr &peer)
    {
        for (auto &user : _online_user)
        {
            if (user == peer)
            {
                return true;
            }
        }
        return false;
    }

    // 添加在线用户
    void AddUser(InetAddr &peer)
    {
        LOG(LogLevel::INFO) << "新增在线用户: " << peer.StringAddr();
        _online_user.push_back(peer);
    }

    // 删除离线用户
    void DeleteUser(InetAddr &peer)
    {
        for (auto iter = _online_user.begin(); iter != _online_user.end(); iter++)
        {
            if (*iter == peer)
            {
                LOG(LogLevel::INFO) << "删除用户: " << peer.StringAddr();
                _online_user.erase(iter);
                break;
            }
        }
    }

public:
    Route()
    {
    }

    // 消息路由: 群发消息+上下线管理
    void MessageRoute(int sockfd, const std::string &message, InetAddr &peer)
    {
        LockGuard lockguard(_mutex);

        // 未入线则加入列表
        if (!IsExist(peer))
            AddUser(peer);

        // 构造转发消息
        std::string send_message = peer.StringAddr() + "# " + message;

        // 转发给所有在线用户
        for (auto &user : _online_user)
        {
            sendto(sockfd, send_message.c_str(), send_message.size(), 0,
                   (const struct sockaddr *)&(user.NetAddr()), sizeof(user.NetAddr()));
        }

        // 收到QUIT则下线
        if (message == "QUIT")
        {
            DeleteUser(peer);
        }
    }

    ~Route()
    {
    }

private:
    std::vector<InetAddr> _online_user; // 在线用户列表
    Mutex _mutex;                       // 互斥锁
};

2)TcpServer.cc

cpp 复制代码
#include "Command.hpp"
#include "TcpServer.hpp"
#include "Dict.hpp"
#include "Route.hpp"

// 默认回调: 回复 haha + 消息
std::string defaulthandler(const std::string &msg, InetAddr &)
{
    return "haha, " + msg;
}

// 打印帮助提示
void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " port" << std::endl;
}

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

    uint16_t port = std::stoi(argv[1]);
    Enable_Console_Log_Strategy();
    
    // 1. 创建路由对象
    Route r;

    // 2. 创建TCP服务器,绑定路由回调
    std::unique_ptr<TcpServer> tsvr= std::make_unique<TcpServer>(port,
        [&r](const std::string& message,InetAddr& client) -> std::string
        {
            // 收到消息 -> 路由处理,返回消息
            return r.MessageRoute(message,client);
        });

    // 3. 初始化服务器
    tsvr->Init();

    // 4. 启动服务器
    tsvr->Run();

    return 0;
}

3)TcpClient.cc

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

using namespace ThreadModule;

// 全局变量
int sockfd = 0;
pthread_t recv_tid;

// 接收线程: 循环读取服务器消息并打印
void Recv()
{
    while (true)
    {
        char buffer[1024];
        ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cerr << buffer << std::endl;
            std::cout << "Please Enter# ";
        }
        else if (n == 0)
        {
            std::cerr << "服务器断开连接" << std::endl;
            break;
        }
        else
        {
            std::cerr << "接收异常" << std::endl;
            break;
        }
    }
    close(sockfd);
}

// 发送线程: 读取输入发送给服务端
void Send()
{
    while (true)
    {
        std::string input;
        std::cout << "Please Enter# ";
        std::getline(std::cin, input);

        send(sockfd, input.c_str(), input.size(), 0);
    }
}

// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }

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

    // 1. 创建TCP套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return 2;
    }

    // 2. 连接服务器
    InetAddr server(server_ip, server_port);
    if(connect(sockfd,server.NetAddrPtr(),server.NetAddrLen())<0)
    {
        std::cerr << "connect error" << std::endl;
        return 3;
    }

    // 3. 创建收发线程
    Thread recver(Recv);
    Thread sender(Send);

    // 4. 启动线程
    recver.Start();
    sender.Start();
    recv_tid = recver.Id();

    // 5. 等待线程结束
    recver.Join();
    sender.Join();

    close(sockfd);
    return 0;
}

客户端:

服务端:

五、CommandServer(远程命令服务)

1、线程池

代码结构:

c++ 复制代码
Mutex.hpp
Cond.hpp 
Log.hpp
Thread.hpp  
ThreadPool.hpp
Common.hpp
InetAddr.hpp
TcpServer.hpp // 线程池版
TcpServer.cc // 改动
TcpClient.cc
Command.hpp

1)Command.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <set>
#include "InetAddr.hpp"
#include "Log.hpp"

using namespace LogModule;

// 命令执行类(带白名单校验)
class Command
{
public:
    // 构造: 初始化命令白名单
    Command()
    {
        _WhiteListCommands.insert("ls");
        _WhiteListCommands.insert("pwd");
        _WhiteListCommands.insert("ls -l");
        _WhiteListCommands.insert("touch haha.txt");
        _WhiteListCommands.insert("who");
        _WhiteListCommands.insert("whoami");
    }

    // 校验命令是否在白名单内
    bool IsSafeCommand(const std::string &cmd)
    {
        auto iter = _WhiteListCommands.find(cmd);
        return iter != _WhiteListCommands.end();
    }

    // 执行安全命令, 返回结果字符串
    std::string Execute(const std::string &cmd, InetAddr &addr)
    {
        // 1. 安全校验
        if (!IsSafeCommand(cmd))
        {
            return "非法命令! "; 
        }

        std::string client = addr.StringAddr(); // 客户端地址

        // 2. 执行命令
        FILE *fp = popen(cmd.c_str(), "r"); 
        if (!fp)
        {
            return std::string("命令执行失败: ") + cmd;
        }

        std::string res;
        char line[1024];
        while (fgets(line, sizeof(line), fp)) 
        {
            res += line;
        }
        pclose(fp); 

        std::string result = client + "execute done, result is: \n" + res;
        LOG(LogLevel::DEBUG) << result; 
        return result;
    }

    ~Command()
    {
    }

private:
    std::set<std::string> _WhiteListCommands; // 命令白名单
};

2)TcpServer.cc

cpp 复制代码
#include "Command.hpp"
#include "TcpServer.hpp"
#include "Dict.hpp"

// 打印帮助提示
void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " port" << std::endl;
}

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

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

    // 1. 创建命令执行对象
    Command cmd;

    // 2. 创建TCP服务器, 绑定命令执行回调
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, 
        [&cmd](const std::string &command, InetAddr &addr)
        {
            // 收到命令 -> 执行并返回结果
            return cmd.Execute(command, addr); 
        });

    // 3. 初始化服务器
    tsvr->Init(); 

    // 4. 启动服务器
    tsvr->Run();  

    return 0;
}

客户端:

服务端:

相关推荐
听风3471 小时前
Arch Linux星火应用商店安装问题解决方案
linux·运维·服务器·archlinux
WangLanguager1 小时前
Unix 命令 mkdir 详细介绍
linux·运维·服务器
上海云盾-小余1 小时前
服务器频繁遭暴力攻击?IP 更换、防护加固一站式解决方案
运维·服务器·tcp/ip
Byron Loong1 小时前
【网络】Wireshark过滤器表达式的规则
网络·测试工具·wireshark
song8581 小时前
imx6ull(2)
linux
wuyoula1 小时前
如何在捷云鲸论坛高效获取高质量技术解答?
服务器·c++·人工智能·tcp/ip·源码
CheungChunChiu1 小时前
Linux 音频子系统完整梳理:ALSA、ASoC、DAPM、Codec、Machine、es8389 与 rk‑multicodecs 全解析
linux·运维·音视频·codec·audio·asla·dapm
你干嘛?哎哟1 小时前
CAN信号质量测试
网络
lzh200409191 小时前
效率与安全并重:C++ 线程安全
linux·c++