Java网络编程API 1

article/2025/6/25 10:34:37

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;//客户端的IPprivate 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.hkcw.cn/article/fTRnoctYIo.shtml

相关文章

window ollama部署模型

注意去官网下载ollama,这个win和linux差别不大,win下载exe,linux用官网提供的curl命令 模型下载表:deepseek-r1 使用命令:Ollama API 交互 | 菜鸟教程 示例: 1.查看已加载模型: 2.文本生成接口 curl -X POST http://localhost:11434/v1/completions -H "Conte…

MySQL安装及启用详细教程(Windows版)

MySQL安装及启用详细教程(Windows版) 📋 概述 本文档将详细介绍MySQL数据库在Windows系统下的下载、安装、配置和启用过程。 📥 MySQL下载 官方下载地址 官方网站: https://dev.mysql.com/downloads/社区版本: https://dev.my…

vscode中的markdown表格列宽

vscode中的markdown表格列宽 当然,这个问题在csdn中应该是没有的,csdn是自适应文档页面的。这个自适应在vscode中好像不太好实现,至少目前我没能实现。 表格生成如上图,缩在一起,当然也会随着表格中录入数据自适应…

CangjieMagic 智能体框架嵌入式系统实测:以树莓派 4B 为例

目录 引言 CangjieMagic 对嵌入式开发板的要求 编译环境准备 本地编译 交叉编译 实战测试 程序编写 对cjpm.toml文件的修改 运行结果 结束语 引言 在人工智能与物联网技术飞速发展的今天,嵌入式系统作为连接物理世界与数字世界的桥梁,承担着越…

数值与字典解决方案二十七讲:两列数据相互去掉重复值后合并

《VBA数组与字典方案》教程(10144533)是我推出的第三套教程,目前已经是第二版修订了。这套教程定位于中级,字典是VBA的精华,我要求学员必学。7.1.3.9教程和手册掌握后,可以解决大多数工作中遇到的实际问题。…

林夏薇否认破产传闻,已发律师声明回应 正交律师处理

近期,TVB“视后”林夏薇被一家公司向香港高等法院申请破产。根据司法机构资料,案件延期至8月26日下午在高等法院提讯。对于破产一事,林夏薇回应称:“我丈夫Jason租住房子的公司有官司,正在上诉中,所以我莫名其妙,现在已交给律师处理,我也没有收到任何通知。”5月30日,…

下次放假安排已定可连休8天 国庆中秋合并假期

今天是端午节假期的最后一天,许多人已经开始期待下一次休假。根据国务院办公厅发布的2025年部分节假日安排通知,下一个长假将在四个月后的国庆节和中秋节期间。届时,国庆节与中秋节将合并放假,共8天。责任编辑:zx0176

24岁台大学霸暗网全球贩毒,3年海1亿多美元

24岁台大学霸暗网全球贩毒,3年海1亿多美元!据台媒报道,近日,美国联邦调查局(FBI)破获暗网毒品交易平台“隐身市场”,而该平台经营者“法老”的真实身份竟是24岁台湾大学资管系学生林睿庠。林睿庠因贩毒资产暴增,3年多其不法所得超过1亿美元(约7.2亿元人民币)。据悉,…

孙连城扮演者直播带货望远镜 从“宇宙区长”到带货主播

5月31日晚,曾在热播电视剧《人民的名义》中饰演“懒政区长孙连城”的演员李威,在短视频平台上开启直播带货,所售商品为天文望远镜。近一周内,李威已进行了三场关于望远镜的直播带货。6月1日上午,李威此前发布的带货直播预告视频疑似已被隐藏或删除,其商品橱窗中的天文望远…

互联网女皇340页AI报告猛料刷屏:AI增速创纪录

当地时间5月30日,玛丽米克尔发布了长达340页的“AI趋势报告”。报告指出,AI的发展速度前所未见,用户增长、使用量和资本支出均呈现出爆炸式增长,其影响力可能远超技术本身。玛丽米克尔是美国风险投资家,曾就职于摩根士丹利和凯鹏华盈,于2018年创立了自己的风投公司邦德资…

为何说中国“车圈恒大论”是误读 主流车企财务稳健

端午前夕,“车圈恒大论”再次成为业内焦点。5月30日,比亚迪集团品牌及公关处总经理李云飞通过社交媒体发布长文,首次正面回应被暗指为“汽车圈恒大”的流言,直言感觉好气又好笑,并列出财报数据逐一反驳,指出中国主流车企根本不存在所谓的“车圈恒大”。事情起源于前不久的…

关于免费版MQTT.fx 1.7.1下载与安装(附带下载链接)

MQTT.fx目前官网已经更新到5.x的版本,该版本需要付费使用,作为学习使用的话还是建议用免费的1.7.1版本,但是官网已经没有这个版本的安装包了,以下链接是我在网上找到的资料,该仓库仅用于学习交流,请勿用于商…

海外tk抓包简单暴力方式

将地址替换下面代码就可以 function hook_dlopen(module_name, fun) {var android_dlopen_ext Module.findExportByName(null, "android_dlopen_ext");if (android_dlopen_ext) {Interceptor.attach(android_dlopen_ext, {onEnter: function (args) {var pathptr …

编译原理实验 之 TINY 之 语义分析(第二次作业)

文章目录 本实验是接着前面的两个实验的基础上进行完善的,所以对于前面的实验如何操作?请看我的另外两个博客~ 编译原理 之 实验一 编译原理实验 之 Tiny C语言编译程序实验 语法分析 首先明确一下这次实验的任务? 主要目的:实现…

数组与元组:TypeScript 的基础

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 🍚 蓝桥云课签约作者、…

Transformer核心技术深度解析:多头注意力机制与架构精粹

一、多头注意力:模型理解的「多棱镜」 核心思想:并行化特征空间探索 传统注意力的局限:单一注意力机制如同单眼观察世界,只能捕捉单一维度的关联 多头机制的本质:为模型配备多组「认知透镜」,同时从不同子…

【C语言入门级教学】assert断⾔和指针的使用

文章目录 1.assert断⾔2.指针的使⽤和传址调⽤2.1 strlen的模拟实现2.2 传值调⽤和传址调⽤ 1.assert断⾔ assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。 a…

拓扑排序 + 深度优先搜索解决问题

如大家所知,使用深度优先搜索实现拓扑排序的总体思想是:对于一个特定节点,如果该节点的所有相邻节点都已经搜索完成,则该节点也会变成已经搜索完成的节点,在拓扑排序中,该节点位于其所有相邻节点的前面。一…

cnn训练并用grad-cam可视化

使用大米图片训练集,包含五个文件,分别是5种品牌的大米,使用cnn进行分类训练。 -Arborio/ :代表 Arborio 品种的大米图像数据,根据 Rice_Citation_Request.txt 文件可知,该数据集中包含 Arborio 品种的大米…

放弃 tsc+nodemon 使用 tsx 构建Node 环境下 TypeScript + ESM 开发环境搭建指南

放弃 tscnodemon 使用 tsx 构建Node 环境下 TypeScript ESM 开发环境搭建指南 目标 在 node 环境下构建 typescript esmodule模块 开发环境,这样可以使用 typescript 提供的类型安全和类型提示便利性。 我们要实现下面的效果 文件目录 src/index.ts 注意是 esmod…