一、Udpserver.hpp
1.创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
功能:创建一个UDP套接字。
参数:
AF_INET
:指定地址族为IPv4。SOCK_DGRAM
:指定套接字类型为UDP,UDP是一种无连接的网络协议,数据包通过套接字发送和接收。0
:默认的协议,对于UDP来说,通常使用默认值。
返回值:
成功返回非负整数(套接字描述符)。
失败返回-1,并设置
errno
。
2. 检查套接字创建是否成功
if(_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error!";
exit(1);
}
LOG(LogLevel::INFO) << "socket success, sockfd: " << _sockfd;
功能:检查套接字是否创建成功。
日志记录:
如果创建失败,记录致命错误并退出程序。
如果创建成功,记录成功信息
3. 绑定套接字信息(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;
功能:填充
sockaddr_in
结构体并绑定套接字。参数:
AF_INET
:地址族为IPv4。htons(_port)
:将主机字节序的端口号转换为网络字节序。INADDR_ANY
:表示绑定到所有可用的网络接口。
4.调用bind
函数
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
功能:将套接字
_sockfd
绑定到sockaddr_in
结构体local
所指定的地址和端口。参数:
_sockfd
:套接字描述符,之前通过socket
函数创建。(struct sockaddr *)&local
:指向sockaddr_in
结构体的指针,sockaddr_in
是sockaddr
的子类型,用于IPv4地址。sizeof(local)
:sockaddr_in
结构体的大小。
返回值:
成功返回0。
失败返回-1,并设置
errno
。
5.接收消息
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
使用
recvfrom
函数从套接字_sockfd
接收数据。参数:
_sockfd
:套接字描述符。buffer
:接收数据的缓冲区。sizeof(buffer) - 1
:缓冲区大小减1,防止溢出。0
:标志位,通常设置为0。(struct sockaddr *)&peer
:存储发送方的地址信息。&len
:发送方地址信息的长度。
返回值:
成功返回接收到的字节数。
失败返回-1,并设置
errno
6.处理接收的消息
int peer_port = ntohs(peer.sin_port);
std::string peer_ip = inet_ntoa(peer.sin_addr);
buffer[s] = 0;
std::string result = _func(buffer);
- 将接收到的数据转换为字符串。
- 调用
_func
函数处理接收到的消息,返回处理结果。
7.发送响应
sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);
使用
sendto
函数将处理结果发送回客户端。参数:
_sockfd
:套接字描述符。result.c_str()
:要发送的数据。result.size()
:数据的大小。0
:标志位,通常设置为0。(struct sockaddr*)&peer
:客户端的地址信息。len
:客户端地址信息的长度
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <cstring>
#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(uint16_t port, func_t func)
: _sockfd(defaultfd),
_port(port),
_isrunning(false),
_func(func)
{
}
void Init()
{
//1.创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);//AF_INET 指定了地址族为 IPv4。
//SOCK_DGRAM 指定了套接字类型为 UDP,这是一种无连接的网络协议,数据包通过套接字发送和接收
//创建成功会返回非负整数
if(_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error!";
exit(1);
}
LOG(LogLevel::INFO) << "socket success, sockfd: " << _sockfd;
//2.绑定socke信息,ip和端口号
//2.1 填充sockaddr_in结构体
struct sockaddr_in local;//sockaddr_in的使用,已放在CSDN上
bzero(&local, sizeof(local));//bzero 函数将指定数量的字节设置为零,通常用于初始化数据结构。
local.sin_family = AF_INET;
//本地格式->网络序列
local.sin_port = htons(_port);
//htons假设主机使用 Little-endian 字节序,端口号 _port 为 12345(在内存中的表示为 0x3039),转换为网络字节序后,它将变为 0x3930。
// IP也是如此,1. IP转成4字节 2. 4字节转成网络序列 -> in_addr_t inet_addr(const char *cp);
local.sin_addr.s_addr = INADDR_ANY;
//为什么服务器的bind是显示的呢?因为IP和端口号是众所周知且不能轻易改变的!
//这段代码是UDP服务器初始化过程中绑定套接字到特定地址和端口的部分。
//它的主要功能是将创建的套接字与服务器的IP地址和端口号关联起来,以便服务器能够在指定的地址和端口上接收和发送数据
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.收消息,client发送数据,让服务器进行处理数据
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
if(s > 0)//如果接收到的数据字节数大于0,表示成功接收到消息。
{
int peer_port = ntohs(peer.sin_port);//从网络中拿到:网络序列
std::string peer_ip = inet_ntoa(peer.sin_addr);//4字节网络风格的IP ->点分十进制的IP
buffer[s] = 0;
std::string result = _func(buffer);
//2.发消息
sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);
}
}
}
~UdpServer()
{
}
private:
int _sockfd;
uint16_t _port;
// std::string _ip; // 用的是字符串风格,点分十进制, "192.168.1.1"
bool _isrunning;
func_t _func;//服务器的回调函数,用于进行对数据的处理
};
二、Udpserver.cc
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, defaulthandler);
1. std::unique_ptr
std::unique_ptr
是一种智能指针,它拥有其所指向的对象,并在std::unique_ptr
被销毁时自动删除其拥有的对象。std::unique_ptr
确保了对象的唯一所有权,即同一时间只有一个std::unique_ptr
可以拥有一个对象。
2. std::make_unique
std::make_unique
是一个辅助函数,用于创建一个std::unique_ptr
并初始化它所指向的对象。它接受对象构造函数的参数,并将这些参数转发给对象的构造函数。
三、Udpclient.cc
1.sockaddr_in
struct sockaddr_in
的定义如下:
struct sockaddr_in {
short sin_family; // 地址族,通常为 AF_INET
unsigned short sin_port; // 端口号,网络字节序
struct in_addr sin_addr; // IP 地址,网络字节序
char sin_zero[8]; // 未使用,通常填充为 0
};
成员变量解释
sin_family
:类型:
short
用途:指定地址族,对于 IPv4,值为
AF_INET
。
sin_port
:类型:
unsigned short
用途:指定端口号,必须使用网络字节序(大端序)。通常使用
htons
函数将主机字节序转换为网络字节序。示例:
server.sin_port = htons(12345);
sin_addr
:类型:
struct in_addr
用途:指定 IP 地址,必须使用网络字节序。
struct in_addr
通常包含一个s_addr
成员,类型为in_addr_t
。示例:
server.sin_addr.s_addr = INADDR_ANY;
或server.sin_addr.s_addr = inet_addr("192.168.1.100");
sin_zero
:类型:
char[8]
用途:未使用,通常填充为 0,以确保结构体的大小与
struct sockaddr
一致。