【网络编程】UDP

news2025/6/19 23:03:00

请添加图片描述

✨个人主页:bit me👇
✨当前专栏:Java EE初阶👇

目 录

  • 🏯一. UDP数据报套接字编程
  • 🏰二. 写一个 UDP 版本的 回显服务器-客户端.(echo server)
    • 🏭1. 服务器:
    • 🗼2. 客户端:
    • ⛺️3. 理清楚客户端和服务器的工作流程:

🏯一. UDP数据报套接字编程

  • DatagramSocket API

socket 类,本质上是相当于一个 “文件”,在系统中,还有一种特殊的 socket 文件,对应到网卡设备。构造一个 DatagramSocket 对象,就相当于是打开了一个内核中的 Socket 文件,打开之后,就可以传输数据了。send 发送数据;receive 接收数据;close 关闭文件。

DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。

DatagramSocket 构造方法:

方法签名方法说明
DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)
DatagramSocket(int port)创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)

DatagramSocket 方法:

方法签名方法说明
void receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacketp)从此套接字发送数据报包(不会阻塞等待,直接发送)
void close()关闭此数据报套接字
  • DatagramPacket API

表示一个 UDP 数据报,UDP 是面向数据报的协议,传输数据,就是以 DatagramPacket 为基本单位

DatagramPacket是UDP Socket 发送和接收的数据报。

DatagramPacket 构造方法:

方法签名方法说明
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发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创
建。

  • InetSocketAddress API

IP 地址 + 端口号

InetSocketAddress ( SocketAddress 的子类 )构造方法:

方法签名方法说明
InetSocketAddress(InetAddress addr, int port)创建一个Socket地址,包含IP地址和端口号

🏰二. 写一个 UDP 版本的 回显服务器-客户端.(echo server)

回显:客户端发啥,服务器就返回啥。不涉及到任何的业务逻辑,而只是单纯的演示 api 的用法。

🏭1. 服务器:

socket = new DatagramSocket(port);

绑定一个端口 => 把这个进程和一个端口号关联起来

一个操作系统上面,有很多端口号,0 - 65535 。
 
程序如果需要进行网络通信,就需要获取到一个端口号,端口号相当于用来在网络上区分进程的身份标识符。(操作系统收到网卡数据,就可以根据网络数据报中的端口号,来确定要把这个数据交给哪个进程)

分配端口号的过程:

  1. 手动指定
new DatagramSocket(port);
  1. 系统自动分配
socket = new DatagramSocket();

一个端口,在通常情况下,是不能被同一个主机上的多个进程同时绑定的;一个进程是可以绑定多个端口的。

如果端口已经被别人占用,再尝试绑定,就会抛出异常 throws SocketException

  • 读取客户端发来的请求,尝试读取,不是说调用了就一定能读到
//1.读取客户端发来的请求
socket.receive();

如果客户端没有发来请求,receive 就会阻塞等待,直到真的有客户端的请求过来了,receive 才会返回。

注意:

receive 是通过参数来放置读取到的数据的,而不是通过返回值。看源码中:
在这里插入图片描述
输出型参数!!需要调用 receive 之前,先构造一个空的 DatagramPacket ,然后把这个空的 DatagramPacket 填到参数中,receive 返回之后自然把读到的数据给放到参数里面。出现异常是 IOException 异常,处理一下就好了。
在这里插入图片描述

  • 对请求进行解析,把 DatagramPacket 转成一个 String
String request = new String(requestPacket.getData(),0,requestPacket.getLength());

还有一种写法:

在这里插入图片描述

但是这种写法是要被舍弃的,String 被画了删除线,可能在未来某一天就被删除了。

  • 根据请求,处理响应,虽然这里此处是个回显服务器,但是还是可以单独搞个方法来做这个事情
String response = process(request);

通过这个方法,实现根据请求计算响应,这个过程由于是回显服务器,所以涉及不到其他逻辑,但是如果是其他服务器,就可以在 process 里面,加上一些其他逻辑的处理

public String process(String req){
    return req;
}
  • 把响应构造成 DatagramPacket 对象(构造响应对象,要搞清楚,对象要发给谁,谁给咱发的请求,就把响应发给谁)
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());

这个也是构造 DatagramPacket 的一种方式,先是拿字符串里面的字节数组,来构造 Packet 的内容,还要把请求中的客户端地址拿过来,也填到包裹里去。
 
response.getBytes().length 可以写作 response.length 吗?
不行 response.getBytes().length 表示的是字节数,response.length 表示的是字符数

requestPacket.getSocketAddress() -> (地址)IP + 端口

  • 把这个 DatagramPacket 对象返回给客户端
 socket.send(responsePacket);
System.out.printf("[%s:%d] req = %s; resp = %s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);

注意是 printf !!!和 C 中的 printf 差不多

服务器总代码:

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

public class UDPEchoServer {
    //想要创建 UDP 服务器,首先要打开一个 socket 文件
    private DatagramSocket socket = null;

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

    //启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true){//服务器一直在运行,所以得一直运行
            //1. 读取客户端发来的请求
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);

            //2. 对请求进行解析,把 DatagramPacket 转成一个 String
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());

            //3. 根据请求,处理响应,虽然这里此处是个回显服务器,但是还是可以单独搞个方法来做这个事情
            String response = process(request);

            //4. 把响应构造成 DatagramPacket 对象
            //构造响应对象,要搞清楚,对象要发给谁,谁给咱发的请求,就把响应发给谁
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());

            //5. 把这个 DatagramPacket 对象返回给客户端
            socket.send(responsePacket);
            System.out.printf("[%s:%d] req = %s; resp = %s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);
        }
    }
    //通过这个方法,实现根据请求计算响应,这个过程由于是回显服务器,所以涉及不到其他逻辑,
    //但是如果是其他服务器,就可以在 process 里面,加上一些其他逻辑的处理
    public String process(String req){
        return req;
    }

    public static void main(String[] args) throws IOException {
        //真正启动服务器,这个端口号说是随便写,但是也有范围的,0 -> 65535
        //但是一般来说 1024 以下的端口,都是系统保留
        //因此咱们自己写代码,端口号还是尽量选择 1024 以上,65535 以下
        UDPEchoServer server = new UDPEchoServer(8000);
        server.start();
    }
}

🗼2. 客户端:

服务器,端口一般是手动指定的,如果自动分配,客户端就不知道服务器的端口是啥了,因此服务器有固定端口客户端才好访问。

客户端,端口一般是自动分配的,客户端程序是安装在用户的电脑上的,用户电脑当前运行哪些程序,是不可控的,如果要是手动指定端口,说不好这个端口就和其他程序冲突了,导致咱们的代码无法运行。

public UDPEchoClient() throws SocketException {
    //客户端的端口号,一般都是由操作系统自动分配的,虽然手动指定也行,习惯上还是自动分配比较好
    socket = new DatagramSocket();
}
  • 让客户端从控制台获取一个请求数据
System.out.println("> ");
String request = scanner.next();
  • 把这个字符串请求发送给服务器,构造 DatagramSocket,构造的 Packet 既要包含 要传输的数据,又要包含把数据发送到哪里(另外一种 DatagramPacket 的构造方法)
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName("127.0.0.1"),8000);

InetAddress.getByName(“127.0.0.1”) -> 通过这个字符串来构造的 InetAddress,此处的 127.0.0.1 回环 IP 就表示当前主机。
 
8000 -> 服务器端口号

这个包裹,就是要从客户端发送给服务器,就需要知道,发送的内容,以及发送的目的地是哪里(收件人地址 + 端口)

目前已经见过三个版本的 DatagramPacket 的构造:

  • 只填写缓冲区,用来接收数据的,一个是空的 Packet
  • 填写缓冲区,并且填写把包发给谁,InetAddress 对象来表示的
  • 填写缓冲区,并且填写把包发给谁,InetAddress + port 来表示的
  • 把数据报发送给服务器
socket.send(requestPacket);
  • 从服务期读取响应数据
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
  • 把响应数据获取出来,转成字符串
String response = new String(responsePacket.getData(),0,responsePacket.getLength());

System.out.printf("req: %s;resp: %s\n",request,response);

客户端总代码:

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

public class UDPEchoClient {
    private DatagramSocket socket = null;

    public UDPEchoClient() throws SocketException {
        //客户端的端口号,一般都是由操作系统自动分配的,虽然手动指定也行,习惯上还是自动分配比较好
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while (true){
            //1. 让客户端从控制台获取一个请求数据
            System.out.println("> ");
            String request = scanner.next();

            //2. 把这个字符串请求发送给服务器,构造 DatagramSocket
            //构造的 Packet 既要包含 要传输的数据,又要包含把数据发送到哪里
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName("127.0.0.1"),8000);

            //3. 把数据报发送给服务器
            socket.send(requestPacket);

            //4. 从服务期读取响应数据
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);

            //5. 把响应数据获取出来,转成字符串
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());

            System.out.printf("req: %s;resp: %s\n",request,response);
        }
    }

    public static void main(String[] args) throws IOException {
        UDPEchoClient client = new UDPEchoClient();
        client.start();
    }
}

⛺️3. 理清楚客户端和服务器的工作流程:

  1. 客户端根据用户输入,构造请求

在这里插入图片描述

  1. 客户端把请求发送给服务器

在这里插入图片描述

  1. 服务器读取请求并解析

在这里插入图片描述

  1. 服务器根据请求计算响应(服务器核心逻辑)

在这里插入图片描述

  1. 服务器构造响应数据,并且返回给客户端

在这里插入图片描述

  1. 客户端读取服务器返回的响应

在这里插入图片描述

  1. 客户端解析响应,并显示给用户

在这里插入图片描述

整体效果演示:

  • 先启动服务器,再启动客户端

客户端中输入一个hello:

在这里插入图片描述

在服务器中:

在这里插入图片描述

继续在客户端中输入一个你好:

在这里插入图片描述

服务器中显示:

在这里插入图片描述

一个服务器是可以同时给多个客户端提供服务的

如在 IDEA 中,你想打开多个客户端,你发现你再运行一次客户端,就会把之前的客户端给关闭了,此时我们需要设置一下,就可以启动多个客户端。

在这里插入图片描述

在这里插入图片描述

此时我们就可以打开多个客户端了

在这里插入图片描述

一个服务器灵魂所在,就是大体框架是一样的,比如这个回显服务器,我们要改成带有业务逻辑的服务器,只需要把 process 改掉即可,如我们简单实现一个词典

import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;

//字典服务器 / 翻译服务器
    //希望实现一个英译汉的效果
    //请求的是一个英文单词,响应是对应的中文翻译
public class UDPDicServer extends UDPEchoServer{
    private Map<String, String> dic = new HashMap<>();

    public UDPDicServer(int port) throws SocketException {
        super(port);

        //这里的数据可以无限的构造下去
        //即使是有道词典这种,也是类似的方法实现(打表)
        dic.put("cat","小猫");
        dic.put("dog","小狗");
        dic.put("fuck","卧槽");
    }

    //和 UDPEchoServer 相比,只是 process 不同,就重写这个方法即可
    public String process(String req){
        return dic.getOrDefault(req,"这个词俺也不会!");
    }

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

在这里插入图片描述

一个服务器要完成的工作,都是通过 “根据请求计算响应” 来体现的

不管是啥样的服务器,读取请求并解析,构造响应并返回,这两个步骤,大同小异,唯有 “根据请求计算响应” 是千变万化,是非常复杂的,可能一次处理请求就要几w,几十w的代码来完成。

拓展:
 
在 DatagramSocket 中有 send,receive 和 close。只有 send 和 receive 写进去了,close 却没有写进去,原因是在上述代码中 socket对象,生命周期都是应该伴随着整个进程的(while(true) 循环),因此进程结束之前,提前用 close 关闭 socket 对象,不合适,当进程已经结束,对应 PCB 没了,PCB 上面的文件描述符表也没了,此时也就相当于关闭了。

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

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

相关文章

咖啡卷到现在,他们开始往里面掺北京豆汁了

咖啡卷到现在&#xff0c;他们开始往里面掺北京豆汁了0. 导读1. 人手一杯&#xff0c;果咖大势所趋2. 双倍成瘾&#xff1a;茶咖和酒咖被重点推荐3. 地方小吃融入咖啡&#xff0c;比如北京豆汁4. 炙手可热的云南咖啡豆5. 咖啡、户外和环保&#xff1a;绑定可持续6. 小结0. 导读…

vue - 【最新】简单快捷的 element ui 组件库的主题色更改,批量统一覆盖替换解决方法,无需npm装包。(适用于新手小白的方法,很简单)

效果图 最新解决方案,简单便捷且不用npm安装任何第三方包就能搞定。 原来的主题色是蓝色 ,可以通过本篇博客提供的方法,统一变成其他主题颜色,比如下面的紫色: 下面就是真实的运行效果,保证可行~ 这样就不用每个组件单独去写样式覆盖颜色了! 定制主

干雾抑尘系统的降尘工作原理

对于干雾抑尘系统&#xff0c;相信大家现在都不陌生&#xff0c;作为工业无组织排放粉尘治理的常用除尘产品&#xff0c;其有着传统喷雾降尘设备无法比拟的优势。今天我们就来了解下&#xff0c;干雾抑尘系统到底是什么&#xff1f;他的工作原理到底是怎样的呢&#xff1f; ​…

进程、线程、协程的区别

目录 1、什么是进程&#xff1f; 2、什么是线程&#xff1f; 3、什么是协程&#xff1f; 4、进程、线程、协程的区别 (1) 进程、线程的区别 (2) 线程、协程的区别 1、什么是进程&#xff1f; 简单来说&#xff0c;进程可以看做是加载到内存里的程序&#xff0c;实际上一…

Qt Quick - ColorDialog 颜色对话框

ColorDialog 颜色对话框使用总结一、概述二、实现的要点三、一些例子的合集1. 按钮点击修改按钮文字颜色一、概述 ColorDialog 就是Qt 提供的标准的颜色对话框。ColorDialog 允许用户选择颜色。 对话框最初是不可见的。你需要首先按需设置属性&#xff0c;然后将visible设置为…

前端 icon 方案该升级了

说到 icon&#xff0c;很多前端开发只能想到 iconfont&#xff0c;或者组件库中提供的那些图标&#xff0c;当然这对于绝大多数开发场景是够用的。 但要知道&#xff0c;iconfont 的方案其实是在 2016 年公开&#xff0c;到现在也已经有 6 年之久&#xff0c;它确实在这一段时…

【产品经理】为什么你画的流程图开发总说看不懂?

在做产品设计时&#xff0c;会碰到非常多的业务流程&#xff0c;当我们需要跟开发解释一个相对复杂的流程时&#xff0c;一个清晰的流程图&#xff0c;便非常重要了。 我们做产品设计的时候&#xff0c;会碰到非常多的业务流程&#xff0c;有简单的&#xff0c;有复杂的&#x…

「业务架构」需求工程——捕获与分析(第二部分)

从用户、客户和其他涉众那里生成系统需求的活动。可行性研究结束后&#xff0c;我们进入下一个阶段;抽取和分析。需求捕获和分析它是一个与客户和最终用户交互的过程&#xff0c;以查明领域需求、系统应该提供什么服务以及其他约束。领域需求反映了系统运行的环境&#xff0c;因…

HTML5 <del> 标签、HTML5 <dd> 标签

HTML5 <del> 标签 实例 一段带有已删除部分和新插入部分的文本&#xff1a; <p>My favorite color is <del>blue</del> <ins>red</ins>!</p> 尝试一下 浏览器支持 所有主流浏览器都支持 <del> 标签。 标签定义及使用说明 …

什么是设计模式?

文章目录01 | 概念02 | 要素03 | 分类04 | 总结“每一个模式描述了一个在我们周围不断重复发生的问题&#xff0c;以及问题的解决方案的核心。这样&#xff0c;你就能一次又一次地使用该方案而不必做重复劳动”。 设计模式的核心&#xff1a;提供了相关问题的解决方案&#xff…

机械硬盘提示格式化的常见原因|3种数据恢复方法

机械硬盘是电脑存储设备中的一种&#xff0c;它的存储容量比较大&#xff0c;通常采用机械运动的方式记录数据。 其内部结构由多个盘片和磁头组成&#xff0c;盘片层层叠放在一起并通过主轴转动&#xff0c;磁头则通过磁臂上下移动并接触盘片表面进行读写操作。但是在日常使用电…

如何用Golang处理每分钟100万个请求

用Golang处理每分钟100万个请求 转载请注明来源&#xff1a;https://janrs.com/9yaq 面临的问题 在我设计一个分析系统中&#xff0c;我们公司的目标是能够处理来自数百万个端点的大量POST请求。web 网络处理程序将收到一个JSON文档&#xff0c;其中可能包含许多有效载荷的集合…

Spring Boot基础学习之(十六):用户认证和授权

本次项目使用静态资源代码免费下载 https://download.csdn.net/download/m0_52479012/87679062?spm1001.2014.3001.5501 在日常的登录网页中&#xff0c;是怎么分辨那些用户是具有那种权限呢&#xff1f; 本次博客实现的功能是&#xff1a;哪些网页是谁有可以访问的&#xff…

进程状态概念详解!(7千字长文详解)

进程状态概念详解 文章目录进程状态概念详解进程状态为什么会有这些状态运行总结阻塞就绪/新建挂起阻塞和挂起的区别&#xff01;linux下的进程状态运行——R睡眠——S暂停——T关于号深度睡眠——D追踪暂停 ——t死亡——X僵尸——Z僵尸进程的危害总结孤儿进程——S状态进程优…

一位腾讯在职7年测试工程师的心声...

作为一个在腾讯工作7年的测试工程师&#xff0c;今天就来聊聊腾讯工作压力到底从何而来。 压力的开始&#xff1a;时间回到7年前&#xff0c;我人生中的第一份实习工作&#xff0c;是在腾讯公司做一个自动化测试工程师。当时的我可谓意气风发&#xff0c;想要大干一场&#xf…

SpringBoot 介绍

1.简介 SpringBoot最开始基于Spring4.0设计&#xff0c;是由Pivotal公司提供的框架。 SpringBoot发展史&#xff1a; 2003年Rod Johnson成立Interface公司&#xff0c;产品是SpringFramework2004年&#xff0c;Spring框架开源&#xff0c;公司改名为Spring Source2008年&…

[MAUI 项目实战] 手势控制音乐播放器(三): 动画

文章目录吸附动画确定位置平移动画回弹动画使用自定义缓动函数多重动画点击动画项目地址上一章节我们创建了手势容器控件PanContainer&#xff0c;它对拖拽物进行包装并响应了平移手势和点击手势。拖拽物现在虽然可以响应手势操作&#xff0c;但视觉效果较生硬&#xff0c;一个…

【ros2】ubuntu18.04同时安装ros1和ros2

序言 ubuntu18.04&#xff08;已安装ros melodic&#xff09;中安装ros2 dashing版本&#xff0c;以支持ros2工程的编译使用 1. 安装ros melodic 参考我之前的文章&#xff1a;docker容器中安装melodic-ros-core过程总结 2. 安装ros2 dashing &#xff08;1&#xff09;设置…

《花雕学AI》12:从ChatGPT的出现看人类与人工智能的互补关系与未来发展

马云说道&#xff0c;ChatGPT这一类技术已经对教育带来挑战&#xff0c;但是ChatGPT这一类技术只是AI时代的开始。 谷歌CEO桑德尔皮猜曾说&#xff1a;“人工智能是我们人类正在从事的最为深刻的研究方向之一&#xff0c;甚至要比火与电还更加深刻。” 360周鸿祎认为&#xf…

用WPF设计一个简易的休息提醒闹钟

目录一.视频演示地址二.代码展示三.源代码&#xff1a;最近利用工作之余&#xff0c;写了一个WPF程序玩玩&#xff0c;用来提醒自己在长时间学习后要休息一会儿哈哈&#xff0c;功能很简单&#xff0c;没啥难点一.视频演示地址 可以设定间隔提醒时长和休息时长&#xff0c;点击…