Socket编程UDP
目录
一、接口介绍
1.1.socket函数
作用:创建一个通信的一端
参数1:域
- 本地通信:AF_UNIX
- 网络通信:AF_INET
参数2:套接字类型
- UDP:SOCK_DGRAM(面向数据报)
- TCP:SOCK_STREAM(面向字节流)
参数3:设置为0
返回值:
- 返回成功:文件描述符
- 返回失败:-1

1.2.bind函数
作用:给一个套接字(网络文件)绑定一个名字
参数1:文件描述符
参数2:sockaddr结构体(填充端口号和IP地址)
参数3:结构体大小


1.3.inet_addr函数
作用:将IP地址由字符串转为四字节

IP地址的格式转换
- 字符串转四字节

- 四字节转字符串

1.4.recvfrom函数
作用:收消息
参数1:文件描述符
参数2:缓冲区地址
参数3:缓冲区大小
参数4:0 - 阻塞式IO
对方不发送数据时,该进程一直在函数内阻塞(类似scanf)
服务端读取客户端的信息
- 客户端发送的数据
- 客户端的套接字信息
参数5:获取发送端的信息(输出型参数)
参数6:发送端结构体大小
返回值:实际收到的字节大小

1.5.sendto函数
作用:发消息
参数1:文件描述符(UDP socket 既可以读,也可以写,为全双工)
参数2:缓冲区地址
参数3:缓冲区大小
参数4:0 - 阻塞式IO
参数5:填充接收端的信息
参数6:接收端结构体大小
返回值:实际发送的字节大小

1.6.本地环回
客户端与服务器在同一台机器,客户端发送的数据不会被推送网络
而是在操作系统内部绕一圈,直接交给服务器,用来测试网络代码

- 服务器绑定公网IP失败(公网IP没有配置到主机,无法被服务器绑定)
- 服务器绑定本地环回,客户端用本地环回访问成功
- 服务器绑定内网IP,客户端用内网IP访问成功
- 服务端绑定内网IP,客户端用本地环回访问失败
如果显示地进行IP地址绑定,客户端访问时,必须使用服务器端绑定的IP地址信息
服务器不建议显示绑定特定的IP地址,不然只能接收这个IP地址对应的客户端消息
将服务器的IP地址设置为任意地址,就能被任意客户端(环回、内网、公网)访问
二、V1回显服务器
2.1.UdpServer.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
using namespace LogModule;
using func_t = std::function<std::string(const std::string &)>;
const int defaultfd = -1;
class UdpServer
{
public:
UdpServer(/*const std::string &ip,*/ 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. 显示绑定
// 填充sockaddr_in结构体
struct sockaddr_in local;
bzero(&local, sizeof(local)); // 初始化
local.sin_family = AF_INET; // 网络通信
// 本地格式 → 网络序列
local.sin_port = htons(_port); // 端口号
// 点分十进制格式 → 四字节格式
// 本地四字节格式 → 网络序列
// 不建议绑定特定IP: local.sin_addr.s_addr = inet_addr(_ip.c_str());
local.sin_addr.s_addr = INADDR_ANY; // 绑定任意IP地址才能被任意客户端访问
// 服务端的IP和端口号必须众所周知并且不能轻易改变(类似: 110、120、119)
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)
{
// 网络序列 → 主机序列
int peer_port = ntohs(peer.sin_port); // 客户端端口号
// 网络序列 → 点分十进制
std::string peer_ip = inet_ntoa(peer.sin_addr); // 客户端IP地址
// 消息末尾加'\0'
buffer[s] = 0;
// 回调函数处理接收数据
std::string result = _func(buffer);
// 向客户端返回处理结果
sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len);
// 消息内容 + 客户端信息
LOG(LogLevel::DEBUG) << "[ " << peer_ip << " : " << peer_port << " ] " << buffer;
}
// 2. 发消息
std::string echo_string = "server echo@ ";
echo_string += buffer;
}
}
~UdpServer()
{
}
private:
int _sockfd;
uint16_t _port;
// std::string _ip;
bool _isrunning;
func_t _func; // 服务器回调函数
};
2.2.UdpServer.cc
cpp
#include <iostream>
#include <memory>
#include "UdpServer.hpp"
std::string defaulthandler(const std::string &message)
{
std::string hello = "hello, ";
hello += message;
return hello;
}
int main(int argc, char* argv[])
{
if(argc != /*3*/ 2)
{
std::cerr << "Usage: " << argv[0] << " /*ip*/ port" << std::endl;
return 1;
}
// 获取IP
// std::string ip = argv[1];
// 获取端口
uint16_t port = std::stoi(argv[1]);
Enable_Console_Log_Strategy();
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(/*ip,*/ port, defaulthandler);
usvr->Init();
usvr->Start();
return 0;
}
2.3.UdpClient.cc
cpp
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
// 判断命令行参数
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
return 1;
}
// 获取服务器IP地址
std::string server_ip = argv[1];
// 获取服务器端口号
uint16_t server_port = std::stoi(argv[2]);
// 1. 创建套接字(打开网络文件)
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
return 2;
}
// 2. 非显示绑定
// 首次发送消息,OS会自动给客户端进行绑定
// OS知道IP,并且随机生成端口号(类似: 普通的电话号码)
// 为了避免客户端的端口发生冲突
// 3. 填写服务器信息
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());
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));
(void)n;
// 客户端从服务器接收消息
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::cout << buffer << std::endl;
}
}
return 0;
}
三、V2字典服务器
3.1.UdpServer.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
using func_t = std::function<std::string(const std::string &, InetAddr &)>;
const int defaultfd = -1;
class UdpServer
{
public:
UdpServer(uint16_t port, func_t func)
: _sockfd(defaultfd), _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. 显示绑定
// 填充sockaddr_in结构体
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; // 绑定任意IP地址才能被任意客户端访问
// 服务端的IP和端口号必须众所周知并且不能轻易改变(类似: 110、120、119)
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);
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (s > 0)
{
// 网络序列 → 主机序列
// int peer_port = ntohs(peer.sin_port); // 客户端端口号
// 网络序列 → 点分十进制
// std::string peer_ip = inet_ntoa(peer.sin_addr); // 客户端IP地址
InetAddr client(peer);
// 消息末尾加'\0'
buffer[s] = 0;
// 回调函数处理接收数据
std::string result = _func(buffer, client);
// 向客户端返回处理结果
sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len);
}
}
}
~UdpServer()
{
}
private:
int _sockfd;
uint16_t _port;
// std::string _ip;
bool _isrunning;
func_t _func; // 服务器回调函数
};
3.2.UdpServer.cc
cpp
#include <iostream>
#include <memory>
#include "UdpServer.hpp" // 网络通信
#include "Dict.hpp" // 字典翻译
std::string defaulthandler(const std::string &message)
{
std::string hello = "hello, ";
hello += message;
return hello;
}
// 翻译系统: 字符串当成英文单词, 把英文单词翻译成汉语
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();
// 字典对象提供翻译功能
Dict dict;
dict.LoadDict(); // 加载字典
// 服务对象提供通信功能
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port,
[&dict](const std::string &word, InetAddr &cli)
->std::string
{
return dict.Translate(word, cli);
});
usvr->Init();
usvr->Start();
return 0;
}
3.3.UdpClient.cc
cpp
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
// 判断命令行参数
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
return 1;
}
// 获取服务器IP地址
std::string server_ip = argv[1];
// 获取服务器端口号
uint16_t server_port = std::stoi(argv[2]);
// 1. 创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
return 2;
}
// 2. 非显示绑定
// 3. 填写服务器信息
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());
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));
(void)n;
// 客户端从服务器接收消息
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::cout << buffer << std::endl;
}
}
return 0;
}
3.4.Dict.hpp
cpp
#pragma once
#include <iostream>
#include <fstream>
#include <string>
#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))
{
// apple: 苹果
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.insert(std::make_pair(english, chinese));
LOG(LogLevel::DEBUG) << "加载: " << line;
}
// 关闭文件输入流
in.close();
return true;
}
std::string Translate(const std::string &word, InetAddr &client)
{
// 查找哈希表
auto iter = _dict.find(word);
if (iter == _dict.end())
{
LOG(LogLevel::DEBUG) << "进入翻译模块, " << client.Ip() << " : "<< client.Port() << "]# " << word << " -> None";
return "None";
}
LOG(LogLevel::DEBUG) << "进入翻译模块, " << client.Ip() << " : "<< client.Port() << "]# " << word << " -> " << iter->second;
return iter->second;
}
~Dict()
{
}
private:
std::string _dict_path; // 路径 + 文件名
std::unordered_map<std::string, std::string> _dict; // 字典映射
};
3.5.dictionary.txt
cpp
apple: 苹果
banana: ⾹蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
hello:
: 你好
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: ⽼师
student: 学⽣
car: 汽⻋
bus: 公交⻋
love: 爱
hate: 恨
hello: 你好
goodbye: 再⻅
summer: 夏天
winter: 冬天
3.6.InetAddr.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.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;
}
~InetAddr()
{
}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
四、V3聊天服务器

4.1.UdpServer.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#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;
class UdpServer
{
public:
UdpServer(uint16_t port, func_t func)
: _sockfd(defaultfd)
, _port(port)
, _isrunning(false)
, _func(func)
{}
// 初始化服务器
void Init()
{
// 创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error!";
exit(1);
}
LOG(LogLevel::INFO) << " socket success, sockfd : " << _sockfd;
// 显示绑定IP地址和端口号
// 填充sockaddr_in结构体
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; // IP地址 (绑定任意IP才能被任意客户端访问)
// 服务端的IP和端口号必须众所周知并且不能轻易改变(类似: 110、120、119)
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);
// 接收客户端消息
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (s > 0)
{
// 客户端网络信息
InetAddr client(peer);
// 消息末尾加'\0'
buffer[s] = 0;
// 回调函数处理接收消息
_func(_sockfd, buffer, client);
}
}
}
~UdpServer()
{}
private:
int _sockfd; // 网络文件描述符
uint16_t _port; // 端口号
bool _isrunning; // 运行标志位
func_t _func; // 服务器回调函数
};
4.2.UdpServer.cc
cpp
#include <iostream>
#include <memory>
#include "Route.hpp"
#include "ThreadPool.hpp"
#include "UdpServer.hpp"
using namespace ThreadPoolModule;
using task_t = std::function<void()>;
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();
// 路由对象
Route r;
// 线程池对象
auto tp = ThreadPool<task_t>::GetInstance();
// 服务器对象(网络通信)
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port,
/* UDP回调方法 */
[&r, &tp](int sockfd, const std::string &message, InetAddr &peer)
{
// 路由对象中的消息路由方法
auto t = std::bind(&Route::MessageRoute, &r, sockfd, message, peer);
// 等价于:
// auto t = [&r, sockfd, message, peer]()
// {
// r->MessageRoute(sockfd, message, peer)
// }
// 将消息路由方法放入线程池
tp->Enqueue(t);
});
usvr->Init(); // 初始化网络通信
usvr->Start();// 开启网络通信
return 0;
}
4.3.UdpClient.hpp
cpp
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Thread.hpp"
using namespace ThreadModlue;
int sockfd = 0; // 客户端套接字
std::string server_ip; // 服务器IP地址
uint16_t server_port = 0; // 服务器端口号
pthread_t id; // 客户端接收消息的线程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; // fd = 2
}
}
}
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";
sendto(sockfd, online.c_str(), online.size(), 0, (struct sockaddr *)&server, sizeof(server));
// 客户端向服务器发送消息
while (true)
{
std::string input;
std::cout << "Please Enter# "; // fd = 1
std::getline(std::cin, input); // fd = 0
int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
(void)n;
if (input == "QUIT")
{
// 取消客户端接收消息线程
pthread_cancel(id);
break;
}
}
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
return 1;
}
// 获取服务器IP地址
server_ip = argv[1];
// 获取服务器端口号
server_port = std::stoi(argv[2]);
// 创建套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
return 2;
}
// 非显示绑定
// 创建线程
Thread recvr(Recv);
Thread sender(Send);
// 启动线程
recvr.Start();
sender.Start();
// 获取客户端接收消息的线程ID
id = recvr.Id();
// 等待线程
recvr.Join();
sender.Join();
// 如果没有发送消息, 接收消息会被阻塞
// 使用多线程, 一个线程发送, 一个接收
return 0;
}
4.4.Route.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Mutex.hpp"
using namespace LogModule;
using namespace MutexModule;
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);
}
// 127.0.0.1:8080# 你好
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()));
}
// 用户退出消息
if (message == "QUIT")
{
LOG(LogLevel::INFO) << "删除一个在线用户: " << peer.StringAddr();
DeleteUser(peer);
}
}
~Route()
{}
private:
std::vector<InetAddr> _online_user; // 所有在线用户
Mutex _mutex; // 互斥锁
};
4.5.InetAddr.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// 网络地址 <=> 主机地址
class InetAddr
{
public:
InetAddr(struct sockaddr_in &addr)
: _addr(addr)
{
// 网络序列 → 主机序列(端口号)
_port = ntohs(_addr.sin_port);
// 网络序列 → 点分十进制(IP地址)
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;
// 主机序列 → 网络序列(端口号)
_addr.sin_port = htons(_port);
// 点分十进制 → 网络序列(IP地址)
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
}
// 获取端口号
uint16_t Port()
{
return _port;
}
// 获取IP地址
std::string Ip()
{
return _ip;
}
// 获取网络地址结构体
const struct sockaddr_in &NetAddr()
{
return _addr;
}
// 判断网路地址是否相同
bool operator==(const InetAddr &addr)
{
return addr._ip == _ip && addr._port == _port;
}
// 获取IP地址 + 端口号
std::string StringAddr()
{
return _ip + " : " + std::to_string(_port);
}
~InetAddr()
{}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
4.6.ThreadPool.hpp
cpp
#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 ThreadModlue;
using namespace LogModule;
using namespace MutexModule;
using namespace CondModule;
static const int gnum = 5;
template <typename T>
class ThreadPool
{
private:
void WakeUpAllThread()
{
LockGuard lockfguard(_mutex);
if (_sleepernum)
{
_cond.Broadcast();
}
LOG(LogLevel::INFO) << "唤醒所有的休眠线程";
}
void WakeUpOne()
{
_cond.Signal();
LOG(LogLevel::INFO) << "唤醒一个的休眠线程";
}
void Start()
{
if (_isrunning)
{
return;
}
_isrunning = true;
for (auto &thread : _threads)
{
thread.Start();
// LOG(LogLevel::INFO) << "start new thread success: " << thread.Name();
}
}
ThreadPool(int num = gnum)
: _num(num), _isrunning(false), _sleepernum(0)
{
for (int i = 0; i < num; i++)
{
_threads.emplace_back(
[this]()
{
HandlerTask();
});
LOG(LogLevel::INFO) << "create new thread success";
}
}
ThreadPool(const ThreadPool<T> &) = delete;
ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;
public:
// static修饰, 类不需要实例化就可以访问该函数
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 lockfguard(_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()
{
}
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.7.Thread.hpp
cpp
#ifndef _THREAD_H_
#define _THREAD_H_
#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>
#include <unistd.h>
#include "Log.hpp"
namespace ThreadModlue
{
using namespace LogModule;
static uint32_t number = 1;
class Thread
{
using func_t = std::function<void()>;
private:
void EnableDetach()
{
_isdetach = true;
}
void EnableRunning()
{
_isrunning = true;
}
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)
{
return;
}
if (_isrunning)
{
pthread_detach(_tid);
}
EnableDetach();
}
std::string Name()
{
return _name;
}
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)
{
LOG(LogLevel::DEBUG) << "Join线程失败";
}
else
{
LOG(LogLevel::DEBUG) << "Join线程成功";
}
}
pthread_t Id()
{
return _tid;
}
~Thread()
{
}
private:
pthread_t _tid; // 线程ID
std::string _name; // 线程名称
bool _isdetach; // 线程是否分离
bool _isrunning; // 线程是否运行
void *_res; // 新线程返回值
func_t _func; // 新线程的方法
};
}
#endif
4.8.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)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;
};
};
4.9.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)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;
};
};
4.10.Log.hpp
cpp
#ifndef __LOG_HPP__
#define __LOG_HPP__
#include <iostream>
#include <string>
#include <filesystem> // C++17中 文件操作的相关封装
#include <fstream>
#include "Mutex.hpp"
#include <memory>
#include <unistd.h>
#include <sstream>
#include <ctime>
#include <cstdio>
namespace LogModule
{
using namespace MutexModule;
const std::string gsep = "\r\n";
// 2. 刷新策略(策略模式: C++多态)
// 策略基类
class LogStrategy
{
public:
~LogStrategy() = default;
virtual void SyncLog(const std::string &message) = 0;
};
// 策略a: 显示器打印
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 = "my.log";
// 策略b: 指定文件写入
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);
// "./log" + "/" + "my.log"
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 LeveltoStr(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 "UNKNOW";
}
}
// 2. 获取时间方法
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;
}
// 日志类
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 << "] "
<< "[" << LeveltoStr(_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; // 进程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 Enbale_File_Log_Strategy() logger.EnableFileLogStrategy()
};
#endif
4.11.Makefile
cpp
.PHONY:all
all:udpclient udpserver
udpclient:UdpClient.cc
g++ -o $@ $^ -std=c++17 #-static
udpserver:UdpServer.cc
g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
rm -f udpclient udpserver
五、补充内容
5.1.地址转换函数
- 字符串转in_addr函数
pton(process to net)函数

cpp
InetAddr(const 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);
// 点分十进制 → 网络序列(IP地址)
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
}
- in_addr转字符串函数
ntoa(net to ascii)函数

cpp
InetAddr(struct sockaddr_in &addr)
: _addr(addr)
{
// 网络序列 → 主机序列
_port = ntohs(_addr.sin_port);
// 网络序列 → 点分十进制
_ip = inet_ntoa(_addr.sin_addr);
}
注:inet_ntoa函数将返回结果存放在静态存储区,多线程多次调用会出现线程安全问题
ntop(net to process)函数

cpp
InetAddr(struct sockaddr_in &addr)
: _addr(addr)
{
// 网络序列 → 主机序列(端口号)
_port = ntohs(_addr.sin_port);
// 网络序列 → 点分十进制(IP地址)
char ipbuffer[64];
inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
_ip = ipbuffer;
}
5.2.remove_if
cpp
#include <iostream>
#include <list>
#include <memory>
#include <algorithm>
int main()
{
std::list<std::shared_ptr<int>> ls;
ls.push_back(std::make_shared<int>(1));
ls.push_back(std::make_shared<int>(2));
ls.push_back(std::make_shared<int>(3));
ls.push_back(std::make_shared<int>(4));
ls.push_back(std::make_shared<int>(4));
ls.push_back(std::make_shared<int>(4));
ls.push_back(std::make_shared<int>(5));
ls.push_back(std::make_shared<int>(6));
for (auto &v : ls)
{
std::cout << *v << std::endl;
}
std::cout << "aa: " << ls.size() << std::endl;
std::cout << "\n";
// int a = 3;
int a = 4;
auto pos = remove_if(ls.begin(), ls.end(), [&a](const std::shared_ptr<int> &elem) -> bool
{ return a == *elem; });
ls.erase(pos, ls.end());
std::cout << "aa: " << ls.size() << std::endl;
for (auto &v : ls)
{
std::cout << *v << std::endl;
}
return 0;
}
六、网络命令
6.1.netstat命令
作用:查看网络状态
常用选项:
- -a:显示所有
- -l:只显示处于listen状态下的网络服务
- -u:UDP协议
- -t:TCP协议
- -n:数字化数据
- -p:携带进程相关信息



6.2.Ping命令
作用:检测网络连通性

常用选项:
-c:Ping的次数

6.3.pidof命令
作用:查看服务器的进程ID

七、Windows作为client访问Linux
cpp
#include <iostream>
#include <cstdio>
#include <thread>
#include <string>
#include <cstdlib>
#include <WinSock2.h> // windows的网络通信
#include <Windows.h>
#pragma warning(disable : 4996) // 屏蔽报错号
#pragma comment(lib, "ws2_32.lib") // windows的socket静态库
std::string serverip = "101.35.8.119"; // 填写你的云服务器ip
uint16_t serverport = 8080; // 填写你的云服务开放的端口号
int main()
{
WSADATA wsd;
// 初始化windows的socket静态库
WSAStartup(MAKEWORD(2, 2), &wsd);
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
// 主机序列 → 网络序列(端口号)
server.sin_port = htons(serverport);
// 点分十进制 → 网络序列(IP地址)
server.sin_addr.s_addr = inet_addr(serverip.c_str());
// 创建套接字
SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == SOCKET_ERROR)
{
std::cout << "socker error" << std::endl;
return 1;
}
std::string message;
char buffer[1024];
while (true)
{
// 给服务器发送消息
std::cout << "Please Enter@ ";
std::getline(std::cin, message);
if (message.empty())
{
continue;
}
sendto(sockfd, message.c_str(), (int)message.size(), 0, (struct sockaddr*)&server, sizeof(server));
// 从服务器接收消息
struct sockaddr_in temp;
int len = sizeof(temp);
int s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);
if (s > 0)
{
buffer[s] = 0;
std::cout << buffer << std::endl;
}
}
// 关闭套接字
closesocket(sockfd);
// 清理windows的socket静态库
WSACleanup();
return 0;
}