首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

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

  • 25-04-25 04:00
  • 4598
  • 13349
blog.csdn.net

在这里插入图片描述

文章目录

  • 协议(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
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

UdpServer.cc

#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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

客户端

客户端不用封装,只需要创建套接字,然后向目标IP和目标端口号发送数据即可,在发送数据的时候需要写成死循环,这样客户端可以一直向服务器发送消息,我们发送一条消息,客户端回一条一模一样的消息,表示服务器接收到了消息。

#include "UdpClient.hpp"
#include "Comm.hpp"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

// ./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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

总结

至此,我们用C++完整实现了一个基于UDP的通信流程,从创建 socket、绑定地址,到收发数据、关闭连接,每一步都围绕 Linux 下的 socket 编程核心展开。虽然 UDP 天生“无连接、不可靠”,但正因如此,它在低延迟、高并发场景下依然扮演着重要角色。希望这篇博客不仅帮你理清了 UDP 的基本用法,也为你后续深入网络编程打下了坚实的地基。别忘了,代码看完了不算真学会,敲一遍才是你自己的!

注:本文转载自blog.csdn.net的lyyyyrics的文章"https://blog.csdn.net/2301_79969994/article/details/147112784"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

120
运维
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top