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

文章目录

  • 前言
  • 一、认识 Socket(套接字), TCP 协议和 UDP 协议
    • 1, 什么是 Socket(套接字)
    • 2, 浅谈 TCP 协议和 UDP 协议的区别和特点
  • 二、基于 UDP 协议的 Socket API
    • 1, DatagramSocket 类
    • 2, DatagramPacket 类
  • 三、逐行代码解析网络编程
    • 1, 逐行解析客户端
      • 1.1, 核心成员方法 start()
    • 2, 逐行解析服务器
      • 2.1, 核心成员方法 start()
  • 四、完整代码
    • 1,客户端
    • 2, 服务器
  • 总结


前言

各位读者好, 我是小陈, 这是我的个人主页
小陈还在持续努力学习编程, 努力通过博客输出所学知识
如果本篇对你有帮助, 烦请点赞关注支持一波, 感激不尽
希望我的专栏能够帮助到你:
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) {
        
        }
    }

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

⚠️⚠️⚠️注意 :
上述代码中, 要分清 : 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) {
        
        }
    }

图解如下 :
Java【网络编程1】使用 UDP 的 Socket API 实现客户端服务器通信(保姆级教学, 附代码)_第2张图片
写完客户端之后再写服务器就相对熟悉了, 都是一个套路 : 在发送或者接收数据报之前, 要先 new 出 DatagramPacket 对象, 只是需要注意构造方法的使用

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

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


客户端运行效果截图 : 注意一定要先运行服务器
Java【网络编程1】使用 UDP 的 Socket API 实现客户端服务器通信(保姆级教学, 附代码)_第3张图片
客户端输出的是请求(字符串) + 响应(字符串)

服务器运行效果截图
Java【网络编程1】使用 UDP 的 Socket API 实现客户端服务器通信(保姆级教学, 附代码)_第4张图片
服务器输出的是本机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 协议会在后续的文章详细介绍

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


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

你可能感兴趣的:(JavaEE初阶,java,udp,网络通信,socket)