TCP Echo Server 深度解析:从单进程到线程池的演进之路(上)-CSDN博客
把tcpserver的实现逻辑梳理一下:
1. Init() 函数深度剖析
void Init()
{
// signal(SIGCHLD,SIG_IGN); //忽略
// 1.创建套接字
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(LogLevel::DEBUG) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // 3
// 2.bind总所周知的端口号
InetAddr local(_port);
int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // 3
// 3.设置socket状态为listen
n = listen(_listensockfd, backlog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success: " << _listensockfd;
}
1.1 socket() ------ 创建通信端点
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
| 参数 | 值 | 含义 |
|---|---|---|
domain |
AF_INET |
Address Family Internet ------ IPv4 协议族 |
type |
SOCK_STREAM |
面向连接的、可靠的字节流服务 ------ TCP |
protocol |
0 |
自动选择协议(TCP 对应 IPPROTO_TCP) |
1.1.1返回值
文件描述符 (file descriptor)


返回的 fd 是文件描述符表中的索引,指向这个socket的结构体
1.2 bind() ------ 绑定地址
InetAddr local(_port);
int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
为什么要 bind?

端口号范围问题:


1.3 listen() ------ 设置监听状态
n = listen(_listensockfd, backlog);
为什么要 listen?

backlog 参数 :


2. Run() 与 Accept 的本质
2.1 accept() 函数解析
int sockfd = accept(_listensockfd, CONV(peer), &len);
核心问题:accept 返回的 fd 和 _listensockfd 有什么区别?



accept 的参数 :
| 参数 | 作用 |
|---|---|
sockfd |
监听套接字 |
addr |
输出参数,填充客户端地址信息 |
addrlen |
输入输出参数,地址结构体长度 |
2.2 telnet ------ 简易 TCP 客户端工具
$ telnet www.baidu.com 80

2.3 netstat 查看连接状态
$ netstat -tnlp
参数含义:
-
-t: TCP 协议 -
-n: 数字形式显示地址和端口 -
-l: 仅显示监听状态的套接字 -
-p: 显示进程信息


这里我们看到有两条, 因为
1 ) TCP是全双工的
2 )主要原因:因为我们的客户端和服务端在一台主机上!!!
3. 基础代码 -- 方便后续我们三个版本的EchoServer的实现
3.1 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;
};
};
3.2 InetAddr.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"
// 网络地址和主机地址之间进行转化的类
class InetAddr
{
public:
InetAddr() {}
InetAddr(struct sockaddr_in &addr) : _addr(addr)
{
// 网络转主机
_port = ntohs(_addr.sin_port); // 这个端口号是从网络中来的!网络序列
//_ip = inet_ntoa(_addr.sin_addr); // 4字节 -》 点分十进制的字符串
char ipbuffer[64];
inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));
_ip = ipbuffer;
}
InetAddr(const std::string &ip, uint16_t port) : _ip(ip), _port(port)
{
// 主机转网络
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
_addr.sin_port = htons(port);
}
InetAddr(uint16_t port) : _port(port), _ip()
{
// 主机转网络
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
_addr.sin_addr.s_addr = INADDR_ANY;
_addr.sin_port = htons(port);
}
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;
};
3.3 Log.hpp
#ifndef __LOG_HPP__
#define __LOG_HPP__
#include <iostream>
#include <filesystem> //c++17
#include <fstream>
#include <string>
#include <cstdio>
#include <memory>
#include <unistd.h>
#include <ctime>
#include <sstream>
#include "Mutex.hpp"
using namespace MutexModule;
namespace LogModule
{
const std::string gsep = "\r\n";
// 策略模式 -- C++多态特性
// 2.刷新策略 a:显示器打印 b:向指定的文件写入
// 刷新策略基类
class LogStrategy
{
public:
~LogStrategy() = default;
virtual void SyncLog(const std::string &message) = 0;
private:
};
// 显示器打印日志的策略:子类
class ConsoleLogStrategy : public LogStrategy
{
public:
ConsoleLogStrategy()
{
}
void SyncLog(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::cout << message << gsep;
}
~ConsoleLogStrategy()
{
}
private:
Mutex _mutex; // 显示器也是临界资源,保证输出线程安全
};
// 文件打印日志的策略 : 子类
const std::string defaultPath = "./log";
const std::string defaultfile = "log.log";
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &path = defaultPath, const std::string &file = defaultfile)
: _path(path),
_file(file)
{
LockGuard lockguard(_mutex);
if (std::filesystem::exists(_path))
{
return;
}
try
{
std::filesystem::create_directories(_path);
}
catch (const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << "\n";
}
}
void SyncLog(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
//"./log/" + "my.log"
std::ofstream out(filename, std::ios::app); // 以追加写入的方式打开
if (!out.is_open())
{
return;
}
out << message << gsep;
out.close();
}
~FileLogStrategy()
{
}
private:
std::string _path; // 日志文件所在的路径
std::string _file; // 日志文件本身
Mutex _mutex;
};
// 形成一条完整的日志&&根据上面的策略,选择不同的刷新方式
// 1.形成日志等级
enum class LogLevel
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
std::string Level2Str(LogLevel level)
{
switch (level)
{
case LogLevel::DEBUG:
return "DEBUG";
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
default:
return "UNKNOWN";
}
}
std::string GetTimeStamp()
{
time_t curr = time(nullptr);
struct tm curr_tm;
localtime_r(&curr, &curr_tm);
char timebuffer[128];
snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d:%02d:%02d",
curr_tm.tm_year+1900,
curr_tm.tm_mon+1,
curr_tm.tm_mday,
curr_tm.tm_hour,
curr_tm.tm_min,
curr_tm.tm_sec);
return timebuffer;
}
// 1.形成日志 && 2.根据不同的策略,完成刷新
class Logger
{
public:
Logger()
{
EnableConsoleLogStrategy();
}
void EnableFileLogStrategy()
{
_fflush_strategy = std::make_unique<FileLogStrategy>();
}
void EnableConsoleLogStrategy()
{
_fflush_strategy = std::make_unique<ConsoleLogStrategy>();
}
// 内部类 表示的是未来的一条日志
class LogMessage
{
public:
LogMessage(LogLevel &level, std::string &src_name, int line_number, Logger &logger)
: _curr_time(GetTimeStamp()),
_level(level),
_pid(getpid()),
_src_name(src_name),
_line_number(line_number),
_logger(logger)
{
// 日志左边部分,合并起来
std::stringstream ss;
ss << "[" << _curr_time << "]"
<< "[" << Level2Str(_level) << "]"
<< "[" << _pid << "]"
<< "[" << _src_name << "]"
<< "[" << _line_number << "]"
<< "- ";
_loginfo = ss.str();
};
// LogMessage() << "hello world" << "xxxx" << 3.14 << 1234;
// 需要支持重载
template <typename T>
LogMessage &operator<<(const T &info)
{
// 日志右边部分,可变的
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
~LogMessage()
{
if (_logger._fflush_strategy)
{
_logger._fflush_strategy->SyncLog(_loginfo);
}
};
private:
std::string _curr_time;
LogLevel _level;
pid_t _pid;
std::string _src_name;
int _line_number;
std::string _loginfo; // 合并完成之后,一条完整的信息
Logger &_logger;
};
// 这里故意写成返回临时对象
LogMessage operator()(LogLevel level, std::string name, int line)
{
return LogMessage(level, name, line, *this);
}
~Logger() {}
private:
std::unique_ptr<LogStrategy> _fflush_strategy;
};
// 全局日志对象
Logger logger;
// 使用宏,简化用户操作,获取文件名和行号
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Stratege() logger.EnableConsoleLogStrategy();
#define Enable_File_Log_Stratege() logger.EnableFileLogStrategy();
}
#endif
3.4 Mutex.hpp
#pragma once
#include <iostream>
#include <pthread.h>
namespace MutexModule
{
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_mutex, nullptr);
}
void Lock()
{
int n = pthread_mutex_lock(&_mutex);
(void)n;
}
void Unlock()
{
int n = pthread_mutex_unlock(&_mutex);
(void)n;
}
~Mutex()
{
pthread_mutex_destroy(&_mutex);
}
pthread_mutex_t *Get()
{
return &_mutex;
}
private:
pthread_mutex_t _mutex;
};
class LockGuard
{
public:
LockGuard(Mutex &mutex) : _mutex(mutex)
{
_mutex.Lock();
};
~LockGuard()
{
_mutex.Unlock();
};
private:
Mutex &_mutex;
};
}
3.5 Thread.hpp
#ifndef _THREAD_H_
#define _THREAD_H_
#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>
namespace ThreadModlue
{
static uint32_t number = 1; // bug
class Thread
{
using func_t = std::function<void()>; // 暂时这样写,完全够了
private:
void EnableDetach()
{
_isdetach = true;
}
void EnableRunning()
{
_isrunning = true;
}
static void *Routine(void *args) // 属于类内的成员函数,默认包含this指针!
{
Thread *self = static_cast<Thread *>(args);
self->EnableRunning();
if (self->_isdetach)
self->Detach();
pthread_setname_np(self->_tid, self->_name.c_str());
self->_func(); // 回调处理
return nullptr;
}
// bug
public:
Thread(func_t func)
: _tid(0),
_isdetach(false),
_isrunning(false),
res(nullptr),
_func(func)
{
_name = "thread-" + std::to_string(number++);
}
void Detach()
{
if (_isdetach)
return;
if (_isrunning)
pthread_detach(_tid);
EnableDetach();
}
bool Start()
{
if (_isrunning)
return false;
int n = pthread_create(&_tid, nullptr, Routine, this);
if (n != 0)
{
return false;
}
else
{
return true;
}
}
bool Stop()
{
if (_isrunning)
{
int n = pthread_cancel(_tid);
if (n != 0)
{
return false;
}
else
{
_isrunning = false;
return true;
}
}
return false;
}
void Join()
{
if (_isdetach)
{
return;
}
int n = pthread_join(_tid, &res);
if (n != 0)
{
}
else
{
}
}
pthread_t Id()
{
return _tid;
}
std::string Name()
{
return _name;
}
~Thread()
{
}
private:
pthread_t _tid;
std::string _name;
bool _isdetach;
bool _isrunning;
void *res;
func_t _func;
};
}
#endif
3.6 ThreadPool.hpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"
// .hpp header only
namespace ThreadPoolModule
{
using namespace ThreadModlue;
using namespace LogModule;
using namespace CondModule;
using namespace MutexModule;
static const int gnum = 15;
template <typename T>
class ThreadPool
{
private:
void WakeUpAllThread()
{
LockGuard lockguard(_mutex);
if (_sleepernum)
_cond.Broadcast();
LOG(LogLevel::INFO) << "唤醒所有的休眠线程";
}
void WakeUpOne()
{
_cond.Signal();
LOG(LogLevel::INFO) << "唤醒一个休眠线程";
}
ThreadPool(int num = gnum) : _num(num), _isrunning(false), _sleepernum(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();
LOG(LogLevel::INFO) << "start new thread success: " << thread.Name();
}
}
ThreadPool(const ThreadPool<T> &) = delete;
ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;
public:
static ThreadPool<T> *GetInstance()
{
if (inc == nullptr)
{
LockGuard lockguard(_lock);
LOG(LogLevel::DEBUG) << "获取单例....";
if (inc == nullptr)
{
LOG(LogLevel::DEBUG) << "首次使用单例, 创建之....";
inc = new ThreadPool<T>();
inc->Start();
}
}
return inc;
}
void Stop()
{
if (!_isrunning)
return;
_isrunning = false;
// 唤醒所有的线层
WakeUpAllThread();
}
void Join()
{
for (auto &thread : _threads)
{
thread.Join();
}
}
void HandlerTask()
{
char name[128];
pthread_getname_np(pthread_self(), name, sizeof(name));
while (true)
{
T t;
{
LockGuard lockguard(_mutex);
// 1. a.队列为空 b. 线程池没有退出
while (_taskq.empty() && _isrunning)
{
_sleepernum++;
_cond.Wait(_mutex);
_sleepernum--;
}
// 2. 内部的线程被唤醒
if (!_isrunning && _taskq.empty())
{
LOG(LogLevel::INFO) << name << " 退出了, 线程池退出&&任务队列为空";
break;
}
// 一定有任务
t = _taskq.front(); // 从q中获取任务,任务已经是线程私有的了!!!
_taskq.pop();
}
t(); // 处理任务,需/要在临界区内部处理吗?1 0
}
}
bool Enqueue(const T &in)
{
if (_isrunning)
{
LockGuard lockguard(_mutex);
_taskq.push(in);
if (_threads.size() == _sleepernum)
WakeUpOne();
return true;
}
return false;
}
~ThreadPool()
{
}
private:
std::vector<Thread> _threads;
int _num; // 线程池中,线程的个数
std::queue<T> _taskq;
Cond _cond;
Mutex _mutex;
bool _isrunning;
int _sleepernum;
// bug??
static ThreadPool<T> *inc; // 单例指针
static Mutex _lock;
};
template <typename T>
ThreadPool<T> *ThreadPool<T>::inc = nullptr;
template <typename T>
Mutex ThreadPool<T>::_lock;
}