当前位置: 首页 > news >正文

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 号端口,并加载字典文件到内存中,将绑定套接字并设置为监听状态。

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

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

相关文章:

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. 相关函数介绍 其中一些函数在之前…...

差动讯号(2):奇模与偶模

我们经常在探讨差动对时经常听到差模&#xff08;Differential mode&#xff09;与共模&#xff08;Common mode&#xff09;&#xff0c;究竟什么是差模&#xff1f; 什么是共模&#xff1f; 这一切就要从奇模&#xff08;Odd mode&#xff09;与偶模&#xff08;Even mode&am…...

口腔牙科小程序源码介绍

基于ThinkPHP、FastAdmin以及UniApp开发的口腔牙科小程序源码&#xff0c;专为口腔牙科行业设计&#xff0c;旨在提供一个便捷、高效的线上服务平台。 从技术层面看&#xff0c;这套源码结合了ThinkPHP的强大后端功能、FastAdmin的快速开发特性以及UniApp的跨平台优势&#xf…...

云计算与大数据进阶 | 27、存储系统如何突破容量天花板?可扩展架构的核心技术与实践—— 分布式、弹性扩展、高可用的底层逻辑(上)

数据中心里&#xff0c;存储系统是至关重要的组成部分。由于相关硬件组件与存储操作系统的多样性和复杂性&#xff0c;如何在保证存储稳定、安全、可靠的同时&#xff0c;实现灵活扩展和自服务&#xff0c;一直是困扰数据中心全面云化的难题。 简单来说&#xff0c;现在的难题…...

企业级物理服务器选型指南 - 网络架构优化篇

在分布式系统架构中&#xff0c;物理服务器的网络质量直接影响业务连续性。本文将通过真实场景演示如何选择符合业务特性的物理服务器。 一、网络拓扑设计原则 当企业需要覆盖多地域用户时&#xff0c;建议采用混合组网方案&#xff1a; # 网络质量检测脚本&#xff08;Pytho…...

可视化图解算法42:寻找峰值

牛客网 面试笔试TOP101 | LeetCode 162. 寻找峰值 1. 题目 描述 给定一个长度为n的数组nums&#xff0c;请你找到峰值并返回其索引。数组可能包含多个峰值&#xff0c;在这种情况下&#xff0c;返回任何一个所在位置即可。 1.峰值元素是指其值严格大…...

java每日精进 5.20【MyBatis 联表分页查询】

1. MyBatis XML 实现分页查询 1.1 实现方式 MyBatis XML 是一种传统的 MyBatis 使用方式&#xff0c;通过在 XML 文件中编写 SQL 语句&#xff0c;并结合 Mapper 接口和 Service 层实现分页查询。分页需要手动编写两条 SQL 语句&#xff1a;一条查询分页数据列表&#xff0c;…...

瀚高安全版4.5.8/4.5.9字符串默认按字节存储导致数据无法写入(APP)

文章目录 环境文档用途详细信息 环境 系统平台&#xff1a;Linux x86-64 Red Hat Enterprise Linux 7 版本&#xff1a;4.5 文档用途 解决安全版4.5.8/4.5.9字符串默认使用字节存储导致插入时提示数据超长。 详细信息 使用sysdba用户执行&#xff0c;重载配置或重启数据库…...

python新手学习笔记①

本笔记是根据Bilibili里的【3小时超快速入门Python | 动画教学【2025新版】【自学Python教程】【零基础Python】【计算机二级Python】【Python期末速成】】 https://www.bilibili.com/video/BV1Jgf6YvE8e/这个视频合集制作的代码笔记&#xff01; 1.字符串连接 运行结果 2.…...

用于管理共享内存的 C# 类 ShareMemory

可以在 Windows 和 Linux 上运行&#xff0c;利用了 .NET Core 的 System.IO.MemoryMappedFiles 库。这个类实现了共享内存的创建、打开、读取和写入功能。以下是对代码的一些分析和建议改进。 代码分析 初始化与打开共享内存: Init 方法用于创建新的共享内存段。OpenMem 方法…...

arcgispro双击打开没反应怎么办

不知道什么原因&#xff0c;突然就打不开了&#xff0c;网上关于arcgispro的教程和求助帖还比较少&#xff0c;参考了几个博主的分享&#xff0c;还是没解决 Arcpro——arcpro启动无反应_arcgispro正在初始化后没反应-CSDN博客 Arcgis Pro安装完成后启动失败的解决办法_arcgi…...

常见高速电路设计与信号完整性核心概念

一、传输线理论&#xff08;Transmission Line Theory&#xff09; 基本定义 当信号频率或边沿速率足够高时&#xff0c;互连线的长度与信号波长可比拟&#xff08;通常为信号上升时间的1/6以上&#xff09;&#xff0c;此时需将互连视为传输线&#xff0c;而非理想导线。 临界…...

青少年编程与数学 02-019 Rust 编程基础 20课题、面向对象

青少年编程与数学 02-019 Rust 编程基础 20课题、面向对象 一、面向对象的编程特性&#xff08;一&#xff09;封装&#xff08;Encapsulation&#xff09;&#xff08;二&#xff09;多态&#xff08;Polymorphism&#xff09;&#xff08;三&#xff09;继承&#xff08;Inhe…...

<uniapp><vuex><状态管理>在uniapp中,如何使用vuex实现数据共享与传递?

前言 本专栏是基于uniapp实现手机端各种小功能的程序&#xff0c;并且基于各种通讯协议如http、websocekt等&#xff0c;实现手机端作为客户端&#xff08;或者是手持机、PDA等&#xff09;&#xff0c;与服务端进行数据通讯的实例开发。 发文平台 CSDN 环境配置 系统&…...

如何使用通义灵码辅助开发鸿蒙OS - AI编程助手提升效率

一、引言 鸿蒙 OS 是华为推出的一款面向全场景的分布式操作系统&#xff0c;其开发应用主要使用华为基于 IntelliJ IDEA 定制的 DevEco Studio。然而&#xff0c;DevEco Studio 的插件生态相对有限&#xff0c;为了提升开发效率和代码质量&#xff0c;我们可以借助通义灵码这一…...

解决git中断显示中文为八进制编码问题

git config --global core.quotepath false 命令用于配置 Git 如何处理非 ASCII 字符&#xff08;如中文、日文、韩文等&#xff09;的文件名显示 core.quotepath Git 的一个核心配置项&#xff0c;控制是否对非 ASCII 文件名进行转义&#xff08;quote&#xff09;处理。 f…...

宿州金博学校开展防震演练:夯实安全根基,守护校园平安

5月13日上午9点30分&#xff0c;金博学校原本宁静的校园被一阵急促的警报声打破&#xff0c;一场精心筹备、紧张有序的防震演练正式开启。本次演练意义重大&#xff0c;旨在强化全体师生的防震减灾意识&#xff0c;提高大家在地震突发时的应急反应与自我保护能力。 紧急避险&am…...

【鸿蒙开发】安全

应用隐私保护最佳实践 使用隐私声明获取用户同意 初次访问使用隐私声明弹窗&#xff0c;只有用户同意后才能开始正常使用。 减少应用的位置访问权限 使用模糊定位获取位置信息 位置权限申请方式 target API level申请位置权限申请结果位置的精确度小于9ohos.permission.L…...

企业级网络安全护盾:剖析高防IP原理与防护策略

在当今数字化时代&#xff0c;网络安全已成为企业不可忽视的关键课题。高防IP作为网络安全防护的重要手段之一&#xff0c;正因其出色的防御能力和应用灵活性受到广泛关注。本文将深入解析高防IP的原理&#xff0c;包括流量清洗、防御策略、节点分布等技术要点&#xff0c;并通…...

智能事件分析边缘服务器:交通管理与安全监测的利器

在当今交通管理和安全监测的领域中&#xff0c;智能化、高效化的设备需求日益增长。智能事件分析边缘服务器凭借其卓越的性能和丰富的功能&#xff0c;成为了该领域的佼佼者。 一、产品概述 智能事件分析边缘服务器是一款采用嵌入式 Linux 操作系统的边缘事件分析终端。它具有…...

Gin--Blog项目-flags文件解析

flags/enter.go文件解析 package flagsimport ("flag""os" )type Options struct {File stringDB boolVersion bool }var FlagOptions new(Options)func Parse() {flag.StringVar(&FlagOptions.File, "f", "settings.yaml&qu…...

JVM的面试相关问题

面试中的相关问题主要是三块 1.JVM 内存区域划分 2.JVM 的类加载机制 3.JVM 的垃圾回收机制 JVM Java虚拟机 VM Virtual Machine 虚拟机,用 软件 来 模拟 硬件 传统意义上的"虚拟机" 更多指的是 VMWare, Virtual Box, Hyper-V, KVM(构造出虚拟的电脑,甚至可以…...

Linux(3)——基础开发工具

一、软件包管理器——yum 1.Linux下安装程序的方式 在Linux环境下安装软件的方式有以下几个方式&#xff1a; 1&#xff09;源码安装&#xff0c;直接下载源代码&#xff0c;让它自行编译运行形成可执行程序。 2&#xff09;软件包安装&#xff0c;下载rpm安装包&#xff0…...

HarmonyOS5云服务技术分享--ArkTS调用函数

✨【HarmonyOS实战指南】手把手教你用ArkTS玩转云函数文件获取✨ 大家好呀今天我们来聊聊如何通过HarmonyOS的ArkTS语言实现云函数文件获取功能。整个过程就像搭积木一样有趣&#xff0c;保证小白也能轻松上手&#xff01;&#xff08;文末有完整代码模板哦&#xff09; &…...

2025年AI搜索引擎发展洞察:技术革新与市场变革

引言&#xff1a;AI搜索的崛起与市场格局重塑 2024-2025年&#xff0c;AI搜索市场迎来了前所未有的变革期。随着DeepSeek-R1等先进大语言模型的推出&#xff0c;传统搜索引擎、AI原生搜索平台以及各类内容平台纷纷加速智能化转型&#xff0c;推动搜索技术从基础信息检索向深度…...

基于开源链动2+1模式AI智能名片S2B2C商城小程序的社群构建与新型消费迎合策略研究

摘要&#xff1a;随着个性化与小众化消费的崛起&#xff0c;消费者消费心理和模式发生巨大变化&#xff0c;社群构建对商家迎合新型消费特点、融入市场经济发展至关重要。开源链动21模式AI智能名片S2B2C商城小程序的出现&#xff0c;为社群构建提供了创新工具。本文探讨该小程序…...

leetcode 旋转数组 java

本来想用栈或者队列来解决&#xff0c;发现一直报k>nums.length的错。 将原数组下标为 i 的元素放至新数组下标为 (ik)modn 的位置&#xff0c;最后将新数组拷贝至原数组即可。 class Solution {public void rotate(int[] nums, int k) {// Stack<Integer> stack n…...

Ansible模块——通过 URL 下载文件

通过 URL 下载文件 ansible.builtin.get_url 可以通过 URL 下载文件。 选项名 类型 默认值 描述 attributesstrnull 设置文件系统对象的属性&#xff0c;格式参考 lsattr&#xff1b;支持 , -, 操作符。别名&#xff1a;attr。 backupboolfalse 创建目标文件的备份副本&am…...

2025年高考考务人员培训监考员学习项目试题

考务人员培训系统 学员端&#xff08;高考&#xff09; 第1部分&#xff1a;单选题 1. 对违背考试公平、公正原则&#xff0c;在考试中存在违规行为的考生&#xff0c;采用何种处理办法&#xff1f;&#xff08;A &#xff09; [2分] A. 根据《国家教育考试违规处理办法》等…...

谈谈jvm的调优思路

目录 1、G1回收器 2、常用的回收器分类 1、cms 2、G1、Hotspot 3、ZGC 4、设置 3、常见的调优策略 3.1、设定大小 1、堆的大小 2、Region的大小 3、年轻代大小调整 3.2、设置最大停顿时间 3.3、设置标记和回收线程 3.4、并发周期触发阈值 背景 正常情况下&#x…...

通过自签名ssl证书进行js注入的技术,适合注入electron开发的app

由于很多软件都是electron或者pyqt做的安装包,没法像浏览器那样可以直接通过浏览器插件注入js,或者很多网站都有csp限制,无法直接注入js,这种使用自签名代理的方式,完美绕过了所有限制,直接将js注入到外链js中。 步骤:1,局域网准备两台电脑,一个windows,一台Linux,…...

异步复位,同步释放

参考链接&#xff1a;数字电路复位信号设计&#xff08;异步复位、同步释放&#xff09;笔记详解_异步复位同步释放的高有效原理-CSDN博客 一、异步复位&#xff08;Asynchronous Reset&#xff09; 含义&#xff1a;当复位信号 reset 为低&#xff08;或高&#xff0c;视具体…...

Vortex GPGPU的github流程跑通与功能模块波形探索(三)

文章目录 前言一、./build/ci下的文件结构二、基于驱动进行仿真过程牵扯的文件2.1 blackbox.sh文件2.2 demo文件2.3 额外牵扯到的ramulator2.3.1 ramulator简单介绍2.3.2 ramulator使用方法2.3.3 ramulator的输出2.3.4 ramulator的复现2.3.4.1 调试与验证&#xff08;第 4.1 节…...

Ubuntu 安装 Node.js 指定版本指南

Ubuntu 安装 Node.js 指定版本指南&#xff08;适用于生产与开发环境&#xff09; 在没有安装 NVM 的服务器环境中&#xff08;如 Docker、CI/CD、虚拟机等&#xff09;&#xff0c;建议使用 Node.js 官方的二进制包源&#xff08;PPA&#xff09;来快速安装特定版本的 Node.j…...

使用 Java 开发 Android 应用:Kotlin 与 Java 的混合编程

使用 Java 开发 Android 应用&#xff1a;Kotlin 与 Java 的混合编程 在开发 Android 应用程序时&#xff0c;我们通常可以选择使用 Java 或 Kotlin 作为主要的编程语言。然而&#xff0c;有些开发者可能会想要在同一个项目中同时使用这两种语言&#xff0c;这就是所谓的混合编…...

安防监控网络摄像机画面异常问题与视频监控管理平台EasyCVR应用

一、方案背景 在安防监控领域&#xff0c;画面卡顿、时有时无等问题犹如隐藏的潜在风险点&#xff0c;不仅严重干扰监控系统的正常运行&#xff0c;更可能在安全防护的关键时刻出现故障&#xff0c;让潜在的风险与隐患有机可乘。想要彻底攻克这些顽疾&#xff0c;就需要我们抽…...

MATLAB中进行语音信号分析

在MATLAB中进行语音信号分析是一个涉及多个步骤的过程&#xff0c;包括时域和频域分析、加窗、降噪滤波、端点检测以及特征提取等。 1. 加载和预览语音信号 首先&#xff0c;你需要加载一个语音信号文件。MATLAB支持多种音频文件格式&#xff0c;如.wav。 [y, fs] audiorea…...

Kotlin 协程 (三)

协程通信是协程之间进行数据交换和同步的关键机制。Kotlin 协程提供了多种通信方式&#xff0c;使得协程能够高效、安全地进行交互。以下是对协程通信的详细讲解&#xff0c;包括常见的通信原语、使用场景和示例代码。 1.1 Channel 定义&#xff1a;Channel 是一个消息队列&a…...

AI 商业化部署中,ollama 和 vllm 的选型对比

介绍 ollama Ollama是指一个开源的大模型服务工具&#xff0c;旨在简化大型语言模型&#xff08;LLM&#xff09;的本地部署、运行和管理。它让用户能够在本地设备上轻松运行和管理各种大语言模型&#xff0c;无需依赖云端服务。 vllm 在深度学习推理领域&#xff0c;vLLM框…...

mysql的乐观锁与悲观锁

1.悲观锁 含义&#xff1a;假设会发生冲突&#xff0c;因此在操作数据之前对数据加锁&#xff0c;确保其他事务无法访问该数据。 应用场景&#xff1a;适用于并发冲突多&#xff0c;写多读少的场景&#xff0c;通过加锁的方式确保数据的安全性。 实现方式&#xff1a;使用行…...

进程——概念及状态

目录 概念 介绍 举例 进程状态 概念 解释 实例 R S T t Z 孤儿进程 概念 介绍 大多数初学者会认为进程就是从硬盘加载到内存的可执行文件&#xff08;当可执行文件被加载到内存里称为程序&#xff09;&#xff0c;实际上并不是这样的&#xff0c;进程其实是操作系…...

服务器数据恢复—Linux系统服务器崩溃且重装系统的数据恢复案例

服务器数据恢复环境&#xff1a; linux操作系统服务器中有一组由4块SAS接口硬盘组建的raid5阵列。 服务器故障&#xff1a; 服务器工作过程中突然崩溃。管理员将服务器操作系统进行了重装。 用户方需要恢复服务器中的数据库、办公文档、代码文件等。 服务器数据恢复过程&#…...

【git】git commit模板

【git】git commit模板 目录 【git】git commit模板1.使用git commit 模板操作步骤&#xff1a;使用示例&#xff1a; 2. gitlab merge 模板 1.使用git commit 模板 操作步骤&#xff1a; 设置模板路径,其中path就是commit模板路径 git config --global commit.template path设…...

IGBT选型时需关注的参数,适用场景(高压大电流低频)以及驱动电路设计注意事项

概述 IGBT&#xff08;绝缘栅双极型晶体管&#xff09;是电力控制和电力转换的核心器件&#xff0c;是由BJT&#xff08;双极型晶体管&#xff09;和MOS&#xff08;绝缘栅型场效应管&#xff09;组成的复合全控型电压驱动式功率半导体器件。有高输入阻抗&#xff08;MOSFET优点…...

hghac集群服务器时间同步(chrony同步)

文章目录 环境文档用途详细信息 环境 系统平台&#xff1a;银河麒麟&#xff08;龙芯&#xff09;svs,银河麒麟 &#xff08;X86_64&#xff09;,银河麒麟 &#xff08;飞腾&#xff09;,银河麒麟 &#xff08;鲲鹏&#xff09;,银河麒麟 &#xff08;海光&#xff09;,银河麒…...

Linux 特权管理与安全——从启用 Root、Sudo 提权到禁用与防护的全景解析

一、前言 为什么关注特权&#xff1f; Root&#xff08;超级用户&#xff09;拥有系统所有权限&#xff0c;一旦被滥用或入侵&#xff0c;后果不堪设想。运维与安全的平衡 既需要日常运维中快速提权执行管理任务&#xff0c;又要避免过度开放特权带来的风险。攻防同源理念 了解…...

初识Linux · 数据链路层

目录 前言&#xff1a; 以太网帧协议 ARP协议 ARP协议理解 ARP协议字段 交换机 前言&#xff1a; 前文我们通过OSI模型&#xff0c;一直到TCP/IP四层模型&#xff0c;经过了三篇文章左右的功夫&#xff0c;我们把网络层介绍完毕&#xff0c;主要还是介绍的IP协议的iphdr…...

Linux探秘:驾驭开源,解锁高效能——基础指令

♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨ 个…...

【Linux】第二十二章 访问网络附加内存

1. NFS的主要功能是什么&#xff1f; NFS是由Linux、UNIX及类似操作系统使用的互联网标准协议&#xff0c;主要功能就是提供网络文件共享&#xff0c;允许不同的计算机系统之间通过网络共享文件&#xff0c;它使得网络上的计算机能够像访问本地文件系统一样访问远程计算机上的…...

Revit BIM 模型批量转换为 Datasmith 格式教程

Revit BIM 模型批量转换为 Datasmith 格式教程 一、背景与痛点 在建筑信息模型(BIM)与游戏开发的协同工作中,常需将 Revit 模型导入虚幻引擎(UE)。虽然 Revit 的 Datasmith 插件可实现单文件转换,但面对成百上千个模型时,手动操作效率极低。本文将分享如何开发一个自动…...