引入日志系统设计:基于UDP协议的 回声系统 服务器-客户端通信实现
目录
一、UdpServer.hpp
1、代码详细讲解:UDP服务器实现
1. 头文件和预处理指令
2. 包含的头文件
3. 类型别名和常量定义
4. UdpServer类定义
4.1 构造函数
4.2 初始化方法 Init()
4.3 启动方法 Start()
4.4 析构函数
4.5 成员变量
5. 代码特点分析
6. 潜在改进点
7. 总结
2、知识点整理
1. 网络编程基础
2. 套接字编程关键函数
3. 地址结构与转换
4. 特殊地址与端口
5. 错误处理与日志
6. 面向对象设计
7. 资源管理
8. 网络数据格式
9. 多线程与扩展性
10. 安全考虑
二、UdpServer.cc
1、头文件引入
2、默认消息处理函数
3、主函数
3.1 参数检查
3.2 解析端口号
3.3 日志配置
3.4 创建UDP服务器
3.5 初始化和启动服务器
4、程序流程总结
5、关键设计点
6、可能的改进
三、UdpClient.cc
1、UDP客户端代码详细讲解
1. 头文件引入
2. 主函数
2.1 参数检查
2.2 解析参数
3. 创建套接字
4. 关于客户端绑定的说明
5. 设置服务器地址
6. 主循环
6.1 获取用户输入
6.2 发送消息
6.3 接收响应
7. 程序特点
8. 可能的改进
9. UDP客户端工作流程总结
2、关键知识点整理
1. UDP协议特点
2. 套接字编程基础
套接字创建
地址结构
3. 关键函数说明
sendto() - 发送数据
recvfrom() - 接收数据
4. 字节序转换
5. IP地址转换
6. 客户端绑定问题
7. 错误处理
代码执行流程
改进建议
重点注意
总结
四、相关必需头文件
Log.hpp
Mutex.hpp
五、运行输出示例
一、UdpServer.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
using namespace LogModule;
// 定义处理函数的类型别名:接收string参数,返回string结果
using func_t = std::function;
// 默认文件描述符值,表示未初始化
const int defaultfd = -1;
// UDP服务器类,用于网络通信
class UdpServer
{
public:
// 构造函数
// @param port: 服务器监听的端口号
// @param func: 数据处理回调函数
UdpServer(uint16_t port, func_t func)
: _sockfd(defaultfd),
_port(port),
_isrunning(false),
_func(func)
{
}
// 初始化服务器,创建socket并绑定端口
void Init()
{
// 1. 创建UDP套接字
// AF_INET: IPv4地址族
// SOCK_DGRAM: 数据报套接字(UDP)
// 0: 让系统自动选择协议(对于UDP是IPPROTO_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. 绑定socket到指定端口
struct sockaddr_in local;
// 清零结构体,避免未初始化数据
bzero(&local, sizeof(local));
// 设置地址族为IPv4
local.sin_family = AF_INET;
// 将端口号从主机字节序转换为网络字节序(大端)
local.sin_port = htons(_port);
// INADDR_ANY表示绑定到所有本地接口
// 服务器通常这样设置以接收所有网络接口的数据
local.sin_addr.s_addr = INADDR_ANY;
// 绑定socket到指定的地址和端口
// 对于服务器必须显式绑定,因为需要固定监听端口
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. 接收客户端消息
// recvfrom是阻塞调用,直到收到数据或出错
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0,
(struct sockaddr *)&peer, &len);
if (s > 0)
{
// 处理接收到的数据
buffer[s] = ' '; // 确保字符串正确终止
// 获取客户端信息
int peer_port = ntohs(peer.sin_port); // 网络字节序转主机字节序
std::string peer_ip = inet_ntoa(peer.sin_addr); // 网络字节序IP转点分十进制
// 调用回调函数处理数据
std::string result = _func(buffer);
// 2. 发送响应消息回客户端
sendto(_sockfd, result.c_str(), result.size(), 0,
(struct sockaddr*)&peer, len);
}
}
}
// 析构函数
~UdpServer()
{
// 注意:实际应该在这里关闭socket,但代码中遗漏了
// 应该在_sockfd != defaultfd时调用close(_sockfd)
}
private:
int _sockfd; // 服务器socket文件描述符
uint16_t _port; // 服务器监听端口
bool _isrunning; // 服务器运行状态标志
func_t _func; // 数据处理回调函数
};
1、代码详细讲解:UDP服务器实现
这段代码实现了一个基于UDP协议的服务器类UdpServer,下面我将从各个方面进行详细讲解。
1. 头文件和预处理指令
#pragma once
-
#pragma once是一个非标准但被广泛支持的预处理指令,用于防止头文件被多次包含。
2. 包含的头文件
#include
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
-
标准库头文件:
,, -
系统头文件:
-
: 提供bzero等字符串操作函数 -
: 定义数据类型 -
: 套接字API -
: 互联网地址族定义 -
: IP地址转换函数
-
-
自定义头文件:
"Log.hpp"(日志模块)
3. 类型别名和常量定义
using func_t = std::function;
const int defaultfd = -1;
-
func_t: 定义了一个函数类型别名,表示接受一个const std::string&参数并返回std::string的函数 -
defaultfd: 定义了一个默认的文件描述符值(-1),用于初始化套接字
4. UdpServer类定义
4.1 构造函数
UdpServer(uint16_t port, func_t func)
: _sockfd(defaultfd),
_port(port),
_isrunning(false),
_func(func)
{
}
-
参数:
-
port: 服务器监听的端口号 -
func: 处理接收数据的回调函数
-
-
初始化列表:
-
_sockfd: 初始化为默认文件描述符(-1) -
_port: 设置为传入的端口号 -
_isrunning: 初始化为false -
_func: 设置为传入的回调函数
-
4.2 初始化方法 Init()
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信息
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;
}
-
创建套接字:
-
使用
socket()函数创建UDP套接字(SOCK_DGRAM) -
失败时记录FATAL日志并退出程序
-
-
绑定地址信息:
-
创建
sockaddr_in结构体并清零 -
设置地址族为AF_INET(IPv4)
-
设置端口号(使用
htons()将主机字节序转换为网络字节序) -
设置IP地址为INADDR_ANY(表示绑定到所有可用接口)
-
调用
bind()绑定套接字到指定地址和端口 -
失败时记录FATAL日志并退出程序
-
4.3 启动方法 Start()
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);
buffer[s] = 0; // 确保字符串以null结尾
// 调用回调函数处理数据
std::string result = _func(buffer);
// 2. 发送响应
sendto(_sockfd, result.c_str(), result.size(), 0,
(struct sockaddr*)&peer, len);
}
}
}
-
主循环:
-
设置
_isrunning为true,进入循环 -
循环会一直运行直到
_isrunning变为false
-
-
接收数据:
-
准备缓冲区
buffer和存储对端地址的sockaddr_in结构体 -
使用
recvfrom()接收UDP数据报 -
如果成功接收(s > 0):
-
获取对端端口号(使用
ntohs()转换字节序) -
获取对端IP地址(使用
inet_ntoa()转换) -
确保缓冲区以null结尾
-
调用回调函数
_func处理接收到的数据
-
-
-
发送响应:
-
使用
sendto()将处理结果发送回对端 -
使用之前接收到的对端地址信息
-
4.4 析构函数
~UdpServer()
{
}
-
当前为空实现,理论上应该在这里关闭套接字和释放资源
4.5 成员变量
private:
int _sockfd; // 套接字文件描述符
uint16_t _port; // 服务器监听端口
bool _isrunning; // 服务器运行状态标志
func_t _func; // 数据处理回调函数
5. 代码特点分析
-
UDP协议:使用SOCK_DGRAM类型的套接字、无连接协议,每次收发都需要指定对端地址
-
回调机制:通过
func_t类型的回调函数处理接收到的数据、使服务器逻辑与通信逻辑分离,提高灵活性 -
日志记录:使用自定义的LogModule记录不同级别的日志、包括FATAL(致命错误)和INFO(信息性)日志
-
网络字节序转换:使用
htons()/ntohs()处理端口号、使用inet_ntoa()处理IP地址 -
多接口支持:使用INADDR_ANY绑定到所有可用网络接口
6. 潜在改进点
-
资源释放:析构函数中应关闭套接字、考虑添加Stop()方法设置
_isrunning为false -
错误处理:可以添加更多错误处理逻辑、考虑非致命错误的恢复机制
-
性能优化:缓冲区大小固定为1024,可能不适合大块数据、可考虑动态缓冲区或分包处理
-
线程安全:当前实现不是线程安全的、如果需要在多线程环境中使用,需要添加同步机制
-
IPv6支持:当前仅支持IPv4、可扩展支持IPv6
这个示例创建了一个监听8080端口的UDP服务器,对收到的每条消息都添加"Response to: "前缀后返回。
7. 总结
这段代码实现了一个基本的UDP服务器框架,具有以下特点:
-
使用面向对象方式封装UDP服务器逻辑
-
通过回调函数实现灵活的数据处理
-
包含基本的错误处理和日志记录
-
支持多网络接口
代码结构清晰,但还有一些可以改进的地方,如资源管理、错误处理和扩展性方面。
2、知识点整理
1. 网络编程基础
-
UDP协议特点:无连接、不可靠、面向数据报、传输效率高
-
套接字类型:
-
SOCK_DGRAM:数据报套接字(UDP) -
SOCK_STREAM:流套接字(TCP)
-
-
地址族:
-
AF_INET:IPv4地址族 -
AF_INET6:IPv6地址族
-
2. 套接字编程关键函数
socket():创建套接字
int socket(int domain, int type, int protocol);

bind():绑定地址和端口
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

recvfrom():接收数据(UDP)
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);

sendto():发送数据(UDP)
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);

3. 地址结构与转换
-
sockaddr_in结构体:
struct sockaddr_in { short sin_family; // 地址族(AF_INET) unsigned short sin_port; // 16位端口号(网络字节序) struct in_addr sin_addr; // 32位IPv4地址(网络字节序) char sin_zero[8]; // 未使用(填充为零) }; -
字节序转换:

-
htons():主机字节序转网络字节序(16位) -
ntohs():网络字节序转主机字节序(16位) -
htonl()/ntohl():32位版本
-
-
IP地址转换:

-
inet_addr():点分十进制字符串转网络字节序(已过时) -
inet_aton():字符串转网络字节序(更安全) -
inet_ntoa():网络字节序IP转点分十进制字符串 -
现代替代方案:
inet_pton()和inet_ntop()
-
4. 特殊地址与端口
-
INADDR_ANY:绑定到所有本地接口(0.0.0.0)、服务器通常使用此方式监听所有网络接口
-
端口号:
-
0:让系统自动分配临时端口
-
1-1023:特权端口(需要root权限)
-
1024-49151:用户注册端口
-
49152-65535:动态/私有端口
-
5. 错误处理与日志
-
代码中使用自定义日志模块(
Log.hpp)记录不同级别日志 -
系统调用失败时应检查返回值并适当处理
-
严重错误(
FATAL)导致程序退出
6. 面向对象设计
-
使用类封装UDP服务器功能
-
构造函数初始化关键参数
-
提供Init()和Start()方法分离初始化和运行逻辑
-
使用函数对象(std::function)实现灵活的数据处理回调
7. 资源管理
-
代码中遗漏了socket的关闭操作
-
最佳实践应在析构函数中关闭socket
-
可考虑使用RAII模式管理资源
8. 网络数据格式
-
UDP是面向数据报的协议
-
每次recvfrom()调用返回一个完整的数据报
-
需要处理缓冲区溢出问题(示例中固定1024字节缓冲区)
9. 多线程与扩展性
-
当前实现是单线程的,一次只能处理一个请求
-
可扩展为多线程/多进程模型处理并发请求
-
或使用I/O多路复用(select/poll/epoll)
10. 安全考虑
-
未验证客户端身份,容易受到伪造源IP攻击
-
未处理消息截断问题(缓冲区固定大小)
-
生产环境需要添加更多安全措施
这个UDP服务器实现展示了基本的网络编程模式,但生产环境需要更多错误处理、性能优化和安全考虑。
二、UdpServer.cc
#include
#include
#include "UdpServer.hpp"
// 仅仅是用来进行测试的
std::string defaulthandler(const std::string &message)
{
std::string hello = "hello, ";
hello += message;
return hello;
}
// 翻译系统,字符串当成英文单词
// ./udpserver port
int main(int argc, char *argv[])
{
if(argc != 2)
{
std::cerr << "Usage: " << argv[0] << " port" << std::endl;
return 1;
}
// std::string ip = argv[1];
uint16_t port = std::stoi(argv[1]);
Enable_Console_Log_Strategy();
std::unique_ptr usvr = std::make_unique(port, defaulthandler);
usvr->Init();
usvr->Start();
return 0;
}
这段代码实现了一个简单的UDP服务器,使用C++编写。下面我将从整体结构、各个部分的功能以及关键点进行详细讲解。
1、头文件引入
#include
#include
#include "UdpServer.hpp"
-
: 提供标准输入输出功能 -
: 提供智能指针支持(这里使用了std::unique_ptr) -
"UdpServer.hpp": 自定义的头文件,应该包含了UDP服务器的类定义
2、默认消息处理函数
std::string defaulthandler(const std::string &message)
{
std::string hello = "hello, ";
hello += message;
return hello;
}
这是一个简单的消息处理函数,它:
-
接收一个字符串参数
message -
创建一个新的字符串"hello, "
-
将输入消息追加到这个字符串后面
-
返回组合后的字符串
这个函数将作为UDP服务器的默认消息处理器,当服务器收到消息时会调用它。
3、主函数
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();
// 创建UDP服务器实例
std::unique_ptr usvr = std::make_unique(port, defaulthandler);
// 初始化并启动服务器
usvr->Init();
usvr->Start();
return 0;
}
3.1 参数检查
if(argc != 2)
{
std::cerr << "Usage: " << argv[0] << " port" << std::endl;
return 1;
}
-
检查命令行参数数量是否正确
-
如果不是2个参数(程序名+端口号),打印使用说明并返回错误码1
-
argv[0]是程序名,argv[1]应该是端口号
3.2 解析端口号
uint16_t port = std::stoi(argv[1]);
-
将命令行参数(字符串)转换为无符号16位整数(端口号的标准类型)
-
使用
std::stoi进行转换
3.3 日志配置
Enable_Console_Log_Strategy();
-
调用一个函数启用控制台日志策略
-
从函数名可知,这是配置日志输出到控制台的函数
-
具体实现在之前实现过的日志文件中(后面会加上展示)
3.4 创建UDP服务器
std::unique_ptr usvr = std::make_unique(port, defaulthandler);
-
使用
std::make_unique创建一个UdpServer的唯一指针 -
构造函数参数:
-
port: 服务器监听的端口号 -
defaulthandler: 前面定义的消息处理函数
-
-
使用智能指针可以自动管理内存,避免内存泄漏

我们可能会遇到这样的错误,这个错误是因为 std::make_unique 是 C++14 引入的特性。解决方案:使用 C++14 或更高标准编译,也就是说在编译命令中添加 -std=c++14 或更高标准:
g++ -std=c++14 your_file.cpp -o your_program
3.5 初始化和启动服务器
usvr->Init();
usvr->Start();
-
Init(): 初始化服务器(可能包括创建socket、绑定端口等操作) -
Start(): 启动服务器,开始监听和处理连接 -
这两个方法的具体实现在
UdpServer.hpp对应的源文件中
4、程序流程总结
-
检查命令行参数,确保提供了端口号
-
解析端口号
-
配置日志输出
-
创建UDP服务器实例,并指定消息处理函数
-
初始化服务器
-
启动服务器,进入事件循环
5、关键设计点
-
使用智能指针:通过
std::unique_ptr管理UdpServer对象,确保异常安全性和自动内存管理。 -
可扩展的消息处理:通过将处理函数作为参数传递给
UdpServer构造函数,实现了处理逻辑与网络层的解耦。可以轻松替换不同的处理函数。 -
简单的错误处理:目前只有基本的参数检查,实际的网络错误处理应该在
UdpServer类的实现中。 -
日志策略:使用
Enable_Console_Log_Strategy()函数,暗示了可能有多种日志输出方式,便于调试和生产环境切换。
6、可能的改进
-
添加更详细的错误处理,特别是网络相关的错误
-
允许通过配置文件或命令行参数指定不同的处理函数
-
添加信号处理,实现优雅关闭
-
增加日志级别控制
这段代码展示了一个基本的UDP服务器框架,核心功能依赖于UdpServer类的实现(未展示),但已经体现了良好的结构设计和资源管理实践。
三、UdpClient.cc
这段代码实现了一个基于UDP协议的简单客户端程序,能够与指定的UDP服务器进行双向通信。程序通过命令行参数接收服务器IP地址和端口号,然后进入交互模式,用户可以输入消息发送给服务器并接收服务器的响应。
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
// 1. 参数检查
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
return 1;
}
// 2. 解析服务器地址和端口
std::string server_ip = argv[1];
uint16_t server_port = std::stoi(argv[2]);
// 3. 创建UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
std::cerr << "socket error" << std::endl;
return 2;
}
// 4. 配置服务器地址结构
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());
// 5. 主交互循环
while (true) {
// 获取用户输入
std::string input;
std::cout << "Please Enter# ";
std::getline(std::cin, input);
// 发送数据到服务器
sendto(sockfd, input.c_str(), input.size(), 0,
(struct sockaddr*)&server, sizeof(server));
// 接收服务器响应
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] = ' ';
std::cout << "Server response: " << buffer << std::endl;
}
}
// 程序不会执行到这里,因为前面是无限循环
close(sockfd);
return 0;
}
1、UDP客户端代码详细讲解
这段代码实现了一个简单的UDP客户端,可以与UDP服务器进行通信。下面我将从整体结构、各个部分的功能以及关键点进行详细讲解。
1. 头文件引入
#include
#include
#include
#include
#include
#include
#include
-
: 提供标准输入输出功能 -
: 提供字符串处理功能 -
: 提供C风格字符串处理函数(如memset) -
: 定义IP地址和端口号等网络相关结构 -
: 提供IP地址转换函数(如inet_addr) -
和: 提供套接字编程所需的各种类型和函数
2. 主函数
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]);
2.1 参数检查
-
检查命令行参数数量是否正确
-
如果不是3个参数(程序名+服务器IP+服务器端口),打印使用说明并返回错误码1
-
argv[0]是程序名,argv[1]是服务器IP,argv[2]是服务器端口
2.2 解析参数
-
server_ip: 存储服务器IP地址 -
server_port: 将字符串端口号转换为无符号16位整数
3. 创建套接字
// 1. 创建socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
std::cerr << "socket error" << std::endl;
return 2;
}
-
使用
socket()函数创建UDP套接字-
AF_INET: 使用IPv4协议族 -
SOCK_DGRAM: 使用数据报套接字(UDP) -
0: 使用默认协议(对于UDP是IPPROTO_UDP)
-
-
检查套接字是否创建成功,失败则打印错误并返回错误码2
4. 关于客户端绑定的说明
代码中的注释详细解释了UDP客户端是否需要绑定的问题:
本地IP和端口如何配置?需要与"文件"相关联吗? 关于客户端绑定问题:
-
必须绑定吗?是,需要绑定
-
需要显式绑定吗?不需要!首次发送消息时,操作系统会自动为客户端绑定。系统会自动获取IP,并使用随机端口号
-
原因:每个端口号只能被一个进程绑定,采用随机端口可以避免客户端端口冲突
-
注意:客户端端口号的具体数值不重要,只要保证唯一性即可
关键点:
-
UDP客户端需要绑定,但通常不需要显式绑定
-
第一次发送数据时,操作系统会自动为客户端分配一个临时端口
-
这样做可以避免端口冲突,因为客户端端口号不重要
5. 设置服务器地址
// 填写服务器信息
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());
-
创建
sockaddr_in结构体存储服务器地址信息 -
使用
memset初始化为0 -
设置:
-
sin_family: 地址族(AF_INET表示IPv4) -
sin_port: 端口号,使用htons()转换为网络字节序 -
sin_addr.s_addr: IP地址,使用inet_addr()将字符串IP转换为网络字节序
-
6. 主循环
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;
}
}
6.1 获取用户输入
-
使用
std::getline从标准输入读取一行文本 -
提示用户输入"Please Enter# "
6.2 发送消息
-
使用
sendto()函数发送消息到服务器-
sockfd: 套接字描述符 -
input.c_str(): 要发送的数据 -
input.size(): 数据长度 -
0: 标志位,通常为0 -
(struct sockaddr*)&server: 服务器地址 -
sizeof(server): 地址长度
-
-
忽略返回值(用
(void)n避免编译器警告)
6.3 接收响应
-
使用
recvfrom()函数接收服务器响应-
sockfd: 套接字描述符 -
buffer: 存储接收数据的缓冲区 -
sizeof(buffer)-1: 缓冲区大小(保留1字节给字符串结束符) -
0: 标志位,通常为0 -
(struct sockaddr*)&peer: 存储对端地址信息 -
&len: 输入输出参数,指定缓冲区大小并返回实际地址长度
-
-
如果接收成功(
m > 0):-
在数据末尾添加字符串结束符
-

