网络编程 - UDP协议

一,UDP基本概念

UDP协议在传输层,有几个重要的特性:

  1. UDP是无连接的:UDP想要进行通信,不需要征得对方的同意,只要在send方法中指定目标的地址(UDP自身是不会存储对端的信息的)
  2. UDP是不可靠传输:UDP没有安全机制,它只负责发送,至于接收端有没有收到,没有收到后怎么处理,它都不关心。(UDP的传输效率更高)
  3. UDP是面向数据报的:这里的数据报是数据传输的一个单位。
  4. UDP是全双工的:UDP可以实现双向通信

 二,UDP协议端格式

这两个图一个意思。为什么没有源IP和目的IP,因为IP协议在网络层不在传输层。

源端口号和目的端口号讲过了,下面我们重点聊一聊什么是UDP长度和UDP校验和

2.1 UDP长度

UDP长度是两个字节,16位表示的数据,表示范围 0 ~ 65535 => 64kb,即UDP数据报最大就是64kb.

2.2 UDP校验和

在网络传输过程中,可能会出现一些外部干扰,就会导致数据传输出错(传输的数据内容出错)。这时就需要有方法来识别出错数据,而UDP校验和就是这样的一种检查方法。

校验和本质是一个字符串,是通过原始数据通过专门的算法生成的,但是体积比原始数据更小,如果原始数据相同,校验和一定相同,反之,校验和相同,原始数据大概率相同(理论上存在不同的可能性,但是概率极低,可忽略不计)

校验的过程:

1) 发送方把发送的数据整理好称为data1,通过专门的算法,计算出校验和sum1

2) 发送方把data1和sum1一起通过网络发送给接收方

3) 接收方把接受到的数据称为data2(由于干扰,可能数据出错),收到sum2(数据也可能出错)

4) 接收方根据data2按照相同的算法,计算出校验和sum3

5) 对比 sum2 和 sum3 是否相同,如果不同,则认为 data1 和 data2 一定不同。如果相同,则认为data1 和 data2 大概率相同(理论上存在不同的可能性,但是概率极低,工程上忽略不计)

UDP协议中使用CRC算法(循环冗余算法)来计算校验和:把当前计算校验和的数据,每个字节都进行累加,把结果保存到UDP校验和中,如果数据溢出也没关系,如果中间某个数据传输错误,第二次计算的校验和与第一次不同。但是可能出现前一个字节恰好少1,后一个字节恰好多1这种类似的情况,虽然概率不大,但是还是会出现这种情况。

介绍一种更加保险更加常见的算法:md5

1)定长:md5的长度是固定的,无论你的数据有多长,计算出的md5都是固定的

2)分散:给定两个原始数据,哪怕绝大部分内容相同,但只要有一个字节不同,得到的md5值的差异也会很大

3)不可逆:给你一个md5值,要你还原出原始数据,由于计算量过于庞大,已经突破现有的算力极限,理论上是不可能的

三,UDP中的Socket api

Socket API 是系统提供的一组用于网络编程的应用程序接口,socket 本质上是一种特殊的文件,它是把网卡这个设备,给抽象成了文件,往socket文件中写数据,就相当于是通过网卡发送数据,从socket文件中读数据,就相当于通过网卡接收数据。

在 JAVA 中使用 DatagramSocket 这个类来表示系统内部 socket 文件。使用 DatagramPacket 这个类来表示UDP数据报。

3.1 DatagramSocket

方法名说明
DatagramSocket()创建一个Socket,绑定到本机任意一个没人使用的端口
DatagramSocket(int port)创建一个Socket,绑定port端口
void receive(DatagramPacket p)接收数据报p
void send(DatagramPacket p)发送数据报p
void close()关闭Socket

3.2 DatagramPacket

方法名说明
DatagramPacket(byte[] buf, int length)构造一个DatagramPacket以用来接收数据报,接收的数据保存在 字节数组(第一个参数buf)中,接收指定长度(第二个参数 length)
DatagramPacket(byte[] buf, int offset, int length,SocketAddress address)构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数 length)。address指定目的主机的IP和端口号
getAddress()从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
int getPort()从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
byte[] getData()获取数据报中的数据

四,使用UDP实现客户端和服务器

4.1 回显服务器

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

//回显服务器UDP
public class Server {
    private DatagramSocket socket = null;
    public Server(int port) throws SocketException {
        socket = new DatagramSocket(port);//端口号
    }
    public void start() throws IOException {
        System.out.println("服务器启动");
        while(true){
            //1.读请求
            DatagramPacket request = new DatagramPacket(new byte[1024],1024);
            socket.receive(request);//如果没有请求,就阻塞
            String r = new String(request.getData(),0,request.getLength());//得到真实长度
            //2.根据请求计算响应
            String response = process(r);
            //3.返回响应
            DatagramPacket res = new DatagramPacket(response.getBytes(),
                    response.getBytes().length,request.getSocketAddress());
            // response.length()得到字符长度,response.getBytes().length得到字节长度
            // 网络传输以字节为单位进行操作
            socket.send(res);
            //4.打印日志
            System.out.printf("[%s:%d] req=%s,res=%s\n",
                    request.getAddress().toString(),request.getPort(),r,response);
            //为什么不close()?
            //socket是文件描述符表中的一个表象,因为我们不使用socket时,进程就要关闭,资源就全部释放了
        }
    }
    public String process(String request){
        return request;
    }
    public static void main(String[] args) throws IOException {
        Server server = new Server(9090);
        server.start();
    }
}

4.2 回显客户端

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class Client {
    private DatagramSocket socket = null;
    private String serverIP = "";
    private int serverPort = 0;
    public Client(String ip, int port) throws SocketException {
        socket = new DatagramSocket();
        //UDP不会存储对端的信息,需要在应用程序中把对端的信息记录下来
        this.serverIP = ip;
        this.serverPort = port;
    }

    public void start() throws IOException {
        System.out.println("客户端请启动");
        Scanner scanner = new Scanner(System.in);
        while(true){
            //1.输入请求
            System.out.print("-> ");
            String req = scanner.next();
            //2.发给服务器
            DatagramPacket request = new DatagramPacket(req.getBytes(),req.getBytes().length,
                    InetAddress.getByName(serverIP),serverPort);
            socket.send(request);
            //3.获取响应
            DatagramPacket res = new DatagramPacket(new byte[1024],1024);
            socket.receive(res);
            //4.显示响应
            String response = new String(res.getData(),0,res.getLength());
            System.out.println(response);
        }
    }
    public static void main(String[] args) throws IOException {
        Client client = new Client("10.162.153.3",9090);
        client.start();
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一叶祇秋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值