
文章目录
- 协议(Protocol)
- UDP协议(用户数据报协议)
-
- [1. 基本定义](#1. 基本定义)
- [2. 核心特性](#2. 核心特性)
- UDP协议实现通信
- 总结
协议(Protocol)
协议 是计算机或通信系统中,不同实体(如设备、程序、服务等)之间进行交互和通信时,共同遵循的一套规则和标准。它定义了数据的格式、传输方式、错误处理、安全机制等,确保通信双方能够正确理解彼此的信息并完成协作。
协议的核心要素
-
语法(Syntax)
- 数据的结构或格式,例如报文如何排列、字段的长度和顺序等。
- 示例:HTTP请求中,请求行、头部、正文的排列方式。
-
语义(Semantics)
- 数据的含义及操作逻辑,解释字段代表的动作或内容。
- 示例:HTTP状态码
200
表示请求成功,404
表示资源未找到。
-
同步(Timing/Synchronization)
- 通信的顺序控制,如数据发送和响应的时序。
- 示例:TCP三次握手建立连接时的顺序规则。
常见协议分类
类别 | 协议示例 | 作用 |
---|---|---|
网络通信 | TCP/IP、HTTP、FTP | 实现数据传输和网络互联 |
安全协议 | SSL/TLS、SSH、HTTPS | 加密通信和身份验证 |
应用层协议 | SMTP(邮件)、DNS | 支持特定应用功能(如邮件解析域名) |
硬件协议 | USB、Bluetooth | 硬件设备间的交互规范 |
UDP协议(用户数据报协议)
1. 基本定义
UDP(User Datagram Protocol) 是一种无连接的传输层协议,位于TCP/IP模型中的传输层(OSI第4层)。它以最小化的协议机制提供高效的数据传输服务。
2. 核心特性
特性 | 说明 |
---|---|
无连接 | 通信前无需建立连接,直接发送数据 |
不可靠传输 | 不保证数据顺序、不重传丢失报文、不检测拥塞 |
无状态 | 发送方和接收方不维护连接状态 |
头部开销小 | 固定8字节头部(TCP至少20字节) |
支持广播/多播 | 可向多个主机同时发送数据 |
UDP协议实现通信
服务器端
由于UDP协议是一种通过数据报在网络中传输的协议,所以我们在创建套接字的时候需要将参数设置为数据报类型,服务器端主要有几个功能,一个是初始化服务器,一个是启动服务器,在启动服务器的时候需要将服务器写成死循环,服务器可以一直接收外部发来的数据。因为在服务器中存在着很多需要将网络字节序转化为本地字节序,所以为了方便,我们将IP地址和端口号封装成一个类InetAddr,这个类中的方法有网络字节序和本地字节序的转化,还有获取网络字节序和本地字节序,方便我们写代码的可读性。
注意:这里面用的LOG是上一章封装过的一个LOG类,可以直接拿过来用
Comm.hpp
cpp
#pragma once
#include <iostream>
#define Die(code) exit(code)
#define CONV(v) (struct sockaddr *)(v)
enum
{
USAGE_ERR,
SOCKET_ERR,
BIND_ERR
};
InetAddr.hpp
cpp
#pragma once
#include "Comm.hpp"
#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class InetAddr
{
private:
void PortNet2Host()
{
_port = ::ntohl(_net_addr.sin_port);
}
void IpNet2Host()
{
//将网络风格转化为字符串风格的
char ipbuffer[64];
const char * ip = inet_ntop(AF_INET,&_net_addr.sin_addr,ipbuffer,sizeof(ipbuffer));
(void)ip;
}
public:
InetAddr()
{
}
//网络转点分十进制---这里是网络转主机
InetAddr(const struct sockaddr_in & addr):_net_addr(addr)
{
PortNet2Host();
IpNet2Host();
}
InetAddr(uint16_t port):_port(port),_ip("")
{
_port =
_net_addr.sin_family = AF_INET;
_net_addr.sin_port = htons(_port);
//这里是主机转网络
_net_addr.sin_addr.s_addr = INADDR_ANY;
}
struct sockaddr* NetAddr()
{
return CONV(&_net_addr);
}
socklen_t NetAddrLen()
{
return sizeof(_net_addr);
}
std::string Ip()
{
return _ip;
}
std::uint16_t Port()
{
return _port;
}
~InetAddr()
{
}
private:
//网络addr
struct sockaddr_in _net_addr;
//网络序列的端口
uint16_t _port;
std::string _ip;
};
UdpServer.hpp
cpp
#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__
#include "InetAddr.hpp"
#include "Comm.hpp"
#include <iostream>
#include <string>
#include <memory>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <string.h>
#include "Log.hpp"
using namespace lyrics;
const static int gsockfd = -1;
// 这个ip表示主机的本地IP,一般用于做本地通信的
const static std::string gdefaultip = "127.0.0.1";
// 测试用的端口号
const static uint16_t defaultport = 8000;
class UdpServer
{
public:
UdpServer(uint16_t port = defaultport)
: _sockfd(gsockfd),_addr(port),_isrunning(false) {}
// 1. 初始化服务器----都是套路---UDP初始化
void InitServer()
{
// 创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0); // IP?PORT?网络?本地?
if (_sockfd < 0)
{
// 输出致命的错误
LOG(LogLevel::FATAL) << "socket: " << strerror(errno);
// 创建失败直接结束:
Die(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success,sockfd is: " << _sockfd;
int n = ::bind(_sockfd,_addr.NetAddr(), _addr.NetAddrLen());
if(n < 0)
{
LOG(LogLevel::FATAL) <<"socket: " << strerror(errno);
Die(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success";
}
void Start()
{
_isrunning = true;
//服务器死循环
while(true)
{
struct sockaddr_in peer;//远端
socklen_t len = sizeof(peer); //必须设定
char inbuffer[1024];
ssize_t n = ::recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&len);
//读取消息成功
if(n > 0)
{
InetAddr cli(peer);
//1. 需要知道消息内容
inbuffer[n] = 0;
std::string clientinfo = cli.Ip() + ":" + std::to_string(cli.Port()) + " # " + inbuffer;
LOG(LogLevel::DEBUG) << clientinfo;
std::string echo_string = "echo ";
echo_string += inbuffer;
//2. 得知道消息是谁发的
::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; // 服务器未来的端口号
// 传递进来的是字符串风格的IP----点分十进制的IP地址
//std::string _ip; // 服务器未来的ip地址
bool _isrunning; // 服务器的运行状态
};
#endif
UdpServer.cc
cpp
#include "UdpServer.hpp"
// ./server_udp localip localport
int main(int argc,char *argv[])
{
if (argc != 2)
{
std::cerr << "Usage" << argv[0] << "localport" << std::endl;
Die(USAGE_ERR);
}
//默认输出在显示器上
ENABLE_CONSOLE_LOG();
std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(std::stoi(argv[1]));
svr_uptr->InitServer();
svr_uptr->Start();
return 0;
}
客户端
客户端不用封装,只需要创建套接字,然后向目标IP和目标端口号发送数据即可,在发送数据的时候需要写成死循环,这样客户端可以一直向服务器发送消息,我们发送一条消息,客户端回一条一模一样的消息,表示服务器接收到了消息。
cpp
#include "UdpClient.hpp"
#include "Comm.hpp"
#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <cstdlib>
#include <string>
// ./client_udp serverip serverport----客户端需r要先知道服务器的端口号和IP
int main(int argc, char *argv[])
{
if (argc != 3)
{
std::cerr << "Usage" << argv[0] << "serverip serverport" << std::endl;
Die(USAGE_ERR);
}
std::string serverip = argv[1];
// 命令行上输入的都是字符串
uint16_t serverport = std::stoi(argv[2]);
// 1. 创建套接字
int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
Die(SOCKET_ERR);
}
// 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 Entrer: ";
std::string message;
std::getline(std::cin, message);
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;
}
总结
至此,我们用C++完整实现了一个基于UDP的通信流程,从创建 socket、绑定地址,到收发数据、关闭连接,每一步都围绕 Linux 下的 socket 编程核心展开。虽然 UDP 天生"无连接、不可靠",但正因如此,它在低延迟、高并发场景下依然扮演着重要角色。希望这篇博客不仅帮你理清了 UDP 的基本用法,也为你后续深入网络编程打下了坚实的地基。别忘了,代码看完了不算真学会,敲一遍才是你自己的!