云计算百科
云计算领域专业知识百科平台

【创建UDP服务器和客户端】

1.创建(服务端)

UDP是什么? 在Linux中,UDP(User Datagram Protocol,用户数据报协议)是一种无连接的、不可靠的网络传输协议,属于TCP/IP协议族的传输层。与TCP不同,UDP不保证数据包的顺序、完整性或可靠性,但它具有低延迟、低开销的特点,适用于对实时性要求高但允许少量数据丢失的场景。

在这里插入图片描述

1.1 套接字 socket

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

参数作用取值
domain 指定套接字使用的协议族,决定了通信的地址格式和协议类型。 AF_INET(IPv4 协议族 最常用) AF_INET6 (IPv6 协议族)
type 指定套接字的数据传输方式,决定通信的可靠性和特性。 AF_INET IPv4 协议族(最常用)。AF_INET6 IPv6 协议族。AF_UNIX Unix 域套接字(本地进程间通信)。
protocol 指定套接字使用的具体协议, 通常设为 0 表示自动选择与 domain 和 type 匹配的默认协议。

#include <sys/socket.h>

// 创建 UDP Socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}

1.2 绑定地址 bind

//绑定前先填充地址结构体
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = ::htons(8080); // 要被发送给对方的,即要发到网络中!
local.sin_addr.s_addr = inet_addr("127.0.0.1"); // IP地址转换
//或者也可以 local.sin_addr.s_addr = INADDR_ANY;
//INADDR_ANY:表示"任意可用的网络接口"或"所有本地IP地址"。

地址结构详情:sockaddr_in和sockaddr

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数作用
sockfd 要绑定的套接字文件描述符(由 socket() 创建)。
addr 指向 sockaddr 结构体的指针,包含要绑定的地址和端口信息。
addrlen sockaddr 结构体的长度(通常用 sizeof(struct sockaddr_in))。

1.3 接收数据 recvfrom

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);

sockfd 已创建的 UDP 套接字文件描述符
buf 接收数据的缓冲区指针
len 缓冲区的最大长度
flags 控制接收行为的标志位(通常设为0) src_addr 用于存储发送方地址信息的结构体指针
src_addr 用于存储发送方地址信息的结构体指针
addrlen 输入时为结构体大小,输出时为实际地址长度

//实例
char inbuffer[1024]; // string
struct sockaddr_in peer;
socklen_t len = sizeof(peer); // 必须设定
ssize_t n = ::recvfrom(sockfd, inbuffer, sizeof(inbuffer) 1, 0, (struct sockaddr*)&peer, &len);
//sockaddr_in 是 IPv4 专用结构体,使用时需强制转换为 sockaddr*。

1.4 发送响应 sendto

#include <sys/types.h>
#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);

sockfd 已创建的 UDP 套接字文件描述符
buf 要发送的数据缓冲区指针
len 要发送的数据长度
flags 控制发送行为的标志位(通常设为0)
dest_addr 目标地址的结构体指针
addrlen 目标地址结构体的长度

std::string echo_string = "echo# ";
echo_string += inbuffer;
::sendto(sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&peer, sizeof(peer));

完整代码

#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__

#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <cerrno>
#include <strings.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

const static int gsockfd = 1;
const static std::string gdefaultip = "127.0.0.1"; // 表示本地主机
const static uint16_t gdefaultport = 8080;

#define Die(code) \\
do \\
{ \\
exit(code); \\
} while (0)

#define CONV(v) (struct sockaddr *)(v)
//宏转

enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR
};

class UdpServer
{
public:
UdpServer(uint16_t port = gdefaultport)
: _sockfd(gsockfd),
_port(gdefaultport),
_isrunning(false)
{
}
// 都是套路
void InitServer()
{
// 1. 创建socket
_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); // IP?PORT?网络?本地?
if (_sockfd < 0)
{
std::cout << "socket: " << strerror(errno) << std::endl;
Die(SOCKET_ERR);
}
std::cout << "socket success, sockfd is : " << _sockfd << std::endl;

// 2. 填充网络信息,并bind绑定
// 2.1 有没有把socket信息,设置进入内核中??没有,只是填充了结构体!
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = ::htons(_port); // 要被发送给对方的,即要发到网络中!
//local.sin_addr.s_addr = ::inet_addr(_ip.c_str()); // 1. string ip->4bytes 2. network order //TODO
local.sin_addr.s_addr = INADDR_ANY;
//INADDR_ANY:表示"任意可用的网络接口"或"所有本地IP地址"。

// 2.2 bind : 设置进入内核中
int n = ::bind(_sockfd, CONV(&local), sizeof(local));
if (n < 0)
{
std::cout << "bind: " << strerror(errno) << std::endl;
Die(BIND_ERR);
}
std::cout << "bind success" << std::endl;
}
void Start()
{
_isrunning = true;
while (true)
{
char inbuffer[1024]; // string
struct sockaddr_in peer;
socklen_t len = sizeof(peer); // 必须设定

ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) 1, 0, CONV(&peer), &len);
if (n > 0)
{
// 1. 消息内容 && 2. 谁发给我的
uint16_t clientport = ::ntohs(peer.sin_port);
std::string clientip = ::inet_ntoa(peer.sin_addr);

inbuffer[n] = 0;

std::string clientinfo = gdefaultip + ":" + std::to_string(_port) + " # " + inbuffer;

std::cout << clientinfo <<std::endl;

std::string echo_string = "echo# ";
echo_string += inbuffer;

::sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, CONV(&peer), sizeof(peer));
}
}
_isrunning = false;
}
~UdpServer()
{
// if(_sockfd > gsockfd)
// ::close(_sockfd);
}

private:
int _sockfd;
uint16_t _port; // 服务器未来的端口号
//std::string _ip; // 服务器所对应的IP
bool _isrunning; // 服务器运行状态
};

#endif

2.创建客户端

2.1 创建套接字

int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
std::cerr << "socket error" << std::endl;
Die(SOCKET_ERR);
}

2.2 地址配置

struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = ::htons(_port);
server.sin_addr.s_addr = ::inet_addr(_ip);

2.3 数据发送

std::cout << "Please Enter# ";
std::string message;
std::getline(std::cin, message);
// client 不需要bind吗?socket <-> socket
// client必须也要有自己的ip和端口!但是客户端,不需要自己显示的调用bind!!
// 而是,客户端首次sendto消息的时候,由OS自动进行bind
// 1. 如何理解client自动随机bind端口号? 一个端口号,只能被一个进程bind
// 2. 如何理解server要显示的bind?服务器的端口号,必须稳定!!必须是众所周知且不能改变轻易改变的!
int n = ::sendto(sockfd, message.c_str(),message.size(), 0, (struct sockaddr*)&server, sizeof(server));

2.4 接收数据

struct sockaddr_in temp;
socklen_t len = sizeof(temp);
char buffer[1024];
n = ::recvfrom(sockfd, buffer, sizeof(buffer)1, 0, (struct sockaddr*)&temp, &len);
if(n > 0)
{
buffer[n] = 0;
std::cout << buffer << std::endl;
}

完整代码

#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define CONV(v) (struct sockaddr *)(v)
int main(int argc, char *argv[])
{
// 检查参数数量是否为3(程序名+服务器IP+服务器端口)
if(argc != 3)
{
std::cerr << "Usage: " << argv[0] << " serverip serverport" << std::endl;
}
// 从命令行参数获取服务器IP(第1个参数)
std::string serverip = argv[1];
// 从命令行参数获取服务器端口(第2个参数)并转换为uint16_t
uint16_t serverport = std::stoi(argv[2]);

// 1. 创建socket
int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
std::cerr << "socket error" << std::endl;
}

// 1.1 填充server信息
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = ::htons(serverport);
server.sin_addr.s_addr = ::inet_addr(serverip.c_str());

// 2. clientdone
while(true)
{
std::cout << "Please Enter# ";
std::string message;
std::getline(std::cin, message);
// client 不需要bind吗?socket <-> socket
// client必须也要有自己的ip和端口!但是客户端,不需要自己显示的调用bind!!
// 而是,客户端首次sendto消息的时候,由OS自动进行bind
// 1. 如何理解client自动随机bind端口号? 一个端口号,只能被一个进程bind
// 2. 如何理解server要显示的bind?服务器的端口号,必须稳定!!必须是众所周知且不能改变轻易改变的!
int n = ::sendto(sockfd, message.c_str(),message.size(), 0, CONV(&server), sizeof(server));
(void)n;

struct sockaddr_in temp;
socklen_t len = sizeof(temp);
char buffer[1024];
n = ::recvfrom(sockfd, buffer, sizeof(buffer)1, 0, CONV(&temp), &len);
if(n > 0)
{
buffer[n] = 0;
std::cout << buffer << std::endl;
}
}
return 0;
}

主函数Main

#include "UdpServer.hpp"

int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " localport" << std::endl;
Die(USAGE_ERR);
}
// std::string ip = argv[1];
uint16_t port = std::stoi(argv[1]);

std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>();
svr_uptr->InitServer();
svr_uptr->Start();
return 0;
}

运行后效果 在这里插入图片描述 在这里插入图片描述

运用上个文章创建的InetAddr类

只需要修改服务端

#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__

#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <cerrno>
#include <strings.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "InetAddr.hpp"

const static int gsockfd = 1;
const static std::string gdefaultip = "127.0.0.1"; // 表示本地主机
const static uint16_t gdefaultport = 8080;

#define Die(code) \\
do \\
{ \\
exit(code); \\
} while (0)

#define CONV(v) (struct sockaddr *)(v)
//宏转

enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR
};

class UdpServer
{
public:
UdpServer(uint16_t port = gdefaultport)
: _sockfd(gsockfd),
_addr(port),
_isrunning(false)
{
}
// 都是套路
void InitServer()
{
// 1. 创建socket
_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); // IP?PORT?网络?本地?
if (_sockfd < 0)
{
std::cout << "socket: " << strerror(errno) << std::endl;
Die(SOCKET_ERR);
}
std::cout << "socket success, sockfd is : " << _sockfd << std::endl;

// // 2. 填充网络信息,并bind绑定
// // 2.1 有没有把socket信息,设置进入内核中??没有,只是填充了结构体!
// struct sockaddr_in local;
// bzero(&local, sizeof(local));
// local.sin_family = AF_INET;
// local.sin_port = ::htons(_port); // 要被发送给对方的,即要发到网络中!
// //local.sin_addr.s_addr = ::inet_addr(_ip.c_str()); // 1. string ip->4bytes 2. network order //TODO
// local.sin_addr.s_addr = INADDR_ANY;
// //INADDR_ANY:表示"任意可用的网络接口"或"所有本地IP地址"。

// 2.2 bind : 设置进入内核中
int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen());
if (n < 0)
{
std::cout << "bind: " << strerror(errno) << std::endl;
Die(BIND_ERR);
}
std::cout << "bind success" << std::endl;
}
void Start()
{
_isrunning = true;
while (true)
{
char inbuffer[1024]; // string
struct sockaddr_in peer;
socklen_t len = sizeof(peer); // 必须设定

ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) 1, 0, CONV(&peer), &len);
if (n > 0)
{
// 1. 消息内容 && 2. 谁发给我的
// uint16_t clientport = ::ntohs(peer.sin_port);
// std::string clientip = ::inet_ntoa(peer.sin_addr);

InetAddr cli(peer);
inbuffer[n] = 0;

std::string clientinfo = cli.Ip() + ":" + std::to_string(cli.Port()) + " # " + inbuffer;

std::cout << clientinfo <<std::endl;

std::string echo_string = "echo# ";
echo_string += inbuffer;

::sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, CONV(&peer), sizeof(peer));
}
}
_isrunning = false;
}
~UdpServer()
{
if(_sockfd > gsockfd)
::close(_sockfd);
}

private:
int _sockfd;
InetAddr _addr;
// uint16_t _port; // 服务器未来的端口号
// // std::string _ip; // 服务器所对应的IP
bool _isrunning; // 服务器运行状态
};

#endif

如果作者哪里错误请及时提出,谢谢大家!

赞(0)
未经允许不得转载:网硕互联帮助中心 » 【创建UDP服务器和客户端】
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!