• 【Linux】【网络】传输层协议:UDP



    UDP 协议

    UDP传输的过程类似于寄信。

    • 无连接:知道对端的IP和端口号就直接进行传输,不需要建立连接。
    • 不可靠:没有确认机制,没有重传机制;如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息。
    • 面向数据报: 不能够灵活的控制读写数据的次数和数量。

    1. 面向数据报

    数据报是独立的一整个,应用层交给 UDP 多长的报文,UDP原样发送,既不会拆分,也不会合并。

    例如:用 UDP 传输 100 个字节的数据:
    	如果发送端调用一次 sendto,发送100个字节,那么接收端也必须调用对应的一次recvfrom,接收100字节。
    	而不能循环调用10次recvfrom, 每次接收10个字节。
    
    • 1
    • 2
    • 3

    2. UDP 协议端格式

    在这里插入图片描述

    UPD 的协议报头长度是 固定的 8字节

    16位 UDP 长度,表示整个数据报(UDP 首部 + UDP 数据)的 最大长度,即 216 = 64kb

    如果校验和出错, 就会直接丢弃。

    报头(协议)的本质其实就是:结构化数据(结构体、位段)

    // 结构体实现
    struct udp_header
    {
    	uint16_t src_port;
    	uint16_t dst_port;
    	uint16_t udp_len;
    	uint16_t check;
    };
    // 位段实现
    struct udp_header
    {
    	uint32_t src_port:16;
    	uint32_t dst_port:16;
    	//...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3. UDP 的封装和解包

    🎯封装

    • 应用层将信息拷贝给传输层,用 char* p 指针指向一个缓冲区,前面放 UDP 结构报头(固定 8 字节),后面放有效载荷,对报头内容的填充就可以写作:
    ((struct udp_header*)p)->src_port = xx;
    ((struct udp_header*)p)->dst_port = xx;
    ((struct udp_header*)p)->udp_len = xx;
    ((struct udp_header*)p)->check = xx;
    
    • 1
    • 2
    • 3
    • 4

    🎯解包

    • 传输层拿到信息,用 char* start 指针指向头部,对 upd 报头、有效数据的提取就可以写作:
    // 读取报头信息
    xx = ((struct udp_header*)p)->src_port;
    xx = ((struct udp_header*)p)->dst_port;
    xx = ((struct udp_header*)p)->udp_len;
    xx =((struct udp_header*)p)->check;
    // 找到有效载荷的起始
    void* p = start + sizeof(struct udp_header);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4. UDP 的缓冲区

    UDP 没有真正意义上的 发送缓冲区。调用 sendto 会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作。

    UDP 具有接收缓冲区。但是这个接收缓冲区不能保证收到的 UDP 报的顺序和发送 UDP 报的顺序一致。如果缓冲区满了,再到达的UDP数据就会被丢弃。

    UDP 的 socket 既能读,也能写, 这个概念叫做 全双工


    接下来用 UDP 实现一个简单的服务器和客户端~
    🔗一些前置知识及 API


    UDP 通用服务器

    udp_server.hpp

    #pragma once
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "err.hpp"
    #include "RingQueue.hpp"
    #include "lockGuard.hpp"
    #include "Thread.hpp"
    
    namespace ns_server
    {
        const static uint16_t default_port = 8080;
        using func_t = std::function<std::string(std::string)>;
    
        class UdpServer
        {
        public:
            UdpServer(uint16_t port = default_port) : port_(port)
            {
                std::cout << "server addr: " << port_ << std::endl;
                pthread_mutex_init(&lock, nullptr);
    
                p = new Thread(1, std::bind(&UdpServer::Recv, this));
                c = new Thread(1, std::bind(&UdpServer::Broadcast, this));
            }
            void start()
            {
                //【1】创建 socket 接口,打开网络文件
                sock_ = socket(AF_INET, SOCK_DGRAM, 0);
                if (sock_ < 0)
                {
                    std::cerr << "create socket error: " << strerror(errno) << std::endl;
                    exit(SOCKET_ERR);
                }
                std::cout << "create socket success: " << sock_ << std::endl; // 3
    
                //【2】给服务器指明IP地址(云服务器上不行)和Port
                struct sockaddr_in local; // 这个 local 定义在 用户空间的特定函数的栈帧上,不在内核中!需要bind函数绑定socket
                bzero(&local, sizeof(local));   // 等效于memset(&local,0,sizeof(local))
    
                local.sin_family = AF_INET; // == PF_INET,是选择通信方式,这里选网络通信
                local.sin_port = htons(port_);  // 主机序列转成望楼序列
                // inet_addr: 1,2
                // 1. 字符串风格的IP地址,转换成为4字节int, "1.1.1.1" -> uint32_t -> 能不能强制类型转换呢?不能,这里要转化
                // 2. 需要将主机序列转化成为网络序列
                // local.sin_addr.s_addr = inet_addr(ip_.c_str());
                // 实际上,云服务器,或者一款服务器,一般不要指明某一个确定的IP!!
                // INADDR_ANY:让我们的 udpserver 在启动的时候,bind 本主机上的任意 IP
                local.sin_addr.s_addr = INADDR_ANY; 
                if (bind(sock_, (struct sockaddr *)&local, sizeof(local)) < 0)
                {
                    std::cerr << "bind socket error: " << strerror(errno) << std::endl;
                    exit(BIND_ERR);
                }
                std::cout << "bind socket success: " << sock_ << std::endl; // 3
    
                p->run();
                c->run();
            }
    
            void addUser(const std::string &name, const struct sockaddr_in &peer)
            {
                //?
                // onlineuserp[name] = peer;
                LockGuard lockguard(&lock);
                auto iter = onlineuser.find(name);
                if (iter != onlineuser.end())
                    return;
                // onlineuser.insert(std::make_pair(name, peer));
                onlineuser.insert(std::pair<const std::string, const struct sockaddr_in>(name, peer));
            }
            void Recv()
            {
                char buffer[1024];
                while (true)
                {
                    // 收
                    struct sockaddr_in peer;        // 要提取信息的缓冲区
                    socklen_t len = sizeof(peer);   // 这里一定要写清楚,未来你传入的缓冲区大小
                    int n = recvfrom(sock_, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
                    if (n > 0)
                        buffer[n] = '\0';
                    else
                        continue;
    
                    std::cout << "recv done ..." << std::endl;
    
                    // 提取client信息 -- debug 
                    std::string clientip = inet_ntoa(peer.sin_addr);
                    uint16_t clientport = ntohs(peer.sin_port);     // 网络中读出来的是网络序列,需要转成主机序列
                    std::cout << clientip << "-" << clientport << "# " << buffer << std::endl;
    
                    // 构建一个用户,并检查
                    std::string name = clientip;
                    name += "-";
                    name += std::to_string(clientport);
                    // 如果不存在,就插入,如果存在,什么都不做
                    addUser(name, peer);
                    rq.push(buffer);
    
                    // // 做业务处理
                    // std::string message = service_(buffer);
    
                    // 发
                    // sendto(sock_, message.c_str(), message.size(), 0, (struct sockaddr*)&peer, sizeof(peer));
                }
            }
            void Broadcast()
            {
                while (true)
                {
                    std::string sendstring;
                    rq.pop(&sendstring);
    
                    std::vector<struct sockaddr_in> v;  // 
                    {
                        LockGuard lockguard(&lock);
                        for (auto user : onlineuser)
                        {
                            v.push_back(user.second);
                        }
                    }
                    for (auto user : v)
                    {
                        // std::cout << "Broadcast message to " << user.first << sendstring << std::endl;
                        sendto(sock_, sendstring.c_str(), sendstring.size(), 0, (struct sockaddr *)&(user), sizeof(user));
                        std::cout << "send done ..." << sendstring << std::endl;
                    }
                }
            }
            ~UdpServer()
            {
                pthread_mutex_destroy(&lock);
                c->join();
                p->join();
    
                delete c;
                delete p;
            }
    
        private:
            int sock_;          // 套接字
            uint16_t port_;     // 端口号
            // func_t service_; // 我们的网络服务器刚刚解决的是网络IO的问题,要进行业务处理
            std::unordered_map<std::string, struct sockaddr_in> onlineuser;
            pthread_mutex_t lock;
            RingQueue<std::string> rq;
            Thread *c;
            Thread *p;
            // 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
    • 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
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161

    udp_server.cc

    #include "udp_server.hpp"
    #include 
    #include 
    #include 
    
    using namespace ns_server;
    using namespace std;
    
    static void usage(string proc)
    {
        std::cout << "Usage:\n\t" << proc << " port\n" << std::endl;
    }
    
    //【业务处理】
    // 上层的业务处理,不关心网络发送,只负责信息处理即可
    std::string transactionString(std::string request) // request 就是一个string
    {
        std::string result;
        char c;
        for (auto &r : request)
        {
            if (islower(r))
            {
                c = toupper(r);
                result.push_back(c);
            }
            else
            {
                result.push_back(r);
            }
        }
    
        return result;
    }
    static bool isPass(const std::string &command)
    {   
        bool pass = true;
        auto pos = command.find("rm");
        if(pos != std::string::npos) pass=false;
        pos = command.find("mv");
        if(pos != std::string::npos) pass=false;
        pos = command.find("while");
        if(pos != std::string::npos) pass=false;
        pos = command.find("kill");
        if(pos != std::string::npos) pass=false;
        return pass;
    }
    
    // 需要实现:client把命令给server,server再把结果给client!
    // ls -a -l
    std::string excuteCommand(std::string command) // command用作一个命令
    {
        // 1. 安全检查
        if(!isPass(command)) return "you are a bad man!";
    
        // 2. 业务逻辑处理
        FILE *fp = popen(command.c_str(), "r");
        if(fp == nullptr) return "None";
        // 3. 获取结果了
        char line[1024];
        std::string result;
        while(fgets(line, sizeof(line), fp) != NULL)
        {
            result += line;
        }
        pclose(fp);
    
        return result;
    }
    
    // 设置执行程序的命令为:./udp_server port
    int main(int argc, char *argv[])
    {
        if (argc != 2)
        {
            usage(argv[0]);
            exit(USAGE_ERR);
        }
        uint16_t port = atoi(argv[1]);
    
        // unique_ptr usvr(new UdpServer("120.78.126.148", 8082));
        // unique_ptr usvr(new UdpServer(transactionString, port));
        // unique_ptr usvr(new UdpServer(excuteCommand, port));
        unique_ptr<UdpServer> usvr(new UdpServer(port));
    
        // usvr->InitServer(); // 服务器的初始化
        usvr->start();
    
        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
    • 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

    UDP 通用客户端

    udp_client.cc

    #include 
    #include 
    #include  /* See NOTES */
    #include 
    #include 
    #include 
    #include 
    #include "err.hpp"
    // 127.0.0.1: 本地环回,就表示的就是当前主机,通常用来进行本地通信或者测试
    
    static void usage(std::string proc)
    {
        std::cout << "Usage:\n\t" << proc << " serverip serverport\n" << std::endl; 
    }
    
    // ./udp_client serverip serverport 客户端必须知道服务器的IP和端口号
    int main(int argc, char *argv[])
    {
        if(argc != 3)
        {
            usage(argv[0]);
            exit(USAGE_ERR);
        }
        std::string serverip = argv[1];
        uint16_t serverport = atoi(argv[2]);
    
        int sock = socket(AF_INET, SOCK_DGRAM, 0);
        if(sock < 0)
        {
            std::cerr << "create socket error" << std::endl;
            exit(SOCKET_ERR);
        }
        // Q1:client 这里要不要bind呢?
        // A1:要的!socket通信的本质[clientip:clientport, serverip:serverport]
        // Q2:要不要自己bind呢?
        // A2:不需要自己bind,也不要自己bind,OS自动给我们进行bind -- 为什么?client的port要随机让OS分配防止client出现
        // 启动冲突 -- server 为什么要自己bind?1. server的端口不能随意改变,众所周知且不能随意改变的 2. 同一家公司的port号
        // 需要统一规范化
    
        // 明确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());
    
        while(true)
        {
            //多线程化??
            
            // 用户输入
            std::string message;
            std::cout << "[蛋哥的服务器]# ";
            // std::cin >> message;
    
            std::getline(std::cin,message);
            // 什么时候bind的?在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport+自己的IP,1. bind 2. 构建发送的数据报文
            //发送
            sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));
    
            //接受
            char buffer[2048];
            struct sockaddr_in temp;
            socklen_t len = sizeof(temp);
            int n = recvfrom(sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&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
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~


  • 相关阅读:
    中国传统节日春节网页HTML代码 春节大学生网页设计制作成品下载 学生网页课程设计期末作业下载 DW春节节日网页作业代码下载
    C++ 虚函数表
    SQL语句
    已解决com.netflix.client.ClientException Eureka客户端异常的正确解决方法,亲测有效!!!
    Paxos 算法详解
    浅谈互联网寒冬Android进阶之路
    聊聊Redis sentinel 机制
    NVMe SSD 学习总结:04 为什么NVME的SSD越来越受欢迎?
    【ORM】浅聊C#和Java的ORM底层框架
    R语言ggplot2可视化:使用patchwork包将两个ggplot2可视化结果组合起来、使用labs函数为第二个子图添加标题信息
  • 原文地址:https://blog.csdn.net/m0_67470729/article/details/133072399