Java【网络编程1】详解DatagramSocket和DatagramPacket类, 逐行代码解析如何服务器客户端通信(附代码)

news2024/11/18 9:44:08

文章目录

  • 前言
  • 一、认识 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) {
        
        }
    }

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

⚠️⚠️⚠️注意 :
上述代码中, 要分清 : 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 协议会在后续的文章详细介绍

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


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/546114.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】-类和对象完结(内部类、匿名对象以及编译器的优化的讲解)(下)

💖作者:小树苗渴望变成参天大树 ❤️‍🩹作者宣言:认真写好每一篇博客 💨作者gitee:gitee 💞作者专栏:C语言,数据结构初阶,Linux,C 如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 …

m1安装svn

背景:电脑是mac m2,好多软件都不太兼容,安装软件成了一个问题,想着装一个SVN,跟大家一起协同开发,这下可麻烦死了,😄,终于弄明白用brew命令了,然后就用brew命…

Metasploitable2靶机渗透学习

目录 一、介绍 二、环境 三、渗透攻击 1.前期渗透 1.1主机发现 1.2.端口扫描 1.3.测试漏洞 2.弱密码漏洞 2.1系统弱密码登录(telnet :23端口) 2.2 MySQL弱密码登录(端口:3306) 2.3 PostgreSQL弱…

K8s全套快速入门

K8s快速入门 1 介绍 google开源的容器化管理工具机器数量十几台、上百台时,就可以考虑使用k8s高可用、自动容灾恢复、灰度更新、一键回滚历史版本、方便伸缩扩展等 k8s集群架构: 通常:一主多从 master:主节点,控制平台…

LeetCode 栈和队列OJ题目分享

目录 有效的括号(括号匹配)用栈实现队列用队列实现栈设计循环队列 有效的括号(括号匹配) 链接: link 题目描述: 题目思路: 1、如果是左括号“( { [ ”就入栈 2、如果是右括号“) }…

Redis--弱口令未授权访问漏洞

Redis--弱口令未授权访问漏洞 一、漏洞简介二、危险等级三、漏洞影响四、入侵事件五、漏洞复现--Redis CrackIT入侵事件5.1、以root启动的redis,可以远程登入到redis console--------A主机5.2、生成公钥5.3、执行: redis-cli flushall 清空redis(非常暴力&#xff0…

2023年春秋杯网络安全联赛 春季赛 wp

文章目录 Cryptocheckinbackdoor WebPhpstudyEasypyezrustqqcms MISCSudohappy2forensic盲人会藏在哪里piphackwordle PWNp2048easy_LzhiFTP_CHELL Crypto checkin 第一部分求解一下pell函数得到x,y def solve_pell(N, numTry 100):a[]b[]cf continued_fraction(sqrt(N))f…

C++的priority_queue

priority_queue 1.priority_queue的介绍2.priority_queue的使用3.priority的模拟实现 1.priority_queue的介绍 优先队列是一种堆,默认是大根堆,可以通过greater的仿函数可以建立小根堆empty():检测容器是否为空 size():返回容器中…

【密码学复习】第七章 公钥加密体制(二)

RSA单向陷门函数及其应用 ElGamal单向陷门函数 1)密钥生成 ① 选择一大素数p,选取Zp * 的生成元g ; ② 任选小于p的随机数x,计算y≡g x mod p; ③(y, g, p)为公开密钥, (x, g, p)为秘密密钥. 2)加密:设待加密…

asp.net就业满意度问调查系统

本系统主要有会员(调查者)和管理员,他们具体的功能如下: 会员功能:注册,登录,修改个人信息,调查,查看调查结果及影响,留言,首先是会员注册,注册后…

【Web开发】Node实现Web图表功能(ECharts.js,React)

🎈🎈🎈Python实现Web图表功能系列:🎈🎈🎈1🎈【Web开发】Python实现Web图表功能(D-Tale入门)🎈2🎈【Web开发】Python实现Web图表功能&a…

整理了一份github上比较热门的ChatGPT项目,值得收藏

ChatGPT已经火了一段时间了,但是,热度依旧是各大自媒体的热榜。由于,国内不能直接访问ChatGPT,国内的开发者依托OpenAI的接口,开发出一些ChatGPT的应用。今天就整理一下github上最热门的ChatGPT项目。 lencx/ChatGPT 该项目是Cha…

java线程的状态

文章目录 1. 线程的状态2. 验证NEW、RUNNALE和TERMINATED状态3. 验证TIMED_WAITING状态4. 验证BLOCKED状态5. 验证BLOCKED状态 1. 线程的状态 线程在不同的运行时期存在不同的状态,状态信息存在于State枚举类中,如下图: 调用线程有关的方法是…

文心一言 VS 讯飞星火 VS chatgpt (18)-- 算法导论4.1 5题

五、使用如下思想为最大子数组问题设计一个非递归的、线性时间的算法。从数组的左边界开始,由左至右处理,记录到目前为止已经处理过的最大子数组。若已知 A[1…j]门的最大子数组,基于如下性质将解扩展为 A[1…j1]的最大子数组:A[1…j1]的最大…

Squid 代理服务器

Squid概述 Squid 主要提供缓存加速、应用层过滤控制的功能。 代理的工作机制 1.代替客户机向网站请求数据,从而可以隐藏用户的真实IP地址。 2.将获得的网页数据(静态 Web 元素)保存到缓存中并发送给客户机&#xff0…

Windows安装Ubuntu双系统

Windows安装Ubuntu双系统 1.下载Ubuntu 16.04,地址https://releases.ubuntu.com/16.04/ 2.下载Rufus,地址https://rufus.ie/zh/ 3.准备U盘,烧录系统 4.磁盘分区 5.重启,按住shift键 本人电脑是联想小新 Windows11系统&#xff0…

QT上位机串口+STM32单片机项目

第一个自己的上位机小项目,嘿嘿,还是有些成绩感的。 目录 1.先看QT上位机部分 1.首先写一个页面 2.mainwindow.cpp主要函数。 2.form.cpp主要函数 3.STM32部分 1.main函数 3.QT完整代码 1.shangwei.pro 2.form.h 3.mainwindow.h 4.form.cpp …

从零入门激光SLAM(十一)——如何求解SLAM问题

大家好呀,我是一个SLAM方向的在读博士,深知SLAM学习过程一路走来的坎坷,也十分感谢各位大佬的优质文章和源码。随着知识的越来越多,越来越细,我准备整理一个自己的激光SLAM学习笔记专栏,从0带大家快速上手激…

(转载)从0开始学matlab(第9天)—第一阶段总结

1.编程实例 下面的例子将向大家介绍如何用 MATLAB 解决问题。 例1 温度转换程序 问题: 设计一个 MATLAB 程序,读取一个华氏温度的输入,输出开尔文温度。 答案: 华氏温度和开尔文温度的转换关系式可在物理学课本中找到。其关系式…

HCIP-RIP双向重发布综合实验

拓扑结构: 要求: 1、两个协议间进行多点双向重发布 2、R7的环回没有宣告在OSPF协议中,而是在后期重发布进入的 3、解决环路,所有路径选择最优,且存在备份 4、R2的环回要在RIP中宣告,R3的环回要在OSPF中宣…