网络编程之 Socket 套接字(使用数据报套接字和流套接字分别实现一个小程序(附源码))
文章目录
- 1. 什么是网络编程
- 2. 网络编程中的基本概念
- 1)发送端和接收端
- 2)请求和响应
- 3)客户端和服务端
- 4)常见的客户端服务端模型
- 3. Socket 套接字
- 1)Socket 的分类
- 2)Java 数据报套接字通信模型
- 3)Java 流套接字通信模型
- 4. UDP 数据报套接字编程
- 1)DatagramSocket API
- 2)DatagramPacket API
- 3)示例
- 5. TCP 流套接字编程
- 1)ServerSocket API
- 2)Socket API
- 3)示例
- a. 短连接版本
- b. 长连接并发版本
1. 什么是网络编程
网络编程是指网络上的主机,通过不同的进程,以编程的方式实现 网络通信(或称为网络数据传输)
只要满足不同的进程就可以进行通信,所以即便是在同一个主机,只要不同的进程,基于网络传输数据,也属于网络编程
2. 网络编程中的基本概念
1)发送端和接收端
在一次网络传输中:
发送端: 数据的 发送方进程,称为发送端。发送端主机即网络通信中的源主机
接收端: 数据的 接收方进程,称为接收端。接收端主机即网络通信中的目的主机
收发端: 发送端和接收端两端,简称为收发端
注意: 发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念
2)请求和响应
一般来说,获取一个网络资源时,涉及到两次网络数据传输
- 第一次:请求数据的发送
- 第二次:响应数据的发送
就好比在饭店里点了一碗面
顾客先发起请求:来一碗面
饭店再做出响应:提供一碗面
3)客户端和服务端
服务端: 在常见的网络传输场景下,把 提供服务 的一方进程,称为服务端,可以对外提供服务
客户端: 获取服务 的一方进程,称为客户端
对于服务来说,一般是提供:
- 客户端获取服务资源
- 客户端保存资源到服务端
4)常见的客户端服务端模型
在常见的场景中,客户端是指给用户使用的程序,服务端是指提供用户服务的程序,具体流程如下:
- 客户端发送请求到服务端
- 服务端根据请求数据,执行相应的业务处理
- 服务端返回响应:发送业务处理结果
- 客户端根据响应数据,展示处理结果
3. Socket 套接字
Socket 套接字时由系统提供用于网络通信的技术,是基于 TCP/IP 协议的网络通信的基本操作单元。基于 Socket 套接字的网络程序开发就是网络编程
1)Socket 的分类
常用的 Socket 套接字有以下两类:
-
流套接字: 使用 TCP 协议
传输数据基于 IO 流,流式数据的特征就是在 IO 流没有关闭的情况下,是无边界的数据,可以多次发送数据,也可以分开多次接收
-
数据报套接字: 使用 UDP 协议
传输数据是一块一块的,每一块数据必须一次性发送,接受也必须一次性接受,不能分开发送或接收
2)Java 数据报套接字通信模型
UDP 协议具有无连接、面向数据报的特征,即每次传输都是没有建立连接,并且一次发送全部数据,一次接收全部数据
Java 中使用 UDP 协议通信时,主要基于 DatagramSocket 类来创建数据报套接字,并使用 DatagramPacket 作为发送或者接受的数据报。
具体流程如下:
3)Java 流套接字通信模型
TCP 协议具有有连接、面向字节流的特征,即传输数据之前要先建立连接,并且数据的传输是基于 IO 流的
具体流程如下:
4. UDP 数据报套接字编程
1)DatagramSocket API
DatagramSocket 是 UDP Socket,用于发送和接收 UDP 数据报
构造方法:
方法签名 | 方法说明 |
---|---|
DatagramSocket() | 创建一个 UDP 数据报套接字的 Socket,绑定到本机任意一个随机端口 |
DatagramSocket(int port) | 创建一个 UDP 数据报套接字的 Socket,绑定到本机的指定端口上 |
实例方法:
方法签名 | 方法说明 |
---|---|
void receive(DatagramPacket p) | 接收数据报到提前构造的 DatagramPacket 对象中(如果没有接收到数据报,该方法会阻塞等待) |
void send(DatagramPacket p) | 发送提前构造的 DatagramPacket 数据报 |
void close() | 关闭套接字 |
2)DatagramPacket API
DatagramPacket 是 UDP Socket 发送和接收的数据报
构造方法:
方法签名 | 方法说明 |
---|---|
DatagramPacket(byte[] buf, int length) | 构造一个 DatagramPacket 对象用来接收数据报,接收的数据保存到字节数组中 |
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) | 构造 DatagramPacket 用来发送数据报,发送的数据为字节数组中的数据。指定目的主机的 IP 和端口号 |
实例方法:
方法签名 | 方法说明 |
---|---|
InetAddress getAddress() | 从数据报中,获取 IP 地址 |
int getPort() | 从数据报中,获取端口号 |
byte[] getData() | 获取数据报中的数据 |
3)示例
使用 UDP Socket 套接字完成一个简单的翻译小程序
客户端向服务端发起翻译请求,服务端接收到请求后,对请求进行处理,再向客户端做出响应
服务端
服务端接收到请求之后,在我们人为规定的 map 中进行查询,再对客户端做出响应
public class Server {private final static HashMap<String, String> map = new HashMap<>();static {map.put("苹果", "apple");map.put("香蕉", "banana");map.put("梨", "pear");map.put("电脑", "computer");map.put("手机", "phone");}public static void main(String[] args) throws IOException {// 得到 socket 对象,并对其绑定端口号Log.println("服务器开启并且绑定了 8080 端口");DatagramSocket socket = new DatagramSocket(8080);// 循环接受和处理请求while (true) {// 准备接收数据的容器byte[] buf = new byte[1024];// 包装数据包DatagramPacket received = new DatagramPacket(buf, 0, 1024);// 接收请求Log.println("服务器准备接受请求");socket.receive(received);Log.println("服务器接受到了请求");// 处理请求InetAddress address = received.getAddress(); // 得到发起请求方的 IPLog.println("对方的地址:" + address);int port = received.getPort(); // 得到发起请求方的端口号Log.println("对方的端口:" + port);int length = received.getLength(); // 得到请求中真实有效的数据长度Log.println("真实的数据长度:" + length);byte[] realData = Arrays.copyOf(buf, length); // 得到请求中真实有效的数据// 将请求数据转换成 StringString request = new String(realData, 0, length, StandardCharsets.UTF_8);Log.println("请求中的数据:\r\n" + request);// 人为规定请求和相应的格式// 请求格式必须以 “我是请求:\r\n” 开始,以 “\r\n” 结束if (!request.startsWith("我是请求:\r\n")) {Log.println("请求格式出错");// 请求出错continue;}if (!request.endsWith("\r\n")) {Log.println("请求格式出错");// 请求出错continue;}// 得到请求中的英文单词String EnglishWord = request.substring("我是请求:\r\n".length(), request.length() - 2);Log.println("请求中的英文单词:" + EnglishWord);// 进行翻译String ChineseWord = map.getOrDefault(EnglishWord, "我不知道");Log.println("翻译后的中文:" + ChineseWord);// 将翻译后的结果进行包装String response = String.format(ChineseWord, "%s\r\n");byte[] bytes = response.getBytes(StandardCharsets.UTF_8);// 包装数据包DatagramPacket send = new DatagramPacket(bytes, 0, bytes.length, address, port);// 发起响应Log.println("准备发送响应");socket.send(send);Log.println("相应发送成功");}}
}
客户端
public class Client {public static void main(String[] args) throws IOException {Scanner sc = new Scanner(System.in);DatagramSocket socket = new DatagramSocket(9999);while (sc.hasNextLine()) {String str = sc.nextLine();String send = "我是请求:\r\n" + str + "\r\n";byte[] bytes = send.getBytes();DatagramPacket request = new DatagramPacket(bytes, 0, bytes.length, InetAddress.getLoopbackAddress(), 8080);socket.send(request);// 接收响应byte[] buf = new byte[1024];DatagramPacket datagramPacket = new DatagramPacket(buf, 0, 1024);socket.receive(datagramPacket);int n = datagramPacket.getLength();String response = new String(buf, 0, n, StandardCharsets.UTF_8);Log.println(response);}}
}
5. TCP 流套接字编程
1)ServerSocket API
用于创建 TCP 服务端 Socket 的 API
构造方法:
方法签名 | 方法说明 |
---|---|
ServerSocket(int port) | 创建一个服务端流套接字 Socket,并绑定到指定端口 |
实例方法:
方法签名 | 方法说明 |
---|---|
Socket accept() | 等待客户端发起连接,连接成功后返回一个 Socket 对象 |
void close() | 关闭 Socket |
2)Socket API
这里的 Socket 是客户端 Socket,或服务端中接收到客户端连接请求后,accept 方法返回的服务端 Socket
不管是客户端还是服务端 Socket,都是双方建立连接后,保存对端信息,以及用来与对方收发数据的
构造方法:
方法签名 | 方法说明 |
---|---|
Socket(String host, int port) | 创建一个客户端流套接字,并与对应 IP 的主机,对应端口的进程建立连接 |
实例方法:
方法签名 | 方法说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的 IP 地址 |
InputStream getInputStream() | 返回套接字的输入流 |
OutputStream getOutputStream() | 返回套接字的输出流 |
3)示例
使用 TCP Socket 套接字完成一个简单的翻译小程序
完成的效果和上文 UDP 的小程序效果一样
不过在 TCP 这里有长短连接的区分,所以有两个版本的代码,在长连接版本中加入了并发编程,使得同一个服务端可以被多个客户端所连接
a. 短连接版本
客户端:
public class 短连接版本Client {public static void main(String[] args) throws IOException {for (int i = 0; i < 3; i++) {Socket socket = new Socket("127.0.0.1", 8080); // 拨号OutputStream os = socket.getOutputStream();OutputStreamWriter writer = new OutputStreamWriter(os, "UTF-8");PrintWriter printWriter = new PrintWriter(writer);printWriter.printf("我是请求\r\n%s\r\n", "苹果");printWriter.flush();InputStream is = socket.getInputStream();Scanner scanner = new Scanner(is, "UTF-8");String header = scanner.nextLine();String word = scanner.nextLine();System.out.println(word);socket.close(); // 挂电话}}
}
服务端:
public class 短连接版本Server {private final static HashMap<String, String> map = new HashMap<>();static {map.put("苹果", "apple");map.put("香蕉", "banana");map.put("梨", "pear");map.put("电脑", "computer");map.put("手机", "phone");}public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(8080);while(true) {Log.println("等待客户端建立连接");Socket socket = serverSocket.accept();Log.println("客户端连接成功");InetAddress address = socket.getInetAddress();Log.println("客户端 IP 地址:" + address);int port = socket.getPort();Log.println("客户端端口号:" + port);InputStream is = socket.getInputStream();Scanner sc = new Scanner(is, "UTF-8");String header = sc.nextLine();String EnglishWord = sc.nextLine();Log.println("请求中的英文单词为:" + EnglishWord);String ChineseWord = map.getOrDefault(EnglishWord, "我不知道");Log.println("翻译后的中文为:" + ChineseWord);Log.println("准备响应");OutputStream os = socket.getOutputStream();OutputStreamWriter writer = new OutputStreamWriter(os);PrintWriter printWriter = new PrintWriter(writer);printWriter.printf("OK!\r\n%s\t\n", ChineseWord);printWriter.flush();Log.println("响应成功");socket.close();Log.println("断开连接");}}
}
b. 长连接并发版本
在长连接版本中,必须服务端和客户端同时支持长连接才可以进行通信
如果不清楚长连接和短连接,可以去看看我上篇文章,里面有介绍长连接和短连接,HTTP 和 HTTPS,有介绍 HTTP 的长短连接,实质上就是 TCP 长短连接
客户端:
public class Client {public static void main(String[] args) throws IOException {Scanner sc = new Scanner(System.in);Socket socket = new Socket("127.0.0.1", 8080);OutputStream os = socket.getOutputStream();OutputStreamWriter writer = new OutputStreamWriter(os, StandardCharsets.UTF_8);PrintWriter printWriter = new PrintWriter(writer);InputStream is = socket.getInputStream();Scanner scanner = new Scanner(is, "UTF-8");while (sc.hasNextLine()) {String str = sc.nextLine();printWriter.printf("我是请求:\r\n%s\r\n", str);printWriter.flush();// 接收响应String header = scanner.nextLine();String response = scanner.nextLine();Log.println(header + "\r\n" + response);}socket.close();}
}
服务端:
public class Server {private final static HashMap<String, String> map = new HashMap<>();static {map.put("苹果", "apple");map.put("香蕉", "banana");map.put("梨", "pear");map.put("电脑", "computer");map.put("手机", "phone");}private final static class ServerTask implements Runnable {private final Socket socket;private ServerTask(Socket socket) {this.socket = socket;}@Overridepublic void run() {InetAddress address = socket.getInetAddress();Log.println("客户端 IP 地址为:" + address);int port = socket.getPort();Log.println("客户端端口号为:" + port);try {InputStream is = socket.getInputStream();// 请求格式必须以 “我是请求:\r\n” 开始,以 “\r\n” 结束Scanner sc = new Scanner(is, "UTF-8");// 进行响应OutputStream os = socket.getOutputStream();OutputStreamWriter writer = new OutputStreamWriter(os, StandardCharsets.UTF_8);PrintWriter printWriter = new PrintWriter(writer);while (sc.hasNextLine()) {String header = sc.nextLine();String EnglishWord = sc.nextLine();Log.println("请求中的词为:" + EnglishWord);// 对请求进行处理String ChineseWord = map.getOrDefault(EnglishWord, "我不知道");Log.println("翻译后的词:" + ChineseWord);Log.println("准备发起响应");printWriter.printf("OK!\r\n%s\r\n", ChineseWord);printWriter.flush();Log.println("响应成功");}socket.close();Log.println("连接已关闭");} catch (IOException e) {e.printStackTrace();}}}public static void main(String[] args) throws IOException {// 使用多线程完成多用户同时在线的功能ExecutorService threadPool = Executors.newFixedThreadPool(3);ServerSocket serverSocket = new ServerSocket(8080);Log.println("绑定端口到 8080");while (true) {Log.println("等待客户端连接");Socket socket = serverSocket.accept();Log.println("客户端连接成功");ServerTask serverTask = new ServerTask(socket);threadPool.execute(serverTask);}}
}
相关文章:

网络编程之 Socket 套接字(使用数据报套接字和流套接字分别实现一个小程序(附源码))
文章目录 1. 什么是网络编程2. 网络编程中的基本概念1)发送端和接收端2)请求和响应3)客户端和服务端4)常见的客户端服务端模型 3. Socket 套接字1)Socket 的分类2)Java 数据报套接字通信模型3)J…...
What Are Docker Image Layers?
Docker images consist of multiple layers that collectively provide the content you see in your containers. But what actually is a layer, and how does it differ from a complete image? In this article you’ll learn how to distinguish these two concepts and…...

范数详解-torch.linalg.norm计算实例
文章目录 二范数F范数核范数无穷范数L1范数L2范数 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 范数是一种数学概念,可以将向量或矩阵映射到非负实数上,通常被…...
postgresdb备份脚本
以下是一个简单的postgresdb备份脚本示例: 复制 #!/bin/bash # 设置备份目录和文件名 BACKUP_DIR/path/to/backup BACKUP_FILEdb_backup_$(date %F_%H-%M-%S).sql # 设置数据库连接参数 DB_HOSTlocalhost DB_PORT5432 DB_NAMEmydatabase DB_USERmyusername DB_PA…...
MATLAB程序员投简历的技巧解析,如何写出有亮点的简历
如果你想在简历中展示你的项目经验,一定要有亮点。一个导出的 Excel 文件过大导致浏览器卡顿的例子就是一个很好的亮点。你可以在简历中写明这个例子。如果面试官问起,可以用浏览器的原理来解释。浏览器内核可以简单地分为以下 5 个线程:GUI …...

颜色空间转换RGB-YCbCr
颜色空间 颜色空间(Color Space)是描述颜色的一种方式,它是一个由数学模型表示的三维空间,通常用于将数字表示的颜色转换成可见的颜色。颜色空间的不同取决于所选的坐标轴和原点,以及用于表示颜色的色彩模型。在计算机…...
年薪40万程序员辞职炒股,把一年工资亏光了,得了抑郁症,太惨了
年薪40万的程序员辞职全职炒股 把一年的工资亏光了 得了抑郁症 刚才在网上看了一篇文章 是一位北京的一位在互联网 大厂上班的程序员 在去年就是股市行情比较好的时候 他买了30多万股票 结果连续三个月都赚钱 然后呢 他是就把每天就996这种工作就辞掉了 然后在家全是炒股 感觉炒…...

10分钟如何轻松掌握JMeter使用方法?
目录 引言 安装jmeter HTTP信息头管理器 JMeter断言 HTTP请求默认值来代替所有的域名与端口 JSON提取器来替换变量 结语 引言 想要了解网站或应用程序的性能极限,JMeter是一个不可或缺的工具。但是,对于初学者来说,该如何上手使用JMe…...

[NLP]如何训练自己的大型语言模型
简介 大型语言模型,如OpenAI的GPT-4或Google的PaLM,已经席卷了人工智能领域。然而,大多数公司目前没有能力训练这些模型,并且完全依赖于只有少数几家大型科技公司提供技术支持。 在Replit,我们投入了大量资源来建立从…...

LeetCode1047. 删除字符串中的所有相邻重复项
1047. 删除字符串中的所有相邻重复项 给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。 在 S 上反复执行重复项删除操作,直到无法继续删除。 在完成所有重复项删除操作后返回最终的字符串。答案保证唯一…...
3。数据结构(3)
嵌入式软件开发第三部分,各类常用的数据结构及扩展,良好的数据结构选择是保证程序稳定运行的关键,(1)部分包括数组,链表,栈,队列。(2)部分包括树,…...
QT停靠窗口QDockWidget类
QT停靠窗口QDockWidget类 QDockWidget类简介函数和方法讲解 QDockWidget类简介 QDockWidget 类提供了一个部件,它可以停靠在 QMainWindow 内或作为桌面上的顶级窗口浮动。 QDockWidget 提供了停靠窗口部件的概念,也称为工具面板或实用程序窗口。 停靠窗…...

【LeetCode】139. 单词拆分
139. 单词拆分(中等) 思路 首先将大问题分解成小问题: 前 i 个字符的子串,能否分解成单词;剩余子串,是否为单个单词; 动态规划的四个步骤: 确定 dp 数组以及下标的含义 dp[i] 表示 s…...

【三维重建】NeRF原理+代码讲解
文章目录 一、技术原理1.概览2.基于神经辐射场(Neural Radiance Field)的体素渲染算法3.体素渲染算法4.位置信息编码(Positional encoding)5.多层级体素采样 二、代码讲解1.数据读入2.创建nerf1.计算焦距focal与其他设置2.get_emb…...

IntelliJ IDEA 社区版2021.3配置SpringBoot项目详细教程及错误解决方法
目录 一、SpringBoot的定义 二、Spring Boot 优点 三、创建一个springboot的项目 四、使用IDEA创建SpringBoot失败案例 一、SpringBoot的定义 Spring 的诞⽣是为了简化 Java 程序的开发的,⽽ Spring Boot 的诞⽣是为了简化 Spring 程序开发的。 Spring Boot 翻…...

Qt中QDebug的使用
QDebug类为调试信息(debugging information)提供输出流。它的声明在<QDebug>中,实现在Core模块中。将调试或跟踪信息(debugging or tracing information)写出到device, file, string or console时都会使用QDebug。 此类的成员函数参考:https://doc…...

vue使用路由的query配置项时如何清除地址栏的参数
写vue项目时,如果想通过路由的query配置项把参数从一个组件传到另一个组件,但是又不希望?idxxx显示在地址栏(如:http://localhost:8080/test?idxxx的?idxxx),该怎么做: 举一个案例࿱…...
Redis-列表(List)
Redis列表(List) 介绍 单键多值Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)它的底层实际是个双向链表,对两端的操作性能很高,通过索…...

ripro主题修改教程-首页搜索框美化教程
先看效果图: 我们来看怎么实现: 1、找到wp-content/themes/ripro/assets/css/diy.css并将下面的内容整体复制进去并保存 /*首页搜索框*/ .bgcolor-fff {background-color: #fff; } .row,.navbar .menu-item-mega>.sub-menu{margin-left:-10px;margin-right:-10px;} .home…...

写作业用白光还是暖光?盘点色温4000K的护眼台灯
台灯的白光或者暖光指的是台灯的色温,低色温的光线看起来发黄发红,高色温的光线发白发蓝。 如果灯光的光源是高品质光源,本身没有蓝光问题,那么色温的选择对护眼的影响是比较少的,更多的是对人学习工作状态,…...

网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...