Linux 的 TCP 网络编程 -- 回显服务器,翻译服务器

目录

[1. 相关函数介绍](#1. 相关函数介绍)

[1.1 listen()](#1.1 listen())

[1.2 accept()](#1.2 accept())

[1.3 connect()](#1.3 connect())

[2. TCP 回显服务器](#2. TCP 回显服务器)

[2.1 Common.hpp](#2.1 Common.hpp)

[2.2 InetAddr.hpp](#2.2 InetAddr.hpp)

[2.3 TcpClient.cc](#2.3 TcpClient.cc)

[2.4 TcpServer.hpp](#2.4 TcpServer.hpp)

[2.5 TcpServer.cc](#2.5 TcpServer.cc)

[2.6 demo 测试](#2.6 demo 测试)

[3. TCP 翻译服务器](#3. TCP 翻译服务器)

[3.1 demo 测试](#3.1 demo 测试)


1. 相关函数介绍

其中一些函数在之前已经介绍过,参考Linux 的 UDP 网络编程 -- 回显服务器,翻译服务器

1.1 listen()

listen() 是 C 语言网络编程中的一个重要函数,主要用于将一个套接字(socket)转换为被动监听套接字,使其能够接受来自其他客户端的连接请求。这个函数是实现 TCP 服务器的关键步骤之一。

cpp 复制代码
原型:
    int listen(int sockfd, int backlog);

头文件:
    #include <sys/types.h>
    #include <sys/socket.h>

参数:
    sockfd:这是通过 socket() 函数创建的套接字描述符,并且该套接字已经通过 bind() 函数绑定到了
特定的地址和端口。

    backlog:表示请求队列的最大长度,即允许在服务器处理当前连接请求的同时,积压的未处理连接请求的
最大数量。当请求队列已满时,新的连接请求可能会被拒绝(具体行为取决于操作系统)。

返回值:
    成功。返回 0.
    失败,返回 -1,并设置 errno 来指示具体的错误原因。

功能:
    主要用于将一个套接字(socket)转换为被动监听套接字,使其能够接受来自其他客户端的连接请求。这
个函数是实现 TCP 服务器的关键步骤之一。

1.2 accept()

accept() 是 C 语言网络编程中的一个核心函数,主要用于**从已完成连接队列中取出一个客户端连接请求,并创建一个新的套接字来专门处理该连接。**这个函数是实现 TCP 服务器的关键步骤之一。

cpp 复制代码
原型:
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

头文件:
    #include <sys/types.h>
    #include <sys/socket.h>

参数:
    sockfd:这是通过 socket()、bind() 和 listen() 函数创建并配置好的监听套接字描述符,用于接收
客户端的连接请求。

    addr(可选):指向 struct sockaddr 类型的指针,用于存储客户端的地址信息(如 IP 地址和端口
号)。如果不需要客户端地址,可以传入 NULL。

    addrlen(可选):指向 socklen_t 类型的指针,用于指定 addr 结构的长度。函数返回时,该参数会
被更新为实际存储的地址结构长度。如果 addr 为 NULL,则 addrlen 也应设为 NULL。

返回值:
    成功,返回一个新的套接字描述符,用于与客户端进行数据通信。原监听套接字 sockfd 依然保持监听状
态,可以继续接收其他连接请求。
    失败:返回 -1,并设置 errno 来指示具体的错误原因。

功能:
    用于从已完成连接队列中取出一个客户端连接请求,并创建一个新的套接字来专门处理该连接。

1.3 connect()

connect() 是 C 语言网络编程中的一个基础函数,主要用于客户端服务器发起连接请求。通过这个函数,客户端可以与指定 IP 地址和端口的服务器建立 TCP 连接。

cpp 复制代码
原型:
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

头文件:
    #include <sys/types.h>
    #include <sys/socket.h>

参数:
    sockfd:这是通过 socket() 函数创建的客户端套接字描述符。

    addr:指向 struct sockaddr 类型的指针,其中包含了服务器的地址信息(如 IP 地址和端口号)。对
于 IPv4,通常使用 struct sockaddr_in 结构体;对于 IPv6,则使用 struct sockaddr_in6 结构体。

    addrlen:addr 结构体的长度,类型为 socklen_t。

返回值:
    成功,返回 0,表示连接已建立。
    失败,返回 -1,并设置 errno 来指示具体的错误原因。

功能:
    主要用于客户端向服务器发起连接请求。通过这个函数,客户端可以与指定 IP 地址和端口的服务器建立 
TCP 连接。

2. TCP 回显服务器

互斥锁的封装模块线程安全的日志模块 参考Linux 的 UDP 网络编程 -- 回显服务器,翻译服务器

这里先给出封装的条件变量模块,线程模块线程池模块

cpp 复制代码
// 条件变量模块 -- Cond.hpp
#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)n;
        }

        void Signal()
        {
            // 唤醒一个在条件变量下等待的线程
            int n = pthread_cond_signal(&_cond);
            (void)n;
        }

        void Broadcast()
        {
            // 唤醒所有在条件变量下等待的线程
            int n = pthread_cond_broadcast(&_cond);
            (void)n;
        }
        ~Cond()
        {
            pthread_cond_destroy(&_cond);
        }
    private:
        pthread_cond_t _cond;
    };
}
cpp 复制代码
// Thread.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <functional>
#include <pthread.h>
#include "Log.hpp"

namespace ThreadModule
{
    using namespace LogModule;
    static uint32_t number = 1;

    class Thread
    {
        using func_t = std::function<void()>;
    private:
        void EnableDetach()
        {
            // LOG(LogLevel::DEBUG) << "thread's detach flag become to true";
            _isdetach = true;
        }

        void EnableRunning()
        {
            // LOG(LogLevel::DEBUG) << _name << " is started";
            _isrunning = true;
        }

        static void* Routine(void* args)
        {
            Thread * self = static_cast<Thread*>(args);
            // 将运行标志位置为true
            self->EnableRunning();
            // 如果分离标志位为true,则分离线程
            if (self->_isdetach)
            {
                int n = pthread_detach(self->_tid);
                // LOG(LogLevel::DEBUG) << "thread is detached in Routine, the return value is " << n;
            }
            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++);
        }

        bool Start()
        {
            // 1. 如果线程已经运行起来,防止再次启动,直接返回false
            if (_isrunning)
                return false;

            // 2. 如果线程第一次启动,则创建线程
            // 这里如果Routine不是静态成员函数,默认会有一个this指针参数,与pthread_create中的参数不匹配
            // 所以这里使用静态成员函数,将该线程对象以参数this的形式传给pthread_create
            int n = pthread_create(&_tid, nullptr, Routine, this);
            // 创建线程失败返回false
            if (n != 0)
            {
                // LOG(LogLevel::DEBUG) << "create thread error " << strerror(n);
                return false;
            }
            else
            {
                // LOG(LogLevel::DEBUG) << _name << " create success";
                return true;
            }
        }

        void Detach() 
        {
            // // 需要处理两种情况
            // // 情况1:在线程还没有启动的时候,调用Detach设置线程分离标志位,然后线程启动之后在Routine函数中进行分离
            // // 情况2:在线程启动之后调用Detach设置线程分离标志位,以及分离线程
            // 如果线程已经分离,直接返回
            if (_isdetach)
            {
                // LOG(LogLevel::DEBUG) << _name << " is already detached. No further action needed.";
                return;
            }

            // 如果线程还没有启动,设置线程分离标志位
            if (!_isrunning)
            {
                EnableDetach();
                return;
            }
            else
            {
                // 启动后设置线程分离,需要设置标志位之后再进行线程分离
                EnableDetach();
                int n = pthread_detach(_tid);
                // LOG(LogLevel::DEBUG) << "thread is detched, the return value is " << n;
            }
        }

        bool Stop()
        {  
            // 如果运行标志位为true,取消线程并将运行标志位置为false
            if (_isrunning)
            {
                int n = pthread_cancel(_tid);
                if (n != 0)
                {
                    // LOG(LogLevel::DEBUG) << "cancel thread error" << strerror(n);
                    return false;
                }
                else
                {
                    _isrunning = false;
                    // LOG(LogLevel::DEBUG) << _name << " stop";
                    return true;
                }
            }

            return false;
        }

        void Join()
        {
            // 分离的线程不能被等待
            if (_isdetach)
            {
                // LOG(LogLevel::DEBUG) << "thread is detached. it can't be joined! ";
                return;
            }
            int n = pthread_join(_tid, &res);
            if (n != 0)
            {
                // LOG(LogLevel::DEBUG) << "join thread error";
            }
            else
            {
                // LOG(LogLevel::DEBUG) << "join thread success";
            }
        }

        std::string GetName()
        {
            return _name;
        }

        pthread_t Id()
        {
            return _tid;
        }
        ~Thread()
        {

        }
    private:
        pthread_t _tid;    // 线程ID
        std::string _name; // 线程名字
        bool _isdetach;    // 线程分离标志位
        bool _isrunning;   // 线程运行标志位
        void *res;         // 线程返回值
        func_t _func;      // 线程入口函数
    };
}
cpp 复制代码
// 线程池模块 -- ThreadPool.hpp
// 懒汉式单例模式线程池

#pragma once

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

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

    static const int gnum = 5; // 使用全局变量来表示一个线程池默认的线程数量

    template <typename T> // 使用模版的方式使线程池支持多类型的任务
    class ThreadPool
    {
    private:
        void WakeUpAllThread()
        {
            if (_sleep_num)
                _cond.Broadcast();

            LOG(LogLevel::DEBUG) << "唤醒所有休眠线程";
        }

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

        // 私有化构造函数
        ThreadPool(int num = gnum)
            : _num(num),
              _isrunning(false),
              _sleep_num(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();
            }
        }

        // 禁用拷贝构造和赋值运算符
        ThreadPool(const ThreadPool<T> &) = delete;
        ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

    public:
        static ThreadPool<T> *GetInstance()
        {
            if (inc == nullptr) // 第一次创建的时候需要加锁,保证创建是原子性的
            {
                LockGuard lockGuard(_gmutex);
                if (inc == nullptr) // 双层判断,保证只会创建一个单例
                {
                    LOG(LogLevel::DEBUG) << "首次使用, 创建单例...";
                    inc = new ThreadPool<T>();
                    inc->Start();
                }
            }
            return inc;
        }

        void HandlerTask()
        {
            char name[64];
            pthread_getname_np(pthread_self(), name, sizeof(name));
            while (true)
            {
                T t;
                // 处理任务
                {
                    LockGuard lockGuard(_mutex);
                    // 1. 队列为空,线程池没有退出,进行休眠
                    while (_taskq.empty() && _isrunning)
                    {
                        _sleep_num++;
                        LOG(LogLevel::INFO) << name << " 进入休眠";
                        _cond.Wait(_mutex);
                        _sleep_num--;
                    }
                    // 2. 任务为空,线程池退出,则该线程退出
                    if (!_isrunning && _taskq.empty())
                    {
                        LOG(LogLevel::INFO) << name << " 退出, 因为线程池退出&&任务队列为空";
                        break;
                    }

                    // 3. 获取任务
                    t = _taskq.front();
                    _taskq.pop();
                }
                t(); // 4. 处理任务
                // LOG(LogLevel::DEBUG) << name << " is running";
            }
        }

        bool Enqueue(const T &in)
        {
            if (_isrunning) // 如果线程池停止,则停止入任务
            {
                LockGuard lockGuard(_mutex);
                _taskq.push(in);
                // if (_threads.size() == _sleep_num) // 如果全部线程都在休眠,则唤醒一个线程
                WakeOne();
                return true;
            }
            return false;
        }

        void Stop()
        {
            // 1. 将运行标志位置为false
            LockGuard lockGuard(_mutex);
            if (!_isrunning)
                return;
            _isrunning = false;

            // 2. 唤醒休眠的线程,然后再HandlerTask中进行退出
            WakeUpAllThread();
        }

        void Join()
        {
            for (auto &thread : _threads)
            {
                thread.Join();
                LOG(LogLevel::INFO) << thread.GetName() << " 被Join";
            }
        }

        ~ThreadPool()
        {
        }

    private:
        std::vector<Thread> _threads;
        int _num;             // 线程数量
        std::queue<T> _taskq; // 任务队列
        Cond _cond;
        Mutex _mutex;
        bool _isrunning;
        int _sleep_num;

        static ThreadPool<T> *inc; // 单例指针
        static Mutex _gmutex;      // 用于多线程场景下保护单例不被多次创建
    };

    template <typename T>
    ThreadPool<T> *ThreadPool<T>::inc = nullptr; // 静态成员变量需要在类外进行初始化
    template <typename T>
    Mutex ThreadPool<T>::_gmutex; // 自动调用Mutex的构造函数进行初始化
}

2.1 Common.hpp

该源文件中包含了整个项目所使用的通用的头文件,宏定义,结构体。

cpp 复制代码
#pragma once

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

using namespace LogModule;
using namespace ThreadPoolModule;

// 强转 struct sockaddr_in * 为 struct sockaddr * 的宏
#define CONV(addr) ((struct sockaddr*)&addr)

// 将各种错误的错误码用一个枚举类型表示
enum EixtCode
{
    OK,
    USAGE_ERR,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
    FORK_ERR
};

// 没有拷贝构造和赋值重载的基类
class NoCopy
{
public:
    NoCopy(){}
    ~NoCopy(){}
    NoCopy(const NoCopy &) = delete;
    const NoCopy &operator=(const NoCopy&) = delete;
};

2.2 InetAddr.hpp

该源文件定义了一个网络序列和主机序列存储及相互转换的类 InetAddr,主要用于主机序列和网络序列之间的相互转换。

cpp 复制代码
#pragma once

#include "Common.hpp"

class InetAddr
{
public:
    InetAddr(){};
    // 使用套接字创建对象的构造函数
    InetAddr(struct sockaddr_in &addr) : _addr(addr)
    {
        _port = ntohs(_addr.sin_port);
        char ipbuffer[64];
        inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));
        _ip = ipbuffer;
    }

    // 使用主机序列创建的构造函数
    InetAddr(std::string &ip, uint16_t port) : _ip(ip), _port(port)
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        _addr.sin_port = htons(_port);
        inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
    }

    // 仅使用端口号创建,ip 设为 INADDR_ANY
    InetAddr(uint16_t port) : _port(port), _ip()
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        _addr.sin_port = htons(_port);
        _addr.sin_addr.s_addr = INADDR_ANY;
    }

    uint16_t Port() { return _port; }
    std::string Ip() { return _ip; }
    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; }
    std::string StringAddr() { return _ip + ":" + std::to_string(_port); }
    ~InetAddr() {}

private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

2.3 TcpClient.cc

该文件为项目中的客户端文件。

cpp 复制代码
#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 server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    // 1. 创建套接字文件
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        LOG(LogLevel::FATAL) << "socket error";
        exit(SOCKET_ERR);
    }
    LOG(LogLevel::INFO) << "socket success";

    // 2. 直接向目标服务器发起建立连接的请求
    InetAddr serverAddr(server_ip, server_port);
    int n = connect(sockfd, serverAddr.NetAddrPtr(), serverAddr.NetAddrLen());
    if (n < 0)
    {
        LOG(LogLevel::FATAL) << "connect error";
        exit(CONNECT_ERR);
    }
    LOG(LogLevel::INFO) << "connect success";

    // 3. echo client
    while (true)
    {
        // 3.1 发消息
        std::string line;
        std::cout << "Please Enter# ";
        std::getline(std::cin, line);
        if (line.empty())
            continue;
        write(sockfd, line.c_str(), line.size());

        // 3.2 收消息
        char buffer[1024];
        ssize_t s = read(sockfd, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << buffer << std::endl;
        }
    }
    return 0;
}

2.4 TcpServer.hpp

该源文件为回显服务器的封装文件,其中给出了回显服务器的多进程版本,多线程版本以及线程池版本。这里选择线程池版本进行测试。

cpp 复制代码
#pragma once

#include "Common.hpp"
#include "InetAddr.hpp"
#include <iostream>
#include <functional>

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;

// 服务器往往是禁止拷贝的
class TcpServer
{
public:
    // 短服务 -- 处理一次之后退出
    // 长服务 -- 客户端不退出服务端不退出
    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;
                std::string echo_string = "echo @ ";
                echo_string += buffer;

                // 2. 写回数据
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0) // 客户端把连接关闭了,读到文件的结尾,类似 pipe
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
                close(sockfd);
                break;
            }
            else // 读取异常
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 读取异常...";
                close(sockfd);
                break;
            }
        }
    }

public:
    TcpServer(uint16_t port)
        : _port(port),
          _listen_sockfd(defaultSockfd),
          _isrunning(false)
    {
    }

    void Init()
    {
        // signal(SIGCHLD, SIG_IGN);   // 子进程退出,自动回收
        // 1. 创建套接字文件
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "socket success: " << _listen_sockfd;

        // 2. bind 端口号,服务器 ip 不显示绑定
        InetAddr local(_port);
        int n = bind(_listen_sockfd, local.NetAddrPtr(), local.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success: " << _listen_sockfd;

        // 3. 设置 _listen_sockfd 为 listen 状态
        n = listen(_listen_sockfd, backlog);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success: " << _listen_sockfd;
    }

    class ThreadData
    {
    public:
        ThreadData(int sockfd, InetAddr addr, TcpServer *tsvr)
            : _sockfd(sockfd),
              _addr(addr),
              _tsvr(tsvr)
        {
        }

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

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

    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 1. 获取连接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listen_sockfd, CONV(peer), &len); // 如果没有连接,accept 会阻塞
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }
            InetAddr addr(peer);
            LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr() << " sockfd: " << sockfd;

            // 2.1 version0 -- test version -- 只能给一个客户端提供服务 -- 不会存在
            // Service(sockfd, addr);

            // 2.2 version1 -- 多进程版本
            // pid_t id = fork();
            // if (id < 0)
            // {
            //     LOG(LogLevel::FATAL) << "fork error";
            //     exit(FORK_ERR);
            // }
            // else if (id == 0)
            // {
            //     // 子进程
            //     close(_listen_sockfd);
            //     if (fork() > 0) // 子进程
            //         exit(OK);

            //     // 孙进程
            //     Service(sockfd, addr);  // 当子进程退出时变成孤儿进程,服务结束系统进行回收
            //     exit(OK);
            // }
            // else
            // {
            //     // 父进程
            //     close(sockfd);
            //     pid_t rid = waitpid(id, nullptr, 0);
            //     (void)rid;
            // }

            // 2.3 version2 -- 多线程版本
            // ThreadData *td = new ThreadData(sockfd, addr, this);
            // pthread_t tid;
            // pthread_create(&tid, nullptr, Routine, td);

            // 2.4 version3 -- 线程池版本 -- 线程池一般比较适合处理短服务
            ThreadPool<task_t>::GetInstance()->Enqueue([this, sockfd, addr](){
                // LOG(LogLevel::DEBUG) << "一个客户端进入线程池";
                this->Service(sockfd, addr);
            });
        }
        _isrunning = false;
    }
    ~TcpServer() {}

private:
    uint16_t _port;
    int _listen_sockfd; // 监听socket
    bool _isrunning;
    // func_t _func; // 回调处理函数
};

2.5 TcpServer.cc

该源文件为服务端的文件。

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

void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " port" << std::endl;
}

// ./tcpserver server_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. 创建通信对象
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);

    tsvr->Init();
    tsvr->Run();
    return 0;
}

2.6 demo 测试

如上图所示,启动服务端,绑定端口号 8888,先创建套接字,然后进行绑定,在将服务器设置为监听状态,使客户端能够进行连接。

当第一个客户端进行连接的时候,首次使用线程池,则创建线程池单例并唤醒一个线程给该客户端进行服务。当第二个客户端进行连接的时候,不用再创建线程池了,则唤醒另一个线程给该客户端提供服务。

当一个客户端退出之后,该线程结束服务,进入休眠状态。

3. TCP 翻译服务器

这里翻译的字典文件 以及字典结构体的封装 参考Linux 的 UDP 网络编程 -- 回显服务器,翻译服务器

仅仅修改 TcpServer.cc 以及 TcpServer.hpp 文件即可,这里的服务器使用多线程版本,将回显服务从服务器中分层到应用层,并替换为翻译服务。

cpp 复制代码
// TcpServer.hpp
#pragma once

#include "Common.hpp"
#include "InetAddr.hpp"
#include <iostream>
#include <functional>

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;

class TcpServer
{
public:
    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;
                std::string echo_string = _func(buffer, peer);
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " # " << buffer;

                // 2. 写回中文数据
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
                close(sockfd);
                break;
            }
            else // 读取异常
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 读取异常...";
                close(sockfd);
                break;
            }
        }
    }

public:
    TcpServer(uint16_t port, func_t func)
        : _port(port),
          _listen_sockfd(defaultSockfd),
          _isrunning(false),
          _func(func)
    {
    }

    void Init()
    {
        // 1. 创建套接字文件
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "socket success: " << _listen_sockfd;

        // 2. bind 端口号,服务器 ip 不显示绑定
        InetAddr local(_port);
        int n = bind(_listen_sockfd, local.NetAddrPtr(), local.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success: " << _listen_sockfd;

        // 3. 设置 _listen_sockfd 为 listen 状态
        n = listen(_listen_sockfd, backlog);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success: " << _listen_sockfd;
    }

    class ThreadData
    {
    public:
        ThreadData(int sockfd, InetAddr addr, TcpServer *tsvr)
            : _sockfd(sockfd),
              _addr(addr),
              _tsvr(tsvr)
        {
        }

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

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

    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 1. 获取连接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listen_sockfd, CONV(peer), &len); // 如果没有连接,accept 会阻塞
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }
            InetAddr addr(peer);
            LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr() << " sockfd: " << sockfd;

            // 2.3 多线程版本
            ThreadData *td = new ThreadData(sockfd, addr, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, Routine, td);
        }
        _isrunning = false;
    }


    ~TcpServer() {}

private:
    uint16_t _port;
    int _listen_sockfd; // 监听socket
    bool _isrunning;
    func_t _func; // 回调处理函数
};
cpp 复制代码
// TcpServer.cc
#include "TcpServer.hpp"
#include "Common.hpp"
#include "Dict.hpp"

void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " port" << std::endl;
}

// ./tcpserver server_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;
    d.LoadDict();

    // 2. 创建通信对象
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, [&d](const std::string &word, InetAddr &client)->std::string{
        return d.Translate(word, client);
    });

    tsvr->Init();
    tsvr->Run();
    return 0;
}

3.1 demo 测试

启动服务器并绑定 8888 号端口,并加载字典文件到内存中,将绑定套接字并设置为监听状态。

启动客户端进行连接,并输入字符串请求服务端服务。

这里再服务端可以看到服务器的运行信息。

相关推荐
文牧之几秒前
Oracle Enqueue Names
运维·数据库·oracle
widder_5 分钟前
软考中级软件设计师——操作系统篇
运维·服务器·数据库
lizz3125 分钟前
离线服务器Python环境配置指南
运维·服务器·python
Dontla41 分钟前
服务器网络配置 netplan一个网口配置两个ip(双ip、辅助ip、别名IP别名)
服务器·网络·tcp/ip
浩浩测试一下1 小时前
红蓝对抗中的网络安全设备操作手册
运维·服务器·网络
feing.1 小时前
防火墙高可靠性
linux·服务器·网络
苒苒鸭1 小时前
远程连接的公私钥加密
linux
破刺不会编程1 小时前
Linux中进程控制(上)
linux·运维·服务器·开发语言·windows
渡我白衣1 小时前
Linux操作系统之进程(二):进程状态
linux
仙袂拂月1 小时前
Day 0014:信息收集工具链
linux·运维·服务器·笔记·网络安全