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
如果作者哪里错误请及时提出,谢谢大家!
评论前必须登录!
注册