> 技术文档 > 【Linux】用C++实现UDP通信:详解socket编程流程_c++ udp通信

【Linux】用C++实现UDP通信:详解socket编程流程_c++ udp通信

【Linux】用C++实现UDP通信:详解socket编程流程_c++ udp通信

文章目录

  • 协议(Protocol)
    • 协议的核心要素
    • 常见协议分类
  • UDP协议(用户数据报协议)
    • 1. 基本定义
    • 2. 核心特性
  • UDP协议实现通信
    • 服务器
      • Comm.hpp
      • InetAddr.hpp
      • UdpServer.hpp
      • UdpServer.cc
    • 客户端
  • 总结

协议(Protocol)

协议 是计算机或通信系统中,不同实体(如设备、程序、服务等)之间进行交互和通信时,共同遵循的一套规则和标准。它定义了数据的格式、传输方式、错误处理、安全机制等,确保通信双方能够正确理解彼此的信息并完成协作。


协议的核心要素

  1. 语法(Syntax)

    • 数据的结构或格式,例如报文如何排列、字段的长度和顺序等。
    • 示例:HTTP请求中,请求行、头部、正文的排列方式。
  2. 语义(Semantics)

    • 数据的含义及操作逻辑,解释字段代表的动作或内容。
    • 示例:HTTP状态码 200 表示请求成功,404 表示资源未找到。
  3. 同步(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

#pragma once#include #define Die(code) exit(code)#define CONV(v) (struct sockaddr *)(v)enum{ USAGE_ERR, SOCKET_ERR, BIND_ERR};

InetAddr.hpp

#pragma once#include \"Comm.hpp\"#include #include #include #include #include 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

#ifndef __UDP_SERVER_HPP__#define __UDP_SERVER_HPP__#include \"InetAddr.hpp\"#include \"Comm.hpp\"#include #include #include #include #include #include #include #include #include #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

#include \"UdpServer.hpp\"// ./server_udp localip localportint 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和目标端口号发送数据即可,在发送数据的时候需要写成死循环,这样客户端可以一直向服务器发送消息,我们发送一条消息,客户端回一条一模一样的消息,表示服务器接收到了消息。

#include \"UdpClient.hpp\"#include \"Comm.hpp\"#include #include #include #include #include #include #include #include // ./client_udp serverip serverport----客户端需r要先知道服务器的端口号和IPint 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 的基本用法,也为你后续深入网络编程打下了坚实的地基。别忘了,代码看完了不算真学会,敲一遍才是你自己的!