目錄
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 號端口,并加載字典文件到內存中,將綁定套接字并設置為監聽狀態。
? ? ? ? 啟動客戶端進行連接,并輸入字符串請求服務端服務。
? ? ? ? 這里再服務端可以看到服務器的運行信息。