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

目录

1. 相关函数介绍

1.1 listen()

1.2 accept()

1.3 connect()

2. TCP 回显服务器

2.1 Common.hpp

2.2 InetAddr.hpp

2.3 TcpClient.cc

2.4 TcpServer.hpp

2.5 TcpServer.cc

2.6 demo 测试

3. TCP 翻译服务器

3.1 demo 测试


1. 相关函数介绍

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

1.1 listen()

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

原型: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 服务器的关键步骤之一。

原型: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 连接。

原型: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 网络编程 -- 回显服务器,翻译服务器。

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

// 条件变量模块 -- 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;};
}
// 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);// 将运行标志位置为trueself->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. 如果线程已经运行起来,防止再次启动,直接返回falseif (_isrunning)return false;// 2. 如果线程第一次启动,则创建线程// 这里如果Routine不是静态成员函数,默认会有一个this指针参数,与pthread_create中的参数不匹配// 所以这里使用静态成员函数,将该线程对象以参数this的形式传给pthread_createint n = pthread_create(&_tid, nullptr, Routine, this);// 创建线程失败返回falseif (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,取消线程并将运行标志位置为falseif (_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;    // 线程IDstd::string _name; // 线程名字bool _isdetach;    // 线程分离标志位bool _isrunning;   // 线程运行标志位void *res;         // 线程返回值func_t _func;      // 线程入口函数};
}
// 线程池模块 -- 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. 将运行标志位置为falseLockGuard 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

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

#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,主要用于主机序列和网络序列之间的相互转换。

#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_ANYInetAddr(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

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

#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 clientwhile (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

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

#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; // 监听socketbool _isrunning;// func_t _func; // 回调处理函数
};

2.5 TcpServer.cc

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

#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 文件即可,这里的服务器使用多线程版本,将回显服务从服务器中分层到应用层,并替换为翻译服务。

// 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; // 监听socketbool _isrunning;func_t _func; // 回调处理函数
};
// 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 号端口,并加载字典文件到内存中,将绑定套接字并设置为监听状态。

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/80749.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity3D仿星露谷物语开发46之种植/砍伐橡树

1、目标 种植一棵橡树&#xff0c;从种子变成大树。 然后可以使用斧头砍伐橡树。 2、删除totalGrowthDays字段 修改growthDays的含义&#xff0c;定义每个值为到达当前阶段的累加天数。此时最后一个阶段就是totalGrowthDays的含义。所以就可以删除totalGrowthDays字段。 &…

容器化-K8s-镜像仓库使用和应用

一、K8s 镜像仓库使用 1、启动镜像仓库 cd/usr/local/harbor ./install.sh2、配置镜像仓库地址 在 master 节点和 slaver 节点上,需要配置 Docker 的镜像仓库地址,以便能够访问本地的镜像仓库。编辑 Docker 的配置文件 vi /etc/docker/daemon.json(如果不存在则创建),添…

塔式服务器都有哪些重要功能?

塔式服务器作为一种拥有着独特立式设计的服务器&#xff0c;能够帮助企业节省一定的放置空间&#xff0c;提供一系列的功能和优势&#xff0c;可以运用在多种应用场景当中&#xff0c;下面将探讨一下塔式服务器的主要功能都有哪些&#xff1f; 塔式服务器可以支持基本的应用程序…

2025年- H36-Lc144 --739. 每日温度(单调栈)--Java版

1.题目描述 2.思路 &#xff08;1&#xff09;单调栈维护单调递增或者单调递减的数列 &#xff08;2&#xff09;因为要求找到当前元素 右边区域&#xff0c;第一个比当前元素大的元素&#xff0c;所以取单调增数量。 &#xff08;3&#xff09;单调栈存储元素的索引。如果遇到…

架构选择/区别

目录 一、分层架构&#xff08;Layered Architecture&#xff09; 二、微服务架构&#xff08;Microservices Architecture&#xff09; 三、分布式架构&#xff08;Distributed Architecture&#xff09; 四、单体架构&#xff08;Monolithic Architecture&#xff09; 五…

Python----循环神经网络(WordEmbedding词嵌入)

一、编码 当我们用数字来让电脑“认识”字符或单词时&#xff0c;最简单的方法是为每个字符或单词分配一个唯一的编号&#xff0c;然后用一个长长的向量来表示它。比如&#xff0c;假设“我”这个字在字典中的编号是第10个&#xff0c;那么它的表示就是一个很多0组成的向量&…

深入解析Spring Boot与微服务架构:从入门到实践

深入解析Spring Boot与微服务架构&#xff1a;从入门到实践 引言 随着云计算和分布式系统的快速发展&#xff0c;微服务架构已成为现代软件开发的主流模式。Spring Boot作为Java生态中最受欢迎的框架之一&#xff0c;为开发者提供了快速构建微服务的强大工具。本文将深入探讨…

DeepSeek 赋能数字孪生:重构虚实共生的智能未来图景

目录 一、数字孪生技术概述1.1 数字孪生的概念1.2 技术原理剖析1.3 应用领域与价值 二、DeepSeek 技术解读2.1 DeepSeek 的技术亮点2.2 与其他模型的对比优势 三、DeepSeek 赋能数字孪生3.1 高精度建模助力3.2 实时数据处理与分析3.3 智能分析与预测 四、实际案例解析4.1 垃圾焚…

Amazon Q 从入门到精通 – 测试与重构

Amazon Q Developer 是亚马逊推出的一个专为专业开发人员设计的人工智能助手&#xff0c;旨在提升代码开发和管理效率。其主要功能包括代码生成、调试、故障排除和安全漏洞扫描&#xff0c;提供一站式代码服务。 众所周知&#xff0c;在软件开发领域&#xff0c;测试代码是软件…

专题五:floodfill算法(图像渲染深度优先遍历解析与实现)

以leetcode733题为例 题目解析&#xff1a; 给一个初始坐标&#xff08;sr&#xff0c;sc&#xff09;比如示例中的粉色的1&#xff0c;如果周围上下左右都是1&#xff0c;就是连通块&#xff08;性质相同的地方&#xff09;&#xff0c;把它涂上颜色&#xff08;2&#xff09…

在金融发展领域,嵌入式主板有什么优点?

在金融发展领域&#xff0c;嵌入式主板能够有力推动金融行业的智能化与高效化进程。主板的强大计算能力可以保障业务高效运行。例如在银行的高频交易场景下&#xff0c;其强大计算能力可确保系统在高负荷下依然保持流畅稳定&#xff0c;快速响应用户需求&#xff0c;大大提升金…

《Python星球日记》 第94天:走近自动化训练平台

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、自动化训练平台简介1. Kubeflow Pipelines2. TensorFlow Extended (TFX)二、自动化训练流程1. 数据预处理2. 模型训练3. 评估与部署三、构建…

PHP、JAVA、Shiro反序列化

目录 一、PHP反序列化 二、JAVA反序列化 三、Shiro反序列化 Shiro-550 反序列化漏洞原理 Shiro-721 反序列化漏洞原理 Padding Oracle 漏洞补充&#xff1a; 防御措施&#xff1a; 一、PHP反序列化 主要是分为有类和无类&#xff1a; 1、有类&#xff1a;就有相关的魔术…

AM32电调学习解读六:main.c文件的函数介绍

最近在学习AM32电调的2.18版本的源码&#xff0c;我用的硬件是AT32F421&#xff0c;整理了部分流程处理&#xff0c;内容的颗粒度是按自己的需要整理的&#xff0c;发出来给有需要的人参考。按自己的理解整理的&#xff0c;技术能力有限&#xff0c;可能理解有误&#xff0c;欢…

WebSocket实时双向通信:从基础到实战

一、WebSocket 基础概念 1. 什么是 WebSocket&#xff1f; 双向通信协议&#xff1a;与 HTTP 的单向请求不同&#xff0c;WebSocket 支持服务端和客户端实时双向通信。 低延迟&#xff1a;适用于聊天室、实时数据推送、在线游戏等场景。 协议标识&#xff1a;ws://&#xff…

【算法】分支限界法和贪心、动态规划、回溯、分治法的区别是

什么是分支限界法 分支限界法是一种用于求解最优化问题的算法,其核心思想是通过剪枝策略减少搜索空间。 分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。 在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就…

[自动化集成] 使用明道云上传附件并在Python后端处理Excel的完整流程

在企业日常自动化场景中,使用低代码平台如明道云搭建前端界面,结合自定义Python后端服务,实现灵活数据处理是一种高效的组合方式。本文将分享一个典型的集成用例:用户通过明道云上传文本和Excel附件,Python后端接收并解析这些信息,最终实现完整的数据处理闭环。 项目背景…

ubuntu下实时检测机械硬盘和固态硬盘温度

sudo apt update sudo apt install smartmontools然后&#xff0c;使用smartctl命令查看硬盘的详细信息&#xff0c;包括温度&#xff1a; sudo smartctl -a /dev/sda实时监控硬盘温度 虽然smartctl不能直接实时显示温度&#xff0c;你可以使用watch命令结合smartctl来定期查…

游戏开发实战(二):Python复刻「崩坏星穹铁道」嗷呜嗷呜事务所---源码级解析该小游戏背后的算法与设计模式【纯原创】

文章目录 奇美拉和队列奇美拉被动技能多对多观察者关系实现自定义元类奇美拉基类 管理奇美拉的队列奇美拉队列类心得体会扩展 规则定义工作相关奇美拉相关 奇美拉属性 在本篇博文&#xff0c;我将介绍本项目的整体框架&#xff0c;以及“编码规则”&#xff0c;这些规则保证了本…

Redis实现分布式锁的进阶版:Redisson实战指南

一、为什么选择Redisson&#xff1f; 在上一篇文章中&#xff0c;我们通过Redis原生命令实现了分布式锁。但在实际生产环境中&#xff0c;这样的基础方案存在三大痛点&#xff1a; 锁续期难题&#xff1a;业务操作超时导致锁提前释放不可重入限制&#xff1a;同一线程无法重复…