Tcp_Sever(线程池版本的 TCP 服务器)

Tcp_Sever(线程池版本的 TCP 服务器)

  • 前言
  • [1. 功能介绍及展示](#1. 功能介绍及展示)
    • [1.1 服务端连接](#1.1 服务端连接)
    • [1.2 客户端连接(可多个用户同时在线连接服务端)](#1.2 客户端连接(可多个用户同时在线连接服务端))
    • [1.3 功能服务](#1.3 功能服务)
      • [1.3.1 defaultService(默认服务)](#1.3.1 defaultService(默认服务))
      • [1.3.2 transform(大小写转换)](#1.3.2 transform(大小写转换))
      • [1.3.3 ping(ping服务)](#1.3.3 ping(ping服务))
      • [1.3.4 translate(翻译)](#1.3.4 translate(翻译))
    • [1.4 服务器重连功能](#1.4 服务器重连功能)
  • [2. 代码实现](#2. 代码实现)
    • [2.1 总体代码设计](#2.1 总体代码设计)
      • [2.1.1 .cc文件](#2.1.1 .cc文件)
      • [2.1.2 .hpp文件](#2.1.2 .hpp文件)
      • [2.1.3 其他文件](#2.1.3 其他文件)
    • 2.2具体实现(代码都有注释)
      • [2.2.1 Log.hpp](#2.2.1 Log.hpp)
      • [2.2.2 nocopy.hpp](#2.2.2 nocopy.hpp)
      • [2.2.3 LockGuard.hpp](#2.2.3 LockGuard.hpp)
      • [2.2.4 Comm.hpp](#2.2.4 Comm.hpp)
      • [2.2.5 Thread.hpp](#2.2.5 Thread.hpp)
      • [2.2.6 ThreadPool.hpp](#2.2.6 ThreadPool.hpp)
      • [2.2.7 InetAddr.hpp](#2.2.7 InetAddr.hpp)
      • [2.2.8 Translate.hpp](#2.2.8 Translate.hpp)
      • [2.2.9 Tcp_Server.hpp](#2.2.9 Tcp_Server.hpp)
      • [2.2.10 Makefile](#2.2.10 Makefile)
      • [2.2.11 Dict.txt](#2.2.11 Dict.txt)
      • [2.2.12 Main.cc](#2.2.12 Main.cc)
      • [2.2.13 Tcp_Client.cc](#2.2.13 Tcp_Client.cc)

前言


已经有半年没有更新博客了,在这期间,时而沉淀,时而因为就业感到迷茫,到现在,忽然看开了一点,不管未来咋样,至少不要让自己后悔,人生需要passion!干就完了!!!

源码地址tcp_server

注:该网络服务只能在有公网ip的机器或者云服务器之间进行
虚拟机上只能进行本地连接,不能连接其他虚拟机

1. 功能介绍及展示

1.1 服务端连接

cpp 复制代码
./tcp_server 8888

执行结果:

1.2 客户端连接(可多个用户同时在线连接服务端)

连接服务器要知道服务器的ip地址

我们执行本地测试时,可以用ifconfig 指令查看本地ip地址

连接

cpp 复制代码
./tcp_client+IP地址+服务器端口号
./tcp_client 192.168.42.200 8888

两个客户端同时连接

与此同时server端打印日志

1.3 功能服务

1.3.1 defaultService(默认服务)

默认服务就是给每个连接的客户端打印一份功能菜单

1.3.2 transform(大小写转换)

将小写字母转换为大写字母

1.3.3 ping(ping服务)

ping服务(心跳机制,用于检测服务是否正常),发送ping,服务器如果正常运行会回复一个Pong

1.3.4 translate(翻译)

输入英文单词,会返回对应的音标和中文解释

1.4 服务器重连功能

在连接过程中,如果服务端出现问题连接不上,可进行5次的重连,重连成功即可继续执行服务

2. 代码实现

2.1 总体代码设计

2.1.1 .cc文件

Main.cc :程序的初始化、配置以及主要逻辑流程。创建服务器或客户端实例,设置网络连接,处理用户输入
Tcp_Client.cc:实现TCP 客户端的功能。负责与服务器建立连接,发送和接收数据。包含连接管理、数据处理和错误处理的逻辑

2.1.2 .hpp文件

ThreadPool.hpp :定义线程池的接口和实现
LockGuard.hpp :实现一个锁的封装类,确保在作用域内自动加锁和解锁
InetAddr.hpp :处理网络地址相关的功能,IP 地址和端口的表示和转换
Comm.hpp :定义错误信息
Log.hpp :负责打印日志的功能。包含日志级别,日志时间
nocopy.hpp :防止类的复制构造和赋值操作,确保对象的唯一性
Tcp_Server.hpp :定义 TCP 服务器的接口和相关功能,实现如何接收客户端连接、处理请求和管理客户端会话
Thread.hpp :定义线程的接口和实现,管理线程的创建、执行和终止,与线程池配合使用
Translate.hpp:实现词典查找翻译功能

2.1.3 其他文件

Makefile :编译Tcp_Client.cc和Main.cc,同时便于管理生成的可执行程序
Dict.txt:存放词典数据

2.2具体实现(代码都有注释)

2.2.1 Log.hpp

cpp 复制代码
#pragma once
#include <iostream>     // 引入输入输出流
#include <cstdarg>      // 引入变长参数处理
#include <ctime>        // 引入时间处理
#include <sys/types.h>  // 引入系统数据类型
#include <unistd.h>     // 引入Unix标准函数
#include <sys/stat.h>   // 引入文件状态处理
#include <fcntl.h>      // 引入文件控制定义
using namespace std;

// 定义日志级别
enum
{
    Debug = 0,
    Info,
    Warning,
    Error,
    Fatal
};

// 定义日志输出样式
enum
{
    Screen = 10, // 输出到屏幕
    OneFile,     // 输出到一个文件
    ClassFile    // 输出到多个分类文件
};

// 常量定义
const int defaultstyle = Screen;            // 默认输出样式为屏幕
const std::string default_filename = "log."; // 默认日志文件名
const std::string logdir = "log";           // 日志目录

// 日志类定义
class Log
{
public:
    // 构造函数
    Log()
        : style(defaultstyle),                // 初始化日志样式
          filename(default_filename)         // 初始化文件名
    {
        mkdir(logdir.c_str(), 0775);        // 创建日志目录
    }

    // 启用指定的日志样式
    void Enable(int sty)
    {
        style = sty;
    }

    // 将日志级别转换为字符串
    std::string LevelToString(int level)
    {
        switch (level)
        {
        case Debug:
            return "Debug";
        case Info:
            return "Info";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "Unknown"; // 返回未知级别
        }
    }

    // 获取当前时间戳并格式化为字符串
    std::string TimeStampExLocalTime()
    {
        time_t currtime = time(nullptr); // 获取当前时间
        struct tm *local_time = localtime(&currtime); // 转换为本地时间
        char time_buff[128];
        snprintf(time_buff, sizeof(time_buff), "%d-%d-%d %d:%d:%d",
                 local_time->tm_year + 1900, local_time->tm_mon + 1, local_time->tm_mday,
                 local_time->tm_hour, local_time->tm_min, local_time->tm_sec);

        return time_buff; // 返回格式化的时间字符串
    }

    // 写入日志到单个文件
    void WriteLogToOneFile(const string &logname, const string &message)
    {
        umask(0); // 重置文件创建掩码
        int fd = open(logname.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666); // 打开(或创建)日志文件
        if (fd < 0)
            return; // 打开失败则返回
        write(fd, message.c_str(), message.size()); // 写入日志消息
        close(fd); // 关闭文件
    }

    // 写入日志到分类文件
    void WriteLogToClassFile(const string &levelstr, const string &message)
    {
        string logname = logdir; // 获取日志目录
        logname += '/';
        logname += filename; // 添加文件名
        logname += levelstr; // 添加级别后缀

        WriteLogToOneFile(logname, message); // 调用写入单个文件的函数
    }

    // 统一写入日志
    void WriteLog(const string &levelstr, const std::string &message)
    {
        switch (style)
        {
        case Screen:
            cout << message << endl; // 输出到屏幕
            break;
        case OneFile:
            WriteLogToOneFile("all", message); // 写入到单个文件
            break;
        case ClassFile:
            WriteLogToClassFile(levelstr, message); // 写入到分类文件
            break;
        }
    }

    // 记录日志信息
    void LogMessage(int level, const char *format, ...)
    {
        char rightbuffer[1024]; // 存储格式化后的消息
        va_list args; // 定义变长参数列表

        va_start(args, format); // 初始化变长参数列表
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, args); // 格式化消息
        va_end(args); // 结束变长参数处理

        char leftbuffer[1024]; // 存储日志头信息
        std::string currtime = TimeStampExLocalTime(); // 获取当前时间
        std::string levelstr = LevelToString(level);   // 获取日志级别字符串
        std::string idstr = std::to_string(getpid());  // 获取当前进程ID
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%s][%s] ", levelstr.c_str(), currtime.c_str(), idstr.c_str()); // 生成日志头

        std::string loginfo = leftbuffer; // 合并日志头和消息
        loginfo += rightbuffer;
        WriteLog(levelstr, loginfo); // 写入日志
    }

    // 析构函数
    ~Log() {}

private:
    int style; // 日志输出样式
    std::string filename; // 日志文件名
};

// 创建全局日志实例
Log lg;

// 配置类定义
class Conf
{
public:
    // 构造函数
    Conf()
    {
        lg.Enable(Screen); // 默认启用屏幕输出
    }
    ~Conf() {} // 析构函数
};

// 创建全局配置实例
Conf conf;

2.2.2 nocopy.hpp

cpp 复制代码
#pragma once // 确保头文件只被包含一次

#include <iostream> // 引入输入输出流库(虽然这里未使用)

// nocopy 类用于禁止对象的复制和赋值
class nocopy
{
public:
    // 默认构造函数
    nocopy()
    {}

    // 禁止复制构造函数
    nocopy(const nocopy &) = delete; // 删除复制构造函数,防止对象复制

    // 禁止赋值操作符重载
    const nocopy &operator=(const nocopy &) = delete; // 删除赋值操作符,防止对象赋值

    // 默认析构函数
    ~nocopy()
    {}
};

2.2.3 LockGuard.hpp

cpp 复制代码
#pragma once // 确保头文件只被包含一次

#include <pthread.h> // 引入 pthread 库以使用 POSIX 线程相关功能

// Mutex 类用于封装 pthread_mutex_t 锁对象
class Mutex
{
public:
    // 构造函数,接受外部传入的锁对象
    Mutex(pthread_mutex_t *lock) : _lock(lock) // 初始化锁对象指针
    {}

    // 锁定函数
    void Lock()
    {
        pthread_mutex_lock(_lock); // 调用 pthread 库的锁定函数
    }

    // 解锁函数
    void Unlock()
    {
        pthread_mutex_unlock(_lock); // 调用 pthread 库的解锁函数
    }

    // 析构函数
    ~Mutex()
    {}

private:
    pthread_mutex_t *_lock; // 指向 pthread_mutex_t 类型的锁对象指针
};

// LockGuard 类用于自动管理锁的获取与释放
class LockGuard
{
public:
    // 构造函数,接受外部传入的锁对象并自动锁定
    LockGuard(pthread_mutex_t *lock) : _mutex(lock) // 初始化 LockGuard 对象
    {
        _mutex.Lock(); // 在构造时锁定
    }

    // 析构函数,自动解锁
    ~LockGuard()
    {
        _mutex.Unlock(); // 在析构时解锁
    }

private:
    Mutex _mutex; // 使用 Mutex 类实例来管理锁
};

2.2.4 Comm.hpp

cpp 复制代码
#pragma once // 确保头文件只被包含一次

// 定义公共错误类型
enum
{
    Usage_Err = 1, // 用法错误
    Socket_Err,    // 套接字错误
    Bind_Err,      // 绑定错误
    Listen_Err     // 监听错误
};

// 宏定义:将地址指针转换为 sockaddr 结构指针
#define CONV(addr_ptr) ((struct sockaddr *)addr_ptr)

2.2.5 Thread.hpp

cpp 复制代码
#pragma once // 确保头文件只被包含一次

#include <iostream>     // 引入输入输出流库
#include <string>       // 引入字符串库
#include <functional>   // 引入函数对象库
#include <pthread.h>    // 引入 pthread 库以支持多线程

// 定义一个函数类型,用于传递线程要执行的函数,参数为 T 类型的引用
template<class T>
using func_t = std::function<void(T&)>;

// 定义一个线程类模板
template<class T>
class Thread
{
public:
    // 构造函数,初始化线程名称、函数、数据
    Thread(const std::string &threadname, func_t<T> func, T &data)
        : _tid(0),               // 初始化线程ID为0
          _threadname(threadname), // 初始化线程名称
          _isrunning(false),      // 初始化线程运行状态为false
          _func(func),            // 初始化要执行的函数
          _data(data)             // 初始化要传递的数据
    {}

    // 线程的执行例程
    static void *ThreadRoutine(void *args) // 静态成员函数
    {
        // (void)args; // 防止编译器警告(若不使用 args)
        Thread *ts = static_cast<Thread *>(args); // 将 void 指针转换为 Thread 指针

        ts->_func(ts->_data); // 调用传入的函数,并传递数据

        return nullptr; // 返回空指针
    }

    // 启动线程
    bool Start()
    {
        // 创建线程,执行 ThreadRoutine,传递当前对象的指针
        int n = pthread_create(&_tid, nullptr, ThreadRoutine, this);
        if (n == 0) // 创建成功
        {
            _isrunning = true; // 更新运行状态
            return true; // 返回成功
        }
        else return false; // 返回失败
    }

    // 等待线程完成
    bool Join()
    {
        if (!_isrunning) return true; // 如果线程没有运行,直接返回成功
        int n = pthread_join(_tid, nullptr); // 等待线程结束
        if (n == 0) // 等待成功
        {
            _isrunning = false; // 更新运行状态
            return true; // 返回成功
        }
        return false; // 返回失败
    }

    // 获取线程名称
    std::string ThreadName()
    {
        return _threadname; // 返回线程名称
    }

    // 检查线程是否正在运行
    bool IsRunning()
    {
        return _isrunning; // 返回当前运行状态
    }

    // 析构函数
    ~Thread()
    {}

private:
    pthread_t _tid;       // 线程ID
    std::string _threadname; // 线程名称
    bool _isrunning;      // 线程运行状态
    func_t<T> _func;     // 要执行的函数
    T _data;             // 传递给函数的数据
};

2.2.6 ThreadPool.hpp

cpp 复制代码
#pragma once // 确保头文件只被包含一次

#include <iostream>      // 引入输入输出流库
#include <queue>        // 引入队列库
#include <vector>       // 引入向量库
#include <pthread.h>    // 引入 pthread 库以支持多线程
#include <functional>    // 引入函数对象库
#include "Log.hpp"      // 引入日志功能
#include "Thread.hpp"   // 引入线程类
#include "LockGuard.hpp" // 引入锁保护类

namespace TreadNs // 定义命名空间 TreadNs
{

    static const int defaultnum = 3; // 默认线程数量

    // 线程数据类,存储线程的名称
    class ThreadData
    {
    public:
        // 构造函数,初始化线程名称
        ThreadData(const std::string &name) : threadname(name)
        {
        }

        // 析构函数
        ~ThreadData()
        {
        }

    public:
        std::string threadname; // 线程名称
    };

    // 线程池类模板
    template <class T>
    class ThreadPool
    {
    private:
        // 构造函数,初始化线程池,创建指定数量的线程
        ThreadPool(int thread_num = defaultnum) : _thread_num(thread_num)
        {
            pthread_mutex_init(&_mutex, nullptr); // 初始化互斥锁
            pthread_cond_init(&_cond, nullptr);   // 初始化条件变量

            // 创建指定数量的线程
            for (int i = 0; i < _thread_num; i++)
            {
                std::string threadname = "thread-"; // 线程名称
                threadname += std::to_string(i + 1); // 生成线程名称

                ThreadData td(threadname); // 创建线程数据对象

                // 创建线程并绑定执行函数 ThreadRun
                _threads.emplace_back(threadname,
                                      std::bind(&ThreadPool<T>::ThreadRun, this,
                                                std::placeholders::_1),
                                      td);
                lg.LogMessage(Info, "%s is created...\n", threadname.c_str()); // 记录线程创建日志
            }
        }

        // 删除复制构造函数和赋值操作符,禁止复制
        ThreadPool(const ThreadPool<T> &tp) = delete;
        const ThreadPool<T> &operator=(const ThreadPool<T>) = delete;

    public:
        // 获取线程池单例
        static ThreadPool<T> *GetInstance()
        {
            if (instance == nullptr) // 如果实例不存在
            {
                LockGuard lockguard(&sig_lock); // 使用锁确保线程安全
                if (instance == nullptr) // 再次检查
                {
                    lg.LogMessage(Info, "创建单例成功...\n"); // 记录单例创建日志
                    instance = new ThreadPool<T>(); // 创建单例实例
                }
            }

            return instance; // 返回线程池实例
        }

        // 启动线程池
        bool Start()
        {
            for (auto &thread : _threads) // 遍历所有线程
            {
                thread.Start(); // 启动线程
                lg.LogMessage(Info, "%s is running ...\n", thread.ThreadName().c_str()); // 记录线程运行日志
            }

            return true; // 返回成功
        }

        // 线程等待函数
        void ThreadWait(const ThreadData &td)
        {
            lg.LogMessage(Debug, "no task, %s is sleeping...\n", td.threadname.c_str()); // 记录线程睡眠日志
            pthread_cond_wait(&_cond, &_mutex); // 等待条件变量
        }

        // 唤醒线程
        void ThreadWakeup()
        {
            pthread_cond_signal(&_cond); // 发送信号唤醒等待的线程
        }

        // 检查线程池自身状态(功能待实现)
        void checkSelf()
        {
            // 1. _task_num > _task_num_high_water && _thread_num < _thread_num_high_water
            // 创建更多的线程,并更新_thread_num

            // 2. _task_num == _task_num_low_water && _thread_num >= _thread_num_high_water
            // 退出线程,并更新_thread_num
        }

        // 线程执行函数
        void ThreadRun(ThreadData &td)
        {
            while (true) // 无限循环,持续处理任务
            {
                T t; // 存储任务

                {
                    LockGuard lockguard(&_mutex); // 使用锁保护临界区
                    while (_q.empty()) // 如果任务队列为空
                    {
                        ThreadWait(td); // 线程等待
                        lg.LogMessage(Debug, "thread %s is wakeup\n", td.threadname.c_str()); // 记录线程唤醒日志
                    }
                    t = _q.front(); // 获取队列首部任务
                    _q.pop(); // 移除任务
                }

                // 处理任务
                t(); // 执行任务
                // lg.LogMessage(Debug, "%s handler task %s done, result is : %s\n",
                //               td.threadname, t.PrintTask().c_str(), t.PrintResult().c_str());
            }
        }

        // 推送任务到队列
        void Push(T &in)
        {
            LockGuard lockguard(&_mutex); // 使用锁保护临界区
            _q.push(in); // 将任务放入队列
            ThreadWakeup(); // 唤醒线程处理新任务
        }

        // 析构函数,清理资源
        ~ThreadPool()
        {
            pthread_mutex_destroy(&_mutex); // 销毁互斥锁
            pthread_cond_destroy(&_cond); // 销毁条件变量
        }

        // 等待所有线程完成(用于调试)
        void Wait()
        {
            for (auto &thread : _threads)
            {
                thread.Join(); // 等待线程结束
            }
        }

    private:
        std::queue<T> _q; // 任务队列
        std::vector<Thread<ThreadData>> _threads; // 线程池中的线程
        int _thread_num; // 线程数量
        pthread_mutex_t _mutex; // 互斥锁
        pthread_cond_t _cond; // 条件变量

        static ThreadPool<T> *instance; // 线程池单例实例
        static pthread_mutex_t sig_lock; // 锁,用于控制单例创建的线程安全
    };

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

    template <class T>
    pthread_mutex_t ThreadPool<T>::sig_lock = PTHREAD_MUTEX_INITIALIZER; // 初始化信号锁

} // 结束命名空间 TreadNs

2.2.7 InetAddr.hpp

cpp 复制代码
#pragma once // 确保头文件只被包含一次

#include <iostream>      // 引入输入输出流库
#include <string>        // 引入字符串库
#include <sys/types.h>   // 引入系统数据类型
#include <sys/socket.h>   // 引入套接字相关函数
#include <netinet/in.h>  // 引入互联网域套接字
#include <arpa/inet.h>   // 引入地址转换函数

// InetAddr 类用于封装和处理 IPv4 地址和端口
class InetAddr
{
public:
    // 构造函数,接受一个 sockaddr_in 结构体引用
    InetAddr(struct sockaddr_in &addr) : _addr(addr)
    {
        // 将网络字节序的端口转换为主机字节序
        _port = ntohs(_addr.sin_port);
        
        // 使用 inet_ntop 将网络字节顺序的 IP 地址转换为字符串形式
        char ipbuffer[64]; // 存储 IP 地址字符串
        inet_ntop(AF_INET, &addr.sin_addr, ipbuffer, sizeof(ipbuffer)); // 将 IPv4 地址转换为可读格式
        _ip = ipbuffer; // 将 IP 地址存储为字符串
    }

    // 获取 IP 地址
    std::string Ip() { return _ip; }

    // 获取端口号
    uint16_t Port() { return _port; }

    // 打印调试信息,格式为 "IP:Port"
    std::string PrintDebug()
    {
        std::string info = _ip; // 获取 IP 地址
        info += ":"; // 添加分隔符
        info += std::to_string(_port); // 添加端口号
        return info; // 返回完整信息
    }

    // 获取 sockaddr_in 结构体的引用
    const struct sockaddr_in& GetAddr()
    {
        return _addr; // 返回地址结构体
    }

    // 重载等于运算符,用于比较两个 InetAddr 对象
    bool operator == (const InetAddr& addr)
    {
        // 检查 IP 和端口是否相等
        return this->_ip == addr._ip && this->_port == addr._port;
    }

    // 析构函数
    ~InetAddr() {}
    
private:
    std::string _ip;          // 存储 IP 地址字符串
    uint16_t _port;           // 存储端口号
    struct sockaddr_in _addr; // 存储 sockaddr_in 结构体
};

2.2.8 Translate.hpp

cpp 复制代码
#pragma once // 确保头文件只被包含一次

#include <iostream>          // 引入输入输出流库
#include <unordered_map>     // 引入无序映射库
#include <string>            // 引入字符串库
#include <vector>            // 引入向量库
#include <fstream>           // 引入文件流库
#include "Log.hpp"          // 引入日志功能

using namespace std; // 使用标准命名空间

const string unknown = "未知的"; // 未知词的默认返回值
const string mydict = "./resource/Dict.txt"; // 默认字典文件路径
const string sep = " "; // 字典文件中词与翻译之间的分隔符

// Translate 类用于实现翻译功能
class Translate
{
public:
    // 构造函数,接受字典路径,默认值为 mydict
    Translate(string dict_path = mydict) : _dict_path(dict_path)
    {
        LoadDict(); // 加载字典文件
        Parse(); // 解析字典内容
    }

    // 加载字典文件
    void LoadDict()
    {
        ifstream in(_dict_path); // 打开字典文件
        string line; // 存储每一行内容
        while (getline(in, line)) // 逐行读取文件
        {
            lines.push_back(line); // 将每行内容存入 lines 向量
        }
        in.close(); // 关闭文件
        lg.LogMessage(Debug, "Load dict success, path : %s\n", _dict_path.c_str()); // 记录加载成功日志
    }

    // 调试函数,输出字典内容
    void debug()
    {
        // 可选:输出所有行
        // for (auto &e : lines)
        // {
        //     cout << e << endl;
        // }
        // 输出字典中的每个词及其翻译
        for (auto &elem : dict)
        {
            cout << elem.first << " : " << elem.second << endl; // 输出格式为 "词 : 翻译"
        }
    }

    // 解析字典内容
    void Parse()
    {
        for (auto &line : lines) // 遍历加载的每一行
        {
            auto pos = line.find(sep); // 查找分隔符位置
            if (pos == string::npos) continue; // 如果未找到,跳过该行
            else
            {
                string word = line.substr(0, pos); // 获取词
                string chinese = line.substr(pos + sep.size()); // 获取翻译
                dict.insert(std::make_pair(word, chinese)); // 将词和翻译插入字典
            }
        }
        lg.LogMessage(Debug, "Parse dict success, path : %s\n", _dict_path.c_str()); // 记录解析成功日志
    }

    // 查找翻译
    string Excute(string word)
    {
        auto iter = dict.find(word); // 查找词在字典中的位置
        if (iter == dict.end()) return unknown; // 如果词未找到,返回默认值
        else return dict[word]; // 返回对应的翻译
    }

    // 析构函数
    ~Translate()
    {
        // 在这里可以添加资源清理代码(如果需要)
    }

private:
    string _dict_path; // 字典文件路径
    unordered_map<string, string> dict; // 存储字典映射(词 -> 翻译)
    vector<string> lines; // 存储字典文件的每一行
};

2.2.9 Tcp_Server.hpp

cpp 复制代码
#pragma once // 确保头文件只被包含一次

#include <iostream>          // 引入输入输出流库
#include <string>            // 引入字符串库
#include <cerrno>            // 引入错误号库
#include <cstring>           // 引入字符串处理库
#include <sys/types.h>       // 引入系统数据类型
#include <sys/socket.h>      // 引入套接字相关函数
#include <stdlib.h>          // 引入标准库
#include <netinet/in.h>      // 引入互联网域套接字
#include <arpa/inet.h>       // 引入地址转换函数
#include <sys/wait.h>        // 引入进程管理相关函数
#include <pthread.h>         // 引入 pthread 库以支持多线程
#include <functional>        // 引入函数对象库
#include <unordered_map>      // 引入无序映射库

#include "ThreadPool.hpp"     // 引入线程池类
#include "InetAddr.hpp"       // 引入地址类
#include "Log.hpp"            // 引入日志功能
#include "nocopy.hpp"         // 引入禁止复制的类
#include "Comm.hpp"           // 引入通信相关类
// #include "Task.hpp"          // 可选的任务类

const static int default_backlog = 5; // 默认最大连接数
using task_t = function<void()>; // 定义任务类型
using callback_t = function<void(int, InetAddr &)>; // 定义回调函数类型

class TcpServer; // 前向声明 TcpServer 类

// TcpServer 类用于实现 TCP 服务器功能
class TcpServer : nocopy // 继承 nocopy,禁止复制
{
public:
    // 构造函数,初始化服务器端口和运行状态
    TcpServer(uint16_t port) : _port(port), _isrunning(false)
    {
    }

    // 初始化服务器
    void Init()
    {
        // 1. 创建套接字
        _listensock = socket(AF_INET, SOCK_STREAM, 0); // 创建 TCP 套接字
        if (_listensock < 0)
        {
            // 如果创建失败,记录日志并退出
            lg.LogMessage(Fatal, "创建套接字失败: %d, error string: %s\n", errno, strerror(errno));
            exit(Fatal);
        }
        lg.LogMessage(Debug, "创建套接字成功: sockfd: %d", _listensock);

        // 解决绑定失败的问题
        int opt = 1; // 设置套接字选项
        setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); // 允许重用地址和端口

        // 2. 填充本地网络信息并绑定
        struct sockaddr_in local; // 定义本地地址结构
        memset(&local, 0, sizeof(local)); // 清空结构体
        local.sin_family = AF_INET; // 使用 IPv4
        local.sin_port = htons(_port); // 设置端口
        local.sin_addr.s_addr = INADDR_ANY; // 允许接受所有连接

        // 2.1 绑定套接字
        if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) != 0)
        {
            // 如果绑定失败,记录日志并退出
            lg.LogMessage(Fatal, "bind套接字失败: %d, error string: %s\n", errno, strerror(errno));
            exit(Bind_Err);
        }
        lg.LogMessage(Debug, "bind套接字成功: sockfd: %d", _listensock);

        // 3. 设置套接字为监听状态
        if (listen(_listensock, default_backlog) != 0)
        {
            // 如果监听失败,记录日志并退出
            lg.LogMessage(Fatal, "监听套接字失败: %d, error string: %s\n", errno, strerror(errno));
            exit(Listen_Err);
        }
        lg.LogMessage(Debug, "监听套接字成功: sockfd: %d\n", _listensock);

        // 启动线程池
        TreadNs::ThreadPool<task_t>::GetInstance()->Start();

        // 注册默认服务
        funcs.insert(std::make_pair("defaultService", std::bind(&TcpServer::DefaultService, this, std::placeholders::_1, std::placeholders::_2)));
    }

    // 处理客户端请求
    void Service(int sockfd, InetAddr &addr)
    {
        char buff[1024]; // 缓冲区
        while (true)
        {
            ssize_t n = read(sockfd, buff, sizeof(buff) - 1); // 从套接字读取数据
            if (n > 0)
            {
                buff[n] = 0; // 添加字符串结束符
                cout << addr.PrintDebug() << "# " << buff << endl; // 输出客户端地址和消息
                string echo_string = "server#:";
                echo_string += buff; // 构造回显字符串
                write(sockfd, echo_string.c_str(), echo_string.size()); // 将回显字符串写回客户端
            }
            else if (n == 0) // 返回值为0,表示对端关闭了连接
            {
                lg.LogMessage(Info, "对端关闭了连接\n");
                break; // 结束循环
            }
            else
            {
                lg.LogMessage(Info, "读消息失败: %d, error string: %s\n", errno, strerror(errno));
                break; // 结束循环
            }
        }
    }

    // 启动服务器
    void Start()
    {
        _isrunning = true; // 设置运行状态为真
        signal(SIGCHLD, SIG_IGN); // 忽略子进程退出信号,以免产生僵尸进程
        while (_isrunning)
        {
            // 4. 获取连接
            struct sockaddr_in peer; // 定义客户端地址结构
            socklen_t len = sizeof(peer); // 地址结构体大小
            int sockfd = accept(_listensock, (struct sockaddr *)&peer, &len); // 接受连接

            if (sockfd < 0)
            {
                lg.LogMessage(Warning, "获取套接字失败: %d, error string: %s\n", errno, strerror(errno));
                continue; // 继续循环,等待下一个连接
            }
            lg.LogMessage(Debug, "获取套接字成功: sockfd: %d", sockfd);

            // 5. 提供服务
            InetAddr addr(peer); // 封装客户端地址
            task_t t = bind(&TcpServer::Routine, this, sockfd, addr); // 绑定任务
            TreadNs::ThreadPool<task_t>::GetInstance()->Push(t); // 将任务推送到线程池
        }
    }

    // 读取数据
    string Read(int sockfd)
    {
        char type[1024]; // 数据类型缓冲区
        ssize_t n = read(sockfd, type, sizeof(type) - 1); // 从套接字读取数据
        if (n > 0)
        {
            type[n] = 0; // 添加字符串结束符
        }
        else if (n == 0) // 返回值为0,表示对端关闭了连接
        {
            lg.LogMessage(Info, "对端关闭了连接\n");
        }
        else
        {
            lg.LogMessage(Info, "读消息失败: %d, error string: %s\n", errno, strerror(errno));
        }
        return type; // 返回读取到的数据
    }

    // 处理例程
    void Routine(int sockfd, InetAddr &addr)
    {
        funcs["defaultService"](sockfd, addr); // 调用默认服务
        string type = Read(sockfd); // 读取请求类型
        lg.LogMessage(Debug, "%s select %s \n", addr.PrintDebug(), type.c_str());

        // 根据请求类型调用相应的服务
        if (type == "ping")
        {
            funcs[type](sockfd, addr); // 处理 ping 请求
        }
        else if (type == "translate") // 翻译服务
        {
            funcs[type](sockfd, addr);
        }
        else if (type == "transform") // 转换服务
        {
            funcs[type](sockfd, addr);
        }
        else
        {
            // 处理其他类型请求
        }

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

    // 默认服务
    void DefaultService(int sockfd, InetAddr& addr)
    {
        (void)addr; // 防止未使用警告
        std::string service_list = " |";
        for (auto func : funcs)
        {
            service_list += func.first; // 拼接服务列表
            service_list += "|";
        }
        write(sockfd, service_list.c_str(), service_list.size()); // 将服务列表写回客户端
    }

    // 注册回调函数
    void RegisterFunc(const string &name, callback_t func)
    {
        funcs[name] = func; // 将函数注册到字典中
    }

    // 析构函数
    ~TcpServer()
    {
        // 在这里可以添加资源清理代码(如果需要)
    }

private:
    uint16_t _port; // 服务器端口
    int _listensock; // 监听套接字
    bool _isrunning; // 服务器运行状态

    // 存储注册的服务函数
    unordered_map<string, callback_t> funcs;
};

2.2.10 Makefile

因为Makefile文件的代码注释看起·来比较乱,所以我分两部分放出来

无注释代码

cpp 复制代码
.PHONY:all
all:tcp_server tcp_client

tcp_server:Main.cc
	g++ -o $@ $^ -lpthread -std=c++14
	
tcp_client:Tcp_Client.cc
	g++ -o $@ $^ -lpthread -std=c++14
.PHONY:clean
clean:
	rm -f tcp_server tcp_client

带注释代码

cpp 复制代码
.PHONY: all         # 声明 'all' 是一个伪目标,不会生成同名文件
all: tcp_server tcp_client  # 默认目标,构建 tcp_server 和 tcp_client

# 目标 tcp_server 的构建规则
tcp_server: Main.cc   # 指定依赖文件 Main.cc
	g++ -o $@ $^ -lpthread -std=c++14  # 使用 g++ 编译 Main.cc,生成可执行文件 tcp_server
	# $@ 表示目标名称(tcp_server),$^ 表示所有依赖文件(Main.cc)

# 目标 tcp_client 的构建规则
tcp_client: Tcp_Client.cc  # 指定依赖文件 Tcp_Client.cc
	g++ -o $@ $^ -lpthread -std=c++14  # 使用 g++ 编译 Tcp_Client.cc,生成可执行文件 tcp_client
	# $@ 表示目标名称(tcp_client),$^ 表示所有依赖文件(Tcp_Client.cc)

.PHONY: clean      # 声明 'clean' 是一个伪目标,用于清理构建文件
clean:             # 定义清理规则
	rm -f tcp_server tcp_client  # 删除生成的可执行文件 tcp_server 和 tcp_client

2.2.11 Dict.txt

词典单词数据,可按格式自行添加,数量不限

cpp 复制代码
accident ['æksidənt]   n.事故,意外,偶然
careful  ['keəful]   a.仔细(小心)的
difficulty  ['difikəlti]   n.困难
flag  [flæg]   n.旗帜
horse  [hɔ:s]   n.马
lock  [lɔk]   n.&v.锁
nut  [nʌt]   n.竖果,螺帽
rain  [rein]   n.&v.雨,下雨
silk  [silk]   n.丝,丝绸
thirty  ['θə:ti]   a.&n.三十(个)
accidental  [.æksi'dentl]   a.意外的,偶然的
carrot  ['kærət]   n.胡萝卜
dinner  ['dinə]   n.正餐,晚餐
flat  [flæt]   a.平的,扁平的;n.套间
hospital  ['hɔspitl]   n. 医院
lonely  ['ləunli]   a.孤单的,孤寂的,偏僻的
Oceania  [.əuʃi'einiə]   n.大洋洲
rainy  ['reini]   a.多雨的
simple  ['simpl]   a.简单的,单纯的,朴素的
though  [ðəu]   ad.可是;conj.虽然,尽管

2.2.12 Main.cc

cpp 复制代码
#include <iostream>            // 引入输入输出流库
#include <memory>              // 引入智能指针库
#include <algorithm>           // 引入算法库
#include "Log.hpp"            // 引入日志功能
#include "Tcp_Server.hpp"     // 引入 TCP 服务器类
#include "Translate.hpp"      // 引入翻译类

using namespace std; // 使用标准命名空间

// 使用说明函数
void Usage(std::string proc)
{
    std::cout << "Usage : \n\t" << proc << " local_port\n" // 输出程序使用说明
              << std::endl;
}

Translate trans; // 创建翻译类的实例

// 交互函数,通过套接字与客户端进行通信
void Interact(int sockfd, string &out, const string &in)
{
    char buff[1024]; // 缓冲区用于接收数据
    ssize_t n = read(sockfd, buff, sizeof(buff) - 1); // 从套接字读取数据
    if (n > 0)
    {
        buff[n] = 0; // 添加字符串结束符
        write(sockfd, in.c_str(), in.size()); // 将响应写回客户端
    }
    else if (n == 0) // 返回值为0,表示对端关闭了连接
    {
        lg.LogMessage(Info, "对端关闭了连接\n");
    }
    else
    {
        lg.LogMessage(Info, "读消息失败: %d, error string: %s\n", errno, strerror(errno));
    }
}

// 心跳机制函数,用于检测服务是否正常
void Ping(int sockfd, InetAddr addr)
{
    lg.LogMessage(Debug, "%s select %s success, fd : %d\n", addr.PrintDebug().c_str(), "ping", sockfd);
    string message; // 用于存储响应消息
    Interact(sockfd, message, "Pong"); // 与客户端交互,发送 "Pong"
}

// 翻译服务函数
void Translate_S(int sockfd, InetAddr addr)
{
    lg.LogMessage(Debug, "%s select %s success, fd : %d\n", addr.PrintDebug().c_str(), "Translate", sockfd);
    char wordbuff[128]; // 缓冲区用于接收单词
    int n = read(sockfd, wordbuff, sizeof(wordbuff) - 1); // 从套接字读取单词
    if (n > 0) wordbuff[n] = 0; // 添加字符串结束符
    std::string chinese = trans.Excute(wordbuff); // 调用翻译功能
    write(sockfd, chinese.c_str(), chinese.size()); // 将翻译结果写回客户端

    lg.LogMessage(Debug, "%s Translate , %s -> %s\n", addr.PrintDebug().c_str(), wordbuff, chinese.c_str()); // 记录翻译日志
}

// 转换服务函数,将消息转换为大写
void Transform(int sockfd, InetAddr addr)
{
    lg.LogMessage(Debug, "%s select %s success, fd : %d\n", addr.PrintDebug().c_str(), "Transform", sockfd);
    char message[128]; // 缓冲区用于接收消息
    int n = read(sockfd, message, sizeof(message) - 1); // 从套接字读取消息
    if (n > 0) message[n] = 0; // 添加字符串结束符
    string messagebuf = message; // 转换为字符串
    transform(messagebuf.begin(), messagebuf.end(), messagebuf.begin(), [](unsigned char c)
    {
        return toupper(c); // 将字符转换为大写
    });
    write(sockfd, messagebuf.c_str(), messagebuf.size()); // 将转换后的消息写回客户端
}

int main(int argc, char *argv[])
{
    if (argc != 2) // 检查命令行参数数量
    {
        Usage(argv[0]); // 输出使用说明
        return Usage_Err; // 返回错误代码
    }
    uint16_t port = stoi(argv[1]); // 将端口号从字符串转换为整型

    unique_ptr<TcpServer> tsvr = make_unique<TcpServer>(port); // 创建 TCP 服务器实例

    // 注册服务函数
    tsvr->RegisterFunc("ping", Ping);
    tsvr->RegisterFunc("translate", Translate_S);
    tsvr->RegisterFunc("transform", Transform);

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

    tsvr->Start(); // 启动服务器

    return 0; // 程序正常结束
}

2.2.13 Tcp_Client.cc

cpp 复制代码
#include <iostream>            // 引入输入输出流库
#include <string>              // 引入字符串库
#include <cerrno>              // 引入错误号库
#include <cstring>             // 引入字符串处理库
#include <sys/types.h>        // 引入系统数据类型
#include <sys/socket.h>       // 引入套接字相关函数
#include <stdlib.h>           // 引入标准库
#include <netinet/in.h>       // 引入互联网域套接字
#include <arpa/inet.h>        // 引入地址转换函数
#include <unistd.h>           // 引入 UNIX 标准函数
#include <signal.h>           // 引入信号处理库

using namespace std; // 使用标准命名空间
#define Retry_count 5 // 定义最大重试次数

// 信号处理函数
void handler(int signo)
{
    std::cout << "signo: " << signo << std::endl; // 输出接收到的信号编号
    exit(0); // 退出程序
}

// 输出程序的使用说明
void Usage(const std::string &process)
{
    std::cout << "Usage: " << process << " + server_ip + server_port" << std::endl; // 指导用户如何使用程序
}

// 访问服务器的函数
bool visitServer(string &server_ip, uint16_t &server_port, int *cnt)
{
    // 1. 创建套接字
    string inbuffer; // 输入缓冲区用于接收用户输入
    char service_list[1024]; // 缓冲区用于存储服务列表
    ssize_t m = 0; // 读取字节数
    ssize_t n = 0; // 写入字节数
    int sock = socket(AF_INET, SOCK_STREAM, 0); // 创建 TCP 套接字
    if (sock < 0)
    {
        cerr << "socket error" << endl; // 记录错误信息
        return false; // 返回失败
    }
    bool ret = true; // 初始化返回值为真
    
    // 2. 建立连接
    struct sockaddr_in server; // 定义服务器地址结构
    server.sin_family = AF_INET; // 使用 IPv4
    server.sin_port = htons(server_port); // 设置端口号
    inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr); // 将 IP 字符串转换为网络字节序

    socklen_t len; // 用于存储地址长度
    int nn = connect(sock, (struct sockaddr *)&server, sizeof(server)); // 尝试连接服务器
    if (nn < 0)
    {
        cerr << "connect error" << endl; // 记录连接错误信息
        ret = false; // 设置返回值为假
        goto END; // 跳转到结束标签
    }
    
    *cnt = 0; // 初始化重试计数

    // 读取服务器提供的服务列表
    m = read(sock, service_list, sizeof(service_list) - 1); // 从套接字读取服务列表
    if (m > 0)
    {
        service_list[m] = 0; // 添加字符串结束符
        cout << "服务器提供的服务列表:" << service_list << endl; // 输出服务列表
    }

    // 选择服务
    cout << "请选择服务:"; // 提示用户选择服务
    getline(cin, inbuffer); // 读取用户输入的服务名称
    write(sock, inbuffer.c_str(), inbuffer.size()); // 将用户选择写入套接字

    // 进行通信
    cout << "请输入:"; // 提示用户输入消息
    getline(cin, inbuffer); // 读取用户输入
    if (inbuffer == "quit") // 检查用户是否输入 "quit"
        return true; // 返回成功,表示结束通信
        
    n = write(sock, inbuffer.c_str(), inbuffer.size()); // 将用户输入写入套接字
    if (n > 0)
    {
        char buff[1024]; // 缓冲区用于接收服务器响应
        ssize_t m = read(sock, buff, sizeof(buff) - 1); // 从套接字读取响应
        if (m > 0)
        {
            buff[m] = 0; // 添加字符串结束符
            cout << buff << endl; // 输出服务器响应
        }
        else if (m == 0) // 如果返回值为0,表示服务器关闭了连接
        {
            return true; // 返回成功
        }
        else
        {
            ret = false; // 设置返回值为假
            goto END; // 跳转到结束标签
        }
    }
    else
    {
        ret = false; // 设置返回值为假
        goto END; // 跳转到结束标签
    }

END:
    close(sock); // 关闭套接字
    return ret; // 返回结果
}

int main(int argc, char *argv[])
{
    if (argc != 3) // 检查命令行参数数量
    {
        Usage(argv[0]); // 输出使用说明
        return 1; // 返回错误代码
    }

    string server_ip = argv[1]; // 获取服务器 IP 地址
    uint16_t server_port = stoi(argv[2]); // 将端口号从字符串转换为整型
    signal(SIGPIPE, SIG_IGN); // 忽略 SIGPIPE 信号
    int cnt = 1; // 初始化重试计数

    // 尝试访问服务器
    while (cnt <= Retry_count) // 在重试次数范围内循环
    {
        bool result = visitServer(server_ip, server_port, &cnt); // 访问服务器
        if (result) // 如果访问成功
        {
            break; // 退出循环
        }
        else
        {
            sleep(1); // 等待 1 秒
            cout << "正在尝试重连中..." << cnt << endl; // 输出重连信息
            cnt++; // 增加重试计数
        }
    }

    return 0; // 程序正常结束
}
相关推荐
黑客学长-刘备1 分钟前
终于有人把网络安全就业方向一口气讲清了(非常详细)零基础入门到精通,收藏这一篇就够了
java·运维·服务器·网络·python·安全·web安全
夏天vs不热1 分钟前
Kubernetes中的网络模型:Service、Ingress、Pod通信详解
网络·容器·kubernetes
学习HCIA的小白2 分钟前
RHCE笔记-DNS服务器
运维·服务器·笔记
clear code20 分钟前
【modbus协议】Modbus-TCP消息帧格式
服务器·网络协议·tcp/ip
小沈熬夜秃头中୧⍤⃝28 分钟前
宝塔FTP服务配置结合内网穿透实现安全便捷的远程文件管理和传输
linux·服务器·安全
J老熊33 分钟前
Nginx 的讲解和案例示范
linux·运维·后端·nginx·面试
师太,答应老衲吧38 分钟前
Linux常用命令
linux
吴巴格40 分钟前
linux ssh 进行 免密登录,通过密钥登录
linux·运维·ssh
小安运维日记42 分钟前
Linux云计算 |【第五阶段】CLOUD-DAY2
linux·运维·云计算·openstack
笨笨聊运维1 小时前
linux离线安装Ollama并完成大模型配置(无网络)
linux·网络·人工智能·php