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.pswp.cn/web/80749.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/80749.shtml
英文地址,請注明出處:http://en.pswp.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;同一線程無法重復…