滚雪球学Java(95):Java网络编程深入解析:TCP与UDP协议的原理与实现

  咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE啦,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~


🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!!

环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8

上期回顾

在上期中,我们深入探讨了JavaFX的图形用户界面(GUI)编程,从基础组件到动画、事件处理等多方面进行了详细的讲解与演示。通过创建实际应用,大家对JavaFX有了更全面的认识。随着对桌面应用的理解加深,我们今天将从图形界面转向网络编程,讨论Java中TCP和UDP协议的实现与应用。这些网络通信协议在现代分布式应用和实时通信中扮演着极其重要的角色。

前言

在计算机网络领域,TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)是两种常见的传输层协议。它们各自有不同的应用场景与特性。TCP是一种面向连接的、可靠的协议,适用于需要精确传输的应用;而UDP则是一种无连接的、轻量级的协议,适合实时性要求较高但对数据可靠性要求不高的场景。

本期内容将详细介绍TCP和UDP协议的特点、工作原理、在Java中的实现方式,并通过案例演示这两种协议的实际应用。通过阅读本期内容,大家将学会在Java中使用这两种协议进行网络编程,并深入理解它们的优缺点及实际使用场景。

摘要

本文首先对TCP和UDP协议进行简要介绍,接着通过分析它们的工作原理、源码与应用场景,帮助读者了解如何在Java中实现基于TCP和UDP的网络编程。我们将从实际案例出发,演示如何构建基于TCP的可靠通信系统,以及如何使用UDP实现轻量级的数据传输。此外,本文将通过对比分析两者的优缺点,为开发者在不同的场景下选择合适的协议提供参考。

正文

TCP和UDP协议简介

TCP协议

TCP(传输控制协议)是一种面向连接的协议,确保了数据包能够准确、可靠地从一端传送到另一端。在通信过程中,TCP使用三次握手(Three-Way Handshake)建立连接,保证数据传输的完整性和正确性。它内置了流量控制、拥塞控制、重传机制等功能,适合于需要高可靠性的数据传输场景,比如HTTP、FTP、电子邮件等。

UDP协议

与TCP不同,UDP(用户数据报协议)是一种无连接协议,不保证数据的可靠性、顺序性或重复性。它更轻量、更高效,但缺乏错误校验和纠正机制。因此,UDP适用于实时性要求较高的场景,如视频直播、在线游戏、语音通话等。

TCP与UDP的工作原理

TCP的三次握手与四次挥手

TCP通信的核心在于连接的建立和释放,它采用三次握手来确保通信双方已经就连接和参数达成一致。具体过程如下:

  • 第一次握手:客户端发送一个SYN请求到服务器,表示希望建立连接。
  • 第二次握手:服务器收到SYN后,返回一个SYN-ACK包,表示同意建立连接。
  • 第三次握手:客户端收到SYN-ACK后,再发送一个ACK包,连接正式建立。

数据传输完成后,TCP通过四次挥手(Four-Way Handshake)释放连接。

  • 第一次挥手:客户端发送FIN包,表示准备断开连接。
  • 第二次挥手:服务器收到FIN包后,返回一个ACK包。
  • 第三次挥手:服务器发送FIN包,表示连接可以断开。
  • 第四次挥手:客户端返回ACK包,连接正式关闭。
UDP的无连接通信

UDP是一种简单的传输协议,它没有建立连接的过程,发送方直接将数据报文发送给接收方,接收方根据情况处理这些数据。由于没有连接管理的开销,UDP具有很高的传输效率,但缺乏可靠性保障。

Java中的TCP与UDP编程

基于TCP的Java编程

在Java中,TCP编程依赖于SocketServerSocket类。这两个类分别用于客户端和服务端之间的通信。

  • Socket:表示客户端与服务端之间的连接。
  • ServerSocket:表示在服务器端监听客户端连接请求的对象。
服务器端代码示例
package com.demo.javase.day95;

import java.io.*;
import java.net.*;

/**
 * @Author bug菌
 * @Source 公众号:猿圈奇妙屋
 * @Date 2024-10-06 22:13
 */
public class TCPServer {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(8080)) {
            System.out.println("服务器已启动,等待客户端连接...");

            while (true) {
                Socket clientSocket = serverSocket.accept();
                System.out.println("客户端已连接:" + clientSocket.getInetAddress().getHostAddress());

                // 创建输入输出流
                BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);

                // 读取客户端消息
                String message = in.readLine();
                System.out.println("收到客户端消息:" + message);

                // 回复消息
                out.println("服务器收到:" + message);

                // 关闭连接
                clientSocket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

代码解析:

  在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。

  这段代码实现了一个简单的 TCP 服务器,它能够接受来自客户端的连接、接收消息并进行回应。该服务器运行在端口 8080,并不断监听客户端的连接。以下是对这段代码的详细解析:

  1. 创建 ServerSocket
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("服务器已启动,等待客户端连接...");
  • ServerSocket:用于在特定端口(这里是 8080)上监听客户端的连接请求。它是 TCP 服务器的核心类。
  • new ServerSocket(8080):创建一个 ServerSocket 实例并绑定到本地端口 8080
  • try-with-resources:使用 try-with-resources 确保在程序结束时自动关闭 serverSocket,释放端口。
  1. 接受客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("客户端已连接:" + clientSocket.getInetAddress().getHostAddress());
  • serverSocket.accept():阻塞方法,服务器会一直等待,直到有客户端请求连接。返回一个表示客户端连接的 Socket 对象。
  • clientSocket:代表与客户端通信的套接字,服务器可以通过该套接字与客户端进行数据交互。
  • clientSocket.getInetAddress().getHostAddress():获取连接客户端的 IP 地址,并打印出来以确认连接成功。
  1. 创建输入输出流
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
  • clientSocket.getInputStream():从客户端读取数据的输入流。
  • BufferedReader:将输入流包装成 BufferedReader,用于逐行读取客户端发送的消息。
  • clientSocket.getOutputStream():向客户端发送数据的输出流。
  • PrintWriter:将输出流包装为 PrintWriter,便于发送文本消息,true 表示自动刷新,确保消息立即发送到客户端。
  1. 接收客户端消息
String message = in.readLine();
System.out.println("收到客户端消息:" + message);
  • in.readLine():从客户端读取一行文本消息。因为 readLine() 是阻塞的,服务器会等待客户端发送消息。
  • 打印接收到的客户端消息到控制台,便于调试和查看交互过程。
  1. 回复客户端消息
out.println("服务器收到:" + message);
  • 服务器向客户端发送响应消息,内容为 "服务器收到:" + message,即确认收到了客户端发送的消息。
  • out.println():通过 PrintWriter 将响应发送给客户端。
  1. 关闭客户端连接
clientSocket.close();
  • 处理完客户端请求后,服务器主动关闭与该客户端的连接,释放资源。虽然服务器关闭了客户端连接,但服务器仍然继续监听其他新的连接。
  1. 异常处理
catch (IOException e) {
    e.printStackTrace();
}
  • 捕获并处理在创建 ServerSocket 或处理客户端请求过程中可能发生的 IOException 异常。如果发生异常,会打印堆栈跟踪,便于调试。

代码运行流程:

  1. 启动服务器:服务器启动后,会在 8080 端口上监听客户端连接。
  2. 等待客户端连接:每次客户端发起连接时,服务器通过 accept() 接受连接请求。
  3. 接收消息:服务器读取来自客户端的消息,并将其打印到控制台。
  4. 回复客户端:服务器回复 "服务器收到:" + message 作为响应。
  5. 关闭客户端连接:每次处理完客户端请求后,服务器关闭与该客户端的连接,但仍继续监听其他客户端的连接。

适用场景:

  • 单客户端通信:服务器处理一个客户端请求后会关闭连接。如果有多个客户端同时连接,则需要修改此设计。
  • TCP 通信入门:这段代码展示了如何使用 Java 的 SocketServerSocket 类进行简单的 TCP 通信,非常适合 TCP 编程的入门学习。

注意事项:

  1. 单线程服务器:该服务器在处理完一个客户端的请求后才能继续处理下一个客户端,因此在高并发情况下性能较差。如果需要同时处理多个客户端请求,可以使用多线程或线程池来处理并发连接。
  2. 阻塞操作accept()readLine() 都是阻塞方法,会一直等待客户端连接和消息,这可能会影响服务器的响应时间。
  3. 客户端实现:要测试该服务器,需要编写相应的 TCP 客户端,或者使用之前的 TCPClient 代码来与服务器进行交互。

运行示例:

启动该服务器后,你可以使用前面的 TCPClient 客户端代码进行测试,启动客户端后,服务器将显示客户端连接,并输出收到的消息:

服务器控制台输出:

服务器已启动,等待客户端连接...
客户端已连接:127.0.0.1
收到客户端消息:你好,服务器!

客户端控制台输出:

服务器回复:服务器收到:你好,服务器!

这样就实现了一个简单的 TCP 客户端-服务器通信模型。

客户端代码示例
import java.io.*;
import java.net.*;

public class TCPClient {
    public static void main(String[] args) {
        try (Socket socket = new Socket("localhost", 8080)) {
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            // 向服务器发送消息
            out.println("你好,服务器!");
            
            // 读取服务器的响应
            String response = in.readLine();
            System.out.println("服务器回复:" + response);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

代码解析:

  在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。

  这段代码实现了一个简单的 TCP 客户端,用于与服务器建立连接、发送消息并接收服务器的响应。它通过 Socket 来进行通信,下面对代码进行详细解析:

  1. 创建 Socket 连接
Socket socket = new Socket("localhost", 8080);
  • Socket:该类用于创建 TCP 连接。这里连接到本地主机 (localhost) 的端口 8080
  • 自动资源管理:使用 try-with-resources 语法,确保在程序执行完成后自动关闭 Socket,释放资源。
  • 如果服务器在指定的主机和端口没有启动或连接失败,会抛出 IOException
  1. 输出流:向服务器发送消息
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("你好,服务器!");
  • socket.getOutputStream():获取与服务器通信的输出流,用于发送数据。
  • PrintWriter:将输出流封装为 PrintWriter,以便于发送文本消息。true 表示自动刷新,每次发送数据时会自动调用 flush()
  • out.println("你好,服务器!"):发送消息 "你好,服务器!" 给服务器。
  1. 输入流:接收服务器响应
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String response = in.readLine();
  • socket.getInputStream():获取与服务器通信的输入流,用于接收数据。
  • BufferedReader:封装输入流,通过 BufferedReader 可以逐行读取服务器发送的响应。
  • in.readLine():读取服务器的响应,假设服务器发送的是一行文本。
  1. 打印服务器的响应
System.out.println("服务器回复:" + response);
  • 将从服务器接收到的响应消息打印到控制台。
  1. 异常处理
catch (IOException e) {
    e.printStackTrace();
}
  • 捕获 IOException,该异常可能在创建套接字连接或读写数据时发生。例如,如果服务器不可用或网络中断,客户端将抛出此异常,并输出堆栈跟踪,便于调试。

代码工作流程:

  1. 建立连接:客户端通过 Socket 尝试连接到 localhost:8080 的服务器。
  2. 发送消息:客户端向服务器发送文本消息 "你好,服务器!"
  3. 接收消息:客户端阻塞等待服务器的响应,并通过输入流读取响应数据。
  4. 打印响应:将接收到的响应打印到控制台。
  5. 关闭连接:程序结束时,Socket 自动关闭(由 try-with-resources 管理)。

示例运行:

假设在 localhost:8080 有一个 TCP 服务器正在运行,当你运行这段代码时,客户端会发送消息并接收服务器的回复,例如:

服务器回复:你好,客户端!

注意事项:

  1. 服务器准备:确保在 localhost:8080 端口有一个 TCP 服务器在监听,能够处理客户端的请求并返回响应。
  2. 阻塞操作in.readLine() 是一个阻塞操作,会等待服务器的响应,直到接收到一行数据为止。如果服务器没有响应,客户端会一直等待。
  3. 异常处理:当服务器不可达、端口无效、连接超时或网络故障时,IOException 将被抛出,需要适当处理以避免程序崩溃。

这段代码展示了如何通过 TCP 在 Java 中进行简单的客户端-服务器通信,发送消息并接收响应。

基于UDP的Java编程

UDP编程在Java中使用DatagramSocketDatagramPacket类来实现。

  • DatagramSocket:用于发送和接收数据报文的套接字。
  • DatagramPacket:表示要发送或接收的数据报文。
服务器端代码示例
import java.net.*;

public class UDPServer {
    public static void main(String[] args) {
        try (DatagramSocket socket = new DatagramSocket(9876)) {
            byte[] receiveBuffer = new byte[1024];

            while (true) {
                // 接收数据报文
                DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
                socket.receive(receivePacket);
                
                String message = new String(receivePacket.getData(), 0, receivePacket.getLength());
                System.out.println("收到消息:" + message);

                // 回复客户端
                String response = "服务器收到:" + message;
                byte[] sendBuffer = response.getBytes();
                DatagramPacket sendPacket = new DatagramPacket(
                        sendBuffer, sendBuffer.length, receivePacket.getAddress(), receivePacket.getPort());
                
                socket.send(sendPacket);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
客户端代码示例
package com.demo.javase.day95;

import java.net.*;

/**
 * @Author bug菌
 * @Source 公众号:猿圈奇妙屋
 * @Date 2024-10-06 18:28
 */
public class UDPClient {
    public static void main(String[] args) {
        try (DatagramSocket socket = new DatagramSocket()) {
            String message = "你好,服务器!";
            byte[] sendBuffer = message.getBytes();

            // 发送数据报文
            DatagramPacket sendPacket = new DatagramPacket(sendBuffer, sendBuffer.length, InetAddress.getByName("localhost"), 9876);
            socket.send(sendPacket);

            // 接收回复
            byte[] receiveBuffer = new byte[1024];
            DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
            socket.receive(receivePacket);

            String response = new String(receivePacket.getData(), 0, receivePacket.getLength());
            System.out.println("服务器回复:" + response);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
代码解析:

  在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。

  这段代码实现了一个简单的 UDP 客户端,用于向服务器发送消息并接收服务器的响应。UDP 是无连接的协议,不需要建立连接就可以发送和接收数据。

  1. DatagramSocket的创建
   DatagramSocket socket = new DatagramSocket();
  • DatagramSocket 类是 UDP 的核心类,用于发送和接收数据报文。这里创建了一个客户端的 DatagramSocket,并绑定到一个本地的随机端口。
  • 使用 try-with-resources 自动关闭资源,确保使用完 socket 后正确关闭。
  1. 发送消息
   String message = "你好,服务器!";
   byte[] sendBuffer = message.getBytes();
  • 定义了一条要发送的消息 "你好,服务器!",并将其转换为字节数组(UDP 发送的是字节数据,而不是字符串)。
   DatagramPacket sendPacket = new DatagramPacket(sendBuffer, sendBuffer.length, InetAddress.getByName("localhost"), 9876);
   socket.send(sendPacket);
  • 创建了一个 DatagramPacket 数据报对象,将消息、消息长度、目标 IP 地址(本地主机 "localhost")以及目标端口(9876)封装在数据报中。
  • 调用 socket.send() 将数据报发送给服务器。
  1. 接收服务器的回复
   byte[] receiveBuffer = new byte[1024];
   DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
   socket.receive(receivePacket);
  • 创建一个 byte[] 数组作为接收缓冲区,并用它来构造 DatagramPacket 对象,用于接收服务器的回复。
  • 调用 socket.receive(receivePacket) 阻塞并等待接收来自服务器的消息。一旦接收到数据,数据会填充到 receivePacket 中。
  1. 处理和打印回复
   String response = new String(receivePacket.getData(), 0, receivePacket.getLength());
   System.out.println("服务器回复:" + response);
  • 将接收到的数据从字节数组转换为字符串,并只截取有效部分(receivePacket.getLength()),防止读取到未填充的空字节。
  • 将从服务器收到的回复打印到控制台。
  1. 异常处理
   catch (Exception e) {
       e.printStackTrace();
   }
  • 捕获异常(例如,网络错误、IO 错误),并输出错误堆栈跟踪,以帮助调试和排查问题。

运行流程:

  1. 客户端通过 UDP 协议向 localhost 端口 9876 发送消息 "你好,服务器!".
  2. 服务器(假设有相应的 UDP 服务器程序在运行)接收消息,并发送响应。
  3. 客户端接收服务器的响应并将其打印出来。

UDP 特点:

  • UDP 是无连接协议,客户端和服务器之间不需要建立持久的连接,数据直接通过数据报文发送。
  • 由于没有连接管理,UDP 比 TCP 更轻量、速度更快,但也因此缺少可靠性保障(如重传、顺序保证)。

这段代码展示了如何使用 UDP 实现一个简单的客户端,与服务器进行无连接的数据传输。

知识点源码分析

通过以上的TCP和UDP案例代码,我们可以深入分析这两种协议在Java中的实现:

  • TCP的可靠性:通过三次握手和确认机制,确保数据传输的可靠性。Java中的Socket类封装了这些底层细节,开发者只需关注如何在连接建立后进行数据的读写操作。
  • UDP的效率:UDP的简单性体现在其无连接的设计中,DatagramSocket直接通过发送数据报文实现数据传输,开发者不需要关注复杂的连接管理,但也因此需要自行处理数据丢失或重传的逻辑。

相关内容拓展

TCP与UDP的应用场景
  • TCP的使用场景
    • HTTP/HTTPS协议:浏览器与服务器之间的通信依赖于TCP的可靠性。
    • FTP

协议:文件传输需要确保数据不丢失且按顺序到达。

  • 邮件协议(SMTP/POP3/IMAP):电子邮件的传输过程要求数据的完整性和准确性。

  • UDP的使用场景

    • 视频流媒体:对于视频直播、会议等场景,实时性远比数据的可靠性更重要。
    • 在线游戏:UDP能够更快地传输数据,避免连接建立的延迟问题。
    • DNS查询:DNS请求往往只需要发送一个简单的请求并接收一个短小的回复,因此不需要复杂的连接管理。

优缺点对比

TCP的优点
  • 可靠性高:通过确认机制、流量控制、拥塞控制,确保数据按顺序且完整地传输。
  • 适合大数据量传输:TCP适用于需要保证数据准确性的场景,如文件传输。
TCP的缺点
  • 开销较大:由于需要维护连接状态,且具备重传和确认机制,TCP的开销较UDP大。
  • 速度较慢:尤其在高延迟网络下,TCP可能因为重传和拥塞控制机制而导致传输速度下降。
UDP的优点
  • 速度快:由于不需要连接管理,UDP的数据传输延迟较小。
  • 适合实时应用:UDP非常适合需要快速响应的场景,如直播、游戏等。
UDP的缺点
  • 不可靠:UDP没有确认机制,无法保证数据包的顺序和完整性。
  • 没有流量控制:在网络不佳的情况下,可能会发生丢包或延迟。

测试用例

为了验证我们的TCP和UDP实现是否正确,以下是一些简单的测试用例。

TCP测试用例
测试代码
import org.junit.jupiter.api.Test;
import java.io.*;
import java.net.*;
import static org.junit.jupiter.api.Assertions.*;

public class TCPTest {
    @Test
    public void testTCPConnection() {
        try (Socket socket = new Socket("localhost", 8080)) {
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            
            out.println("测试TCP");
            String response = in.readLine();
            
            assertEquals("服务器收到:测试TCP", response);
        } catch (IOException e) {
            e.printStackTrace();
            fail("TCP连接测试失败");
        }
    }
}
测试代码解析

这段代码是一个基于 JUnit 5 的单元测试,用于测试一个 TCP 客户端与服务器之间的通信。它连接到本地的服务器 (localhost),并发送一条消息,验证服务器是否正确接收并返回预期的响应。下面是对代码的详细解析:

  1. 测试类声明
public class TCPTest {
  • 该类是一个使用 JUnit 5 的测试类。JUnit 5 是一个流行的 Java 单元测试框架,帮助开发者编写和执行自动化测试。
  1. testTCPConnection 测试方法
@Test
public void testTCPConnection() {
  • @Test 注解:标注该方法为一个测试方法。在执行测试时,JUnit 会自动识别并执行该方法。
  • 方法功能:这个测试方法将模拟一个 TCP 客户端,与运行在 localhost:8080 的服务器进行通信。
  1. 创建 TCP 连接
Socket socket = new Socket("localhost", 8080);
  • Socket:创建一个 TCP 连接,连接到本地的端口 8080。如果没有服务器在该端口上运行,这里会抛出 IOException
  • 资源管理:使用 try-with-resources 自动管理资源,确保在测试结束后关闭 Socket 连接。
  1. 发送数据到服务器
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("测试TCP");
  • PrintWriter:将客户端的输出流包装成 PrintWriter,用于向服务器发送消息。
  • socket.getOutputStream():获取客户端的输出流,将消息 测试TCP 发送给服务器。
  1. 接收服务器的响应
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String response = in.readLine();
  • BufferedReader:用于读取服务器返回的消息。它通过 socket.getInputStream() 获取服务器的输入流,并包装成 BufferedReader,便于逐行读取。
  • readLine():读取服务器的响应消息,假设服务器以行的方式返回数据。
  1. 断言服务器返回的数据
assertEquals("服务器收到:测试TCP", response);
  • assertEquals:断言方法,判断服务器返回的消息是否等于 "服务器收到:测试TCP"。如果消息相同,测试通过;否则,测试失败。
  1. 异常处理与测试失败
catch (IOException e) {
    e.printStackTrace();
    fail("TCP连接测试失败");
}
  • catch:捕获在连接和通信过程中可能发生的 IOException 异常。
  • fail:如果出现异常,则通过 fail() 方法标记测试失败,并输出一条消息 “TCP连接测试失败”。

总结:

  • 这段代码模拟了一个 TCP 客户端的通信流程,并验证与服务器之间的数据交互是否按预期进行。它首先连接到 localhost:8080,发送一条测试消息 “测试TCP”,并验证服务器返回的响应是否是 "服务器收到:测试TCP"
  • 如果服务器没有按预期响应,或连接过程中出现任何问题,测试将会失败,并显示相应的错误信息。

注意事项:

  1. 服务器端准备:要运行这个测试,确保在 localhost:8080 上有一个服务端在监听并能正确处理消息。
  2. 同步问题:如果服务端存在同步处理问题或需要时间处理请求,测试可能会因为超时或响应不匹配而失败。
  3. 网络异常:在测试网络程序时,需要考虑本地网络环境问题,例如端口被占用、服务器未运行等。

这个测试适用于简单的端到端 TCP 连接验证。

UDP测试用例
测试代码
import org.junit.jupiter.api.Test;
import java.net.*;
import static org.junit.jupiter.api.Assertions.*;

public class UDPTest {
    @Test
    public void testUDPConnection() {
        try (DatagramSocket socket = new DatagramSocket()) {
            String message = "测试UDP";
            byte[] sendBuffer = message.getBytes();
            
            DatagramPacket sendPacket = new DatagramPacket(sendBuffer, sendBuffer.length, InetAddress.getByName("localhost"), 9876);
            socket.send(sendPacket);
            
            byte[] receiveBuffer = new byte[1024];
            DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
            socket.receive(receivePacket);
            
            String response = new String(receivePacket.getData(), 0, receivePacket.getLength());
            assertEquals("服务器收到:测试UDP", response);
        } catch (Exception e) {
            e.printStackTrace();
            fail("UDP连接测试失败");
        }
    }
}
测试代码解析

这段代码是一个用于测试 UDP 连接的 JUnit 5 单元测试。它通过 DatagramSocket 来模拟 UDP 客户端与服务器之间的通信,发送一条消息并验证服务器的响应。接下来,我们逐步解析代码:

  1. 类声明和方法
public class UDPTest {
    @Test
    public void testUDPConnection() {
  • @Test 注解:标注该方法为 JUnit 测试方法,JUnit 会自动执行该方法来测试 UDP 连接。
  • testUDPConnection 方法:该方法用于测试 UDP 客户端和服务器之间的连接,并验证服务器的响应。
  1. 创建 DatagramSocket
DatagramSocket socket = new DatagramSocket();
  • DatagramSocket:创建一个用于发送和接收 UDP 数据报的套接字(DatagramSocket)。这是 UDP 通信的核心类,表示无连接的通信。
  • 使用了 try-with-resources,保证在测试结束时自动关闭 socket
  1. 准备消息并发送数据
String message = "测试UDP";
byte[] sendBuffer = message.getBytes();
  • 准备发送的消息 "测试UDP",并将其转换为字节数组,因为 UDP 传输的是字节数据。
DatagramPacket sendPacket = new DatagramPacket(sendBuffer, sendBuffer.length, InetAddress.getByName("localhost"), 9876);
socket.send(sendPacket);
  • DatagramPacket:构造一个用于发送的 UDP 数据包。参数包括:
    • 消息的字节数组 sendBuffer
    • 目标 IP 地址:localhost(表示本地主机)。
    • 目标端口号 9876,服务器必须在此端口上监听。
  • 调用 socket.send(sendPacket) 发送数据包。
  1. 接收服务器的响应
byte[] receiveBuffer = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
socket.receive(receivePacket);
  • receiveBuffer:创建一个字节数组缓冲区,用于接收来自服务器的响应。
  • DatagramPacket:构造一个接收的 UDP 数据包,缓冲区大小为 1024 字节。
  • socket.receive(receivePacket):调用该方法阻塞客户端,直到接收到来自服务器的响应。数据会填充到 receivePacket 中。
  1. 验证响应
String response = new String(receivePacket.getData(), 0, receivePacket.getLength());
assertEquals("服务器收到:测试UDP", response);
  • receivePacket.getData():获取接收到的数据。
  • receivePacket.getLength():返回接收到的实际数据长度,避免读取多余的空字节。
  • 将接收到的数据转换为字符串,并使用 assertEquals 方法断言服务器的响应是否为 "服务器收到:测试UDP"
  • 如果响应不匹配,测试将失败。
  1. 异常处理
catch (Exception e) {
    e.printStackTrace();
    fail("UDP连接测试失败");
}
  • 捕获并处理在通信过程中可能发生的异常(如网络故障、IO 错误等)。如果发生异常,打印堆栈跟踪,并调用 fail() 标记测试失败。

总结:

  • 该测试方法通过 UDP 向服务器发送一条消息 "测试UDP",并等待接收服务器的响应。
  • 服务器需要在 localhost:9876 上运行,并能够处理来自客户端的消息并返回 "服务器收到:测试UDP" 作为响应。
  • 测试验证了客户端接收到的消息是否与预期相符。

注意事项:

  1. 服务器端准备:测试之前需要确保有一个 UDP 服务器在 localhost9876 端口上监听并能返回预期的响应。
  2. UDP 无连接特性:UDP 是无连接的协议,因此通信的可靠性(如丢包、乱序等)不受保证。如果测试失败,可能是由于网络问题或服务器没有正常运行。
  3. 阻塞操作socket.receive() 是一个阻塞操作,它会一直等待,直到接收到数据包,因此,确保服务器能够及时响应,否则可能导致测试挂起。

这个测试模拟了一个简单的 UDP 客户端,验证了消息的发送和接收流程。

使用场景

  • 实时通信:例如视频通话和直播,UDP的低延迟传输使其在这些场景中有明显优势。
  • 高可靠性数据传输:如电子邮件和文件传输服务则更适合TCP,因为这些场景要求确保数据的完整性和顺序性。

全文总结

通过本文的学习,大家了解了TCP和UDP这两种协议的基本原理和在Java中的实现。我们详细讲解了TCP的可靠性机制和UDP的轻量级传输特性,并通过代码演示了如何实现这两种协议的网络编程。最后,我们对比了TCP和UDP的优缺点,明确了各自的使用场景。网络编程是Java开发中不可忽视的部分,了解这两种协议将帮助你在开发分布式系统或实时应用时做出正确的选择。

… …

  ok,以上就是我这期的全部内容啦,如果还想学习更多,你可以看看如下的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬。

「赠人玫瑰,手留余香」,咱们下期拜拜~~

附录源码

  如上涉及所有源码均已上传同步在「Gitee」,提供给同学们一对一参考学习,辅助你更迅速的掌握。

🧧🧧 福利赠与你 🧧🧧

  无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学Java」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门Java编程,就像滚雪球一样,越滚越大,指数级提升。

最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。

同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。

如果你有求职或者找新工作的需求,正好可以推荐你上万码优才【#小程序://万码优才/02DbDEM0iOuGPLj】,求职面试率高。

✨️ Who am I?

我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云2023年度十佳博主,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;更多精彩福利点击这里;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。

-End-

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

bug菌¹

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

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

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

打赏作者

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

抵扣说明:

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

余额充值