UDP Socket API 的讲解,以及回显服务器客户端的实现

news/2024/5/18 16:36:54 标签: udp, 回显服务器, socket api

文章目录

    • UDP
      • DatagramSocktet API
      • DatagramPacket API
    • UDP 客户端服务器实现

UDP

先来认识一下 UDP 的 socket api,两个核心的类:DatagramSocket、DatagramPacket.

DatagramSocktet API

是一个 socket 对象。

什么是 socket?

操作系统,使用文件这样的概念,来管理一些软硬件资源。网卡,操作系统也是使用 文件 的方式来管理网卡的。表示网卡的这类文件,称为 Socket 文件。Java 中的 socket 对象,就对应 系统里的 socket 文件。

因此,想要进行网络通信,必须得先有 socket 对象。

DatagramSocket构造方法:

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

DatagramSocket() 在客户端这边使用,客户端使用哪个端口,是系统自动分配的。

一个客户端的主机,上面运行的程序很多,天知道你手动指定的端口是不是被别的程序占用了。因此,让系统自动分配一个端口是更明智的选择.

DatagramSocket(int port) 在服务器这边使用,服务器使用哪个端口,是手动指定的。

对于服务器来说,需要有一个固定的端口号,方便其他客户端找到。

DatagramSocket 方法:

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

DatagramPacket API

表示了一个 UDP 发送和接收的数据报。

代表了系统中设定的 UDP 数据报的二进制结构。

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 客户端服务器实现

接下来手动写 UDP 客户端服务器

实现一个最简单的回显服务器 (Echo Server).

回显服务器:顾名思义,就是客户端发啥,服务器就返回啥。

我们知道,一个服务器可以供多个客户端同时使用,因此我们最先想到的是利用多线程来实现 UDP.
但事实上 UDP 服务器不需要多线程,是因为UDP是无连接的,每个数据包都是独立的,服务器只需要监听一个端口,接收数据包并处理即可。

核心思路

  1. 服务器:
    • 接收客户端发送过来是请求 ( 收到的请求是一个 DatagramPacket 类 ),并解析出请求内容 ( 转换成 String 类)。
    • 根据请求做出响应
    • 把响应返回给客户端
  2. 客户端
    • 从控制台读取用户输入的内容.
    • 构造请求对象,并发给服务器.
    • 接收服务器给出的响应,并解析出响应内容.
    • 将响应内容打印出来.
  3. 客户端服务器相互关联:通过 ip 和 端口号.
    • ip:每个服务器都有自己的 ip 地址,客户端需要通过 ip 找到服务器。(127.0.0.1 就表示自己的电脑)
    • 端口号:每个服务器有很多个端口,端口号就是用于客户端到底是访问服务器的哪个端口。

实现 UDP 会用到的方法:

getSocketAddress() :就是 getAddress() 和 getPort() 的结合体. [address:port]

InetAddress.getByName(“主机名”):如果传入的是主机名,则该方法会尝试解析该主机名,如果解析成功,则返回对应的 IP 地址;如果解析失败,则抛出 UnknownHostException 异常.

getData():获取数据报中的数据.

客户端代码:

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

public class UdpEchoClient {
    
    private DatagramSocket socket = null;
    private String serverIp ;
    private int serverPort;
    
    public UdpEchoClient(String ip, int port) throws SocketException {
        serverIp = ip;
        serverPort = port;
        // 这个 new 操作,就不再指定端口了,让系统自动分配一个空闲端口
        socket = new DatagramSocket();
    }
    
    public void start () throws IOException {
        Scanner scanner  = new Scanner(System.in);
        System.out.println("客户端启动!");
        while (true) {
            // 1. 从控制台读取用户输入的内容
            System.out.print("->");
            String request = scanner.nextLine();
            // 2. 构造请求对象,并发给服务器
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIp), serverPort);
            socket.send(requestPacket);
            // 3. 读取服务器的响应, 并解析出响应内容.
            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 {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
        client.start();
    }
}

/*
    客户端启动!
    ->hello
    hello
*/

服务器代码:

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

//回显服务器
//客户端发的请求是啥,服务器返回的响应就是啥
public class UdpEchoServer {
    
    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);
            //  转换成字符转
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
            // 2.根据请求,计算出响应
            String response = process(request);
            // 3.把响应写回给客户端
            //   此时需要告知网卡,要发的内容是啥,要发给谁
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress());
            socket.send(responsePacket);
            //记录日记,方便观察程序执行效果
            System.out.printf("[%s:%d] req: %s , resp: %s\n", responsePacket.getAddress().toString(), responsePacket.getPort(),request, response);
        }
    }
    
    //根据请求计算响应,由于是回显程序,响应内容和请求完全一样
    private String process(String request) {
        return request;
    }
    
    public static void main(String[] args) throws IOException {
        UdpEchoServer server = new UdpEchoServer(9090);    //设定服务器的端口号为 9090
        server.start();
    }
}

/*
	服务器启动!
	[/127.0.0.1:54015] req: hello , resp: hello
*/

问题1:我电脑上的 udp 服务器,别人可以访问吗??

答:不可以,因为我当前的电脑上没有 “外网IP”。解决办法就是买一个有外网的云服务器 😆

问题2:socket 对象用完后需要关闭吗??

答:需要,我们要知道为什么要关闭 socket 对象。最主要的就是释放系统中的 socket 文件,从而释放文件描述符

但是上述代码中我们为什么没去关闭 socket 对象呢?

因为对于咱们这个服务器来说,DatagramSocket 不关闭,问题不大。整个程序中只有一个 socket 对象,不是频繁创建的,生命周期是跟随整个进程的。但是如果是有多个 socket 对象, 且 socket 对象生命周期更短需要频繁创建释放。一定要记得去 close 。


http://www.niftyadmin.cn/n/5229152.html

相关文章

解决CentOS下PHP system命令unoconv转PDF提示“Unable to connect or start own listener“

centos系统下,用php的system命令unoconv把word转pdf时提示Unable to connect or start own listene的解决办法 unoconv -o /foo/bar/public_html/upload/ -f pdf /foo/bar/public_html/upload/test.docx 2>&1 上面这个命令在shell 终端能执行成功&#xff0c…

Flink源码解析零之重要名词的理解

名词解释 1)StreamGraph 根据用户通过 Stream API 编写的代码生成的最初的图。 (1)StreamNode 用来代表 operator 的类,并具有所有相关的属性,如并发度、入边和出边等。 (2)StreamEdge 表示连接两个StreamNode的边。 2)JobGraph StreamGraph经过优化后生成了 J…

提升网页交互体验的秘密武器——防抖和节流

说在前面 在现代Web开发中,提高网页性能是至关重要的。本文介绍了防抖和节流这两种常用的性能优化技术,通过控制函数的执行频率,有效减少不必要的计算和网络请求,从而提升用户体验和页面加载速度。 函数节流 节流是指限制一个函数…

利用GPU进行训练如何如何动态显示nvidia-smi的信息

使用watch命令 在Linux中,watch命令可以用来周期性地执行一个命令,并显示其输出。例如: watch -n 1 nvidia-smi这个命令会每秒执行一次nvidia-smi并显示其输出。你可以更改-n 1中的数字来改变更新频率(单位是秒)。 …

校园圈子系统丨交友丨地图找伴丨二手市场等功能丨源码交付支持二开丨APP小程序H5三端交付!

校园圈子系统是一款专为校园生活设计的智能应用,拥有丰富多样的功能模块,提供全方位的服务。无论您是师生还是校友,我们都为您打造了一个与校园紧密相连的交流平台。 通过校园圈子系统,您可以方便地浏览校内最新动态,包…

利用ARCGIS做地下水脆弱性评价分析

(一)行政边界数据、土地利用数据和土壤类型数据 本文所用到的河北唐山行政边界数据、土地利用数据和土壤类型数据均来源于中国科学院资源环境科学与数据中心(https://www.resdc.cn/Default.aspx)。 (二)地…

设计模式之美学习笔记-理论篇1-面向对象的特性

一、设计模式前言 面向对象 主流的编程范式或者是编程风格有三种,它们分别是面向过程、面向对象和函数式编程。面向对象这种编程风格又是这其中最主流的。现在比较流行的编程语言大部分都是面向对象编程语言。大部分项目也都是基于面向对象编程风格开发的。面向对…

⭐ Unity + ARKIT ARFace脸部追踪

相比之前的图像物体检测,这脸部检测实现起来会更加的简单。 (1)首先我们先在场景中的物体上添加一个AR Face Mananger组件: (2)以上組件的 Face Prefab所代表的就是脸部的模型也就是覆盖在脸部上面的投影模…