Java网络编程API 1

news2025/6/7 3:11:22

Java中的网络编程API一共有两套:一套是UDP协议使用的API;另一套是TCP协议使用的API。这篇文章我们先来介绍UDP版本的API,并尝试来写一个回显服务器(接收到的请求是什么,返回的响应就是什么)。

UDP数据报套接字编程

API介绍

DatagramSocket

DatagramSocket是UDPSocket,用于发送和接收UDP数据报,就相当于网卡的作用。

DatagramSocket的构造方法:

DaragramSocket方法:DatagramPacket

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

DatagramPacket构造方法:

 DatagramPacket方法:

 构造UDP发送数据报的时候,需要传入SocketAddress(要发送信息的地址)。

补充:

UDPSocket中API的使用,是Java把操作系统中的原生API进行了一层封装。

其中最核心的类就是上面两个,DatagramSocket和DatagramPacket。

DatagramSocket:操作系统中有一类文件,就叫做socket(文件普通文件和目录文件都是存放在硬盘上的,socket文件,就抽象地表示了“网卡”这样的硬件设备,进行网络通信最核心的硬件设备就是网卡(通过网卡发送数据,就相当于写socket文件;通过网卡接收数据,就相当于读socket文件)。

DatagramPacket:UDP面向的是数据报,每次发送、接收数据的基本单位,就是一个UDP数据报,而DatagramPacket就表示了一份UDP数据报。

下面我们通过写一个回显服务器,来认识这些API的使用,并且了解在实际开发中,服务器需要做哪些事情,客户端需要做哪些事情。

回显服务器

回显服务器:客户端发什么请求,服务器就返回什么响应。(并没有什么业务逻辑,实际开发中,我们要返回什么响应是要根据业务逻辑来的)。

服务器端

第一步,先创建DatagramSocket对象,并且通过构造方法为服务器指定一个端口号:

我们的服务器程序一启动,就需要绑定/关联上操作系统的一个端口号,端口号也是一个整数,用来区分一个主机上进行网络通信的程序(一个端口号只能绑定一个程序,反过来,一个程序能够绑定多个端口号,上面抛出的SocketException就是为了处理多个程序绑定了同一个端口号导致Socket创建不成功的情况),在创建服务器时需要我们手动指定一个端口号

下面我们来写start方法:

对于服务器来说,需要不停地接收请求,返回响应,接收请求,返回响应。(一个服务器单位时间能处理地请求,能返回地响应越多,服务器水平越高)。

服务器往往都是7*24小时运行,因此这里地while(true)并没有退出的必要,如果我们确实向重启服务器,直接“杀”进程即可。

当然,我们这里的while(true)是非常简单粗暴的写法。实际开发中的服务器,很可能要实现“优雅退出”的效果,即确保当前正在进行的请求做完了之后再进行退出。

在start方法中,我们要完成四步工作;

1、读取请求并解析。

2、根据请求计算响应(但我们这里只是简单构造回显服务器,这一步啥也不用干)。

3、把响应返回给客户端。 

4、打印日志。


1、读取请求并解析 

读取请求并解析的过程中,需要使用socket的receive方法:

使用receive需要传入一个输出型参数DatagramPacket对象,这个对象里有一个内置的字节数组(我们可以设置数组的最大长度),会保存我们收到的消息正文(应用层数据包,UDP数据报的载荷部分)。此外UDP报头、数据的源IP和源端口号等都会被这个DatagramPacket对象所保存。 注意:这里的receive方法是自带阻塞功能的,如果客户端没有发来请求就会阻塞等待。

我们可以将得到的字节数组,转换成String,方便后续根据请求计算响应:

基于字节数组构造出String,字节数组里面保存的内容不一定是二进制数据,也可能是文本数据,如果是文本数据,将其交给String ,也是没有问题的;如果是二进制数据,String也是可以保存的。

第一个参数中,通过requestPacket对象调用getData方法,将上面字节数组中的信息获取到,然后第二个参数表示从字节数组的0号位置,开始构造String,第三个参数,是通过requestPacket对象获取到字节数组的有效长度(如果通过最大长度构造,那么字符串后面的很大部分都会是空白)。通过这三个参数来构造我们的String对象。

补充:

receive是传输层的UDP协议提供的一个API,传输层会给每个socket对象在内核中分配一个缓冲区,每次网卡读到一个数据读到一个数据,都会层层分用,解析好之后,最后放到这个缓冲区中,应用程序调用receive本质上就是从缓冲区中拿走一个数据。

2、根据请求计算响应

因为是回显服务器,这一步我们就只需要简单return以下即可 

 3、把响应返回到客户端

要想把响应返回给客户端,这里需要使用send方法,这里面同样需要传一个DatagramPacket对象:

此时我们构造DatagramPacket时,参数又不一样了: 

第一个参数:response.getByte()是将我们刚才得到的响应(String类型)以字节数组的形式得到。

第二个参数:response.getBytes().length这里是获取字节数组的长度,以字节为单位进行计算,如果传入的参数是response.length(),此时的单位就是字符了。

第三个参数:requestPacket.getSocketAddress(),这里调用方法的的对象是requestPacket,这个对象是我们用来读取请求的,表示的是客户端来的数据报。调用getSocketAddress方法,调用这个方法,会获取到一个InetAddress对象,这个INetAddress对象,就包含了和服务器通信对应的客户端的ip和端口号(即响应的目的ip和目的端口号)。

通过上面的代码,我们也可以看出UDP是无连接的通信。UDP的DatagramSocket对象自身不保存对端的IP和端口。而是在每一份数据报中,都有一个对端的IP和端口。另外代码中也没有建立连接和接收连接的操作。

而UDP的不可靠传输,无法在代码中体现。但是UDP面向数据报的特点,可以看到,在send返回响应和receive读取请求时,都是以DatagramPacket为单位的。

4、打印日志

注意:这里的requestPacket.getAddress().toString会以点分十进制的方式输出客户端的IP地址 

再写一个main方法: 

注意:这里传入的端口号一般在1024~65535之间(但不可以和其他进程冲突),1~1024之前的端口号是系统保留自用的端口号——知名端口号(不可占用)。

完整代码: 

package UDPEcho;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;

public class UDPEchoSever {
    //先创建一个DatagramSocket对象
    private DatagramSocket socket = null;
    //服务启动需要绑定端口号
    //一个主机的一个端口只能绑定一个进程,反过来一个进程可以绑定多个端口
    public  UDPEchoSever(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    public void  start() throws IOException {
        System.out.println("服务器启动!");
        //需要不停接收请求并返回响应
        while(true){
            //1、读取请求并解析
            //通过这个字节数组保存收到的消息正文(应用层数据包),也就是UDP数据报的载荷部分
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            //此处的receive就从网卡读取到了一个UDP数据报,放到requestPacket中,载荷部分就被放到requestPacket内置的字节数组中了
            //此处的UDP报头、源ip和端口号也会被保存
            socket.receive(requestPacket);
            //把读到的字节数组转为字符串
            String request = new String(requestPacket.getData(),0, requestPacket.getLength());
            //2、根据请求构造响应
            String response = process(request);
            //3、把响应返回给客户端
            //构造体格DatagramPacket作为响应对象,里面不是空白的字节数组了,而是把String里面包含的字节数组拿进来了
            //注意:获取长度是字节数组,单位是字节。如果直接获取字符串长度,单位是字符不一样的
            //requestPacket.getSocketAddress()这个对象就包含了你和服务器对应的ip和端口号
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
            socket.send(responsePacket);
            //打印日志
            System.out.printf("[%s:%d],res:%s,response:%s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);
        }
    }

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

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

客户端

编写客户端的代码,也需要创建一个DatagramSocket对象。但是此处就不必指定端口号了。 

服务器编写代码的时候需要手动指定端口号,但是在客户端这边,一般不需要手动指定,系统会自动分配一个空闲的端口号。

代码中手动指定端口号,可以保证端口号始终都是固定的,如果不手动指定,依赖系统自动分配,导致服务器每次重启之后,端口号可能就变了,一旦变了,客户端就有可能找不到这个服务器在哪里了,所以我们的服务器代码,一般要手动指定端口号。

但是客户端,端口号让系统随机分配一个空闲的即可。(主要是我们无法确保手动指定的端口号是可用的(可能被其他进程占用了))。

服务器的端口就不会被占用吗?为什么只担心客户端呢?

服务器的机器是在程序员手里的,是可控的。程序员事先编写代码之前,是能知道服务器上有哪些端口是空闲的。但是,客户端时在普通用户的机器上的,用户千千万万,上面的环境也是千差万别,天知道某个用户会装什么奇奇怪怪的程序把端口号占用,此时,我们的程序无法正常启动,用户只会怪到程序员的头上。

上面的客户端构造方法还需要,我们需要指定服务器短的ip地址和端口号。因为时客户端主动给服务器发起请求,发起请求的前提就是需要知道服务器在哪里~~~(比如说我要去餐厅吃饭,我就需要知道餐厅的位置在哪里,才能去吃饭)

此处,客户端发起请求的目的ip(severIp)和目的端口(severPort)就是客户端的ip和端口,而请求的源ip就是客户端本机的ip,源端口就是客户端本机分配的端口。

接下来,和服务器一样我们的客户端也需要实现一个start方法,在start方法中,我们也要做四件事:

1、从控制台读取要发送的请求数据

2、构造请求并发送

3、读取服务器的响应

4、把响应显示在控制台上

因为我们是要从控制台读取要发送的数据,所以需要先创建一个Scanner对象,还需要一个循环不断读取数据并发送给服务器。 

1、从控制台读取要发送的请求数据

这里的if语句是可以让用户在结束输入的时候,程序可以正确地跳出循环,避免程序一直等待用户输入。

从控制台读取数据的时候,最好使用scanner读取字符串,最好使用next而不是nextLine(如果是文件的话就无所谓了) 

如果使用nextLine读取,就需要手动输入换行符——Enter来控制。而由于Enter键不仅会产生\n这样的换行,还会产生其他字符,就可能会导致读到的内容出现问题。

而next是以"空白符"作为分隔符,包括但不限于,换行,空格,制表符……

 2、构造请求并发送

同样的socket调用send方法,同样需要传入一个DatagramPacket类型的参数。​​​​​​​​​​​​​​​​​​​​​

此处的Datagram参数又有所不同,第三个参数是我们当前要通信服务器的ip(注意:这里并不能传入String类型的severIP,而是需要调用InetAddress.getByName(severIP)),第四个参数则是我们当前要通信服务器上的端口号。

3、读取服务器的响应

这里仍然是使用socket的receive方法同样参数需要传入一个DatagramPacket类型的对象,此时构造方法仍然搭配receive使用,所以构造方法只需要指空白的字节数组即可 

4、把响应显示在控制台上 

这里也需要基于DatagramPacket构造字符串,进行打印。 

主函数:

其中127.0.0.1表示的是本机的ip,9090就是我们刚才在服务器中指定的端口号

完整代码: 

package UDPEcho;

import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;

public class UDPEchoCline {
    private DatagramSocket socket = null;
    //客户端的IP
    private String severIp;
    //客户端端口号
    private int severPort;

    public UDPEchoCline(String severIp,int severPort) throws SocketException {
        this.severIp = severIp;
        this.severPort = severPort;
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);
        while(true) {
            System.out.print("->");
            //1、从控制台读取要发送请求的数据
            if(!scanner.hasNext()){
                break;
            }
            String request = scanner.next();
            //2、构造请求并发送
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(severIp),severPort);
            socket.send(requestPacket);
            //3、读取服务器响应
            DatagramPacket responesePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responesePacket);
            //4、把响应打印到控制台上
            String response = new String(responesePacket.getData(),0,responesePacket.getLength());
            System.out.println(response);
        }
    }

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

基于DatagramPacket的补充

第一种:构造时指定空白的字节数组和字节数组的长度即可(搭配receive进行使用):

第二种:构造的时候指定有内容的字节数组,并且通过requestPacket.getSocketAddress方法获取请求方的IP和端口号。(用于服务器向客户端返回响应)。

第三种:构造的时候指定有内容的字节数组,并且分别将数据的目的IP和端口号作为两个参数。(用于客户端向服务器发起请求

代码演示:

注意:这里一定要先启动服务器,再启动客户端

此时,我们再客户端控制台输入,就会返回同样的值

同时,我们的服务器也会打印日志 

梳理代码 

下面是一个大致的流程图:

图文并茂讲解:

1、服务器启动之后,进入receive阻塞,等待客户端的请求。 

2、客户端在启动之后,在hasnext进入阻塞,等待用户输入。 3、用户在控制台输入之后,客户端会拿到用户输入的字符串,构造出请求,发送请求,并且在receive处等待响应返回。

4、服务器收到响应,就从receive解除阻塞,继续往下执行。 

 这里服务器执行完上述逻辑后,就会进入到下次循环中的receive的阻塞等待中,等待下一个客户端的请求过来。

5、客户端receive中返回,得到了服务器返回的响应数据,并且将数据打印在控制台上。

客户端在打印完毕之后也会进行下一次循环,等待用户再次从控制台输入信息。

补充 :

我们可以把写好的服务器代码,放到一个云服务器上,从而实现跨主机通信。

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

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

相关文章

兰亭妙微 | 医疗软件的界面设计能有多专业?

从医疗影像系统到手术机器人控制界面,从便携式病原体检测设备到多平台协同操作系统,兰亭妙微为众多医疗设备研发企业,打造了兼具专业性与可用性的交互界面方案。 我们不仅做设计,更深入理解医疗场景的实际需求: 对精…

前端原生构建交互式进度步骤组件(Progress Steps)

在现代网页设计中,进度步骤(Progress Steps) 是一种常见的 UI 模式,常用于引导用户完成注册流程、多步表单、教程或任何需要分步骤操作的场景。本文将带你从零开始构建一个美观且功能完整的 “进度步骤”组件,并详细讲…

【基于阿里云搭建数据仓库(离线)】Data Studio创建资源与函数

Data Studio支持在您的数据分析代码中引用自定义的资源和函数(支持MaxCompute、EMR、CDH、Flink),您需要先创建或上传资源、函数至目标工作空间,上传后才可在该工作空间的任务中使用。您可参考本文了解如何使用DataWorks可视化方式…

web3-以太坊智能合约基础(理解智能合约Solidity)

以太坊智能合约基础(理解智能合约/Solidity) 无需编程经验,也可以帮助你了解Solidity独特的部分;如果本身就有相应的编程经验如java,python等那么学起来也会非常的轻松 一、Solidity和EVM字节码 实际上以太坊链上储存…

【C++项目】负载均衡在线OJ系统-2

文章目录 oj_server模块编写oj_server框架的搭建-oj_server/oj_server.cpp 路由框架 oj_model模块编写题目信息设置v1.文件版本-common/util.hpp boost库spilt函数的使用-oj_server/oj_model_file.hpp 文件版本model编写v2.mysql数据库版本1.mysql创建授权用户、建库建表录入操…

GC1809:高性能24bit/192kHz音频接收芯片解析

1. 芯片概述 GC1809 是数字音频接收芯片,支持IEC60958、S/PDIF、AES3等协议,集成8选1输入切换、低抖动时钟恢复和24bit DAC,适用于家庭影院、汽车音响等高保真场景。 核心特性 高精度:24bit分辨率,动态范围105dB&…

2025年06月05日Github流行趋势

项目名称:onlook 项目地址url:https://github.com/onlook-dev/onlook项目语言:TypeScript历史star数:16165今日star数:1757项目维护者:Kitenite, drfarrell, spartan-vutrannguyen, apps/devin-ai-integrat…

基于BI PaaS架构的衡石HENGSHI SENSE平台技术解析:重塑企业级数据分析基座

在数据驱动决策的时代,传统BI工具日益显露出扩展性弱、灵活性差、资源利用率低等痛点。衡石科技推出的HENGSHI SENSE平台,创新性地采用BI PaaS(平台即服务)架构,为企业构建了一个强大、开放、可扩展的数据分析基础设施…

【R语言编程绘图-plotly】

安装与加载 在R中使用plotly库前需要安装并加载。安装可以通过CRAN进行,使用install.packages()函数。加载库使用library()函数。 install.packages("plotly") library(plotly)测试库文件安装情况 # 安装并加载必要的包 if (!requireNamespace("p…

通信刚需,AI联手ethernet/ip转profinet网关打通工业技术难关

工业人工智能:食品和饮料制造商的实际用例通信刚需 了解食品饮料制造商如何利用人工智能克服业务挑战 食品和饮料制造商正面临劳动力短缺、需求快速变化、运营复杂性加剧以及通胀压力等挑战。如今,生产商比以往任何时候都更需要以更少的投入实现更高的…

JavaEE->多线程:定时器

定时器 约定一个时间,时间到了,执行某个代码逻辑(进行网络通信时常见) 客户端给服务器发送请求 之后就需要等待 服务器的响应,客户端不可能无限的等,需要一个最大的期限。这里“等待的最大时间”可以用定时…

<el-table>构建树形结构

最佳实践 el-table实现树形结构主要依靠row-key和tree-props来实现的。 &#x1f4ab; 无论是el-table实现的树形结构还是el-tree组件都是绑定的树形结构的数据&#xff0c;因此如果数据是扁平的话&#xff0c;需要进行树化。 代码 <template><div><el-table:d…

linux——磁盘和文件系统管理

1、磁盘基础简述 1.1 硬盘基础知识 硬盘&#xff08;Hard Disk Drive&#xff0c;简称 HDD&#xff09;是计算机常用的存储设备之一. p如果从存储数据的介质上来区分&#xff0c;硬盘可分为机械硬盘&#xff08;Hard Disk Drive, HDD&#xff09;和固态硬盘&#xff08;Soli…

云原生 DevOps 实践路线:构建敏捷、高效、可观测的交付体系

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 一、引言&#xff1a;DevOps 与云原生的深度融合 在传统软件工程范式下&#xff0c;开发与运维之间存在天然的壁垒。开发希望尽快…

gateway 网关 路由新增 (已亲测)

问题&#xff1a; 前端通过gateway调用后端接口&#xff0c;路由转发失败&#xff0c;提示404 not found 排查&#xff1a; 使用 { "href":"/actuator/gateway/routes", "methods":[ "POST", "GET" ] } 命令查看路由列表&a…

Python 训练营打卡 Day 33-神经网络

简单神经网络的流程 1.数据预处理&#xff08;归一化、转换成张量&#xff09; 2.模型的定义 继承nn.Module类 定义每一个层 定义前向传播流程 3.定义损失函数和优化器 4.定义训练过程 5.可视化loss过程 预处理补充&#xff1a; 分类任务中&#xff0c;若标签是整…

如何有效删除 iPhone 上的所有内容?

“在出售我的 iPhone 之前&#xff0c;我该如何清除它&#xff1f;我担心如果我卖掉它&#xff0c;有人可能会从我的 iPhone 中恢复我的信息。” 升级到新 iPhone 后&#xff0c;你如何处理旧 iPhone&#xff1f;你打算出售、以旧换新还是捐赠&#xff1f;无论你选择哪一款&am…

AI大模型学习三十二、飞桨AI studio 部署 免费Qwen3-235B与Qwen3-32B,并导入dify应用

一、说明 ‌Qwen3-235B 和 Qwen3-32B 的主要区别在于它们的参数规模和应用场景。‌ 参数规模 ‌Qwen3-235B‌&#xff1a;总参数量为2350亿&#xff0c;激活参数量为220亿‌。‌Qwen3-32B‌&#xff1a;总参数量为320亿‌。 应用场景 ‌Qwen3-235B‌&#xff1a;作为旗舰模型&a…

操作系统中的设备管理,Linux下的I/O

1. I/O软件分层 I/O 层次结构分为五层&#xff1a; 用户层 I/O 软件设备独立性软件设备驱动程序中断处理程序硬件 其中&#xff0c;设备独立性软件、设备驱动程序、中断处理程序属于操作系统的内核部分&#xff0c;即“I/O 系统”&#xff0c;或称“I/O 核心子系统”。 2.用…

LabVIEW与Modbus/TCP温湿度监控系统

基于LabVIEW 开发平台与 Modbus/TCP 通信协议&#xff0c;设计一套适用于实验室环境的温湿度数据采集监控系统。通过上位机与高精度温湿度采集设备的远程通信&#xff0c;实现多设备温湿度数据的实时采集、存储、分析及报警功能&#xff0c;解决传统人工采集效率低、环境适应性…