目录
[1.1 Mutex.hpp](#1.1 Mutex.hpp)
[1.2 Log.hpp](#1.2 Log.hpp)
[1.3 Thread.hpp](#1.3 Thread.hpp)
[1.4 InetAddr.hpp](#1.4 InetAddr.hpp)
[1.5 Route.hpp](#1.5 Route.hpp)
[1.6 UdpClient.cc](#1.6 UdpClient.cc)
[1.7 UdpServer.hpp](#1.7 UdpServer.hpp)
[1.8 UdpServer.cc](#1.8 UdpServer.cc)
[2.1 Mutex.hpp](#2.1 Mutex.hpp)
[2.2 Log.hpp](#2.2 Log.hpp)
[2.3 Cond.hpp](#2.3 Cond.hpp)
[2.4 Thread.hpp](#2.4 Thread.hpp)
[2.5 InetAddr.hpp](#2.5 InetAddr.hpp)
[2.6 Route.hpp](#2.6 Route.hpp)
[2.7 ThreadPool.hpp](#2.7 ThreadPool.hpp)
[2.8 UdpClient.cc](#2.8 UdpClient.cc)
[2.9 UdpServer.hpp](#2.9 UdpServer.hpp)
[2.10 UdpServer.cc](#2.10 UdpServer.cc)
[三 、总结接口](#三 、总结接口)
[3.1 套接字(UDP)](#3.1 套接字(UDP))
[3.2 IP 转换](#3.2 IP 转换)
[3.3 字节序](#3.3 字节序)
[3.4 pthread 线程](#3.4 pthread 线程)
[3.5 互斥锁](#3.5 互斥锁)
[3.6 条件变量](#3.6 条件变量)
一、简单聊天室v1
1.1 Mutex.hpp
【Linux】28:封装线程,锁,条件变量,日志-CSDN博客
1.2 Log.hpp
【Linux】28:封装线程,锁,条件变量,日志-CSDN博客
1.3 Thread.hpp
【Linux】28:封装线程,锁,条件变量,日志-CSDN博客
1.4 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>
// 网络地址和主机地址转换的类
class InetAddr
{
public:
InetAddr(struct sockaddr_in &addr)
: _addr(addr)
{
_port = ntohs(addr.sin_port);
_ip = inet_ntoa(addr.sin_addr);
}
uint16_t Port() { return _port; }
std::string Ip() { return _ip; }
const struct sockaddr_in &NetAddr()
{
return _addr;
}
bool operator==(const InetAddr &addr)
{
return _ip == addr._ip && _port == addr._port;
}
std::string StringAddr()
{
return _ip + ":" + std::to_string(_port);
}
~InetAddr() {}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
1.5 Route.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include "InetAddr.hpp"
#include "Log.hpp"
using namespace LogModule;
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)
{
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, (struct sockaddr *)&(user.NetAddr()), sizeof(user.NetAddr()));
}
// 这个用户一定已经存在了
if (message == "QUIT")
{
LOG(LogLevel::INFO) << "删除了一个在线用户" << peer.StringAddr();
DeleteUser(peer);
}
}
~Route() {}
private:
// 首次给我发消息,等同于登录
std::vector<InetAddr> _online_user; // 在线用户
};
1.6 UdpClient.cc
cpp
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include "Thread.hpp"
using namespace ThreadModule;
std::string server_ip;
uint16_t server_port;
int sockfd;
pthread_t id;
void Recv()
{
while (1)
{
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 = "online";
int n = 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;
}
}
}
// ./updclient 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:创建套接字
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);
recver.Start();
sender.Start();
id = recver.Id();
recver.Join();
sender.Join();
return 0;
}
1.7 UdpServer.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"
using func_t = std::function<void(int, const std::string &, InetAddr &)>;
using namespace LogModule;
const int defaultfd = -1;
class UdpServer
{
public:
UdpServer(uint16_t port, func_t func)
: _sockfd(defaultfd),
//_ip(ip),
_port(port),
_isrunning(false),
_func(func)
{
}
void Init()
{
// 1:创建套接字
_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:绑定socket信息,主要是ip和端口.
// 2.1填充socketaddr_in结构体
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
// 在网络通信的时候,服务器会不会将ip和端口号发送给client?
// ip和端口号一定会发送到网络
// ip和端口号是本地存储格式,所以需要将本地格式转换为网络序列!!
local.sin_port = htons(_port);
// 1:ip转成4字节 2:4字节转成网络序列 ->in_addr_t inet_addr(const char *cp);
// local.sin_addr.s_addr = inet_addr(_ip.c_str());
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;
_func(_sockfd, buffer, client);
}
}
}
~UdpServer() {}
private:
int _sockfd;
uint16_t _port;
// std::string _ip; // 用的是字符串风格,点分十进制,"192.168.1.1"
bool _isrunning;
func_t _func; // 服务器的回调函数,用来进行对数据进行处理
};
1.8 UdpServer.cc
cpp
#include <iostream>
#include <memory>
#include "UdpServer.hpp" //网络通信的功能
#include "Route.hpp"
// ./udpserver ip 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:网络服务器对象提供通信功能
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&r](int sockfd, const std::string &message, InetAddr &client)
{ r.MessageRoute(sockfd, message, client); });
usvr->Init();
usvr->Start();
return 0;
}
当客户端这样写的话,如果不发送消息,就会卡在getline这里,就收不到其他人的消息。
所以客户端需要多线程改造,让一个线程不断从标准输入获取消息,发送消息,另外一个线程不断的获取消息。
输入的消息和输出的消息混在一起了。我们可以打开两个xshell,让上面的xshell当成输出窗口,下面的当作输入窗口。如下所示:
二、简单聊天室v2
增加线程池功能,把收到的消息当成任务,放进阻塞队列中,提前创建一批线程,让这一批线程从阻塞队列里面拿取任务,执行任务。

当Udpserver收到消息之后,不应该由udpserver发送,而是由线程发送。当Udpserver收到消息之后,Udpserver应该创建一个任务,把任务push到线程池里面就可以了。
2.1 Mutex.hpp
cpp
#pragma once
#include <iostream>
#include <pthread.h>
namespace MutexModule
{
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_mutex, nullptr);
}
void Lock()
{
pthread_mutex_lock(&_mutex);
}
void UnLock()
{
pthread_mutex_unlock(&_mutex);
}
pthread_mutex_t *Get()
{
return &_mutex;
}
~Mutex()
{
pthread_mutex_destroy(&_mutex);
}
private:
pthread_mutex_t _mutex;
};
class LockGuard
{
public:
LockGuard(Mutex &mutex) : _mutex(mutex)
{
_mutex.Lock();
}
~LockGuard()
{
_mutex.UnLock();
}
private:
Mutex &_mutex;
};
}
2.2 Log.hpp
cpp
#ifndef __LOG_HPP
#define __LOG_HPP
#include <iostream>
#include <filesystem>
#include <string>
#include <memory>
#include <unistd.h>
#include <fstream>
#include <ctime>
#include <cstdio>
#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:
ConsoleLogStrategy() {}
~ConsoleLogStrategy() {}
void SyncLog(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::cout << message << gsep;
}
private:
Mutex _mutex;
};
// 文件打印日志的策略
const std::string defaultpath = "./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() << "\n";
}
}
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();
}
~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();
}
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_Strategy() logger.EnableConsoleLogStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}
#endif
2.3 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)
{
pthread_cond_wait(&_cond, mutex.Get());
}
void Singal()
{
pthread_cond_signal(&_cond);
}
void BroadCast()
{
pthread_cond_broadcast(&_cond);
}
~Cond()
{
pthread_cond_destroy(&_cond);
}
private:
pthread_cond_t _cond;
};
}
2.4 Thread.hpp
cpp
#ifndef _THREAD_H_
#define _THREAD_H_
#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <cstring>
#include <pthread.h>
#include <functional>
namespace ThreadModule
{
static uint32_t number = 1;
class Thread
{
using func_t = std::function<void()>;
void EnableDetach()
{
// std::cout << "线程被分离了" << std::endl;
_isdetach = true;
}
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();
}
void EnableRunning()
{
_isrunning = true;
}
static void *Routine(void *args)
{
Thread *self = (Thread *)args;
self->EnableRunning();
if (self->_isdetach) // 如果线程的分离标志是true,就调用系统调用pthread_detach来将线程设置分离
self->Detach();
self->_func();
return nullptr;
}
bool Start()
{
if (_isrunning)
return false;
int n = pthread_create(&_tid, nullptr, Routine, this);
if (n != 0)
{
// std::cerr << "create pthread error" << strerror(n) << std::endl;
return false;
}
else
{
// std::cout << _name << " create sucess" << std::endl;
}
return true;
}
bool Stop()
{
if (_isrunning)
{
int n = pthread_cancel(_tid);
if (n != 0)
{
// std::cerr << "cancel pthread error" << strerror(n) << std::endl;
return false;
}
else
{
_isrunning = false;
std::cerr << _name << " stop" << std::endl;
}
}
return true;
}
bool Join()
{
if (_isdetach)
{
// std::cout << "你的线程已经是分离的,不能进行join" << std::endl;
return true;
}
int n = pthread_join(_tid, &_res);
if (n != 0)
{
// std::cerr << "join pthread error" << strerror(n) << std::endl;
return false;
}
else
{
// std::cerr << "join pthread sucess" << std::endl;
}
return true;
}
pthread_t Id()
{
return _tid;
}
std::string GetName()
{
return _name;
}
~Thread() {}
private:
pthread_t _tid; // 线程id
std::string _name; // 线程名字
bool _isdetach; // 是否分离,默认不分离
bool _isrunning; // 线程状态,是否运行
void *_res; // 线程返回值
func_t _func; // 线程执行的任务
};
}
#endif
2.5 InetAddr.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
// 网络地址和主机地址转换的类
class InetAddr
{
public:
InetAddr(struct sockaddr_in &addr)
: _addr(addr)
{
// 网络序列转主机序列
_port = ntohs(addr.sin_port);
//_ip = inet_ntoa(addr.sin_addr);
char ipbuffer[64];
inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
_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);
}
uint16_t Port() { return _port; }
std::string Ip() { return _ip; }
const struct sockaddr_in &NetAddr()
{
return _addr;
}
bool operator==(const InetAddr &addr)
{
return _ip == addr._ip && _port == addr._port;
}
std::string StringAddr()
{
return _ip + ":" + std::to_string(_port);
}
~InetAddr() {}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
2.6 Route.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Mutex.hpp"
using namespace MutexModule;
using namespace LogModule;
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, (struct sockaddr *)&(user.NetAddr()), sizeof(user.NetAddr()));
}
// 这个用户一定已经存在了
if (message == "QUIT")
{
LOG(LogLevel::INFO) << "删除了一个在线用户" << peer.StringAddr();
DeleteUser(peer);
}
}
~Route() {}
private:
// 首次给我发消息,等同于登录
std::vector<InetAddr> _online_user; // 在线用户
Mutex _mutex;
};
2.7 ThreadPool.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
namespace ThreadPoolModule
{
using namespace LogModule;
using namespace MutexModule;
using namespace CondModule;
using namespace ThreadModule;
static const int gnum = 5;
template <typename T>
class ThreadPool
{
void WakeUpAllthread()
{
LockGuard lockguard(_mutex);
if (_sleepnum > 0)
_cond.BroadCast();
LOG(LogLevel::INFO) << "唤醒所有的线程";
}
void WakeUpOne()
{
_cond.Singal();
LOG(LogLevel::INFO) << "唤醒一个休眠的线程";
}
ThreadPool(int num = gnum)
: _num(num), _isrunning(false), _sleepnum(0)
{
for (int i = 0; i < _num; i++)
{
_threads.emplace_back(
[this]()
{
HandlerTask();
});
}
}
ThreadPool(const ThreadPool<T> &) = default;
ThreadPool<T> &operator=(const ThreadPool<T> &) = default;
void Start()
{
if (_isrunning)
return;
_isrunning = true;
for (auto &t : _threads)
{
t.Start();
LOG(LogLevel::INFO) << "start new thread success " << t.GetName();
}
}
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);
while (_isrunning && _taskq.empty())
{
++_sleepnum;
_cond.Wait(_mutex);
--_sleepnum;
}
// 线程被唤醒
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() - _sleepnum == 0)
WakeUpOne();
return true;
}
return false;
}
~ThreadPool() {}
private:
std::vector<Thread> _threads;
int _num; // 线程池中的线程个数
std::queue<T> _taskq;
Cond _cond;
Mutex _mutex;
bool _isrunning;
int _sleepnum;
static ThreadPool<T> *inc; // 单例指针
static Mutex _lock;
};
template <typename T>
ThreadPool<T> *ThreadPool<T>::inc = nullptr;
template <typename T>
Mutex ThreadPool<T>::_lock;
}
2.8 UdpClient.cc
cpp
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include "Thread.hpp"
using namespace ThreadModule;
std::string server_ip;
uint16_t server_port;
int sockfd;
pthread_t id;
void Recv()
{
while (1)
{
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 = "online";
int n = 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;
}
}
}
// ./updclient 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:创建套接字
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);
recver.Start();
sender.Start();
id = recver.Id();
recver.Join();
sender.Join();
return 0;
}
2.9 UdpServer.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"
using func_t = std::function<void(int, const std::string &, InetAddr &)>;
using namespace LogModule;
const int defaultfd = -1;
class UdpServer
{
public:
UdpServer(uint16_t port, func_t func)
: _sockfd(defaultfd),
//_ip(ip),
_port(port),
_isrunning(false),
_func(func)
{
}
void Init()
{
// 1:创建套接字
_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:绑定socket信息,主要是ip和端口.
// 2.1填充socketaddr_in结构体
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
// 在网络通信的时候,服务器会不会将ip和端口号发送给client?
// ip和端口号一定会发送到网络
// ip和端口号是本地存储格式,所以需要将本地格式转换为网络序列!!
local.sin_port = htons(_port);
// 1:ip转成4字节 2:4字节转成网络序列 ->in_addr_t inet_addr(const char *cp);
// local.sin_addr.s_addr = inet_addr(_ip.c_str());
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;
_func(_sockfd, buffer, client);
}
}
}
~UdpServer() {}
private:
int _sockfd;
uint16_t _port;
// std::string _ip; // 用的是字符串风格,点分十进制,"192.168.1.1"
bool _isrunning;
func_t _func; // 服务器的回调函数,用来进行对数据进行处理
};
2.10 UdpServer.cc
cpp
#include <iostream>
#include <memory>
#include "UdpServer.hpp" //网络通信的功能
#include "Route.hpp"
#include "ThreadPool.hpp"
using namespace ThreadPoolModule;
using task_t = std::function<void()>;
// ./udpserver ip 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:网络服务器对象提供通信功能
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&r, &tp](int sockfd, const std::string &message, InetAddr &client)
{
task_t t=std::bind(&Route::MessageRoute,&r,sockfd,message,client);
tp->Enqueue(t); });
usvr->Init();
usvr->Start();
return 0;
}
这个代码是有一点问题的,我们让线程池种的每一个线程都去执行消息路由的功能。路由模块内部存在在线用户列表,有可能在多线程的情况下,有的线程来进行判断,有的线程正在做广播,都需要同时对在线用户的列表访问。所以当多线程访问在线用户的列表的时候,有的线程发送消息,有的线程可能删除用户,就可能会发生错误。最简单的做法就是访问在线用户列表的时候加锁。
我们将网络序列转换成为主机序列的时候,使用的是如下接口:我们将主机序列转换成为网络序列的时候,使用的是如下接口:
除了上面这几个接口,还有几个接口,如下所示:
1:inet_pton:将一个字符串IP转换为四字节IP。(这个接口更加安全)
2:inet_ntoa:将ip地址从四字节转换为字符串IP。(不推荐使用)
inet_ntoa这个函数返回了一个char*,很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果.那么是否需要调用者手动释放呢?
man手册上说,inet_ntoa函数,是把这个返回结果放到了静态存储区.这个时候不需要我们手动进行释放.
那么问题来了,如果我们调用多次这个函数,会有什么样的效果呢?参见如下代码:
运行结果如下:
因为inet_ntoa把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果.
- 思考:如果有多个线程调用inet_ntoa,是否会出现异常情况呢?
- 在APUE中,明确提出inet_ntoa不是线程安全的函数;
- 但是在centos7上测试,并没有出现问题,可能内部的实现加了互斥锁;
- 同学们课后自己写程序验证一下在自己的机器上inet_ntoa是否会出现多线程的问题;
- 在多线程环境下,推荐使用inet_ntop,这个函数由调用者提供一个缓冲区保存结果,可以规避线安全问题;
3:inet_ntop:将ip地址从四字节转换为字符串IP。(这个接口更加安全)
三 、总结接口
3.1 套接字(UDP)
int socket(int domain, int type, int protocol);创建套接字int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);绑定地址端口ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);UDP 收数据 + 获取对端地址ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);UDP 定向发送int close(int fd);关闭 fd
3.2 IP 转换
in_addr_t inet_addr(const char *cp);字符串 IP→网络序整数,过时char *inet_ntoa(struct in_addr in);网络 IP→字符串,非线程安全int inet_pton(int af, const char *src, void *dst);字符串 IP→二进制 IP,推荐const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);二进制 IP→字符串,推荐
3.3 字节序
uint16_t htons(uint16_t hostshort);主机序短→网络序(端口)uint16_t ntohs(uint16_t netshort);网络序短→主机序
3.4 pthread 线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);创建线程int pthread_join(pthread_t thread, void **retval);等待回收线程int pthread_detach(pthread_t thread);设置分离int pthread_cancel(pthread_t thread);发送取消请求int pthread_getname_np(pthread_t thread, char *name, size_t len);获取线程名(GNU 扩展)
3.5 互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);初始化锁int pthread_mutex_lock(pthread_mutex_t *mutex);加锁int pthread_mutex_unlock(pthread_mutex_t *mutex);解锁int pthread_mutex_destroy(pthread_mutex_t *mutex);销毁锁
3.6 条件变量
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);初始化条件变量int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);等待int pthread_cond_signal(pthread_cond_t *cond);唤醒一个int pthread_cond_broadcast(pthread_cond_t *cond);唤醒全部int pthread_cond_destroy(pthread_cond_t *cond);销毁条件变量









