网络原理 | TCP与UDP协议的区别以及回显服务器的实现

news2025/12/15 14:59:06

目录

TCP与UDP协议的区别

基于 UDP 协议实现回显服务器 

UDP Socket 编程常用 Api

UDP 服务器

UDP 客户端

基于 TCP 协议实现回显服务器

TCP Socket 编程常用 Api

TCP 服务器

TCP 客户端 

TCP 服务端常见的 bug 

客户端发送数据后,没有响应

服务器仅支持与一个客户端建立连接 


TCP与UDP协议的区别

        传输控制协议(TCP)与 用户数据报协议(UDP)是传输层两个重要的协议,二者互补共存,共同支撑互联网的多层次传输需求。

 二者的区别如下:

        · TCP 协议是有连接的、可靠传输、面向字节流、全双工

        · UDP 协议是无连接的、不可靠传输、面向数据报、全双工

        其中有/无连接是指:如果通信双方保存了通信对端的信息,就相当于是有连接;如果不保存对端的信息,就是无连接。 

        可靠传输/不可靠传输:此处的可靠不是指 100% 能到达对方,而是指“尽可能”确保数据的传输,而“不可靠”则意味着完全不保证数据是否能成功到达对方。TCP 通过一些内置机制(确认应答机制、重传机制等)保证了可靠传输,UDP则没有可靠性机制。

        面向字节流/面向数据报:TCP 是面向字节流的,TCP 的传输过程就和文件流/水流是一样的;而 UDP 是面向数据报的,其传输数据的基本单位是数据报,一次发送/接收必须发送/接收完整的数据报。

        全双工/半双工:全双工是指一个通信链路既可以发送数据也可以接收数据(双向通信),半双工是指一个通信链路只能发送/接收数据(单向通信)。

TCP 和 UDP 的选择以及适用场景

· 当数据准确性 > 传输延迟时(例如软件更新下载、文件传输、网页浏览等场景),选择 TCP。

        因为 TCP 能够保证数据的可靠传输,通过确认应答、重传机制等手段确保数据的完整性和准确性,适用于对数据传输准确性要求较高的应用。

· 当实时性 > 数据完整性时(例如多人游戏同步、在线视频会议、实时语音通话等场景),选择 UDP。

        UDP适用于对实时性要求较高的场景,虽然它不保证数据的完整性,但能减少延迟,确保数据传输的实时性。特别是在实时通信中,丢失少量数据包对用户体验的影响相对较小,而过高的延迟可能影响整体体验。

基于 UDP 协议实现回显服务器 

UDP Socket 编程常用 Api

· DatagramSocket:是一种用于网络通信的套接字对象,代表了操作系统中一个特定类型的“文件”资源。可以将其理解为操作系统对网络设备(如网卡)的一种抽象表示,就像操作系统将硬盘、键盘等设备抽象为文件一样,DatagramSocket 抽象了网络数据的发送与接收通道。它专门用于通过 UDP(用户数据报协议) 发送和接收数据报(Datagram),支持无连接、不可靠但高效的通信方式。通过 DatagramSocket,应用程序能够将数据以数据报的形式发送到目标地址,同时也能接收来自网络上其他节点的数据报,实现轻量级的网络通信。

· DatagramSocket的构造方法包括以下两种形式:

        DatagramSocket():创建一个 UDP 数据报套接字的 Socket,绑定到本机任意一个随机端口(通常用于客户端)

        DatagramSocket(int port):创建一个 UDP 数据报套接字的 Socket,绑定到本机指定的端口(通常用于服务端)

· DatagramSocket类下的方法:

        void receive(DatagramPacket p):从套接字接收数据报,如果没有接收到数据报就会阻塞等待

        void send(DatagramPacket p):从套接字发送数据报,不会阻塞等待,直接发送

        void close():关闭此数据报套接字

 · DatagramPacket:是 UDP Socket 发送/接收的数据报,其构造方法包括:

        DatagramPacket(byte[] buf, int length):构造⼀个DatagramPacket以⽤来接收数据报,接收的数据保存在字节数组(第⼀个参数buf)中,接收指定长度(第⼆个参数length)

        DatagramPacket(byte[] buf, int offset, int length, SocketAddress address):构造⼀个DatagramPacket以⽤来发送数据报,发送的数据为字节数组(第⼀个参数buf)中,从0到指定长度(第⼆个参数length)。address指定⽬的主机的 IP 和端口号。

· DatagramPacket类下的方法:

        InetAddress getAddress():从接收的数据报中,获取发送端主机 IP 地址;或从发送的数据报中,获取接收端主机 IP 地址

        int getPort():从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号

        byte[] getData():获取数据报中的数据

        回显服务器需要实现两个程序:UDP 服务器和 UDP 客户端,主动发起通信的一方称为客户端,被动接收的一方的是服务器。

UDP 服务器

package network;

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

/**
 * Created with IntelliJ IDEA.
 * Description: 回显服务器
 * 客户端发啥样的请求,服务器就返回啥样的响应
 * User: Li_yizYa
 * Date: 2025/5/13
 * Time: 15:29
 */
public class UdpEchoServer {
    private DatagramSocket socket = null;

    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    /**
     * 通过 start 启动服务器的核心流程
     */
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            // 此处通过 “死循环” 不停的处理客户端的请求.

            // 1. 读取客户端的请求并解析
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPacket);
            // 上述收到的数据,是二进制 byte[] 的形式体现的,后续代码如果要进行打印之类的处理操作
            // 需要转成字符串才好处理
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());

            // 2. 根据请求计算响应,由于此处是回显服务器,响应就是请求
            String response = process(request);

            // 3. 把响应写回到客户端
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);

            // 4. 打印日志
            System.out.printf("[%s:%d] req=%s, resp=%s\n", requestPacket.getAddress(), requestPacket.getPort(),
                    request, response);
        }
    }

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

    public static void main(String[] args) throws IOException {
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();

    }
}

UDP 客户端

package network;

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

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Li_yizYa
 * Date: 2025/5/13
 * Time: 15:29
 */
public class UdpEchoClient {
    DatagramSocket socket = null;
    private String serverIP;
    private int serverPort;

    public UdpEchoClient(String serverIP, int serverPort) throws IOException {
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }

    public void start() throws IOException {
        System.out.println("启动客户端");
        Scanner scanner = new Scanner(System.in);

        while (true) {
            // 1. 从控制台读取到用户的输入
            System.out.print("-> ");
            String request = scanner.next();
            // 2. 构造一个 UDP 请求,发送给服务器
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(this.serverIP), this.serverPort);
            socket.send(requestPacket);
            // 3. 从服务器读取到响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            // 4. 把响应打印到控制台上
            System.out.println(response);
        }
    }

    public static void main(String[] args) throws IOException {
        // 127.0.0.1 特殊 IP,环回 IP
        // 如果客户端和服务器在同一个主机上,就使用这个 IP
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}

基于 TCP 协议实现回显服务器

TCP Socket 编程常用 Api

        在TCP Socket编程中,核心类是 ServerSocket(专门给服务器使用的 Socket 对象)和 Socket(既会给客户端使用,又会给服务器使用)。由于 TCP 协议是面向字节流的,其数据的基本传输单位就是 byte,因此不需要像 UDP 协议那样定义一个类来表示 “数据报” 对象。

· ServerSocket:是创建 TCP 服务器 Socket 的 Api,其构造方法为:

        ServerSocket(int port):创建⼀个服务端流套接字Socket,并绑定到指定端口

· ServerSocket 类下常用的方法:

        Socket accept():开始监听指定端口(创建时绑定的端口),有客户端连接后,返回⼀个服务端 Socket 对象,并基于该 Socket 建立与客户端的连接,否则阻塞等待

        void close():关闭此套接字

 · Socket:其是客户端 Socket,或服务端接收到客户端建立连接(accept方法)的请求后,返回的服务端 Socket。不管是客户端还是服务端 Socket,都是双方建立连接以后,保存的对端信息,及时用来与对方收发数据的。其构造方法为:

        Socket(String host, int port):创建⼀个客户端流套接字 Socket,并与对应 IP 的主机 上,对应端口的进程建立连接。

· Socket 类下的方法:

        InetAddress getInetAddress():返回套接字所在的地址

        InputStream getInputStream():返回此套接字的输入流

        OutputStream getOutputStream():返回此套接字的输出流

TCP 服务器

package network;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

/**
 * Created with IntelliJ IDEA.
 * Description: TCP回显服务器
 * 客户端发啥样的请求,服务器就返回啥样的响应
 * User: Li_yizYa
 * Date: 2025/5/21
 * Time: 20:19
 */
public class TcpEchoServer {
    private ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("启动服务器");
        while (true) {
            Socket clientSocket = serverSocket.accept();
            Thread t = new Thread(() -> {
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
    }

    // 针对一个连接,提供处理逻辑
    private void processConnection(Socket clientSocket) throws IOException {
        // 先打印一下客户端信息
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
        // TCP 是全双工的通信,一个 socket 对象,既可以读,也可以写
        // 获取到 socket 中持有的流对象
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            // 使用 Scanner 包装一下 inputStream,就可以更方便的读取这里的请求数据了
            while (true) {
                // 1. 读取请求并解析
                Scanner scanner = new Scanner(inputStream);
                PrintWriter printWriter = new PrintWriter(outputStream);
                if (!scanner.hasNext()) {
                    // 如果 scanner 无法读取出数据,说明客户端关闭了连接,导致服务器这边读取到 “末尾”
                    break;
                }
                String request = scanner.next();
                // 2. 根据请求计算响应
                String response = process(request);
                // 3. 把响应写回给客户端
                // 此处可以按照字节数组来写,也可以右另外一种写法
                // outputStream.write(response.getBytes());
                printWriter.println(response);
                printWriter.flush();

                // 打印日志
                System.out.printf("[%s:%d] req=%s; resp=%s\n", clientSocket.getInetAddress(),
                        clientSocket.getPort(), request, response);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
            clientSocket.close();
        }

    }

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

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

TCP 客户端 

package network;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Li_yizYa
 * Date: 2025/5/21
 * Time: 20:20
 */
public class TcpEchoClient {
    private Socket socket = null;

    /**
     * 构造方法
     * @param serverIp 服务器 Ip
     * @param serverPort 服务器端口号
     */
    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        socket = new Socket(serverIp, serverPort);
    }

    public void start() {
        System.out.println("客户端启动");

        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            Scanner scanner = new Scanner(inputStream);
            Scanner scannerIn = new Scanner(System.in);
            PrintWriter printWriter = new PrintWriter(outputStream);
            while (true) {
                // 1. 从控制台读取数据
                System.out.print("-> ");
                String request = scannerIn.next();
                // 2. 把请求发给服务器
                printWriter.println(request);
                // 引入 flush(冲刷) 操作,主动刷新缓冲区
                printWriter.flush();
                // 3. 从服务器读取响应
                if (!scanner.hasNext()) {
                    break;
                }

                String response = scanner.next();
                // 4. 打印响应结果
                System.out.println(response);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}

TCP 服务端常见的 bug 

客户端发送数据后,没有响应

 在客户端中,我们通过下面的方式给服务端发送请求:

// 2. 把请求发给服务器
printWriter.println(request);

        之所以这里没有响应,是因为其实客户端的数据并没有发送出去,因为 PrintWriter 这个类以及 IO 流中的很多类,都是自带缓冲区的,引入缓冲区之后,进行 写入数据操作 时,不会立即触发 IO,而是先将其放在内存缓冲区中,等缓冲区数据到达一定数量后,才会统一发送。而上述的问题,其实就是因为数据比较少,并未触发发送操作。因此,我们通过引入 flush 操作就可以解决该问题:

// 引入 flush(冲刷) 操作,主动刷新缓冲区
printWriter.flush();

服务器仅支持与一个客户端建立连接 

        对于该问题,引入多线程操作修改服务器代码中的 start 方法即可,每有一个客户端请求建立与服务器的连接,就创建一个线程来专门处理与该客户端之间的数据发送/接收,具体代码如下:

    public void start() throws IOException {
        System.out.println("启动服务器");
        while (true) {
            Socket clientSocket = serverSocket.accept();
            Thread t = new Thread(() -> {
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
    }

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

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

相关文章

【Unity3D】将自动生成的脚本包含到C#工程文件中

我们知道,在用C#开发中,通过vs编辑器新建的脚本,会自动包含到vs工程中,而通过外部创建,比如复制别的工程或代码创建的C#脚本不会包含到vs工程。 在我们的日常开发中,通常会自动创建C#脚本,特别…

【Python 深度学习】1D~3D iou计算

一维iou 二维 import numpy as npdef iou_1d(set_a, set_b):# 获得集合A和B的边界 x1, x2 set_ay1, y2 set_b# 计算交集的上下界low max(x1,y1)high - min(x2, y2)# 计算交集if high - low < 0:inter 0else:inter high - low# 计算并集union (x2 -x1) (y2 - y1) - in…

java23

1.美化界面 添加背景图片 所以我们添加背景图片要放在后面添加 添加图片边框 绝对路径&#xff1a; 相对(模块)路径&#xff1a; 第一个是绝对路径&#xff0c;第二个是相对路径&#xff0c;但是斜杠的方向不对 总结&#xff1a; 2.图片移动 先实现KeyListener接口&#xf…

LitCTF2025 WEB

星愿信箱 使用的是python&#xff0c;那么大概率是ssti注入 测试{{5*5}} 发现需要包含文字&#xff0c;那么添加文字 可以看到被waf过滤了&#xff0c;直接抓包查看参数上fenjing 可以看到这里是json格式&#xff0c;其实fenjing也是支持json格式的 https://github.com/Marv…

Linux 下VS Code 的使用

这里以创建helloworld 为例。 Step 0:准备工作&#xff1a; Install Visual Studio Code. Install the C extension for VS Code. You can install the C/C extension by searching for c in the Extensions view (CtrlShiftX). Step 1: 创建工作目录 helloworld&#xff0…

Qt 布局管理器的层级关系

1、HomeWidget.h头文件&#xff1a; #ifndef HOMEWIDGET_H #define HOMEWIDGET_H#include <QWidget> #include <QPushButton> #include <QVBoxLayout> #include <QHBoxLayout>class HomeWidget : public QWidget {Q_OBJECTpublic:HomeWidget(QWidget …

maven模块化开发

使用方法 将项目安装到本地仓库 mvn install 的作用 运行 mvn install 时&#xff0c;Maven 会执行项目的整个构建生命周期&#xff08;包括 compile、test、package 等阶段&#xff09;&#xff0c;最终将构建的 artifact 安装到本地仓库&#xff08;默认路径为 ~/.m2/repos…

云原生安全之网络IP协议:从基础到实践指南

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 IP协议&#xff08;Internet Protocol&#xff09;是互联网通信的核心协议族之一&#xff0c;负责在设备间传递数据包。其核心特性包括&…

C++——QT 文件操作类

QFile 概述 QFile是Qt框架中用于文件操作的类&#xff08;位于QtCore模块&#xff09;&#xff0c;继承自 QIODevice&#xff0c;提供文件的读写、状态查询和路径管理功能。它与 QTextStream、QDataStream 配合使用&#xff0c;可简化文本和二进制数据的处理&#xff0c;并具备…

[spring] spring 框架、IOC和AOP思想

目录 传统Javaweb开发的困惑 loC、DI和AOP思想提出 Spring框架的诞生 传统Javaweb开发的困惑 问题一&#xff1a;层与层之间紧密耦合在了一起&#xff0c;接口与具体实现紧密耦合在了一起 解决思路&#xff1a;程序代码中不要手动new对象&#xff0c;第三方根据要求为程序提…

尚硅谷redis7 37-39 redis持久化之AOF简介

37 redis持久化之AOF简介 AOF 以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工…

GitLab 备份所有仓库(自动克隆)

一、准备工作 1. 环境要求 已安装 Git&#xff08;版本 2.10&#xff09;本地磁盘空间充足&#xff08;根据仓库总大小预估&#xff09;已配置 SSH 密钥到 GitLab&#xff08;推荐方式&#xff09; 2. 获取 GitLab API 访问权限 登录 GitLab&#xff0c;点击右上角头像 → …

[浏览器]缓存策略机制详解

在做页面性能优化的时候&#xff0c;有一个点容易被忽略&#xff0c;那就是资源缓存优化。 浏览器里缓存策略分为强缓存&#xff0c;协商缓存以及不缓存&#xff0c;每个缓存策略都有其适用的优化场景。 下面为大家详解何为强缓存&#xff0c;协商缓存 先说结论强缓>协商&g…

OpenCV CUDA 模块图像过滤-----创建一个计算图像导数的滤波器函数createDerivFilter()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::cuda::createDerivFilter 是 OpenCV CUDA 模块中的一个工厂函数&#xff0c;用于创建一个计算图像导数的滤波器。这个滤波器可以用来计算图像…

AWS関連職種向け:日本語面接QA集

1. 自己紹介&#xff08;じこしょうかい&#xff09; Q&#xff1a;簡単に自己紹介をお願いします。 A&#xff1a; はい、〇〇と申します。これまで約4年間、主にAWSを基盤としたインフラ設計・構築・運用に従事してまいりました。VPCやEC2、RDS、S3などの基本サービスの設計…

(01)华为GaussDB((基于PostgreSQL))高斯数据库使用记录,dbeaver客户端配置高斯驱动,连接高斯数据库

高斯数据库是华为推出的一款基于PostgreSQL的企业级数据库产品&#xff0c;客户端使用通用的dbeaver dbeaver客户端配置高斯驱动 建议使用 dbeaver24.3.1及以上客户端&#xff0c;选择模式后执行sql会绑定模式名&#xff0c;如果使用dbeaver23.2版本&#xff0c;选择模式后执…

ARM Linux远程调试

准备 虚拟机既能ping通开发板,又能ping通外网,还要能ping通Windows主机(如果你有上位机通信(tftp、vsftp、ssh)的需求) VMware 添加网络适配器2用作桥接网卡,原有的网络适配器保持为NAT模式 打开虚拟网络编辑器,配置VMnet0为桥接模式,外部连接设置为Realtek PCIe G…

day24Node-node的Web框架Express

1. Express 基础 1.1 什么是Express node的web框架有Express 和 Koa。常用Express 。 Express 是一个基于 Node.js 的快速、极简的 Web 应用框架,用于构建 服务器端应用(如网站后端、RESTful API 等)。它是 Node.js 生态中最流行的框架之一,以轻量、灵活和易用著称。 …

让MySQL更快:EXPLAIN语句详尽解析

前言 在数据库性能调优中&#xff0c;SQL 查询的执行效率是影响系统整体性能的关键因素之一。MySQL 提供了强大的工具——EXPLAIN 语句&#xff0c;帮助开发者和数据库管理员深入分析查询的执行计划&#xff0c;从而发现潜在的性能瓶颈并进行针对性优化。 EXPLAIN 语句能够模…

[CSS3]rem移动适配

前言 什么是移动端适配? 让页面的元素在屏幕尺寸变化时, 同比放大或缩小 移动适配的方案 rem&#xff1a;目前多数企业在用的解决方案 vw/vh&#xff1a;未来的解决方案 rem 体验rem适配 目标: 能够使用rem单位设置网页元素的尺寸 网页效果: 屏幕宽度不同&#xff0c;网…