Java【网络编程1】使用 UDP 的 Socket API 实现客户端服务器通信(保姆级教学, 附代码)


前言

📕各位读者好, 我是小陈, 这是我的个人主页
📗小陈还在持续努力学习编程, 努力通过博客输出所学知识
📘如果本篇对你有帮助, 烦请点赞关注支持一波, 感激不尽
📙 希望我的专栏能够帮助到你:
JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等
Java数据结构: 顺序表, 链表, 堆, 二叉树, 二叉搜索树, 哈希表等
JavaEE初阶: 多线程, 网络编程, TCP/IP协议, HTTP协议, Tomcat, Servlet, Linux, JVM等(正在持续更新)

上篇文章介绍了网络原理中 TCP/IP 五层网络模型, 以及数据在网络上使如何传输的基本知识

本篇将介绍网络编程中 : 基于 UDP 协议的 Socket 套接字的相关知识


提示:是正在努力进步的小菜鸟一只,如有大佬发现文章欠佳之处欢迎批评指点~ 废话不多说,直接上干货!

一、认识 Socket(套接字), TCP 协议和 UDP 协议

上篇提到, 我们程序员进行网络编程主要是在 TCP/UDP 五层网络模型中的应用层, 而数据在网络上传输, 需要进行封装和分用, 其中应用层需要调用传输层提供的 API , 这一组 API 就被称作 Socket API

1, 什么是 Socket(套接字)

📌概念 : Socket 套接字是由系统提供于网络通信的技术, 是基于 TCP/IP 协议的网络通信的基本操作党员, 基于 Socket 套接字的网络程序开发就是网络编程

👉要进行网络通信, 需要有一个 socket 对象, 一个 socket 对象对应着一个 socket 文件, 这个文件在 网卡上而不是硬盘上, 所以有了 sokcet 对象才能通过操作内存来操作网卡
在 socket 文件中写数据相当于通过网卡发送数据, 在 socket 文件中读数据相当于通过网卡接收数据

Socket API 分为两类 : 基于 TCP 协议的 API , 和基于 UDP 协议的 API, 下面先认识一下 TCP 协议和 UDP 协议的区别和特点


2, 浅谈 TCP 协议和 UDP 协议的区别和特点

TCP 协议说明UDP 协议说明
有连接通信双方需要刻意保存对方的相关信息无链接通信双方不需要刻意保存对方的信息
可靠传输如果数据发送不成功, 发送方会知道不可靠传输发送方不关心数据是否发送成功
面向字节流发送的数据以字节为单位面向数据报发送的数据以 UDP 数据报为单位
全双工双向通信全双工双向通信

这里只做简单介绍, 这两个协议后续会单独详细介绍


二、基于 UDP 协议的 Socket API

UDP 的 Sokcet API 相对更简单易学, 本文先介绍这部分内容, TCP 的 Sokcet API 下篇文章介绍

❗️❗️❗️基于 UDP 协议的 Socket API 中, 要分清楚以下两个类 :

类名解释
DatagramSocket这个类表示一个 Socket, 用于发送和接收 UDP 数据报
DatagramPacket这个类表示一个 UDP 数据报

这个两个类的关系就相当于 : DatagramSocket 是取餐和送餐的外卖小哥, 而 DatagramSocket 就是外卖 (Datagram 就是数据报的意思, packet 是小包裹的意思)

记清楚啊 ! 别搞混了 ! ! 下面分别介绍这两个类的构造方法和成员方法, 待会写代码要用


1, DatagramSocket 类

👉 DatagramSocket 类的构造方法 :

方法签名作用
DatagramSocket()创建一个 UDP Socket 对象, 一般用于客户端, 不需要指定本机端口号, 由操作系统随机分配
DatagramSocket (int port)创建一个 UDP Socket 对象, 一般用于服务器, 需要指定本机端口号(port)

👉DatagramSocket 类的成员方法 :

方法签名作用
void receive(DatagramPacket p)接收数据报, 如果没接收到, 阻塞等待
void send(DatagramPacket p)发送数据报, 不会阻塞等待
void close()关闭套接字

表示 Socket 的这个类小总结 :
有两个构造方法, 一个是给服务器提供的, 一个是给客户端提供的
有三个成员方法, 分别是接收数据报, 发送数据报, 关闭套接字


2, DatagramPacket 类

👉 DatagramPacket 类的构造方法 :

方法签名作用
DatagramPacket(byte buf[], int length)创建一个数据报对象, 用于接收数据报, 接收到的数据存在 buf (第一个参数: 字节数组) 中, 需要指定 length (第二个参数: 长度),
DatagramPacket(byte buf[], int length, InetAddress address, int port创建一个数据报对象, 用于发送数据报, 发送的数据存在 buf (第一个参数: 字节数组) 中, 需要指定 length (第二个参数: 长度), address (第三个参数: 主机 IP 地址), port (第四个参数: 主机端口号)

👉 DatagramPacket 类的成员方法 :

方法签名作用
InetAddress getAddress()从接收或发送的数据报中获取对端的 IP 地址
int getPort()从接收或发送的数据报中获取对端的端口号
byte[] getData()获取数据报中的数据, 数据存在字节数组中
int getLength()获取数据报中的数据的实际长度

表示 Packet 的这个类的小总结 :
有两个构造方法, 一个用于接收数据包, 一个用于发送数据报
有四个成员方法, 分别是获取 IP 地址, 获取端口号, 获取数据, 获取数据长度

看到这可能有点蒙了, 这都是啥啊, 太多了太乱了
别急, 有个大概的印象即可, 接下来逐行解析如何从 0 到 1 地进行客户端和服务器之间地网络编程, 代码敲完之后再消化吸收


三、逐行代码解析网络编程

下面我们写一个最简单的客户端服务器网络通信模型 : 客户端给服务器发送什么请求, 服务器就给客户发送什么响应(这是最简单但是毫无意义的回显服务器, 只是方便熟悉 UDP Socket 的 API 使用)

客户端和服务器各自为一个进程在运行, 双方互不干涉(当然我们现在要写的客户端服务器程序是在同一台主机上的)

一定是服务器先启动, 一直等待客户端发来请求, 所以按照时间顺序, 代码逻辑应该如下所示 :

客户端服务器
/1,启动服务器, 阻塞等待, 时刻准备接收客户端发来的请求
2, 发送请求/
/3,接收请求
/4,处理请求
/5,返回响应给客户端
6, 接收响应/

有了这个思路, 下面正式开始使用上述 API 进行网络编程


1, 逐行解析客户端

创建一个类 UDPEchoClient 作为客户端

👉成员属性 :
1️⃣首先定义一个 Scoket 对象来接收和发送数据报
2️⃣客户端发送的请求数据报, 需要指定服务器的 IP 地址和端口号, 所以需要定义 serverIP 和 serverPort

public class UDPEchoClient {  
    private DatagramSocket socket = null;// 1,socket对象
    private String serverIP = null;// 2,服务器IP地址
    private int serverPort = 0;// 3,服务器端口号
}

👉构造方法 :
在构造方法中对上述三个成员属性进行初始化

public class UDPEchoClient {  
	// 成员属性
    private DatagramSocket socket = null;// 1,socket对象
    private String serverIP = null;// 2,服务器IP地址
    private int serverPort = 0;// 3,服务器端口号
    
    // 构造方法
    public UDPEchoClient(String serverIP, int serverPort) throws SocketException {
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }
}

👉main 方法 :
1️⃣构造 udpEchoClient 对象, 由于服务器在本机, IP 地址为"127.0.0.1", 端口号随意指定 [1024, 65535] 之间的任意一个
2️⃣调用 UDPEchoClient 类的核心成员方法 start(), 这个方法实现了客户端的核心逻辑

public class UDPEchoClient {  
	// 成员属性
    private DatagramSocket socket = null;// 1,socket对象
    private String serverIP = null;// 2,服务器IP地址
    private int serverPort = 0;// 3,服务器端口号
    
    // 构造方法
    public UDPEchoClient(String serverIP, int serverPort) throws SocketException {
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }

	// main 方法
    public static void main(String[] args) throws IOException {
        UDPEchoClient udpEchoClient = new UDPEchoClient("127.0.0.1", 9999);
        udpEchoClient.start();
    }
}

1.1, 核心成员方法 start()

1️⃣构造一个 Scanner 对象, 从控制台输入字符串, 这个字符串当作请求的内容
2️⃣核心逻辑在一个 while(true) 循环中, 实现多次发送请求

    public void start() throws IOException {
    	Scanner in = new Scanner(System.in);
        while(true) {
        
        }
    }

图解如下
在这里插入图片描述

⚠️⚠️⚠️注意 :
上述代码中, 要分清 : socket.send(DatagramPacket p)用于发送数据报, socket.receive(DatagramPacket p)用于接收数据报,
而参数都是 DatagramPacket 类型, 所以在发送和接收之前, 需要 new 出来数据报对象呀, 但是用于发送和接受的数据报的构造方法是不同的, 请结合上图中黄色部分再做区分

方法签名作用
DatagramPacket(byte buf[], int length)创建一个数据报对象, 用于接收数据报, 接收到的数据存在 buf (第一个参数: 字节数组) 中, 需要指定 length (第二个参数: 长度),
DatagramPacket(byte buf[], int length, InetAddress address, int port创建一个数据报对象, 用于发送数据报, 发送的数据存在 buf (第一个参数: 字节数组) 中, 需要指定 length (第二个参数: 长度), address (第三个参数: 主机 IP 地址), port (第四个参数: 主机端口号)

另外, start()中第三步相当于是把数据报中的数据(btye[])解析成字符串, 方便我们观察


2, 逐行解析服务器

创建一个类 UDPEchoServer 作为服务器

👉成员属性 :
服务器不需要指定客户端的 IP 地址和端口号, 所以构造方法只需要定义一个 socket 对象即可

public class UDPEchoServer {
    // 成员属性
    private  DatagramSocket socket = null;// socket 对象
}

👉构造方法 :
用于实例化服务器的 socket 对象, 别忘了服务器使用的 socket 需要绑定本机的端口号

public class UDPEchoServer {
    // 成员属性
    private  DatagramSocket socket = null;// socket 对象

    // 构造方法
    public UDPEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }
}

👉main 方法 :
1️⃣用于实例化 UDPEchoServer 对象, 端口号为刚才客户端指定的端口号
2️⃣调用 UDPEchoServer 类的核心成员方法 start(), 这个方法实现了服务器的核心逻辑

public class UDPEchoServer {
    // 成员属性
    private  DatagramSocket socket = null;// socket 对象

    // 构造方法
    public UDPEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }
    
    // main方法
    public static void main(String[] args) throws IOException {
        UDPEchoServer server = new UDPEchoServer(9999);
        server.start();
    }
}

2.1, 核心成员方法 start()

服务器启动之后, 等待客户端发来消息, 实际上一台服务器会服务多个客户端, 所以核心代码写在 while(true) 循环中, 这样才能够处理完一个客户端发来的请求之后继续处理

    public void start() throws IOException {
        while(true) {
        
        }
    }

图解如下 :
在这里插入图片描述
写完客户端之后再写服务器就相对熟悉了, 都是一个套路 : 在发送或者接收数据报之前, 要先 new 出 DatagramPacket 对象, 只是需要注意构造方法的使用

需要补充的是, 在第一步中, 发送完数据报之后, 把数据报解析成了字符串方便处理(并不一定所有的服务器程序都需要这么做, 在当前这个程序下处理成字符串更方便而已)

而 process 方法用于处理请求, 真实的服务器的这一步会根据业务逻辑把代码写的非常完善, 而我们实现的仅仅是最简单的回显服务器, 所以我们的 process 方法只需要 return 即可


👇客户端运行效果截图 : 注意一定要先运行服务器
在这里插入图片描述
客户端输出的是请求(字符串) + 响应(字符串)

👇服务器运行效果截图
在这里插入图片描述
服务器输出的是本机IP地址, 客户端端口号(系统自动分配的), 请求(String), 响应(String)


四、完整代码

1,客户端

import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UDPEchoClient {
    // 成员属性: 1,socket对象 2,服务器IP地址 3,服务器端口号
    private DatagramSocket socket = null;
    private String serverIP = null;
    private int serverPort = 0;

    // 构造方法
    public UDPEchoClient(String serverIP, int serverPort) throws SocketException {
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }

    public void start() throws IOException {
        Scanner in = new Scanner(System.in);
        while(true) {
            // 1,从控制台输入数据, 构造成 DatagramPacket , 把这个请求发给服务器
            String requestString = in.next();
            DatagramPacket requestPacket = new DatagramPacket(requestString.getBytes()
                                                , requestString.getBytes().length, InetAddress.getByName(serverIP), serverPort);
            socket.send(requestPacket);

            // 2,接收服务器返回的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[9999], 9999);
            socket.receive(responsePacket);

            // 3,转换成字符串
            String responseString = new String(responsePacket.getData(), 0, responsePacket.getLength());

            // 打印客户端请求 + 服务器响应
            System.out.println(requestString + " + " + responseString);
        }
    }

    public static void main(String[] args) throws IOException {
        UDPEchoClient udpEchoClient = new UDPEchoClient("127.0.0.1", 9999);
        udpEchoClient.start();
    }
}

2, 服务器

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;

public class UDPEchoServer {
    // 成员属性: 定义一个Socket对象
    private  DatagramSocket socket = null;

    // 构造方法
    public UDPEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        while(true) {
            // 1,从客户端读取请求数据
            DatagramPacket requestPacket = new DatagramPacket(new byte[9999], 9999);
            socket.receive(requestPacket);
            String requestString = new String(requestPacket.getData(), 0, requestPacket.getLength());

            // 2,处理请求
            String responseString = process(requestString);

            // 3,把响应发送给客户端
            DatagramPacket responsePacket = new DatagramPacket(responseString.getBytes()
                                                , responseString.getBytes().length, requestPacket.getAddress(), requestPacket.getPort());
            socket.send(responsePacket);

            // 打印客户端IP地址 + 客户端端口号 + 客户端请求服务器相应
            System.out.println(requestPacket.getAddress().toString() + " + " + requestPacket.getPort() + " + "
                    + requestString + " + " + responseString);
        }
    }

    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        UDPEchoServer server = new UDPEchoServer(9999);
        server.start();
    }
}

总结

以上就是本篇的全部内容, 主要介绍了 : 基于 UDP 协议的 Socket API , 以及利用这些 API 写了一个最简单但无意义的客户端服务器网络通信程序

有一点需要说明 : 这个程序中没有调用 sockt.close() 是因为这客户端服务器的生命周期很长, 并没有频繁的销毁和创建,
如果我们的客户端和服务器停止运行了, 那么我们的main方法也结束了, 整个 Java 进程就结束了, 所以没必要显式的调用 close
方法

再回顾一下, 基于 UDP 协议的 Socket API 主要有两个类:

👉表示 Socket 的这个类, DatagramSocket :
有两个构造方法, 一个是给服务器提供的, 一个是给客户端提供的
有三个成员方法, 分别是接收数据报, 发送数据报, 关闭套接字

👉表示 Packet 的这个类, DatagramPacket :
有两个构造方法, 一个用于接收数据包, 一个用于发送数据报
有四个成员方法, 分别是获取 IP 地址, 获取端口号, 获取数据, 获取数据长度

下篇分享基于 TCP 协议的 Socket API, 关于 TCP 和 UDP 协议会在后续的文章详细介绍

如果本篇对你有帮助,请点赞收藏支持一下,小手一抖就是对作者莫大的鼓励啦😋😋😋~


上山总比下山辛苦
下篇文章见

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
UDP协议是一种无连接的传输协议,它不对数据传输进行可靠性保证,因此在进行数据传输时,可能会出现数据丢失、重复、乱序等问题。为了保证UDP数据传输的可靠性,需要使用一些技术手段进行处理,其中最常用的是使用UDP Socket实现可靠数据传输。 UDP Socket是在UDP协议基础上进行实现的一个套接字。UDP Socket可以通过设置一些参数和使用一些技术手段,来实现UDP数据传输的可靠性。下面就具体介绍UDP Socket实现可靠数据传输的原理。 1.数据分片 UDP Socket将数据分片传输,每个数据分片都包含一个序号,接收端可以通过序号来判断数据分片是否存在丢失、重复或者乱序等问题。如果数据分片存在以上问题,则可以进行相应的处理,从而保证数据传输的可靠性。 2.确认机制 UDP Socket采用确认机制来保证数据传输的可靠性。发送端在发送每个数据分片后,会等待接收端返回确认信息,确认信息包含接收到的数据的序号。如果接收端返回的确认信息与发送端发送的序号不一致,则说明数据分片存在丢失或者乱序问题,发送端需要重新发送该数据分片。 3.超时重传机制 UDP Socket采用超时重传机制来保证数据传输的可靠性。发送端在发送每个数据分片后,会设置一个超时时间,在超时时间内如果未收到接收端的确认信息,则发送端会重新发送该数据分片。通过这种方式,可以保证数据分片不会因为网络问题而丢失。 4.流量控制 UDP Socket采用流量控制来保证数据传输的可靠性。在数据传输的过程中,发送端需要根据接收端的处理能力来控制数据的发送速度,防止数据发送过快导致接收端无法及时处理。通过流量控制,可以避免数据丢失或者传输延迟过大的问题。 综上所述,UDP Socket实现可靠数据传输的原理主要包括数据分片、确认机制、超时重传机制和流量控制等方面。通过这些技术手段,可以保证UDP数据传输的可靠性,从而满足各种网络应用的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

灵魂相契的树

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

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

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

打赏作者

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

抵扣说明:

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

余额充值