6.网络编程套接字(上)

news2024/11/26 14:45:43

文章目录

  • 1.网络编程基础
    • 1.1为什么需要网络编程?——丰富的网络资源
    • 1.2什么是网络编程
    • 1.3网络编程中的基本概念
      • 1.3.1发送端和接收端
      • 1.3.2请求和响应
      • 1.3.3客户端和服务端
      • 1.3.4常见的客户端服务端模型
  • 2.Socket套接字
    • 2.1概念
    • 2.2分类
    • 2.3Java数据报套接字通信模型
    • 2.4Java流套接字通信模型
    • 2.5Socket编程注意事项
  • 3.UDP数据报套接字编程
    • 3.1DatagramSocket API
    • 3.2DatagramPacket API
    • 3.3InetSocketAddress API
    • 3.4示例一:一发一收(无响应)
      • 3.4.1UDP服务端
      • 3.4.2UDP客户端
    • 3.5示例二:请求响应
      • 3.5.1UDP服务端
      • 3.5.2UDP客户端
    • 3.6示例三:一发一收(有响应) - 根据信息返回对应的响应
      • 3.6.1UDP服务端
      • 3.6.2UDP客户端
    • 3.7客户端和服务端区别
    • 3.8客户端和服务端相互配合图解
    • 3.9服务器端口冲突异常

大家好,我是晓星航。今天为大家带来的是 网络编程套接字 相关的讲解!😀

1.网络编程基础

1.1为什么需要网络编程?——丰富的网络资源

用户在浏览器中,打开在线视频网站,如优酷看视频,实质是通过网络,获取到网络上的一个视频资源。

与本地打开视频文件类似,只是视频文件这个资源的来源是网络。

相比本地资源来说,网络提供了更为丰富的网络资源:

所谓的网络资源,其实就是在网络中可以获取的各种数据资源。

而所有的网络资源,都是通过网络编程来进行数据传输的。

1.2什么是网络编程

网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)。

当然,我们只要满足进程不同就行;所以即便是同一个主机,只要是不同进程,基于网络来传输数据, 也属于网络编程。

特殊的,对于开发来说,在条件有限的情况下,一般也都是在一个主机中运行多个进程来完成网络编 程。

但是,我们一定要明确,我们的目的是提供网络上不同主机,基于网络来传输数据资源:

  • 进程A:编程来获取网络资源
  • 进程B:编程来提供网络资源

1.3网络编程中的基本概念

1.3.1发送端和接收端

在一次网络数据传输时:

发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。

接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。

收发端:发送端和接收端两端,也简称为收发端。

注意:发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念。

1.3.2请求和响应

一般来说,获取一个网络资源,涉及到两次网络数据传输:

  • 第一次:请求数据的发送
  • 第二次:响应数据的发送

好比在快餐店点一份炒饭:

先要发起请求:点一份炒饭,再有快餐店提供的对应响应:提供一份炒饭

1.3.3客户端和服务端

服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务。

客户端获取服务的一方进程,称为客户端。

对于服务来说,一般是提供:

  • 客户端获取服务资源

  • 客户端保存资源在服务端

好比在银行办事:

  • 银行提供存款服务:用户(客户端)保存资源(现金)在银行(服务端)
  • 银行提供取款服务:用户(客户端)获取服务端资源(银行替用户保管的现金)

1.3.4常见的客户端服务端模型

最常见的场景,客户端是指给用户使用的程序,服务端是提供用户服务的程序:

  1. 客户端先发送请求到服务端
  2. 服务端根据请求数据,执行相应的业务处理
  3. 服务端返回响应:发送业务处理结果
  4. 客户端根据响应数据,展示处理结果(展示获取的资源,或提示保存资源的处理结果)

2.Socket套接字

至于为什么英译为插座,我们却将他用为网络编程的API我们不得而知…

可以认为 Socket api 是和传输层密切相关的

传输层里,提供了两个最核心的协议 UDP TCP

2.1概念

Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基 于Socket套接字的网络程序开发就是网络编程。

2.2分类

Socket套接字主要针对传输层协议划分为如下三类:

流套接字:使用传输层TCP协议

TCP,即Transmission Control Protocol(传输控制协议),传输层协议。

以下为TCP的特点(细节后续再学习):

  • 有连接
  • 可靠传输
  • 面向字节流
  • 有接收缓冲区,也有发送缓冲区
  • 大小不限

对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情 况下,是无边界的数据,可以多次发送,也可以分开多次接收。

数据报套接字:使用传输层UDP协议

UDP,即User Datagram Protocol(用户数据报协议),传输层协议。

以下为UDP的特点(细节后续再学习):

无连接

不可靠传输

面向数据报

有接收缓冲区,无发送缓冲区

大小受限:一次最多传输64k

对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如100个字节,必须一 次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节。

原始套接字

原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。

2.3Java数据报套接字通信模型

对于UDP协议来说,具有无连接,面向数据报的特征,即每次都是没有建立连接,并且一次发送全部数 据报,一次接收全部的数据报。

java中使用UDP协议通信,主要基于 DatagramSocket 类来创建数据报套接字,并使用 DatagramPacket 作为发送或接收的UDP数据报。对于一次发送及接收UDP数据报的流程如下:

以上只是一次发送端的UDP数据报发送,及接收端的数据报接收,并没有返回的数据。也就是只有请 求,没有响应。对于一个服务端来说,重要的是提供多个客户端的请求处理及响应,流程如下:

2.4Java流套接字通信模型

2.5Socket编程注意事项

  1. 客户端和服务端:开发时,经常是基于一个主机开启两个进程作为客户端和服务端,但真实的场 景,一般都是不同主机。
  2. 注意目的IP和目的端口号,标识了一次数据传输时要发送数据的终点主机和进程
  3. Socket编程我们是使用流套接字和数据报套接字,基于传输层的TCP或UDP协议,但应用层协议, 也需要考虑,这块我们在后续来说明如何设计应用层协议。
  4. 关于端口被占用的问题

如果一个进程A已经绑定了一个端口,再启动一个进程B绑定该端口,就会报错,这种情况也叫端 口被占用。对于java进程来说,端口被占用的常见报错信息如下:

此时需要检查进程B绑定的是哪个端口,再查看该端口被哪个进程占用。以下为通过端口号查进程的方式:

  • 在cmd输入 netstat -ano | findstr 端口号 ,则可以显示对应进程的pid。如以下命令显 示了8888进程的pid

图片

  • 在任务管理器中,通过pid查找进程

图片

解决端口被占用的问题

  • 如果占用端口的进程A不需要运行,就可以关闭A后,再启动需要绑定该端口的进程B
  • 如果需要运行A进程,则可以修改进程B的绑定端口,换为其他没有使用的端口。

3.UDP数据报套接字编程

无连接(发微信发qq信息)

不可靠传输(没有已读功能)

面向数据报 (数据传输则以一个个的"数据报"为基本单位。 一个数据报可能是若干个字节,带有一定的格式的)

全双工(一个通信通道,可以双向传输。 既可以发送又可以接收)- 一根网线,里面其实有 8 根线

共 8字节

2字节 源端口 发送方的端口

2字节 目的端口 接收方的端口

2字节 UDP 报文长度

2字节 校验和

发送方,把要接受的数据计算出校验和(checksum1)

接收方,收到的数据就把数据按照同样的方式再计算一次校验和(checksum2)

同时接收方也收到了 checksum1.接收方对比 checksum1 和 checksum2 是否相同。

检验和:作用是验证传输的数据是否是正确的。(网络传输,本质上就是光信号/电信号,他们可能受到一些物理环境的影响[电场/磁场/高能射线])

3.1DatagramSocket API

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

用他来表示一个 socket 对象。

DatagramSocket 构造方法:

port - 是一个端口号(简单的整数)

第二个构造方法中传入 port 就是让当前的 socket 对象和这个指定的端口,关联起来。

而第一个构造方法中没有指定端口号,此时系统则会自动分配一个空闲的端口。

DatagramSocket 方法:

第一个方法 receive 传入的相当于是一个空对象。receive 方法内部,会对参数的这个空对象进行内容填充,从而构造出结果数据了。参数也是一个"输出型参数"。

第三个方法 close 是用来释放资源的,用完之后要记得关闭。

3.2DatagramPacket API

UDP 来说,传输数据的基本单位,DatagramPacket。

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

用它来表示 UDP 中传输的一个报文。

DatagramPacket 构造方法:

第一个构造方法中的 buf 参数相当于是把 buf 这个缓冲区给设置进去了,length用来接收指定长度。

第二个构造方法,相当于 构造缓冲区buf + 地址address,构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。

DatagramPacket 方法:

构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建。

3.3InetSocketAddress API

InetSocketAddressSocketAddress 的子类 )构造方法:

3.4示例一:一发一收(无响应)

以下为一个客户端一次数据发送,和服务端多次数据接收(一次发送一次接收,可以接收多次),即只有客户端请求,但没有服务端响应的示例:

3.4.1UDP服务端

服务器的工作流程:

  1. 读取请求并解析
  2. 根据请求计算响应
  3. 构造响应并写回客户端
package network;

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

//UDP 版本的回服务器
public class UdpEchoServer {
    //网络编程,本质上是要操作网卡
    //但是网卡不方便直接操作,在操作系统内核中,使用了一种特殊的叫做"socket"这样的文件来抽象表示网卡
    //因此进行网络通信,是必须要先有一个 socket 对象。
    private DatagramSocket socket = null;
    //对于服务器来说,创建 socket 对象的同时,要让他绑定上一个具体的端口号。
    //服务器一定要关联上一个具体的端口的!!!
    //服务器是网络传输中,被动的一方,如果是操作系统随机分配的端口,此时客户端就不知道端口是啥,也就无法进行通讯了。
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        //服务器不是只给一个客户端提供服务,需要服务很多客户端!
        while (true) {
            //只要有客户端过来,就可以提供服务。
            //1.读取客户端发来的请求是啥
            //receive 方法的参数是一个输出型参数,需要先构造好一个空白的 DatagramPacket 对象,交给 receive 来进行填充。
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            //此时这个 DatagramPacket 是一个特殊的对象,并不方便直接进行处理,可以把这里包含的数据拿出来,构造成一个字符串。
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            //2.根据请求来计算响应,由于此处是回显服务器,响应和请求相同
            String response = process(request);
            //3.把响应写回客户端,send 的参数也是 DatagramPacket,需要把这个 Packet 对象构造好。
            //  此处构造的响应对象,不能是用空的字节数组构造了,而是要使用响应数据来构造
            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().toString(),
                    requestPacket.getPort(), request, response);
        }
    }

    //这个方法就表示 "根据请求来计算响应"
    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        //端口号的指定可以随便指定,在1024 -> 65535 这个范围里随便挑个数字就行
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }
}

运行后,服务端就启动了,控制台输出如下:

可以看出,此时代码是阻塞等待在 socket.receive(packet) 代码行,直到接收到一个UDP数据报。

在客户端输入字符串 “hello world” 之后:

这里的数字 是我们客户端的 IP 和 端口号,此处 IP 是环回 IP ,这里的端口是系统随机分配的。

对于客户端服务器程序来说一个服务器要给很多客户提供服务的,咱们也就可以需要构造出多个客户端来进行测试。

但是我们idea是不支持多个客户端同时启动的,这时我们可以这样设置:

多个客户端运行时客户端的截图:

多个客户端运行时服务器的截图:

在客户端输入 exit 后:

可以发现虽然我们客户端退出了,但是我们服务端是会继续保持在线的,因为我们要确保每一个用户在客户端登陆的时候我们都能为其提供服务。

代码解析:

new DatagramPacket 相当于刚开始给他一个空白的纸条,客户写好之后,再把纸条给老板,老板根据纸条写的需求来做食物。

receive 内部会针对 参数 对象填充数据 ,填充的数据来自于网卡。

第一个参数为 传响应对象的这个字节数组

第二个参数为指定长度,此处这个长度,使用 response.length() 是否可以? 是不是必须写作 response.getByte().length ?

答:不可以,response.length() 是计算字符个数,而 response.getByte().length 是在算字节个数

那么为什么我们这里一定要返回字节的个数呢,字符不行吗?

答:不行,因为我们这里的 DatagramPacket 不认识字符,只认识字节!!!

最后的 requestPacket.getSocketAddress() 这个参数是用来 获取到客户端 ip 和 端口号。(这两个信息本身就在 requestpacket 中)

对应API解释参数:

会快速进行循环,每循环一次,就处理一次请求。

如果客户端发来了请求,receive就能顺利读出来

如果客户端没有发请求,此时receive就会阻塞!!!(和Scanner操作类似,取决于用户啥时候输入数据(请求) )

如果有很多客户端,并且请求发的很快,那么此时我们的这个 while 循环会不会因为客户端请求太多,处理不过来了呢?

答:是会的,我们此时使用多线程,更充分的调动计算机的硬件资源,多加机器(分布式),但是相应的消耗的硬件资源也会增多。

第一个参数:获取到 packet 里面的 ip

第二个参数:获取到里面的端口

注意:

  1. 服务器不可以是随机端口,需要是固定端口,这样才方便客户端找到我。

3.4.2UDP客户端

package org.example.udp.demo1;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class UdpClient {
    // 服务端socket地址,包含域名或IP,及端口号
    private static final SocketAddress ADDRESS = new
InetSocketAddress("localhost", 8888);
    public static void main(String[] args) throws IOException {
        // 4.创建客户端DatagramSocket,开启随机端口就行,可以发送及接收UDP数据报
        DatagramSocket socket = new DatagramSocket();
        // 5-1.准备要发送的数据
        byte[] bytes = "hello world!".getBytes();
        // 5-2.组装要发送的UDP数据报,包含数据,及发送的服务端信息(服务器IP+端口号)
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length, 
ADDRESS);
        // 6.发送UDP数据报
        socket.send(packet);
   }
}

客户端启动后会发送一个"hello world!" 的字符串到服务端,在服务端接收后,控制台输出内容如下:

---------------------------------------------------
等待接收UDP数据报...
客户端IP127.0.0.1
客户端端口号:57910
客户端发送的原生数据为:[104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 
0, 0, 0, ...此处省略很多0]
客户端发送的文本数据为:hello world!                                                 
                                                                                
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
   
---------------------------------------------------
等待接收UDP数据报...

从以上可以看出,发送的UDP数据报(假设发送的数据字节数组长度为M),在接收到以后(假设接收 的数据字节数组长度为N):

  1. 如果N>M,则接收的byte[]字节数组中会有很多初始化byte[]的初始值0,转换为字符串就是空白 字符;
  2. 如果N<M,则会发生数据部分丢失(可以自己尝试,把接收的字节数组长度指定为比发送的字节数组长度更短)。

要解决以上问题,就需要发送端和接收端双方约定好一致的协议,如规定好结束的标识或整个数据的长 度。

自己版本的UDP客户端:

package network;

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

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIp = null;
    private int serverPort = 0;
    //一次通讯,需要有两个ip,两个端口
    //客户端的 ip 是 127.0.0.1 已知
    //客户端的 port 是系统自动分配的
    //服务器 ip 和 端口 也需要告诉客户端,才能顺利把消息发给服务器
    public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
        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();
            if (request.equals("exit")) {
                System.out.println("goodbye");
                break;
            }
            //2.构造成 UDP 请求,并发送
            //构造这个 Packet 的时候,需要把 serverIp 和 port 都传入过来, 但是此处 IP 地址需要填写的是一个32位的整数形式
            //上述的 IP 地址是一个字符串,需要使用 InetAddress.getByName 来进行一个转换
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIp),serverPort);
            socket.send(requestPacket);
            //3.读取服务器的 UDP 响应,并解析
            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 {
        //端口号的指定可以随便指定,在1024 -> 65535 这个范围里随便挑个数字就行
        UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
        client.start();
    }
}

启动之后:

输入字符串"helloworld" 之后:

在客户端输入了 exit 后:

可以看到我们输入了 exit 之后,我们客户端就退出了程序。

多个客户端一起运行时:

多个客户端运行时服务器的截图:

代码解析:

对于服务器发给客户端这个操作来说。服务器的端口就是源端口,客户端的端口就是目的端口。

对于客户端发给服务器的操作来说,服务器的端口就是目的端口,客户端的端口就是源端口。

构造这个 Socket 对象,不需要显示绑定一个端口。(让操作系统自动分配一个端口)

和我们这里的 服务端 对比就可以发现我们服务端是需要手动绑定一个端口的而客户端来说,端口可以是系统分配的。

3.5示例二:请求响应

示例一只是客户端请求和服务端接收,并没有包含服务端的返回响应。以下是对应请求和响应的改造:

构造一个展示服务端本地某个目录(BASE_PATH)的下一级子文件列表的服务

(1)客户端先接收键盘输入,表示要展示的相对路径(相对BASE_PATH的路径)

(2)发送请求:将该相对路径作为数据报发送到服务端

(3)服务端接收并处理请求:根据该请求数据,作为本地目录的路径,列出下一级子文件及子文件夹

(4)服务端返回响应:遍历子文件和子文件夹,每个文件名一行,作为响应的数据报,返回给客户端

(5)客户端接收响应:简单的打印输出所有的响应内容,即文件列表。

为了解决空字符或长度不足数据丢失的问题,客户端服务端约定好统一的协议:这里简单的设计为 ASCII结束字符 \3 表示报文结束。

以下为整个客户端服务端的交互执行流程:

3.5.1UDP服务端

package org.example.udp.demo2;
import java.io.File;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.nio.charset.StandardCharsets;
public class UdpServer {
    //服务器socket要绑定固定的端口
    private static final int PORT = 8888;
    //本地文件目录要展示的根路径
    private static final String BASE_PATH = "E:/TMP";
    public static void main(String[] args) throws IOException {
        // 1.创建服务端DatagramSocket,指定端口,可以发送及接收UDP数据报
        DatagramSocket socket = new DatagramSocket(PORT);
        //不停的接收客户端udp数据报
        while (true){
            // 2.创建数据报,用于接收客户端发送的数据
            byte[] requestData = new byte[1024];//1m=1024kb, 1kb=1024byte, UDP最64k(包含UDP首部8byte)
            DatagramPacket requestPacket = new DatagramPacket(requestData, 
requestData.length);
            System.out.println("------------------------------------------------
---");
            System.out.println("等待接收UDP数据报...");
            // 3.等待接收客户端发送的UDP数据报,该方法在接收到数据报之前会一直阻塞,接收到数
据报以后,DatagramPacket对象,包含数据(bytes)和客户端ip、端口号
            socket.receive(requestPacket);
System.out.printf("客户端IP:%s%n", 
requestPacket.getAddress().getHostAddress());
            System.out.printf("客户端端口号:%s%n", requestPacket.getPort());
            // 7.接收到的数据作为请求,根据请求数据执行业务,并返回响应
            for (int i = 0; i < requestData.length; i++) {
                byte b = requestData[i];
                if(b == '\3') {
                    // 7-1.读取请求的数据:读取到约定好的结束符(\3),取结束符之前的内容
                    String request = new String(requestData, 0, i);
                    // 7-2.根据请求处理业务:本地目录根路径+请求路径,作为要展示的目录,列
出下一级子文件
                    //请求的文件列表目录
                    System.out.printf("客户端请求的文件列表路径为:%s%n", BASE_PATH +
request);
                    File dir = new File(BASE_PATH + request);
                    //获取下一级子文件,子文件夹
                    File[] children = dir.listFiles();
                    // 7-3.构造要返回的响应内容:每个文件及目录名称为一行
                    StringBuilder response = new StringBuilder();
                    if(children != null){
                        for (File child : children) {
                            response.append(child.getName()+"\n");
                       }
                   }
                    //响应也要约定结束符
                    response.append("\3");
                    byte[] responseData =
response.toString().getBytes(StandardCharsets.UTF_8);
                    // 7-4.构造返回响应的数据报DatagramPacket,注意接收的客户端数据报包IP和端口号,要设置到响应的数据报中
                    DatagramPacket responsePacket = new
DatagramPacket(responseData, responseData.length, 
requestPacket.getSocketAddress());
                    // 7-5.发送返回响应的数据报
                    socket.send(responsePacket);
                    break;
               }
           }
       }
   }
}

以上服务端运行结果和示例一是一样的:

---------------------------------------------------
等待接收UDP数据报...

3.5.2UDP客户端

package org.example.udp.demo2;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class UdpClient {
    // 服务端socket地址,包含域名或IP,及端口号
    private static final SocketAddress ADDRESS = new
InetSocketAddress("localhost", 8888);
    public static void main(String[] args) throws IOException {
        // 4.创建客户端DatagramSocket,开启随机端口就行,可以发送及接收UDP数据报
        DatagramSocket socket = new DatagramSocket();
        // 5-1.准备要发送的数据:这里调整为键盘输入作为发送的内容
        Scanner scanner = new Scanner(System.in);
        while(true){
            System.out.println("------------------------------------------------
---");
            System.out.println("请输入要展示的目录:");
            // 5-2.每输入新行(回车),就作为UDP发送的数据报,为了接收端获取有效的内容(去除
空字符串),约定\3为结束
            String request = scanner.nextLine() + "\3";
            byte[] requestData = request.getBytes(StandardCharsets.UTF_8);
            // 5-3.组装要发送的UDP数据报,包含数据,及发送的服务端信息(服务器IP+端口号)
            DatagramPacket requestPacket = new DatagramPacket(requestData, 
requestData.length, ADDRESS);
            // 6.发送UDP数据报
            socket.send(requestPacket);
            // 8.接收服务端响应的数据报,并根据响应内容决定下个步骤(我们这里简单的打印即可)
            // 8-1.创建数据报,用于接收服务端返回(发送)的响应
            byte[] responseData = new byte[1024];
            DatagramPacket responsePacket = new DatagramPacket(responseData, 
responseData.length);
            // 8-2.接收响应数据报
            socket.receive(responsePacket);
            System.out.println("该目录下的文件列表为:");
            // byte[]下次解析的起始位置
            int next = 0;
            for (int i = 0; i < responseData.length; i++) {
                byte b = responseData[i];
                if(b == '\3')//结束符退出
                    break;
                if(b == '\n'){//换行符时进行解析
                    //起始位置到换行符前一个索引位置为要解析的内容
                    String fileName = new String(responseData, next, i-next);
                    System.out.println(fileName);
                    //下次解析从换行符后一个索引开始
                    next = i+1;
               }
           }
       }
   }
}

客户端启动后会等待输入要展示的路径:

---------------------------------------------------
请输入要展示的目录:

在输入想查看的目录路径后,会接收并打印服务端响应的文件列表数据:

---------------------------------------------------
请输入要展示的目录:
/
该目录下的文件列表为:
1
2
60441b1b8a74be3695ccc0d970693815
8f25103aa249707ee4ab17635142cd0e
---------------------------------------------------
请输入要展示的目录:

此时服务端也会打印接收到的客户端请求数据:

---------------------------------------------------
等待接收UDP数据报...
客户端IP127.0.0.1
客户端端口号:57297
客户端请求的文件列表路径为:E:/TMP/
---------------------------------------------------
等待接收UDP数据报...

3.6示例三:一发一收(有响应) - 根据信息返回对应的响应

3.6.1UDP服务端

package network;

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

//UDP 版本的回服务器
public class UdpEchoServer {
    //网络编程,本质上是要操作网卡
    //但是网卡不方便直接操作,在操作系统内核中,使用了一种特殊的叫做"socket"这样的文件来抽象表示网卡
    //因此进行网络通信,是必须要先有一个 socket 对象。
    private DatagramSocket socket = null;
    //对于服务器来说,创建 socket 对象的同时,要让他绑定上一个具体的端口号。
    //服务器一定要关联上一个具体的端口的!!!
    //服务器是网络传输中,被动的一方,如果是操作系统随机分配的端口,此时客户端就不知道端口是啥,也就无法进行通讯了。
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        //服务器不是只给一个客户端提供服务,需要服务很多客户端!
        while (true) {
            //只要有客户端过来,就可以提供服务。
            //1.读取客户端发来的请求是啥
            //receive 方法的参数是一个输出型参数,需要先构造好一个空白的 DatagramPacket 对象,交给 receive 来进行填充。
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            //此时这个 DatagramPacket 是一个特殊的对象,并不方便直接进行处理,可以把这里包含的数据拿出来,构造成一个字符串。
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            //2.根据请求来计算响应,由于此处是回显服务器,响应和请求相同
            String response = process(request);
            //3.把响应写回客户端,send 的参数也是 DatagramPacket,需要把这个 Packet 对象构造好。
            //  此处构造的响应对象,不能是用空的字节数组构造了,而是要使用响应数据来构造
            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().toString(),
                    requestPacket.getPort(), request, response);
        }
    }

    //这个方法就表示 "根据请求来计算响应"
    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        //端口号的指定可以随便指定,在1024 -> 65535 这个范围里随便挑个数字就行
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }
}
package network;

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

//对于我们的 DictServer 来说,和 EchoServer 相比,大部分的东西都是一样的
//主要是 "根据请求计算响应" 这个步骤不太一样。
public class UdpDictServer extends UdpEchoServer{
    private Map<String, String> dict = new HashMap<>();
    public UdpDictServer(int port) throws SocketException {
        super(port);

        //给这个 dict 设置点内容
        dict.put("cat","小猫");
        dict.put("dog","小狗");
        dict.put("duck","小鸭");
        dict.put("pig","小猪");
        //当然这里可以无限多的设置键值对......

    }
    @Override
    public String process (String request) {
        //查词典的过程。
        return dict.getOrDefault(request,"当前单词没有查到结果!");
    }

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

由于我们之前的示例一已经谢过了一遍,因此我们这里直接继承 UdpEchoServer 然后重写一下peocess方法和构造方法即可!!!

服务器运行结果图:

3.6.2UDP客户端

package network;

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

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIp = null;
    private int serverPort = 0;
    //一次通讯,需要有两个ip,两个端口
    //客户端的 ip 是 127.0.0.1 已知
    //客户端的 port 是系统自动分配的
    //服务器 ip 和 端口 也需要告诉客户端,才能顺利把消息发给服务器
    public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
        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();
            if (request.equals("exit")) {
                System.out.println("goodbye");
                break;
            }
            //2.构造成 UDP 请求,并发送
            //构造这个 Packet 的时候,需要把 serverIp 和 port 都传入过来, 但是此处 IP 地址需要填写的是一个32位的整数形式
            //上述的 IP 地址是一个字符串,需要使用 InetAddress.getByName 来进行一个转换
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIp),serverPort);
            socket.send(requestPacket);
            //3.读取服务器的 UDP 响应,并解析
            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 {
        //端口号的指定可以随便指定,在1024 -> 65535 这个范围里随便挑个数字就行
        UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
        client.start();
    }
}

这里我们的客户端和示例一的客户端代码是一模一样的,所以直接复制过来了。

由上图可以看到,我们客户端输入对应的英文,则会返回他们对应的翻译出来。

3.7客户端和服务端区别

1.定义不同

客户端与服务器相对应,为客户提供本地服务的程序,而服务器是指网络中能对其他机器提供某些服务的计算机系统。

2.程序编写不同

客户端程序通常不需要我们编写,可以使用浏览器等现成的程序。而服务器端则需要编写相应的服务端程序。

3.组成不同

客户端通常只需要一个浏览器等软件,而服务器端则由中央处理器、内存、芯片组、I/O总线、I/O设备、电源、机箱和相关软件等组成。

4.储存方式不同

客户端通常不需要储存数据,而服务器端需要储存大量的数据。部署服务器端闪存的方式包括SAS和SATA、PCIe闪存卡、NVMe闪存和双列直插式内存插槽等多种方式。

5.服务对象不同

客户端是使用服务的对象,而服务器端则是为客户端提供服务的对象。

3.8客户端和服务端相互配合图解

服务器:

客户端:

3.9服务器端口冲突异常

故名思意就是 一个端口被两个服务器同时抢占,那么此时必有一个服务器会阻塞等待抛出异常。

此时我们有 UdpDictServerUdpEchoServer 两个服务其同时抢占9090端口导致 我们的 UdpEchoServer 端口冲突抛出异常。

这里我们圈出的异常就是告诉我们此时出现了 服务器端口冲突。

Address 表示的含义为 “绑定IP + 端口”

感谢各位读者的阅读,本文章有任何错误都可以在评论区发表你们的意见,我会对文章进行改正的。如果本文章对你有帮助请动一动你们敏捷的小手点一点赞,你的每一次鼓励都是作者创作的动力哦!😘

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

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

相关文章

HTTPS协议详解:基本概念与工作原理

个人主页&#xff1a;insist--个人主页​​​​​​ 本文专栏&#xff1a;网络基础——带你走进网络世界 本专栏会持续更新网络基础知识&#xff0c;希望大家多多支持&#xff0c;让我们一起探索这个神奇而广阔的网络世界。 目录 一、HTTPS协议的基本概念 二、为什么需要HTTP…

三相PMSM的坐标变换

三相PMSM的坐标变换 三相PMSM的数学模型具有复杂性和耦合性的多变量系统。因此需要对其进行降阶和解耦变换。 Vα&#xff0c;Vb&#xff0c;Vc是自然坐标系。 Vα&#xff0c;Vβ是静止坐标系。 Vd&#xff0c;Vq是同步旋转坐标系。 自然坐标系 三相永磁同步电机的驱动电路…

PixelSNAIL论文代码学习(1)——总体框架和平移实现因果卷积

文章目录 引言正文目录解析README.md阅读Setup配置Training the model训练模型Pretrained Model Check Point预训练的模型训练方法 train.py文件的阅读model.py文件阅读h12_noup_smallkey_spec模型定义_base_noup_smallkey_spec模型实现一、定义因果卷积过程通过平移实现因果卷…

PCIe DL_Feature详解

DL_Feature的引入 Data Link Control and Management State Machine在PCIe Gen4引入了DL_Feature这个状态&#xff0c;该状态主要用来协商PCIe link 两端是否支持新的DL Feature&#xff0c;目前为止DL Feature只引入了Scaled Flow Control 来提高Gen4及以上的效率。   DL_Fe…

Qt 简单闹钟

//wiget.h#ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTime> //时间类 #include <QTimer> //定时器类 #include <QTextToSpeech> #include <QDebug> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPA…

YoloV8改进策略:轻量级Slim Neck打造极致的YoloV8

文章目录 摘要Yolov8官方结果源码改进方法测试结果总结摘要 论文链接:https://arxiv.org/ftp/arxiv/papers/2206/2206.02424.pdf 作者研究了增强 CNN 学习能力的通用方法,例如 DensNet、VoVNet 和 CSPNet,然后根据这些方法的理论设计了 Slim-Neck 结构。 使用轻量级卷积…

机械制图(CAD)

目录 第一课&#xff08;80分钟&#xff09; 第二课&#xff08;80分钟&#xff09; 力啥学机械制图&#xff1f;我们的工厂要加工机械&#xff0c;而加工机械零件前&#xff0c;我们要先给工人图纸来看,工人才知道该怎样加工&#xff0c;所以我们今天就来学习下怎样画出符何…

任意文件读取和漏洞复现

任意文件读取 1. 概述 一些网站的需求&#xff0c;可能会提供文件查看与下载的功能。如果对用户查看或下载的文件没有限制或者限制绕过&#xff0c;就可以查看或下载任意文件。这些文件可以是漂代码文件&#xff0c;配置文件&#xff0c;敏感文件等等。 任意文件读取会造成&…

电脑开机操作系统引导过程

电脑开机时操作系统引导&#xff1a; CPU首先执行主存中ROM中的引导程序。 将磁盘的第一块——主引导记录(MBR)读入到内存中&#xff0c;执行磁盘引导程序&#xff0c;扫描分区表。 在主分区(C盘)执行分区引导程序。 在C盘根目录下找到完整的操作系统初始化程序并执行。

系统虚拟机(VM)

系统虚拟机&#xff1a;将一台物理机器虚拟化为多台虚拟机&#xff0c;每个虚拟机上都可以运行一个独立的操作系统&#xff0c;由虚拟机管理程序(VMM)来管理。 第一种直接运行在硬件上&#xff0c;可以直接分配物理资源&#xff0c;性能更好&#xff0c;可支持更多的虚拟机&am…

ModaHub魔搭社区专访百度智能云李莅:企业需要的是一款企业级向量数据库还是向量引擎?

ModaHub魔搭社区&#xff1a;虽然您认为它是刚需&#xff0c;但也有人认为我可能不是需要一款企业级的向量数据库&#xff0c;而是需要一个向量引擎。我可以在传统的数据库上加一个向量引擎&#xff0c;然后它就变成了一款向量数据库&#xff0c;您觉得他们这样的想法是可行的吗…

布隆过滤器Moudule安装

Redis帮我们实现了布隆过滤器算法&#xff0c;可以通过module方式安装&#xff0c;直接使用&#xff0c;其实可以自己实现这样的算法。 安装遇到的问题&#xff0c;通过官网下载的zip或者git下载的源码&#xff0c;都是master版本&#xff0c;make会出问题&#xff0c;报错文件…

springboot初试elasticsearch

引入依赖 elasticsearch的依赖版本与你elasticsearch要一致 <dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId> </dependency> 索引库的操作 创建索引库 impo…

stack和queue容器

1 stack 基本概念 概念&#xff1a;stack是一种先进后出(First In Last Out,FILO)的数据结构&#xff0c;它只有一个出口 栈中只有顶端的元素才可以被外界使用&#xff0c;因此栈不允许有遍历行为 栈中进入数据称为 — 入栈 push 栈中弹出数据称为 — 出栈 pop 2 stack 常用…

Linux——进程间信号(超级详解!!)

索引 一.初始信号1.什么是信号2.前后台进程3.信号的种类4.信号的管理 二.信号产生前1.验证键盘是可以产生信号的2.通过系统调用接口发送信号3.由软件条件产生信号4.硬件异常产生信号5.总结6.core dump 信号产生中1.信号在内核中的表示2.信号集操作函数 信号产生后1.了解内核态和…

Java Web3J :使用web3j监听、查询、订阅智能合约的事件

前面有文章写如何使用Docker-compose方式部署blockscout浏览器+charts图表,区块链浏览器已经部署成功了,同时我们在链上增加了治理投票流程,如何实时的把治理事件快速同步到浏览器呢?这时就想到了Web3J来监听智能合约的事件,来达到同步事件的效果 目录 Web3J简介功能简介m…

lv3 嵌入式开发-4 linux shell命令(进程管理、用户管理)

目录 1 进程处理相关命令 1.1 进程的概念 1.2 查看进程的命令 1.3 发送信号命令 2 用户管理相关命令 2.1 用户管理相关文件介绍 2.2 用户管理相关命令介绍 1 进程处理相关命令 1.1 进程的概念 进程的概念主要有两点&#xff1a; 进程是一个实体。每一个进程都有它自己…

【计算机组成原理】第一章 计算机概述

1.冯诺依曼结构&#xff1a; 计算机由运算器、控制器、存储器、输入设备、输出设备五大部件组成 运算器和控制器称为CPU&#xff1b;CPU和存储器称为计算机主机&#xff1b;其余输入、输出设备、外存储器称为计算机外部设备采用二进制表示数据和指令 指令由操作码&#xff08;…

20230831-完成登录框的按钮操作,并在登录成功后进行界面跳转

登录框的按钮操作&#xff0c;并在登录成功后进行界面跳转 app.cpp #include "app.h" #include <cstdio> #include <QDebug> #include <QLineEdit> #include <QLabel> #include <QPainter> #include <QString> #include <Q…

golang 通用的 grpc http 基础开发框架

go-moda golang 通用的 grpc http 基础开发框架仓库地址: https://github.com/webws/go-moda仓库一直在更新,欢迎大家吐槽和指点 特性 transport: 集成 http&#xff08;echo、gin&#xff09;和 grpc。tracing: openTelemetry 实现微务链路追踪pprof: 分析性能config: 通用…