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

这篇具有很好参考价值的文章主要介绍了【Linux】【网络】传输层协议:UDP。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


UDP 协议

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

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

1. 面向数据报

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

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

2. UDP 协议端格式

【Linux】【网络】传输层协议:UDP,网络,Linux,网络,linux,udp,1024程序员节

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;
	//...
};

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;

🎯解包

  • 传输层拿到信息,用 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);

4. UDP 的缓冲区

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

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

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


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


UDP 通用服务器

udp_server.hpp

#pragma once
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unordered_map>
#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<const std::string, const struct sockaddr_in>(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_; 
    };
} 

udp_server.cc

#include "udp_server.hpp"
#include <memory>
#include <string>
#include <cstdio>

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<UdpServer> usvr(new UdpServer("120.78.126.148", 8082));
    // unique_ptr<UdpServer> usvr(new UdpServer(transactionString, port));
    // unique_ptr<UdpServer> usvr(new UdpServer(excuteCommand, port));
    unique_ptr<UdpServer> usvr(new UdpServer(port));

    // usvr->InitServer(); // 服务器的初始化
    usvr->start();

    return 0;
}



UDP 通用客户端

udp_client.cc

#include <iostream>
#include <string>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#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;
}

🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~文章来源地址https://www.toymoban.com/news/detail-731527.html


到了这里,关于【Linux】【网络】传输层协议:UDP的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 08-linux网络管理-nc命令(TCP|UDP网络联通测试,文件传输,带宽测试)

    - 监听TCP端口(默认) 说明: -l 启动监听模式(作为服务器监听指定端口) -v 显示信息和错误 - 监听UDP端口 说明: -u UDP模式 - 链接TCP端口 - 链接UDP端口 说明: -z 链接不传输数据 - 接收数据重定向 - 上传数据 检查本地服务器是和 10.10.239.65的80端口是否能建立TCP链接。 如上

    2024年01月24日
    浏览(46)
  • Linux内核--网络协议栈(二)UDP数据包发送

    一、引言 二、数据包发送 ------2.1、数据发送流程 三、协议层注册 ------3.1、socket系统调用 ------3.2、socket创建 ------3.3、协议族初始化 ------3.4、对应协议的socket创建 ------------3.4.1、sock ------3.5、协议注册 四、通过套接字发送网络数据 ------4.1、inet_sendmsg 本文首先从宏观上概述了

    2024年01月15日
    浏览(41)
  • 【计算机网络】传输层协议 -- UDP协议

    传输层是计算机网络中的一个重要层次,位于网络层和应用层之间,它的主要功能是为应用层提供端到端的数据传输服务,负责确保数据可靠传输、流浪控制和拥塞控制等。 传输层的两个主要协议是传输控制协议(TCP)和用户数据报协议(UDP)。它们各自有不同的特点和用途

    2024年02月15日
    浏览(73)
  • 【传输层】网络基础 -- UDP协议 | TCP协议

    端口号(Port)标识了一个主机上进行通信的不同的应用程序 在TCP/IP协议中,用 “源IP”, “源端口号”, “目的IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信(可以通过 netstat -n 查看) 0 - 1023:知名端口号,HTTP,FTP,SSH等这些广为使用的应用层协议,他

    2024年02月09日
    浏览(49)
  • 【网络原理】| 应用层协议与传输层协议 (UDP)

    🎗️ 主页:小夜时雨 🎗️ 专栏:javaEE初阶 🎗️ 乾坤未定,你我皆黑马 应用层是和代码直接相关的一层,决定了数据要传输什么,怎么去使用这些数据等问题。 应用层这里,虽然存在一些现有的协议(比如HTTP),但是也有很多的情况,需要我们去自定义一些协议,这里的自

    2024年02月06日
    浏览(49)
  • 计算机网络-传输层(UDP协议报文格式,伪首部,UDP校验过程)

    UDP只在IP数据报服务之上增加了很少功能,即复用分用和差错检测功能。 UDP的主要特点: UDP是无连接的,减少开销和发送数据之前的时延。 UDP使用最大努力交付,即不保证可靠交付。 UDP是面向报文的,适合一次性传输少量数据的网终应用。 应用层给UDP多长的报文,UDP就照样

    2023年04月25日
    浏览(54)
  • 网络原理(四):传输层协议 TCP/UDP

    目录 应用层 传输层 udp 协议  端口号 报文长度(udp 长度) 校验和 TCP 协议 确认应答 超时重传 链接管理 滑动窗口 流量控制 拥塞控制 延时应答 捎带应答 总结 我们第一章让我们对网络有了一个初步认识,第二章和第三章我们通过代码感受了网络通信程序。 而本章的 通信原

    2023年04月27日
    浏览(53)
  • 网络传输层协议:UDP和TCP

    端口号(Port)标识了一个主机上进行通信的不同的应用程序; 在TCP/IP协议中, 用 \\\"源IP\\\", \\\"源端口号\\\", \\\"目的IP\\\", \\\"目的端口号\\\", \\\"协议号\\\" 这样一个五元组来标识一个通信(可以通过 netstat -n查看);  0 - 1023: 知名端口号, HTTP, FTP, SSH 比特科技 等这些广为使用的应用层协议, 他们的端口号

    2024年02月15日
    浏览(50)
  • 网络传输层协议详解(TCP/UDP)

    目录 一、TCP协议 1.1、TCP协议段格式  1.2、TCP原理  确认应答机制 超时重传机制 (安全机制) 连接管理机制(安全机制)  滑动窗口  流量控制(安全机制)  拥塞控制  延迟应答(效率机制) 捎带应答(效率机制)  ​编辑面向字节流(粘包问题)  缓冲区  TCP异常情况  二、UDP协议

    2024年02月06日
    浏览(55)
  • 计算机网络笔记:TCP协议 和UDP协议(传输层)

    TCP 和 UDP都是传输层协议,他们都属于TCP/IP协议族。 TCP的全称是 传输控制协议 是一种 面向连接的、可靠的、基于字节流 的 传输层 通信协议。TCP 是面向连接的、可靠的流协议(流就是指不间断的数据结构) TCP报文 是TCP层传输的数据单元,也称为 报文段 ,一个TCP报文段由

    2024年02月02日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包