当前位置: 首页 > news >正文

【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);// 服务器端返回OKreturn "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

相关文章:

  • 基于CLIP4Clip的DRL的WTI模块实现
  • Three.js Tri-panner (三面贴图) 材质 两种实现方式
  • 舞动微服务的安全舞伴:服务熔断与服务降级的精妙演绎
  • C#,入门教程(24)——类索引器(this)的基础知识
  • OPENGL光线追踪
  • Kafka-服务端-DelayedOperationPurgatory
  • docker:Java通过nginx获取客户端的真实ip地址
  • 【云原生之kubernetes实战】在k8s环境下部署Mikochi文件管理工具
  • 【STM32调试】寄存器调试不良问题记录持续版
  • etcd安装
  • Idea 开发环境不断切换git代码分支导致冲掉别人代码
  • 运用分布式锁 redisson
  • 第十五章 : Spring Cloud全链路监控(Pinpoint实战)
  • Docker(二)安装指南:主要介绍在 Linux 、Windows 10 和 macOS 上的安装
  • 准备的一些爬虫面试题
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • Docker 笔记(1):介绍、镜像、容器及其基本操作
  • ES6 学习笔记(一)let,const和解构赋值
  • linux学习笔记
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • MQ框架的比较
  • Rancher-k8s加速安装文档
  • Rancher如何对接Ceph-RBD块存储
  • Vue2 SSR 的优化之旅
  • webpack入门学习手记(二)
  • 程序员最讨厌的9句话,你可有补充?
  • 观察者模式实现非直接耦合
  • 记录一下第一次使用npm
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 原生Ajax
  • ​LeetCode解法汇总2808. 使循环数组所有元素相等的最少秒数
  • (1)Nginx简介和安装教程
  • (4)Elastix图像配准:3D图像
  • (动手学习深度学习)第13章 计算机视觉---图像增广与微调
  • (分类)KNN算法- 参数调优
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境
  • (一)kafka实战——kafka源码编译启动
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • (转)C#调用WebService 基础
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • (转)淘淘商城系列——使用Spring来管理Redis单机版和集群版
  • .describe() python_Python-Win32com-Excel
  • .NET 实现 NTFS 文件系统的硬链接 mklink /J(Junction)
  • .Net 中Partitioner static与dynamic的性能对比
  • .NET的微型Web框架 Nancy
  • .NET建议使用的大小写命名原则
  • .NET牛人应该知道些什么(2):中级.NET开发人员
  • .NET下ASPX编程的几个小问题
  • .net中生成excel后调整宽度
  • @font-face 用字体画图标
  • [ C++ ] template 模板进阶 (特化,分离编译)
  • [AIGC] Spring Interceptor 拦截器详解
  • [Asp.net mvc]国际化
  • [BZOJ2281][SDOI2011]黑白棋(K-Nim博弈)