文章目录
- 一、函数介绍
-
- [1、字符串IP -> 网络序IP](#1、字符串IP -> 网络序IP)
-
- [1)inet_aton 函数](#1)inet_aton 函数)
- [2)inet_addr 函数](#2)inet_addr 函数)
- [3)inet_pton 函数](#3)inet_pton 函数)
- [2、网络序IP → 字符串IP](#2、网络序IP → 字符串IP)
-
- [1)inet_ntoa 函数](#1)inet_ntoa 函数)
- [2)inet_ntop 函数](#2)inet_ntop 函数)
- [3、UDP 收发数据](#3、UDP 收发数据)
-
- [1)sendto 函数](#1)sendto 函数)
- [2)recvfrom 函数](#2)recvfrom 函数)
- 二、EchoServer(回显服务)
- 三、DictServer(翻译服务)
- 四、ChatServer(简单聊天室服务)
一、函数介绍
1、字符串IP -> 网络序IP
1)inet_aton 函数

2)inet_addr 函数

3)inet_pton 函数

2、网络序IP → 字符串IP
1)inet_ntoa 函数

2)inet_ntop 函数

3、UDP 收发数据
1)sendto 函数

2)recvfrom 函数

二、EchoServer(回显服务)
说明: 简单的回显服务器和客户端代码
1、单进程
代码结构:
c++
Mutex.hpp
Log.hpp
InetAddr.hpp
UdpServer.hpp
UdpServer.cc
UdpClient.cc
1)Mutex.hpp
cpp
#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 Unlock()
{
int n = pthread_mutex_unlock(&_mutex);
}
// 获取锁指针
pthread_mutex_t *Get()
{
return &_mutex;
}
// 销毁锁
~Mutex()
{
pthread_mutex_destroy(&_mutex);
}
private:
pthread_mutex_t _mutex; // 原生互斥锁对象
};
// RAII 自动锁
class LockGuard
{
public:
LockGuard(Mutex &mutex)
: _mutex(mutex)
{
_mutex.Lock();
}
~LockGuard()
{
_mutex.Unlock();
}
private:
Mutex &_mutex;
};
}
```### 1)Mutex.hpp
```cpp
#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 Unlock()
{
int n = pthread_mutex_unlock(&_mutex);
}
// 获取锁指针
pthread_mutex_t *Get()
{
return &_mutex;
}
// 销毁锁
~Mutex()
{
pthread_mutex_destroy(&_mutex);
}
private:
pthread_mutex_t _mutex; // 原生互斥锁对象
};
// RAII 自动锁
class LockGuard
{
public:
LockGuard(Mutex &mutex)
: _mutex(mutex)
{
_mutex.Lock();
}
~LockGuard()
{
_mutex.Unlock();
}
private:
Mutex &_mutex;
};
}
2)Log.hpp
cpp
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <memory>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "Mutex.hpp"
namespace LogModule
{
using namespace MutexModule;
const std::string gsep = "\r\n";
// 日志策略基类(接口)
class LogStrategy
{
public:
~LogStrategy() = default;
virtual void SyncLog(const std::string &message) = 0;
};
// 控制台日志输出(线程安全)
class ConsoleLogStrategy : public LogStrategy
{
public:
void SyncLog(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::cout << message << gsep;
}
~ConsoleLogStrategy() {}
private:
Mutex _mutex;
};
const std::string defaultpath = "./log/"; // /var/log
const std::string defaultfile = "my.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() << std::endl;
}
}
// 追加写入日志文件
void SyncLog(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
std::ofstream out(filename, std::ios::app);
if (!out.is_open())
return;
out << message << gsep;
out.close();
}
private:
std::string _path;
std::string _file;
Mutex _mutex;
};
// 日志等级类
enum class LogLevel
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
// 日志等级转字符串
std::string LevelStr(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 buf[128]; // 出参
snprintf(buf, sizeof(buf), "%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 buf;
}
// 日志核心管理类
class Logger
{
public:
Logger()
{
EnableConsoleLogStrategy();
}
// 切换为文件输出
void EnableFileLogStrategy()
{
_fflush_strategy = std::make_unique<FileLogStrategy>();
}
// 切换为控制台输出
void EnableConsoleLogStrategy()
{
_fflush_strategy = std::make_unique<ConsoleLogStrategy>();
}
// 日志消息构造: 负责拼接内容, 析构时自动输出
class LogMessage
{
public:
// 构造日志头部(时间、等级、进程ID、文件名、行号)
LogMessage(LogLevel level, std::string src_name, int line_number, Logger &logger)
: _logger(logger)
{
std::stringstream ss;
ss << "[" << GetTimeStamp() << "] "
<< "[" << LevelStr(level) << "] "
<< "[" << getpid() << "] "
<< "[" << src_name << "] "
<< "[" << line_number << "] - ";
_loginfo = ss.str();
}
// 流方式拼接日志内容
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 _loginfo;
Logger &_logger;
};
// 仿函数接口, 创建日志消息
LogMessage operator()(LogLevel level, std::string name, int line)
{
return LogMessage(level, name, line, *this);
}
private:
std::unique_ptr<LogStrategy> _fflush_strategy;
};
Logger logger;
// 简化调用宏
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}
3)InetAddr.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
// IPv4地址封装: 主机格式 <-> 网络格式
class InetAddr
{
public:
// 构造: 网络地址 -> 主机格式
InetAddr(struct sockaddr_in &addr)
: _addr(addr)
{
_port = ntohs(_addr.sin_port); // port
char ipbuffer[64]; // ip
inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
_ip = ipbuffer;
}
// 构造: 指定IP+端口 -> 网络地址
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); // ip
_addr.sin_port = htons(_port); // port
}
// 获取点分十进制IP
std::string Ip() { return _ip; }
// 获取主机字节序端口
uint16_t Port() { return _port; }
// 获取原生网络地址结构体
const struct sockaddr_in &NetAddr() { return _addr; }
// 转为 ip:port 格式字符串
std::string StringAddr() { return _ip + ":" + std::to_string(_port); }
// 比较两个地址是否相同
bool operator==(const InetAddr &addr)
{
return addr._ip == _ip && addr._port == _port;
}
~InetAddr() {}
private:
struct sockaddr_in _addr; // 网络字节序地址
std::string _ip; // 点分十进制IP
uint16_t _port; // 主机字节序端口
};
2)UdpServer.hpp
cpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <string>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
// 回调函数类型: 处理客户端消息
using func_t = std::function<std::string(const std::string &, InetAddr &)>;
const int defaultfd = -1;
// UDP服务器封装
class UdpServer
{
public:
UdpServer(uint16_t port, func_t func)
: _sockfd(defaultfd),
_port(port),
_isrunning(false),
_func(func)
{
}
// 初始化服务器: 创建socket + 绑定端口
void Init()
{
// 1. 创建UDP套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket create error";
exit(1);
}
LOG(LogLevel::INFO) << "socket create success: " << _sockfd;
// 2. 绑定本机IP和端口
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port); // 端口转网络字节序
local.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(2);
}
LOG(LogLevel::INFO) << "bind success, sockfd: " << _sockfd;
}
// 启动服务器: 循环接收 -> 处理 -> 回复
void Start()
{
_isrunning = true;
while (_isrunning)
{
char buffer[1024];
struct sockaddr_in peer; // 客户端地址
socklen_t len = sizeof(peer);
// 1. 接收客户端消息
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (s > 0)
{
InetAddr client(peer);
buffer[s] = 0;
// 2. 调用回调函数处理业务
std::string result = _func(buffer, client);
// 3. 把处理结果发回客户端
sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len);
}
}
}
~UdpServer()
{
}
private:
int _sockfd; // 套接字文件描述符
uint16_t _port; // 服务器端口
bool _isrunning; // 运行状态
func_t _func; // 业务处理回调
};
3)UdpServer.cc
cpp
#include <iostream>
#include <memory>
#include "UdpServer.hpp"
// 默认回调: 回复 hello + 消息
std::string defaulthandler(const std::string &msg, InetAddr &)
{
return "hello, " + msg;
}
// ./udpserver port
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " port" << std::endl;
return 1;
}
uint16_t port = std::stoi(argv[1]);
Enable_Console_Log_Strategy();
// 1. 创建UDP服务器, 绑定默认回调
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, defaulthandler);
// 2. 初始化服务器
usvr->Init();
// 3. 启动服务器
usvr->Start();
return 0;
}
4)UdpClient.cc
cpp
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// ./udpclient 服务端IP 端口
int main(int argc, char *argv[])
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
return 1;
}
std::string server_ip = argv[1];
uint16_t server_port = std::stoi(argv[2]);
// 1. 创建UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
return 2;
}
// 2. 填充服务端地址
struct sockaddr_in server; // 出参
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
// 3. 循环: 发送消息+接收回显
while (true)
{
// 输入消息
std::string input;
std::cout << "Please Enter# ";
std::getline(std::cin, input);
// 发送给服务端
int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
// 接收服务端回复
char buffer[1024];
struct sockaddr_in peer;
socklen_t len = sizeof(buffer) - 1;
int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (m > 0)
{
buffer[m] = 0;
std::cout << buffer << std::endl;
}
}
close(sockfd);
return 0;
}
进行测试:
客户端

服务端

三、DictServer(翻译服务)
说明: 实现一个简单的英译汉的网络字典
代码结构:
cpp
Mutex.hpp
Log.hpp
InetAddr.hpp
UdpServer.hpp
UdpServer.cc // 改动
UdpClient.cc
dictionary.txt
Dict.hpp
1、单进程
1)dictionary.txt
cpp
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
hello:
: 你好
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天
2)Dict.hpp
cpp
#pragma once
#include<iostream>
#include<string>
#include<fstream>
#include<unordered_map>
#include"Log.hpp"
#include"InetAddr.hpp"
const std::string defaultdict = "./dictionary.txt";
const std::string sep = ": ";
using namespace LogModule;
// 翻译字典类: 加载文件+单词翻译
class Dict
{
public:
Dict(const std::string& path=defaultdict)
:_dict_path(path)
{}
// 加载字典文件
bool LoadDict()
{
std::ifstream in(_dict_path);
if(!in.is_open())
{
LOG(LogLevel::DEBUG) << "打开字典失败: " << _dict_path;
return false;
}
std::string line; // 出参
while(std::getline(in,line)) // 一行一行读取
{
auto pos = line.find(sep);
if(pos==std::string::npos)
{
LOG(LogLevel::WARNING) << "解析失败: " << line;
continue;
}
std::string english = line.substr(0, pos);
std::string chinese = line.substr(pos + sep.size());
if(english.empty()||chinese.empty())
{
LOG(LogLevel::WARNING) << "无效内容: " << line;
continue;
}
_dict[english] = chinese;
LOG(LogLevel::DEBUG) << "加载: " << line;
}
in.close();
return true;
}
// 翻译单词
std::string Translate(const std::string& word,InetAddr& client)
{
auto it = _dict.find(word);
if(it==_dict.end())
{
LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << ":" << client.Port() << "]# " << word << "->None";
return "None";
}
LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << ":" << client.Port() << "]# " << word << "->" << it->second;
return it->second;
}
~Dict()
{}
private:
std::string _dict_path; // 字典文件路径
std::unordered_map<std::string, std::string> _dict; // 英汉对照词典
};
3)UdpServer.cc
cpp
#include <iostream>
#include <memory>
#include "UdpServer.hpp"
#include "Dict.hpp"
// ./udpserver port
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " port" << std::endl;
return 1;
}
uint16_t port = std::stoi(argv[1]);
Enable_Console_Log_Strategy();
// 1. 创建字典对象
Dict d;
// 2. 加载字典文件
d.LoadDict();
// 3. 创建UDP服务器, 绑定翻译回调
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port,
[&d](const std::string &word, InetAddr &client)
{
// 收到单词->翻译并返回结果
return d.Translate(word, client);
});
// 4. 初始化服务器
usvr->Init();
// 5. 启动服务器
usvr->Start();
return 0;
}
客户端

服务端

四、ChatServer(简单聊天室服务)
1、线程池
代码结构:
c++
Mutex.hpp
Cond.hpp
Log.hpp
Thread.hpp
ThreadPool.hpp
InetAddr.hpp
UdpServer.hpp // 改动
UdpServer.cc // 改动
UdpClient.cc // 改动
Route.hpp
1)Cond.hpp
cpp
#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 Signal()
{
int n = pthread_cond_signal(&_cond);
}
// 唤醒所有等待线程
void Broadcast()
{
int n = pthread_cond_broadcast(&_cond);
}
~Cond()
{
pthread_cond_destroy(&_cond);
}
private:
pthread_cond_t _cond; // 底层条件变量
};
}
2)Thread.hpp
cpp
#pragma once
#include <iostream>
#include <string.h>
#include <functional>
#include <pthread.h>
namespace ThreadModule
{
// 全局线程编号(自动命名)
static uint32_t number = 1;
class Thread
{
using func_t = std::function<void()>; // 线程执行函数类型
private:
// 标记线程已分离
void EnableDetach()
{
_isdetach = true;
}
// 标记线程运行中
void EnableRunning()
{
_isrunning = true;
}
// 线程入口(必须static)
static void *Routine(void *args)
{
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;
}
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 || !_isrunning) return;
pthread_detach(_tid);
_isdetach = true;
}
// 创建并启动线程
bool Start()
{
if (_isrunning) return false;
int n = pthread_create(&_tid, nullptr, Routine, this);
if (n == 0)
{
std::cout << _name << " create success\n";
return true;
}
std::cerr << "create error: " << strerror(n) << "\n";
return false;
}
// 取消线程
bool Stop()
{
if (!_isrunning) return false;
int n = pthread_cancel(_tid);
if (n == 0)
{
_isrunning = false;
std::cout << _name << " stop\n";
return true;
}
std::cerr << "cancel error: " << strerror(n) << "\n";
return false;
}
// 等待线程结束
void Join()
{
if (_isdetach)
{
std::cout << "线程已分离,无法join\n";
return;
}
pthread_join(_tid, &res);
std::cout << "join success\n";
}
// 获取线程名
std::string Name()
{
return _name;
}
~Thread() {}
private:
pthread_t _tid; // 线程ID
std::string _name; // 线程名
bool _isdetach; // 是否分离
bool _isrunning; // 是否运行
void *res; // 线程返回值
func_t _func; // 线程执行函数
};
}
3)ThreadPool.hpp
cpp
#pragma once
#include <iostream>
#include <vector>
#include <queue>
#include "Mutex.hpp"
#include "Cond.hpp"
#include "Log.hpp"
#include "Thread.hpp"
namespace ThreadPoolModule
{
using namespace MutexModule;
using namespace LogModule;
using namespace CondModule;
using namespace ThreadModule;
const int gnum = 5;
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();
}
}
public:
// 禁用拷贝与赋值
ThreadPool(const ThreadPool<T> &) = delete;
ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;
// 获取单例实例
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);
// 任务队列为空, 等待任务
while (_taskq.empty() && _isrunning)
{
_sleepernum++;
_cond.Wait(_mutex); // 释放锁并休眠,被唤醒后重新获取锁
_sleepernum--;
}
// 线程退出条件
if (!_isrunning && _taskq.empty())
{
LOG(LogLevel::INFO) << name << " 退出了, 线程池退出&&任务队列为空";
break;
}
// 取出任务
t = _taskq.front();
_taskq.pop();
}
// 执行任务
t();
}
}
// 任务入队
bool Enqueue(const T &in)
{
if (_isrunning)
{
{
LockGuard lockguard(_mutex);
_taskq.push(in);
}
// 所有线程都在休眠, 唤醒一个
if (_threads.size() == _sleepernum)
WakeUpOne();
return true;
}
return false;
}
~ThreadPool() = default;
private:
std::vector<Thread> _threads; // 线程数组
int _num; // 线程数量
std::queue<T> _taskq; // 任务队列
Cond _cond; // 条件变量
Mutex _mutex; // 互斥锁
bool _isrunning; // 运行状态
int _sleepernum; // 休眠线程数
static ThreadPool<T> *inc; // 单例指针
static Mutex _lock; // 单例创建锁
};
// 静态成员初始化
template <typename T>
ThreadPool<T> *ThreadPool<T>::inc = nullptr;
template <typename T>
Mutex ThreadPool<T>::_lock;
}
4)Route.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <vector>
#include "Log.hpp"
#include "Mutex.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
using namespace MutexModule;
// 路由模块: UDP群聊转发
class Route
{
private:
// 判断用户是否在线
bool IsExist(InetAddr &peer)
{
for (auto &user : _online_user)
{
if (user == peer)
{
return true;
}
}
return false;
}
// 添加在线用户
void AddUser(InetAddr &peer)
{
LOG(LogLevel::INFO) << "新增在线用户: " << peer.StringAddr();
_online_user.push_back(peer);
}
// 删除离线用户
void DeleteUser(InetAddr &peer)
{
for (auto iter = _online_user.begin(); iter != _online_user.end(); iter++)
{
if (*iter == peer)
{
LOG(LogLevel::INFO) << "删除用户: " << peer.StringAddr();
_online_user.erase(iter);
break;
}
}
}
public:
Route()
{
}
// 消息路由: 群发消息+上下线管理
void MessageRoute(int sockfd, const std::string &message, InetAddr &peer)
{
LockGuard lockguard(_mutex);
// 未入线则加入列表
if (!IsExist(peer))
AddUser(peer);
// 构造转发消息
std::string send_message = peer.StringAddr() + "# " + message;
// 转发给所有在线用户
for (auto &user : _online_user)
{
sendto(sockfd, send_message.c_str(), send_message.size(), 0,
(const struct sockaddr *)&(user.NetAddr()), sizeof(user.NetAddr()));
}
// 收到QUIT则下线
if (message == "QUIT")
{
DeleteUser(peer);
}
}
~Route()
{
}
private:
std::vector<InetAddr> _online_user; // 在线用户列表
Mutex _mutex; // 互斥锁
};
5)UdpServer.hpp
cpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <cstdlib>
#include <string>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
// 回调函数类型: 处理消息+客户端地址
using func_t = std::function<void(int sockfd, const std::string &, InetAddr &)>;
const int defaultfd = -1;
// UDP服务器封装
class UdpServer
{
public:
UdpServer(uint16_t port, func_t func)
: _sockfd(defaultfd),
_port(port),
_isrunning(false),
_func(func)
{
}
// 初始化服务器: 创建socket + 绑定端口
void Init()
{
// 1. 创建UDP套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error!";
exit(1);
}
LOG(LogLevel::INFO) << "socket success, sockfd: " << _sockfd;
// 2. 绑定本机IP和端口
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port); // 端口转网络字节序
local.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(2);
}
LOG(LogLevel::INFO) << "bind success, sockfd: " << _sockfd;
}
// 启动服务器: 循环接收 -> 调用回调处理
void Start()
{
_isrunning = true;
while (_isrunning)
{
char buffer[1024];
struct sockaddr_in peer; // 客户端地址
socklen_t len = sizeof(peer);
// 1. 接收客户端消息
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (s > 0)
{
InetAddr client(peer);
buffer[s] = 0;
// 2. 调用回调函数处理业务
_func(_sockfd, buffer, client);
}
}
}
~UdpServer()
{
}
private:
int _sockfd; // 套接字文件描述符
uint16_t _port; // 服务器端口
bool _isrunning; // 运行状态
func_t _func; // 业务处理回调
};
6)UdpServer.cc
cpp
#include <iostream>
#include <memory>
#include "Route.hpp"
#include "UdpServer.hpp"
#include "ThreadPool.hpp"
using namespace ThreadPoolModule;
// 任务类型
using task_t = std::function<void()>;
// ./udpserver port
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " port" << std::endl;
return 1;
}
uint16_t port = std::stoi(argv[1]);
Enable_Console_Log_Strategy();
// 1. 创建路由对象
Route r;
// 2. 获取线程池单例
auto tp = ThreadPool<task_t>::GetInstance();
// 3. 创建UDP服务器: 收到消息 -> 提交线程池 -> 群聊转发
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port,
[&r, &tp](int sockfd, const std::string &message, InetAddr &peer)
{
// 绑定路由处理任务
task_t t = std::bind(&Route::MessageRoute, &r, sockfd, message, peer);
// 把任务扔进线程池
tp->Enqueue(t);
});
// 4. 初始化服务器
usvr->Init();
// 5. 启动服务器
usvr->Start();
return 0;
}
7)UdpClient.cc
cpp
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Thread.hpp"
using namespace ThreadModule;
// 全局变量
int sockfd = 0;
std::string server_ip;
uint16_t server_port = 0;
pthread_t id;
// 接收线程: 循环读取服务端消息并打印
void Recv()
{
while (true)
{
char buffer[1024];
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (m > 0)
{
buffer[m] = 0;
std::cerr << buffer << std::endl;
}
}
}
// 发送线程: 读取用户输入并发送给服务端
void Send()
{
// 填充服务端地址
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
// 上线通知
const std::string online = "inline";
sendto(sockfd, online.c_str(), online.size(), 0, (struct sockaddr *)&server, sizeof(server));
// 循环发送消息
while (true)
{
std::string input;
std::cout << "Please Enter# ";
std::getline(std::cin, input);
int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
// 退出处理
if (input == "QUIT")
{
pthread_cancel(id);
break;
}
}
}
// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
return 1;
}
server_ip = argv[1];
server_port = std::stoi(argv[2]);
// 1. 创建UDP套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
return 2;
}
// 2. 创建收发线程
Thread recver(Recv);
Thread sender(Send);
// 3. 启动线程
recver.Start();
sender.Start();
id = recver.Id();
// 5. 等待线程结束
recver.Join();
sender.Join();
return 0;
}
客户端1

客户端2

服务端
