网络编程套接字之UDP实现回显服务器及客户端

news2024/11/15 10:37:40

目录

前言:

基础理解

传输层协议

UDP

TCP

Socket API

DatagramSocket API

DatagramPacket API

UDP实现回显服务器

完整代码展现(有详细注释)

UDP实现回显客户端

完整代码展现(有详细注释)

小结:


前言:

    通过套接字Socket就可以实现客户端发送请求,服务起接收请求,处理完成后就可以响应给客户端。这样的一套流程就实现了数据在网络上的传输。

基础理解

    网络编程中,在硬件上使用网卡发送和接收数据。在java中使用Socket直接操作网卡,而对于操作系统来说一切皆文件,那么这个Socket对象在操作系统中是被当作文件处理的。Socket就是操作系统给应用程序提供的接口。

    Socket所提供的api和传输层密切相关,应用层首先接触的就是传输层。使用Socket所提供的api就可以实现应用层的代码并且和传输层进行交互。

    客户端发起请求 --> 服务器接收请求 --> 服务器处理请求并响应给客户端 --> 客户端接收响应

传输层协议

UDP

    特点:无连接,不可靠传输,面向数据报,全双工,大小首先(一次最多64k),有接收缓冲区无发送缓冲区。

TCP

    特点:有连接。可靠传输,面向字节流,全双工,大小不限,有接收缓冲区和发送缓冲区。

理解:

    1)无连接:不需要建立客户端和服务器之间的连接,就可以发送数据。(例如微信发消息)

    2)有连接:需要建立客户端和服务器之间的连接,才可发送数据。(例如打电话,需要接听)

    3)不可靠传输:发送方不知道数据是发过去了,还是丢包了。

    4)可靠传输:发送方知道自己的消息是否发送过去。

    注意:可靠性就是针对发送方是否清楚数据是否发送过去。

    5)面向数据报:数据传输以“数据报”为基本单位,一块一块的发数据。

    6)面向字节流:数据传输和读文件类似,“流式”的。一次发送部分数据,也可以发送全部数据。

    7)全双工:可以同时发送和接收数据,那么半双工就不支持。

Socket API

    java中使用UDP协议通信,主要基于 DatagramSocket 类来创建数据报套接字,并使用
DatagramPacket 作为发送或接收的UDP数据报。

DatagramSocket API

DatagramSocket构造方法

 注意:

    创建一个UDP数据报套接字的Socket,绑定本机任意一个随机端口(一般用于客户端)

 注意:

    创建一个UDP数据报套接字的Socket,绑定指定端口(一般用于服务端)

DatagramSocket方法

注意:

    从网卡接收数据报。这个参数需要一个空的DatagramPacket对象,当从网卡接收到数据报就会填充好这个空的对象,以便供我们处理数据。

    如果没有接收数据报,这个方法会阻塞等待。

 注意:

    将已经构造好的数据报发送到网卡。不会阻塞等待直接发送。

 注意:

     在操作系统中Socket对象是被当作文件处理的,那么就需要释放pcb中文件描述符表中的资源。

DatagramPacket API

DatagramPacket构造方法

 注意:

    构造一个DatagramPacket以用来接收数据报,接收的数据保存在buf缓冲数组中,接收的指定长度。

 注意:

    构造一个DatagramPacket以用来接收数据报,数据填充为字节数组,从0起始位置到指定长度(offset,length),address指定目的主机IP和端口号。(一般处理完请求后,构造成数据报来发送)

DatagramPacket方法

 注意:

     从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址。

 注意:

     从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号。

 注意:

     获取数据报中的数据,就是缓冲数组。

UDP实现回显服务器

    服务器是被动的一方,需要接收客户端发起的请求。那么客户端就必须明确服务器的ip和具体进程的端口号。所以在实现服务器时就必须指定端口号,这里实现的是本机到本机的数据发送,ip就使用环回ip即可。

    由于不清楚客户端什么时候发起请求,那么服务器不能休息(随时待命)。这里使用死循环的方式,但它不会一直循环,因为receive()方法当没有接收到请求时会阻塞等待。

    我们首先需要明确服务器的工作流程。接收客户端的请求 --> 处理请求 --> 将响应发送给客户端。

DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(requestPacket);

注意:

    首先构造一个空的DatagramPacket对象,传入缓冲数组,和指定长度。当下面receive()方法从网卡接收到客户端请求时就会填充这个空对象。(数据是写入了缓冲数组)

    当receive()方法当没有接收到请求时会阻塞等待。(随时待命)

String request = new String(requestPacket.getData(), 0, requestPacket.getLength());

注意:

    由于接收的数据构造成了数据报,这样不利于我们处理数据。我们将数据报中的数据取出来构造成字符串。

String response = process(request);
public String process(String request) {
    return request;
}

注意:

    服务器针对请求进行需求处理,这里的process是一个方法。由于我们实现的是回显服务器,即直接返回这个字符串即可。

 DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());
socket.send(responsePacket);

注意:

    这里将处理完后的响应构造成数据报,然后发送给客户端程序。这里需要传入字节数组,填充的具体长度,(这里需要用字节数组的长度,不能用字符串的长度。转换之后两者长度是不一致的)和客户端的ip和端口号(getSocketAddress()方法可以获得发送方的ip和端口号)。

    当构造完成之后直接将数据报发给客户端即可。

完整代码展现(有详细注释)

public class UdpEchoSever {
    //Socket对象直接操作的是网卡,在操作系统中任务Socket对象是文件(一切皆文件)
    //通过Socket对象接收和发送数据
    private DatagramSocket socket = null;
    public UdpEchoSever(int port) throws SocketException {
        //服务器是被动的一方,客户端必须找到服务器的端口,才能找到指定程序,因此服务器必须指定端口号
        socket = new DatagramSocket(port);
    }
    public void start() throws IOException {
        System.out.println("启动服务器");
        while (true) {
            //构造空的Packet对象,传入缓冲数组
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            //receive从网卡接收数据,解析后填充这个空对象(输出形参数)(可以认为写入了缓冲数组)
            //客户端如果没有发请求receive就会阻塞,直到客户端发送请求(保证这里不会一直循环)
            socket.receive(requestPacket);

            //根据接收的数据(由于接收的数据不方便处理),因此构造成字符串
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
            //服务器响应处理
            String response = process(request);
            //构造发送的数据报,字节数组,字节数组长度,IP和端口(根据响应的字符串)
            //这个DatagramPacket只认字节数组,因此就需要获取字节数组的长度而不是字符的个数
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());
            //发送数据到Ip和端口指定的客户端程序
            socket.send(responsePacket);
            //打印下,请求响应的中间结果
            System.out.printf("源IP:%s 源端口:%d 请求数据:%s 响应数据:%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 里指定
        UdpEchoSever sever = new UdpEchoSever(8280);
        sever.start();
    }
}

UDP实现回显客户端

    客户端发送数据需要明确服务器的ip和具体进程的端口号。客户端的端口号我们不需要手动指定,因为客户端程序是存在于客户主机上,我们如果手动指定就很可能与其他进程端口号冲突,这样就直接抛异常了(Address already in use)。直接让操作系统随机分配一个空闲的端口号。

    那么为什么服务端我们可以指定端口号,这样就不怕与其他进程冲突了么?因为服务器在我们自己手里,我们明确里面的各种端口号,简单来说就是可控的。 

    我们首先明确客户端的工作流程。用户输入数据 --> 发送到服务器 --> 接收服务器的响应。这里也使用死循环,和上面一样receive()方法会阻塞,不会一直循环。

 Scanner scanner = new Scanner(System.in);
 System.out.println("输入你要发送的数据:");
 String request = scanner.next();
 if (request.equals("exit")) {
        System.out.println("bye bye");
        break;
 }

注意:

    提示用户输入数据,这里做了简单的判断。

 DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(severIp), severPort);
 socket.send(requestPacket);

注意:

    根据用户输入的数据构造成数据报。需要字节数组,具体填充的长度(同样的需要字节数组长度而不是字符串长度),ip(由于这里需要一个32位的ip,而上面的是字符串,因此需要转换)和服务器端口号。然后直接发送即可。

 DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
 socket.receive(responsePacket);

注意:

    接收服务器的响应。首先构造一个空的DatagramPacket对象,传入缓冲数组和指定长度。receive()方法从网卡接收到数据报然后构造好这个空的对象。

    receive()当没有接收到响应前同样的也会阻塞等待。

 String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
 System.out.println(response);

注意:

    由于是数据报不利于用户观察数据,因此转转换为字符串。获取到数据报中的缓冲数组,从0位置到指定长度来构造这个字符串,最终显示给用户。

完整代码展现(有详细注释)

public class UdpEchoClient {
    private DatagramSocket socket = null;
    //客户端需要知道服务器的IP,和端口,这里先存一下
    private String severIp = null;
    private int severPort = 0;
    public UdpEchoClient(String severIp, int severPort) throws SocketException {
        //客户端不需要指定端口号,客户端程序在用户手里,指定端口号就可能和其他进程重复。因此让操作系统分配一个空闲的端口
        //服务器为什么指定端口不怕重复呢?因为服务器在程序员手里我们清楚端口号的使用(可控的),而客户端是(不可控的)
        socket = new DatagramSocket();
        this.severIp = severIp;
        this.severPort = severPort;
    }
    //客户端启动
    public void start() throws IOException {
        //用户输入数据
        while (true) {
            Scanner scanner = new Scanner(System.in);
            System.out.println("输入你要发送的数据:");
            String request = scanner.next();
            if (request.equals("exit")) {
                System.out.println("bye bye");
                break;
            }
            //发送数据报(构造DatagramPacket对象)
            //此处的IP需要一个32位的整数,而上面的是字符串,需要转换
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(severIp), severPort);
            socket.send(requestPacket);

            //接收数据报(填充这个空对象)(阻塞到服务器发送过来数据)
            //receive的阻塞操作系统实现的,JAVA只是封装了一下
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(responsePacket);
            //显示数据报(将数据报转换为字符串)
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.println(response);
        }
    }

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

小结:

    这里大多是api的使用,我们要理解其中的原理,便能得心应手。

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

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

相关文章

使用OpenCV对点集从左上到右下排序

本文实现如何使用OpenCV对点集从左上到右下排序。本文使用的案例图片如下: 需要实现的效果如下: 1.分阶段实现 让我们看看考虑4行的工作流。我考虑的是沿着y轴将图像分成4段,形成4行。对于图像的每一段,找出每一个以该段为中心…

虚拟主机怎么用?香港虚拟主机搭建网站教程

在了解使用方法之前要先明白什么是虚拟主机、它的用处以及服务器、VPS和虚拟主机之间有什么区别。香港虚拟主机也称为网站空间,顾名思义就是存放网站文件的空间。主要用于搭建网站、提供程序运行。下面就介绍一下香港虚拟主机搭建网站的使用方法以及教程。 一、香港…

职场新人应该如何培养项目管理的能力?

李强在一家企业工作,前阵子升任项目经理。但他这两天却一副愁眉不展的样子,像霜打的茄子。原来是因为他缺乏管理经验,设定的工作任务不合理,项目成员职责不清,导致项目混乱,项目进度不清晰。 项目管理涉及…

Redis搭建基于docker跨服务器的一主两从三哨兵集群模式

文章目录1 整体拓扑图2 redis与哨兵配置文件2.1 主节点配置文件2.1.1 主节点redis.conf配置文件2.1.2 主节点哨兵配置文件2.2 从节点配置文件2.1.1 从节点redis.conf配置文件2.1.2 从节点哨兵配置文件3 docke-compose编排文件4 启动并测试查看哨兵日志查看集群状态测试集群是否…

在 Visual Studio 中更好地进行搜索

当在 IDE 中查找特定的代码文件、类、方法或者功能、选项,找到它们并不总是那么容易。有多种搜索方式,到底该选择哪一种才最便捷呢?新的“一站式”搜索(All-In-One Search)将代码和功能搜索合并到同一个 UI 中&#xf…

从GPT到chatGPT(三):GPT3(一)

#GPT3 文章目录前言正文摘要介绍方法模型结构训练数据集训练过程评估小结前言 OpenAI在放出GPT2后,并没有引起业界太大的影响和关注,究其原因,并不是zero-shot这种想法不够吸引人,而是GPT2表现出来的效果依然差强人意&#xff0c…

Unity数据持久化-PlayerPrefs

1. PlayerPrefs 是什么? 是Unity提供的可以用于存储读取玩家数据的公共类 1.1. 存储相关 PlayerPrefs的数据存储类似于键值对存储 一个键对应一个值 提供了存储3种数据的方法int float string 键:string类型 值: int float string对应3种API PlayerPrefs.SetInt(…

如何将宿主机工作文件夹映射到vmware虚拟机内

将宿主机工作文件夹映射到vmware虚拟机内vmware启用共享文件夹在虚拟机中使用vmtools进行挂载虚拟机为linux系统,操作中使用的是凝思系统 vmware启用共享文件夹 在vmware中将需要共享的工作文件夹添加到共享列表中,如图 在虚拟机中使用vmtools进行挂…

Tomcat部署项目及Tomcat间会话同步

目录一、Tomcat中部署项目的相关配置1.相关配置2.测试二、Tomcat间的会话同步1.配置Tomcat2.修改web工程的WEB-INF下的web.xml3.配置Nginx4.测试一、Tomcat中部署项目的相关配置 1.相关配置 在Tomcat中进入conf/tomcat-users.xml文件&#xff0c;将以下代码放到最后 <rol…

本地Exchange备份方案

Exchange Server的Windows服务器备份只能执行基础还原&#xff0c;并且不支持对邮箱项执行粒度还原。在需要恢复单个已删除的电子邮件或文件夹的情况下&#xff0c;本机解决方案提供了一个耗时且低效的过程。Exchange ServerRecoveryManager Plus帮助您克服这些限制。使用Recov…

国内有哪些移动开发平台?各有什么特点

开年找解决方案的时候接触了一批移动开发平台&#xff0c;猛然发现近几年移动开发平台如雨后春笋般出现&#xff0c;于是乎就想针对市场上越来越多的移动开发平台做一次盘点对比&#xff0c;看看他们的基本情况和自身特点优势&#xff0c;也希望能够对有需要的开发者有一定的参…

Docker - 4. 容器常用命令 docker run、exit、ps、rm、start、stop、kill

目录 1. 新建容器并启动 2. 从容器中退回主机 3. 列出所有运行的容器 4. 退出容器 5. 删除容器 6. 启动和停止容器 7. 易混淆地方 1. 新建容器并启动 语法&#xff1a;docker run [可选参数] 镜像名 参数&#xff1a; 参数含义--name "Name" 容器名字&a…

java泛型1

泛型&#xff08;为了集合&#xff09; Java 5增加泛型支持在很大程度上都是为了让集合能记住其元素的数据类型。在没有泛型之前&#xff0c;一旦把一个对象“丢进”Java集合中&#xff0c;集合就会忘记对象的类型&#xff0c;把所有的对象当成Object类型处理。当程序从集合中取…

基于深度学习框架设计的货运管家(功能总结)

项目背景&#xff1a; 在快递行业发达的今天&#xff0c;有数不胜数的货运公司、快递公司&#xff0c;这些公司都有自己的运输车辆&#xff0c;请师傅开车送货。 比如&#xff1a;快递公司、烟草运输公司、货物运输公司等等。 为了能方便管理货车&#xff0c;了解车辆行驶路线…

49.Isaac教程--Cartographer

Cartographer ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 文章目录Cartographer数据要求配置启动 Cartographer 示例应用程序监控和可视化输出Cartographer 是 Google 的一个&#xff08;同步定位和映射&#xff09;SLAM 系统&#xff0c;能够…

2023最新前端面试题5(持续更新)

Vue 74、vue 生命周期 74.1、什么是 Vue 生命周期? Vue 实例从创建到销毁的过程&#xff0c;就是生命周期。也就是从开始创建、初始化数据编译模板、挂载 Dom一渲染、更新一渲染、卸载等一系列过程&#xff0c;我们称这是 Vue的生命周期 74.2、Vue 生命周期的作用是什么?…

函数递归+青蛙跳台阶——“C”

各位CSDN的uu们你们好呀&#xff0c;今天小雅兰的内容终于要回到我们的C语言了&#xff0c;在之前&#xff0c;我写函数这篇博客的时候就讲过&#xff0c;会把函数递归的内容单独拿出来&#xff0c;然后呢当时是说下一篇博客就会更函数递归和青蛙跳台阶&#xff0c;由于一系列原…

拼多多控价,低价投诉,不被支持怎么办

治理电商平台上的低价乱价链接&#xff0c;首选方法是沟通调整&#xff0c;成本低、见效快。针对沟通调整不成功的&#xff0c;再进行平台投诉&#xff0c;通过电商平台的力量对其进行约束。 但是&#xff0c;平台投诉一定要注意一点&#xff1a;不要以低价为理由&#xff01;…

原命题和逆否命题

看Boyd的凸优化看到这样一个证明&#xff1a; 从左到右的证明是 使用了一个逆否命题的方法进行证明&#xff0c;有点忘记了原命题和逆否命题之间的相互转换&#xff0c;记录一下。 简单形式命题 简单形式命题没有全称量词∀\forall∀和存在两次∃\exists∃&#xff0c;也没有…

字符流、字节流、缓冲流、序列化流

1.文件拷贝的基本代码 2.一次读取多个字节 3.异常处理 但是上述的释放过程太繁琐&#xff0c;于是便用AutoCloseable接口简化释放过程 JDK7: JDK9: 4.各种字词计算机存储规则 所以底层二进制文件就是根据正负来区别英文和中文的。 5.乱码 6.字符流 7.字符输入流和输出流底层原理…