应用层中的UDP协议原理

在前面网络编程基础中,我们简单解释了TCPIP五层协议,并且通过QQ收发消息的示例来展示五层协议中每一层的作用.在此我先简单回顾一下TCPIP五层协议

  • TCPIP五层协议
    1. 应用层:网络上传输的数据,应用程序如何使用(要寄出的物品)
    2. 传输层:负责端到端的传输,通行双方只关心起点和终点(快递公司及收发件人具体的门牌号,确保交到正确的人手里)
    3. 网络层:负责主机到主机的传输,着重规划传输路线(负责计算快递从武汉发往广州走哪条路最快)
    4. 数据链路层:负责两个相邻节点的数据转发(快递车在两个相邻驿站或集运中心的转运过程)
    5. 物理层:负责将数据转化成能在物理介质中传播的信号(快递车走的高速公路)
应用层协议
协议是什么?

协议就是计算机之间约定好的沟通规则

  • 为什么要使用协议?
    如果两个不同国家的人,一个说的是法语,一个说的是中文.他们不管如何说自己的语言对方都听不懂,但如何他们都遵守一种协议(说英语).那么这两个人就可以通过英语来交流和理解对方的意思了
    协议作为统一规范.只有使用相同的协议电脑的各种硬件,操作系统,软件才能互相交流,理解对方的意思.

  • 常见的传输协议

    1. http协议
      Web程序中最常用的协议,通过浏览器看视频或新闻都是通过http协议通信的.下面是一个http请求的简单示例
    http 复制代码
    GET /index.html HTTP/1.1  # 【请求行】
                              # GET:获取数据
                              # /index.html:要访问的页面
                              # HTTP/1.1:使用的协议版本
    Host: www.example.com     # 【请求头】要访问的网站地址
    User-Agent: Chrome/120.0  # 【请求头】告诉服务器我是浏览器
    Accept: text/html         # 【请求头】我想要网页内容
    
                              # 【空行】分隔请求头和请求体
                              # GET 请求没有请求体,到这里就结束
    1. xml协议
      xml协议属于比较老旧的格式比例,现在更多用来作为配置文件
    xml 复制代码
    <?xml version="1.0" encoding="UTF-8"?>
    <bookstore>
    <book id="1">
      <name>XML入门教程</name>
      <author>编程新手</author>
      <price>39.9</price>
      <publish_date>2025-01-01</publish_date>
    </book>
    <book id="2">
      <name>Python零基础</name>
      <author>代码达人</author>
      <price>49.9</price>
      <publish_date>2025-02-01</publish_date>
    </book>
    </bookstore>
    • 缺点:
      • 结构复杂
      • 不易读
      • 冗余字符过多
    1. json协议
      json是目前java开发中的绝对主流
    json 复制代码
    {
    "姓名": "张三",
    "年龄": 20,
    "是否学生": true,
    "爱好": ["看书", "编程", "打球"],
    "地址": {
     "省份": "广东省",
     "城市": "广州市"
    },
    "余额": null
    }
    • 优点:
      • 简洁:同样的信息json占用的字符更少\
    • 缺点:
      • 不支持注释

以上只是部分协议,还有protobuf,IBM等各种各样的协议

  • 自定义协议
    在日常开发过程中,自定义协议是很常见的.下面是一个基于自定义协议的在线计算器程序
java 复制代码
package OnlineCalculator;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpCalServer {
    ServerSocket serverSocket = null;

    public TcpCalServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        ExecutorService service = Executors.newCachedThreadPool();
        while (true) {
            Socket socket = serverSocket.accept();

            service.submit(() -> {
                try {
                    connect(socket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
        }
    }

    private void connect(Socket socket) throws IOException {
        System.out.printf("%s[%s:%d]\n","客户端上线",socket.getInetAddress().toString(),socket.getPort());
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()) {
            Scanner scannerConnect = new Scanner(inputStream);
            while(scannerConnect.hasNext()) {
                //接收请求
                String requestString = scannerConnect.next();
                Request request = Request.requestFromString(requestString);
                //构造响应
                Response response = calculate(request);
                //返回响应
                outputStream.write(response.responseToString().getBytes());
                outputStream.flush();
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            System.out.printf("%s[%s:%d]\n","客户端下线",socket.getInetAddress().toString(),socket.getPort());
            socket.close();
        }
    }

    private Response calculate(Request request) {
        double num;
        if(request.getOperator().equals("+")) {
            num = request.getNum1() + request.getNum2();
        } else if (request.getOperator().equals("-")) {
            num = request.getNum1() - request.getNum2();
        } else if (request.getOperator().equals("*")) {
            num = request.getNum1() * request.getNum2();
        } else if (request.getOperator().equals("/")) {
            num = request.getNum1() / request.getNum2();
        }else {
            throw new RuntimeException("错误的运算符" + request.getOperator());
        }
        return new Response(num);
    }

    public static void main(String[] args) throws IOException {
        TcpCalServer tcpCalServer = new TcpCalServer(9090);
        tcpCalServer.start();
    }
}
package OnlineCalculator;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class TcpCalClient {
    Socket socket = null;
    private String ip;
    private int port;

    public TcpCalClient(String ip, int port) throws IOException {
        this.ip = ip;
        this.port = port;
        socket = new Socket(ip, port);
    }
    public void start() {
        try(OutputStream outputStream = socket.getOutputStream();
            InputStream inputStream = socket.getInputStream()) {
            Scanner scannerClient = new Scanner(inputStream);
            Scanner scanner = new Scanner(System.in);
            while (true) {
                //输入内容
                System.out.println("输入运算符(+,-,*,/):");
                String operator = scanner.next();
                System.out.println("输入第一个数字");
                double num1 = scanner.nextDouble();
                System.out.println("输入第二个数字");
                double num2 = scanner.nextDouble();
                //构造请求
                Request request = new Request(operator,num1,num2);
                //发送请求
                outputStream.write(request.requestToString().getBytes());
                outputStream.flush();
                //接收请求
                String responseString = scannerClient.next();
                Response response = Response.responseFromString(responseString);
                //打印结果
                System.out.printf("%s[%s:%d]%f\n","服务端返回",socket.getInetAddress().toString(),socket.getPort(),response.getNum());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws IOException {
        TcpCalClient tcpCalClient = new TcpCalClient("127.0.0.1",9090);
        tcpCalClient.start();
    }
}
package OnlineCalculator;

public class Request {
    private String operator;
    private double num1;
    private double num2;

    public String getOperator() {
        return operator;
    }

    public double getNum1() {
        return num1;
    }

    public double getNum2() {
        return num2;
    }

    public Request(String operator, double num1, double num2) {
        this.operator = operator;
        this.num1 = num1;
        this.num2 = num2;
    }
    //构造协议内容
    public  String requestToString() {
        return String.format("%s,%f,%f\n",operator,num1,num2);
    }
    //解析协议内容
    public static Request requestFromString(String request) {
        String[] split = request.split(",");
        String operator = split[0];
        double num1 = Double.parseDouble(split[1]);
        double num2 = Double.parseDouble(split[2]);
        return new Request(operator,num1,num2);
    }
}
package OnlineCalculator;

public class Response {
    private double num;

    public Response(double num) {
        this.num = num;
    }

    public String responseToString() {
         return String.format("%f\n",num);
    }
    public static Response responseFromString(String response) {
        return  new Response(Double.parseDouble(response));

    }
    public double getNum() {
        return num;
    }
}

在这个在线计算器程序中,Request和Response就属于是自定义协议.通过自己定义的规则来进行客户端和服务端通信

端口号

端口号是由两个字节表示的无符号整数,因此端口号的范围在0~65535

而端口号的作用就是用来区分不同的应用程序

就拿前面在线计算机为例.9090端口就代表着我们编写的这个计算机程序

在每一个服务创建cocket时,都必须指定对应的端口号.一个进程可以创建多个socket,所以一个程序可以具有多个端口号.但一个socket只能绑定一个端口号

一个端口并不是只能被一个进程绑定.当使用的协议不同时,也是可以正常绑定重复的端口的.比如前文写过的TCP回显服务器和UDP回显服务器,因为基于的协议不同,因此都能绑定同一个9090端口

通常情况下:服务器都是由程序员自定义的端口,客户端则是系统自动分配端口

  • "知名"端口号
    对于0~1023端口.这些都被"知名"服务器给默认使用了.所以我们在自定义端口时应尽量避免知名端口
    为什么"知名"端口号的知名打了引号?因为现在所说的知名端口号是20~30年前计算机刚发展时的知名服务器使用的.而现在大部分"知名"服务器都已经消失了

现在我们还是能接触到很多知名端口的.例如

ftp服务器:使用21端口

ssh服务器:使用22端口

telnet服务器:使用23端口

http服务器:使用80端口

https服务器:使用443端口

UDP原理

首先我们先简单回顾一下UDP的特点

  • UDP特点
    1. 无连接
      直接通过IP和端口号进行传输数据,无需使用accpet()来进行连接
    2. 不可靠传输
      UDP协议发完数据报后不会再做任何处理,无法得知数据是否到达且完整
    3. 面向数据报
      UDP报文既不会拆分,也不会合并
    4. 全双工
      可以同时进行收发数据
    5. 大小受限
      UDP能传输的最大长度是64KB

知道这些后,我们就能接着学习下UDP的协议格式了

  • 协议格式

  • UDP报头

    1. 源端口号
    2. 目的端口号
    3. UDP长度:UDP报头长度加上UDP载荷长度.由于16位最大仅能表示65535.因此一个UDP数据报最大只能那个传输约64KB(实际上总是小于64KB)
    4. UDP效验和:用来数学方法检测在网络传输中数据有无损坏.在网络传输中容易受到电磁干扰,内存错误等问题.所以在处理报文的过程中就把UDP数据报中除了效验和的内容都进行16位反码加法来获得唯一效验值.收到数据报后会优先计算效验值是否相同,如果不同则数据损坏
  • UDP载荷

    1. 应用层数据:最大传输数据长度仅约64KB.当传输大于64KB的数据时,要么在应用层手动分包拼装,要么就换成TCP协议
  • UDP协议的主要应用场景

    对效率要求高,可以接收少量丢包的场景

    如:DNS查询,在线语音等等

相关推荐
hbugs0012 小时前
EVE-NG桥接外网的5种方式
开发语言·网络·php·eve-ng·rstp·流量洞察
QuestLab2 小时前
③-进阶篇:vLLM实战——多卡部署、压测与排障
linux·服务器·网络
颖火虫盟主2 小时前
Claude Code Hook 系统详解与 Hello World 实操
前端·网络·数据库
汤愈韬2 小时前
TK_HCIP-Security_FW的可靠性_双机热备场景_上接路由器下接交换机
网络·网络协议·网络安全
优化Henry2 小时前
5G基站设备替换过程中因参数配置与硬件不匹配产生的告警排查案例
运维·网络·5g·信息与通信
皮卡蛋炒饭.2 小时前
网络层相关学习
网络
WindLOR3 小时前
车联网TBOX开发实战三,APN路由转发
网络·tbox
satadriver3 小时前
ip-guard网络通信加解密
linux·网络·tcp/ip
cd_949217214 小时前
星思半导体:深耕芯片研发,助力卫星互联网产业高质量发展
网络·人工智能