【Java网络编程02】套接字编程

【Java网络编程02】套接字编程

1. Socket套接字

概念:Socket套接字,就是系统提供用于实现网络通信的技术,是基于TCP/IP协议的网络通信基本操作单元。基于Socket套接字的网络程序开发就是网络编程。
分类
我们可以把Socket套接字分为两类

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

TCP,即Transmission Control Protocol(传输控制协议)
以下为TCP的特点:(细节后续有专门章节解释)

  • 有连接的
  • 可靠传输
  • 面向字节流的
  • 全双工的
  1. 数据报套接字:使用传输层UDP协议

UDP,即User Datagram Protocol(用户数据报协议)
一下为UDP的特点(细节后续有专门章节解释)

  • 无连接的
  • 不可靠传输
  • 面向数据报
  • 全双工

这里简单介绍一些相关概念:

面向字节VS面向数据报
这里与文件流中的字符流与字节流很类似,面向字节表明网络传输数据是以字节为单位的,而面向数据报表明UDP传输依靠UDP数据报进行传输(稍后我们在代码中会体现)

全双工VS半双工
半双工:通信双方基于管道进行传输,但是数据只能单向流动,如图所示:
image.png
全双工:通信双方可以实现数据的双向流动,如图所示:
image.png

2. UDP数据报套接字编程

2.1 相关API

在运用UDP进行网络编程之前,我们需要先熟悉UDP套接字编程相关API的使用,只有掌握了这些API工具才能更好地进行编程的实现,我们主要学习的有两个类:DatagramSocketDatagramPacket

  1. DatagramSocket:OS提供了网络编程所需的API,也叫做"Socket API",而Java又进行了一层封装,使用提供的类DatagramSocket就可以实现对于网卡等硬件设备文件的读写操作。
  2. DatagramPacket:前面我们有介绍过,UDP协议是面向数据报的,因此网络传输单位不是字节而是数据报,Java提供类DatagramPacket相当于数据报的抽象,因此实例化该对象相当于构建了一个数据报。在编程中我们发送的与接收数据的参数就是DatagramPacket对象

DatagramSocket(列举部分)

修饰符+返回类型签名说明
构造方法DatagramSocket()无参构造,创建一个实例对象(通常用于客户端)
构造方法DatagramSocket(int port)含参构造,参数为端口号,创建一个实例对象(通常用于服务器端)
voidsend(DatagramPacket p)向socket中发送一个数据报
voidreceive(DatagramPacket p)从socket中接收一个数据报(接收不到就阻塞等待)

DatagramPacket(列举部分)

修饰符+返回类型签名说明
构造方法DatagramPacket(byte[] buf, int length)构建一个用于接收数据长度为length的数据报对象
构造方法DatagramPacket(byte[] buf, int length, InetAddress address, int port)构建一个将要发送的数据长度为length的数据报,并指定发送目的IP与端口号
byte[]getData()从数据缓冲区中读取数据
intgetLength()返回发送或接收的数据长度

2.2 UDP编程代码

2.2.1 实现需求

作为我们的第一个UDP实验,我们希望实现一个回显服务器的效果(这相当于网络编程的"Hello World"),需求如下:

  1. 程序分为两部分,服务器端和客户端
  2. 客户端可以接收键盘输入内容,封装报文向指定服务器发送数据报
  3. 服务器端接收数据报后在显示器上打印格式为[/127.0.0.1, 52523]服务器接收到请求: xxx,并回复给客户端OK
  4. 客户端发送数据报后等待服务器响应内容,然后将响应内容打印在显示器上
  5. 要求服务器可以持续接收客户端请求,客户端可以不停接收用户键盘输入
2.2.2 代码编写

UDP服务器端代码

/**
 * UDP服务器端代码
 */
public class UdpServer {
    private int serverPort = 0; // 服务器端端口
    private DatagramSocket socket = null;

    public UdpServer(int port) throws SocketException {
        this.serverPort = port;
        this.socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器开始启动....");
        // 1. 循环处理客户端请求
        while (true) {
            // 2. 阻塞等待客户端请求
            DatagramPacket request = new DatagramPacket(new byte[4096], 4096);
            socket.receive(request);
            // 3. 获得请求后进行处理
            String responseMsg = process(request);
            // 4. 将响应回传客户端
            DatagramPacket response = new DatagramPacket(responseMsg.getBytes(), responseMsg.getBytes().length, request.getSocketAddress());
            socket.send(response);
        }
    }

    public String process(DatagramPacket request) {
        // 根据请求数据读取构造字符串
        String msg = new String(request.getData(), 0, request.getLength());
        System.out.printf("[%s, %d]服务器接收到请求: %s\n", request.getAddress(), request.getPort(), msg);
        // 服务器端返回OK
        return "OK";
    }

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

UDP客户端代码

/**
 * UDP客户端代码
 */
public class UdpClient {
    private String serverIP;

    private int serverPort;

    private DatagramSocket socket;

    public UdpClient(String serverIP, int serverPort) throws SocketException {
        this.serverIP = serverIP;
        this.serverPort = serverPort;
        this.socket = new DatagramSocket();
    }

    public void start() throws IOException {
        System.out.println("客户端启动....");
        Scanner scanner = new Scanner(System.in);
        // 1. 用户持续输入
        System.out.print("->");
        while (scanner.hasNext()) {
            String input = scanner.next();
            // 2. 将用户输入内容构造成数据报
            DatagramPacket request = new DatagramPacket(input.getBytes(), input.getBytes().length, InetAddress.getByName(serverIP), serverPort);
            // 3. 向服务器端发送数据报
            socket.send(request);
            // 4. 阻塞等待服务器端响应
            DatagramPacket response = new DatagramPacket(new byte[4096], 4096);
            socket.receive(response);
            // 5. 打印响应内容
            String responseMsg = new String(response.getData(), 0, response.getLength());
            System.out.println(responseMsg);
            System.out.print("->");
        }
    }

    public static void main(String[] args) throws IOException {
        UdpClient udpClient = new UdpClient("127.0.0.1", 9090);
        udpClient.start();
    }
}

运行效果

客户端:
image.png

服务器端:
image.png

2.2.3 流程分析

我们以客户端输入"hello"为例分析客户端和服务器端各自的执行流程

  1. 服务器端执行socket.receive(request);进入阻塞状态,等待客户端的请求
  2. 客户端执行while(scanner.hasNext()) {...}阻塞等待用户键盘输入
  3. 客户端用户在键盘敲下"hello",客户端停止阻塞,执行以下代码
String input = scanner.next();
// 2. 将用户输入内容构造成数据报
DatagramPacket request = new DatagramPacket(input.getBytes(), input.getBytes().length, InetAddress.getByName(serverIP), serverPort);
// 3. 向服务器端发送数据报
socket.send(request);
// 4. 阻塞等待服务器端响应
DatagramPacket response = new DatagramPacket(new byte[4096], 4096);
socket.receive(response);

将用户输入内容构造成DatagramPacket对象,然后执行socket.send(request)向服务器发送请求。然后执行socket.receive(response);进入阻塞状态,等待服务器响应
image.png

  1. 服务器端停止阻塞,开始执行以下代码
// 3. 获得请求后进行处理
String responseMsg = process(request);
// 4. 将响应回传客户端
DatagramPacket response = new DatagramPacket(responseMsg.getBytes(), responseMsg.getBytes().length, request.getSocketAddress());
socket.send(response);

服务器获得请求数据报后开始解析,然后构建响应数据报返回给客户端,即调用socket.send(response);,向客户端发送数据报socket.send(response);,之后再次执行while循环执行socket.receive(request);,阻塞等待下一次的客户端请求
image.png

  1. 客户端接收到服务器端响应,停止阻塞,执行以下代码
// 5. 打印响应内容
String responseMsg = new String(response.getData(), 0, response.getLength());
System.out.println(responseMsg);
System.out.print("->");

将响应内容显示在屏幕上后,继续执行while(scanner.hasNext()) {...}进入阻塞等待下一次用户输入,由此进入闭环。

总结:无论是客户端还是服务器端,都需要各自执行通过套接字发送请求、接收响应的过程即客户端调用一次send、一次receive方法,服务器端调用一次send、一次receive方法。而且send方法中的参数一定是载有实际发送内容的字节数组,而receive方法参数所需的DatagramPacket对象内部则为空的字节数据,是需要被响应内容所填充的 输出型参数

完整流程图:

image.png

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GO 的那些 IDE

“程序员为什么要使用 IDE”,在一些社区论坛,经常可以看到这样的提问。关于是否应该使用IDE,每个人都有着自己的看法。 早期,程序的开发并不需要 IDE,那是以机器码编程为主的时代。后来随着计算机行业发展&#xff0c…

基于yolov5-master和pyqt5的森林火灾监测软件

文章目录 项目背景效果演示一、实现思路① 算法原理② 程序流程图 二、系统设计与实现三、模型评估与优化 项目背景 火灾作为威胁人类生命生产安全的隐患之一,一直是人们关注的重点。传统的火灾监测装置根据温度来检测火灾,不仅灵敏度差,而且…

2024年pmp考试还有多久啊?怎么备考?

一般来说每年3、6、9、12月考试,一年四次,具体时间以官网通知为准。报考时间提前2个月报名,2023年的最后一次考试时间是11月,已经截止报名了。所以下一次考试就得等到2024年3月了。 想知道怎么备考先来分析一下现在的“新”考纲&…

flutter 五点一点一:MaterialApp Theme

factory ThemeData({...TargetPlatform? platform, //目标平台ScrollbarThemeData? scrollbarTheme, //滚动条主题样式...}platform 目标平台 貌似表示的是当前Theme 的目标平台枚举值 例如 我将 platform 设置为ios platform: TargetPlatform.iOS然后运行到 android模拟…

Eureka整合seata分布式事务

文章目录 一、分布式事务存在的问题二、分布式事务理论三、认识SeataSeata分布式事务解决方案1、XA模式2、AT模式3、SAGA模式4.SAGA模式优缺点:5.四种模式对比 四、微服务整合Seata AT案例Seata配置微服务整合2.1、父工程项目创建引入依赖 2.2、Eureka集群搭建2.3、…

Unity Binding冲突解决探究

主要参考: 官方文档:Unity官方Input System手册与API本文节选自:【Unity学习笔记】第十二 New Input System 及其系统结构 和 源码浅析 可能存在冲突的几种情况: 同一Action下不同binding引用同一个control,且都非组合…

阿里云ECS(CentOS镜像)安装docker

目录 1.前置条件 2.连接至ECS 3.yum软件包更新 4.安装docker前置所需软件包 5.添加docker 官方的 yum 软件源 6.安装docker 7.检测是否成功 8.配置阿里云镜像加速器 1.前置条件 在看本文前保证未安装过docker,或者安装过但是清理干净 如果多次安装失败过,…

C语言第五弹---分支语句(上)

✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】 分支语句 1、if语句1.1、if1.2、 else1.3、 分支中包含多条语句1.4、嵌套if1.5、 悬空else问题 2、关系操作符3、 条件操作符总结 C语言是结构化的程序设计语言&…

优思学院|怎样制作优秀的标准作业流程(SOP)?

我相信你一定去过众所周知的麦当劳。它在世界各地都有许多分店。不知道你是否曾在不同地方的麦当劳尝试过他们的美食,但你可能会好奇,为什么无论在世界哪个角落,麦当劳的食物口味都如此一致?它们是如何确保全球各地分店都能提供同…

【C++】类和对象(上篇)

文章目录 🛟一、面向过程和面向对象初步认识🛟二、类的引入🛟三、类的定义📝1、类的两种定义方式📝2、成员变量命名规则的建议 🛟四、类的访问限定符及封装🍩1、访问限定符🍩2、封装…

Docker(九)Docker Buildx

作者主页: 正函数的个人主页 文章收录专栏: Docker 欢迎大家点赞 👍 收藏 ⭐ 加关注哦! Docker Buildx Docker Buildx 是一个 docker CLI 插件,其扩展了 docker 命令,支持 [Moby BuildKit] 提供的功能。提…

Unity中实现捏脸系统

前言 目前市面上常见的捏脸一般是基于BlendShapes和控制骨骼点坐标两种方案实现的。后者能够控制的精细程度更高,同时使用BlendShapes来控制表情。 控制骨骼点坐标 比如找到控制鼻子的骨骼节点修改localScale缩放,调节鼻子大小。 BlendShapes控制表…

PLC网关BL121PO 实现低成本的PLC接入OPC UA的解决方案

随着工业4.0的迅猛发展,人们深刻认识到在工业生产和生活中,实时、可靠、安全的数据传输至关重要。在此背景下,高性能的工业自动化数据传输解决方案——协议转换网关应运而生,广泛应用于工业自动化和数字化工厂应用环境中。 无缝衔…

Linux 命令大全 CentOS常用运维命令

文章目录 1、Linux 目录结构2、解释目录3、命令详解3.1、shutdown命令3.1、文件目录管理命令ls 命令cd 命令pwd 命令tree 命令mkdir 命令touch 命令cat 命令cp 命令more 命令less 命令head 命令mv 命令rm 命令ln 命令tail 命令cut命令 3.2、用户管理useradd/userdel 命令用户的…

51单片机LED点阵屏

LED点阵屏 LED点阵屏是一种由许多小型LED灯组成的矩阵式显示屏。这些LED灯可以是单色、双色或全彩的,它们排列成行和列的网格,可以根据需要点亮来显示图像、文字或动画等内容。LED点阵屏广泛应用于户外广告牌、室内显示、交通信号灯、电子价格标签和其他…

[小程序]基于token的权鉴测试

一、服务器配置 服务器基于flask,需要额外安装flask_jwt_extended包 from flask import Flask #导入Flask包 from flask import request from flask import jsonify #用来返回json消息 from flask_jwt_extended import create_access_token, jwt_requi…

Docker(十)Docker Compose

作者主页: 正函数的个人主页 文章收录专栏: Docker 欢迎大家点赞 👍 收藏 ⭐ 加关注哦! Docker Compose 项目 Docker Compose 是 Docker 官方编排(Orchestration)项目之一,负责快速的部署分布式…

轻松打卡:使用Spring Boot和Redis Bitmap构建高效签到系统【redis实战 四】

欢迎来到我的博客,代码的世界里,每一行都是一个故事 轻松打卡:使用Spring Boot和Redis Bitmap构建高效签到系统【redis实战 四】 引言(redis实战)前言回顾bitmap基本概念核心特性使用场景 为什么使用redis中的bitmap实现?1. 存储效…

探索全球DNS体系 | 从根服务器到本地解析

DNS 发展 DNS(Domain Name System)的起源可以追溯到互联网早期。 早期的挑战: 早期互联网主要通过IP地址进行通信,用户需要记住复杂的数字串来访问网站。 需求的催生: 随着互联网的扩大,更简单、易记的…

binary_search_tree的介绍与实现(二叉搜索树精美图示详解哦)

二叉搜搜索树 引言二叉搜索树的介绍二叉搜索树的实现框架默认成员函数构造析构赋值重载 InsertR(插入)EraseR(删除)SearchR(查找) 源码概览总结 引言 在C语言部分,我们已经认识了树与二叉树的结…