网络编程2—— UDP Socket实现的 客户端服务器通信完整代码(详细注释帮你快速理解)


前言

本人是一个刚刚上路的IT新兵,菜鸟!分享一点自己的见解,如果有错误的地方欢迎各位大佬莅临指导,如果这篇文章可以帮助到你,劳请大家点赞转发支持一下!

今天分享的内容是UDP数据报套接字实现的客户端与服务器,一定要理解 DatagramSocket,DatagramPacket 这两个类的作用以及方法,十分有助于你理解服务器,客户端代码。


一、理论准备

Socket套接字是什么

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

程序猿👨‍💻编写网络程序,主要编写的是 应用层的程序代码 ,但是真正想要发送或接收数据,都是要 通过应用层调用传输层

因此传输层就为应用层(为我们编写代码)提供了一组api统称为
Socket api

简单来说,这一组api是提供给咱们 编写网络程序使用的接口 用来发送 / 接收网络数据使用的接口

Socket套接字主要针对传输层协议划分为如下三类:
1️⃣ 数据报套接字:使用传输层UDP协议 (本文重点讲解)
2️⃣ 流套接字:使用传输层TCP协议 (下篇文章重点讲解)
3️⃣原始套接字(不做介绍)


UDP协议的特点

特点说明
无连接不刻意保存对端的相关信息
不可靠传输不关心对端是否收到数据,不关注传输数据是否成功
面向数据报以一个UDP数据报为基本单位
有接收缓冲区,无发送缓冲区后续文章介绍
大小受限一次最多传输64k
全双工一条通信路径,双向通信。(可以同时发送和接收数据)

二、UDP数据报套接字 提供的API

DatagramSocket API

DatagramSocket 是UDP的Socket套接字, 用于发送和接收UDP数据报

DatagramSocket对象可以理解为一个遥控器,指挥网卡设备实施各种操作

DatagramSocket构造方法方法说明
DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)
DatagramSocket(intport)创建一个UDP数据报套接字的Socket,绑定到本机指定的端口号(intport)(一般用于服务端)
DatagramSocket方法方法说明
void receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacketp)从此套接字发送数据报包(不会阻塞等待,直接发送)
void close()关闭此数据报套接字

DatagramPacket API

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

DatagramPacket对象是 数据报

DatagramPacket构造方法方法说明
DatagramPacket(byte[] buf, int length)构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length)
DatagramPacket(byte[]buf, int offset, int length,SocketAddress address)构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从offset(第二个参数offset)下标开始,数据报中的数据大小即共接收length个字节(第三个参数length)。address(第四个参数address)指定目的主机的IP和端口号
DatagramPacket方法方法说明
InetAddress getAddress()从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
int getPort()从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
byte[] getData()获取数据报中的数据

三、代码实现请求响应式 客户端服务器

服务器

服务器大致就分为三个功能。
1️⃣ 读取解析客户端发来的请求
2️⃣ 根据请求计算影响
3️⃣ 把响应结果写回客户端

下面代码中一步一步实现了这三个功能,并配有详细的注释帮你快速理解

核心思路:
1️⃣核心成员属性DatagramSocket socket(网卡的遥控器)。
2️⃣构造方法要指定端口号进行绑定,便于客户端连接。
3️⃣start()方法服务器的主逻辑,负责读取请求,然后通过process(String request)方法得到响应并返回给客户端。
4️⃣start()方法

读取功能 :通过构造一个新的空的数据报来接收客户端发来的数据,并从这个数据报中可以获得客户端的IP地址与端口号。

发送功能 :将响应字符串与客户端的IP地址与端口号构造成一个新的数据报进行发送。

// 服务器
public class UdpEchoServer {
    // 需要先定义一个socket对象
    // 网络通信,必须要使用socket对象
    private DatagramSocket socket = null;

    // 绑定一个端口号不一定会成功
    // 如果该端口号已被别的进程占用或绑定,此时的绑定操作就会抛出异常
    // 同一个主机,一个端口号,同一时刻,只能被一个进程绑定
    public UdpEchoServer(int port) throws SocketException {
        // 构造socket的同时指定要关联/绑定的端口号
        // socket对象==遥控器,与网卡进行交互的遥控器
        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);
            
            System.out.println("客户端上线");
            //    为了方便处理请求,将请求转换为字符串
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());


            // 2.根据请求计算响应
            String response = process(request);

            // 3.把响应结果写回到客户端
            // 根据response字符串构造一个数据报
            // 和请求 packet 不同, 此处构造响应数据报的时候, 需要指定这个包要发给谁.
            DatagramPacket responsePacker = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    // requestPacket 是从客户端这里收来的. getSocketAddress 就会得到客户端的 ip 和 端口
                    requestPacket.getSocketAddress());

            // 发送到客户端
            socket.send(responsePacker);

            // 分别打印:ip,端口号,请求内容,响应内容
            System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }

    }

    // 这个方法是根据请求计算响应
    // 有具体业务,就写相关逻辑
    // 这是服务器中的重要环节
    public String process(String request) {
        // 测试客户端与服务器是否网络通信成功
        // 并无逻辑需求
        // 直接将请求作为响应返回
        // 证明通信成功
        return request;
    }

    // 运行main方法,启动服务器
    public static void main(String[] args) throws IOException {
        // 实例化服务器对象
        UdpEchoServer udpEchoServer = new UdpEchoServer(9090);
        // 启动主逻辑
        udpEchoServer.start();
    }
}

客户端

客户端大致就分为三个功能。
1️⃣ 读取用户输入的请求
2️⃣ 将请求转换为UDP数据报并发送至服务器
3️⃣ 尝试读取服务器的响应
4️⃣ 将响应转换为字符串并打印

下面代码中一步一步实现了这四个功能,并配有详细的注释帮你快速理解

核心思路:
核心成员属性DatagramSocket socket。
与服务器的思路大致相同,不过客户端的构造方法中要指明服务器的IP地址与端口号。

// 客户端
public class UdpEchoClient {
    // 需要先定义一个socket对象
    // 网络通信,必须要使用socket对象
    private DatagramSocket socket = null;
    private String serverIP;// 服务器IP
    private int serverPort;// 服务器端口号

    public UdpEchoClient(String serverIP, int serverPort) throws SocketException {
        // 对于客户端来说, 不需要显示关联端口.
        // 不代表没有端口, 而是系统自动分配了个空闲的端口.
        this.socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }

    // 客户端主逻辑
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        // 通过这个客户端可以多次和服务器进行交互.
        while (true) {
            // 1. 先从控制台, 读取一个字符串过来
            //    先打印一个提示符, 提示用户要输入内容
            System.out.print("-> ");
            String request = scanner.next();

            // 2. 把字符串构造成 UDP packet, 并进行发送.
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    // 数据报的目的IP与目的端口号
                    InetAddress.getByName(serverIP),serverPort);
                    // InetAddress.getByName这个静态方法可以将代表IP的字符串转换为IP

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

            // 3. 客户端尝试读取服务器返回的响应
            // 构造一个空的数据报
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            // 从网卡中读取数据存入 手动构造的空的数据报
            // 如果网卡中没有数据则会阻塞等待
            socket.receive(responsePacket);

            // 4. 把响应数据转换成 String 显示出来.
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());
            System.out.printf("req: %s, resp: %s\n", request, response);
        }
    }

    // 运行main方法,启动客户端
    public static void main(String[] args) throws IOException {
        // 实例化客户端对象
        UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",9090);
        // 启动客户端主逻辑
        udpEchoClient.start();
    }
}

通信结果:
在这里插入图片描述

疑惑解答

为什么服务器进程需要手动指定端口号而客户端进程不需要

服务器的功能是用来处理其他客户端发来的请求,因此需要为客户端提供自己的端口号,方便客户端进行访问。

虽然服务器要给客户端一个响应,但是客户端的IP地址与端口号都可以在客户端发来请求的数据报中获得,因此客户端不需要手动指定端口号


为什么用空的数据报接收网卡中的数据

一般情况下,一般是使用 参数 作为 “输入”,用返回值作为方法的 “输出”

而用来读取数据的reveve方法却没有返回值。
在这里插入图片描述
因此需要传入一个引用类型的参数,通过这个方法改变这个参数,此时这个参数就是返回值,这个参数也被称为 输出型参数

因此这个方法就是 以参数为返回值


为什么客户端中的服务器IP与端口号是"127.0.0.1" 与 9090

127.0.0.1 是主机环回地址。主机环回是指地址为 127.0.0.1 的任何数据包都不应该离开计算机(主机),发送它——而不是被发送到本地网络或互联网,它只是被自己“环回”,并且发送数据包的计算机成为接收者。

端口号是9090是因为是随意指定的,当然也有一些特殊端口号被指定分配给了一些牛逼的程序。


为什么客户端与服务器都没有调用close方法

DatagramSocket对象也会产生文件描述符,如果如果文件描述符表满了会产生文件资源泄露的严重bug,那么为什么此处没有调用close方法???

原因是,无论客户端进程还是服务器进程, 在他的生命周期中,只需要用到一个DatagramSocket对象 ,而他需要调用close方法的时候也正是他进程结束的时候,此时会自动释放对象也就相当于调用了close方法,因此可以手动调用close方法,但是也可以不调用。


总结

以上就是今天要分享的内容,本文介绍了Socket套接字,以及使用UDP协议的特点以及UDP数据报套接字实现的客户端与服务器。网络编程让我愈发感觉到了编程的魅力,也让我领略到了科技的神奇。各位加油!

路漫漫不止修身,也养性。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UDP协议是一种无连接的传输协议,它不对数据传输进行可靠性保证,因此在进行数据传输时,可能会出现数据丢失、重复、乱序等问题。为了保证UDP数据传输的可靠性,需要使用一些技术手段进行处理,其中最常用的是使用UDP Socket实现可靠数据传输。 UDP Socket是在UDP协议基础上进行实现的一个套接字。UDP Socket可以通过设置一些参数和使用一些技术手段,来实现UDP数据传输的可靠性。下面就具体介绍UDP Socket实现可靠数据传输的原理。 1.数据分片 UDP Socket将数据分片传输,每个数据分片都包含一个序号,接收端可以通过序号来判断数据分片是否存在丢失、重复或者乱序等问题。如果数据分片存在以上问题,则可以进行相应的处理,从而保证数据传输的可靠性。 2.确认机制 UDP Socket采用确认机制来保证数据传输的可靠性。发送端在发送每个数据分片后,会等待接收端返回确认信息,确认信息包含接收到的数据的序号。如果接收端返回的确认信息与发送端发送的序号不一致,则说明数据分片存在丢失或者乱序问题,发送端需要重新发送该数据分片。 3.超时重传机制 UDP Socket采用超时重传机制来保证数据传输的可靠性。发送端在发送每个数据分片后,会设置一个超时时间,在超时时间内如果未收到接收端的确认信息,则发送端会重新发送该数据分片。通过这种方式,可以保证数据分片不会因为网络问题而丢失。 4.流量控制 UDP Socket采用流量控制来保证数据传输的可靠性。在数据传输的过程中,发送端需要根据接收端的处理能力来控制数据的发送速度,防止数据发送过快导致接收端无法及时处理。通过流量控制,可以避免数据丢失或者传输延迟过大的问题。 综上所述,UDP Socket实现可靠数据传输的原理主要包括数据分片、确认机制、超时重传机制和流量控制等方面。通过这些技术手段,可以保证UDP数据传输的可靠性,从而满足各种网络应用的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值