「网络编程」基于 UDP 协议实现回显服务器

🎇个人主页Ice_Sugar_7

🎇所属专栏计网

🎇欢迎点赞收藏加关注哦!

实现回显服务器

🍉socket api

操作系统给我们提供的进行网络编程的 api 称为 socket api(网络编程套接字),具体到传输层,有两个重要的协议的 api ------ UDP apiTCP api,本文我们介绍的是 UDP api

UDP 有四个特点:无连接、不可靠传输、面向数据报、全双工。这在后文中会解释

Java 对系统原生的 api 进行了封装,UDP socket 有两个核心的类

  1. DatagramSocket

操作系统中有一类文件,叫作 socket 文件,它和我们之前所说的"文件"不太一样,我们平时所说的普通文件、目录文件位于硬盘上,而 socket 文件则是抽象表示了网卡这样的硬件设备(网卡是网络通信中的核心硬件设备),也就是把网卡等硬件视为一种文件。通过网卡发送数据就是写 socket 文件;接收数据就是读 socket 文件

说回 DatagramSocket,它负责读写 socket 文件,也就是借助网卡发送或接收数据

它有两个构造方法:

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

负责发送和接收的方法如下:

方法 说明
void reveive(DatagramPacket p) 让 p 接收数据报(如果没接收到数据报,这个方法就会阻塞等待)(注意这里的参数是输出型参数,实际上 DatagramPacket 内部包含了一个字节数组
void send(DatagramPacket p) 从 p 发送数据报(直接发送出去,不会阻塞)

  1. DatagramPacket

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


🍉回显服务器

这是网络编程中最简单的程序,相当于 hello world,不过还是有一定的难度

服务器在接收客户端的请求后会返回响应,具体返回什么响应,要根据实际的业务场景分析。对于回显服务器,它没有业务逻辑,客户端发什么请求,服务器就返回什么响应

🍌实现

接下来我们通过 UDP 协议来实现一个回显服务器

🥝服务器

首先要创建一个 DatagramSocket 对象,然后要通过这个 socket 对象来操作网卡

java 复制代码
public class UdpEchoServer {
    DatagramSocket socket = null;
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port); //在运行一个服务器程序时,通常会手动指定端口
    }
}

补充:这里的 SocketException 是网络编程中一个常见的异常,通常表示 socket 创建失败,比如端口号已经被别的进程占用了

接下来服务器主要做三件事

①读取请求并解析
②根据请求计算响应。对于回显服务器来说,这一步啥都不用做
③把响应返回到客户端

要读取请求得先创建一个 DatagramPacket 接收请求

java 复制代码
DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);
socket.receive(requestPacket);
String request = new String(requestPacket.getData(),0,requestPacket.getLength());


使用字节数组构造字符串的方法一定要记住


调用 receive 涉及到缓冲区,下面通过图示补充一下:

第二步是根据请求计算响应,虽然回显服务器这一步不用做什么,不过为了逻辑完整,我们写一个 process 方法,它只返回 request
(如果是具有特定业务的服务器,process 中就写其他你想要的逻辑)

java 复制代码
public String process(String request) {
    return request;
}

最后就是把响应返回给客户端,这一步要用到 send 方法

java 复制代码
//3.把响应返回到客户端
//构造一个 DatagramPacket 作为响应对象
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),0,
        response.getBytes().length,requestPacket.getSocketAddress());
socket.send(responsePacket);

接下来在主方法中启动服务器

服务器的代码如下:

java 复制代码
public class UdpEchoServer {
    DatagramSocket socket = null;
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    //服务器的启动逻辑
    public void start() throws IOException {
        System.out.println("服务器启动");
        while(true) {
            //每次循环就是处理一个请求,然后返回响应的过程
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);
            //1.读取请求并解析
            socket.receive(requestPacket);
            //填充字节数组后,将其转为 String 方便后续处理逻辑
            //getData 方法获取到 DatagramPacket 内部的字节数组
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            //2.根据请求计算响应
            String response = process(request);
            //3.把响应返回到客户端
            //构造一个 DatagramPacket 作为响应对象
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),0,
                    response.getBytes().length,requestPacket.getSocketAddress());
            socket.send(responsePacket);
            
            //打印日志
            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 {
        UdpEchoServer server = new UdpEchoServer(7000);
        server.start();
    }
}

🥝客户端

接下来编写客户端的代码

首先要创建 socket 对象,注意客户端这里不需要手动指定端口号

1.在代码中手动指定端口号,可以保证端口号始终固定;如果不手动指定,那就是系统自动分配,这样的话服务器每次重启之后端口号可能就变了,一旦变了,客户端就可能找不到服务器在哪儿了,所以服务器需要手动指定
2.而对于客户端,因为无法确保手动指定的端口是可用的(可能被其他进程占用了),这就可能导致程序因为端口绑定失败而无法启动,所以让系统随机分配一个空闲的端口就 ok 了

接下来客户端要做四件事

1.从控制台读取要发送的请求数据
2.构造请求并发送
3.读取服务器的响应
4.把响应显示到控制台上

第一步就是先创建一个 Scanner 对象来读取字符串
这里补充一点,使用 Scanner 从控制台读取字符串的话最好使用 next,因为如果用 nextLine 读取需要手动输入换行符 enter,但是 enter 键除了产生 \n 还会产生其他字符,这就会导致读取到的内容容易出问题;而如果从文件读取的话那就用哪个都行

第二步构造请求就用接收的字符串来构造一个 DatagramPacket 对象,然后发送

java 复制代码
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
        InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);

到这里我们已经了解了三种构造 DatagramPacket 对象的方法,总结一下:

java 复制代码
//第一种:搭配 receive 使用。构造的时候指定空白的字节数组
DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);

//第二种:发数据时使用。构造时指定有内容的字节数组,并指定 IP 和端口
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());

//第三种:发数据时使用。构造时指定有内容的字节数组,并指定 IP 和 端口,这两者分开指定
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(serverIp),serverPort);

回到正题,第三步是读取服务器响应

java 复制代码
DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);
socket.receive(responsePacket);

最后就是把它显示到控制台:

java 复制代码
String response = new String(responsePacket.getData(),0,responsePacket.getLength()); //再次强调,这里的 getLength 方法得到的是有效长度
System.out.println(response);

客户端代码如下:

java 复制代码
public class UdpEchoClient {
    DatagramSocket socket;
    String serverIp;
    int serverPort;
    public UdpEchoClient(String serverIp,int serverPort) throws SocketException {
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        socket = new DatagramSocket();
    }

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

    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1",7000);
        client.start();
    }
}

接下来运行一下看看效果:

相关推荐
seabirdssss4 分钟前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续13 分钟前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
benben04416 分钟前
ReAct模式解读
java·ai
LJC_Superman44 分钟前
Web与Nginx网站服务
运维·服务器·前端·网络·数据库·nginx·vim
proware44 分钟前
昇腾310i Pro固件说明
linux·运维·服务器
轮到我狗叫了1 小时前
牛客.小红的子串牛客.kotori和抽卡牛客.循环汉诺塔牛客.ruby和薯条
java·开发语言·算法
小鸟啄米1 小时前
Elixir通过Onvif协议控制IP摄像机,扩展ExOnvif的摄像头停止移动 Stop 功能
网络协议·elixir·onvif
yudiandian20141 小时前
【QT 5.12.12 下载 Windows 版本】
开发语言·qt
高山有多高1 小时前
详解文件操作
c语言·开发语言·数据库·c++·算法
狂奔的sherry2 小时前
单例模式(巨通俗易懂)普通单例,懒汉单例的实现和区别,依赖注入......
开发语言·c++·单例模式