Java Websocket 02: 原生模式通过 Websocket 传输文件
目录
Websocket 原生模式 传输文件
关于 Websocket 传输的消息类型, 允许的参数包括以下三类
- 以下类型之一, 同时只能出现一个
- 文本类型 (text messages) 的消息: String, Java primitive, 阻塞的 Stream Reader, 带text decoder(Decoder.Text or Decoder.TextStream)的对象
- 二进制类型 (binary messages) 的消息: byte[] 或 ByteBuffer, 阻塞的 InputStream, 带 binary decoder (Decoder.Binary or Decoder.BinaryStream)的对象
- Pong messages: PongMessage
- 通过 PathParam 指定的0个或多个基础类型
- 会话参数 Session, 可选
因此对于不同的消息类型, 可以有不同参数类型的 onMessage() 方法, 分别用于处理不同格式的内容, 对于传输文件, 需要使用 ByteBuffer 类型的参数
void onMessage(ByteBuffer byteBuffer, Session session)
在处理过程中和普通的文件传输是一样的, 需要将文件分片传输, 并约定合适的消息头用于判断文件传输的阶段, 在服务端根据不同的阶段进行文件创建, 写入和结束.
演示项目
与前一篇项目结构相同, 只需要修改 SocketServer 和 SocketClient
完整示例代码: https://github.com/MiltonLai/websocket-demos/tree/main/ws-demo02
SocketServer.java
增加了 onMessage(ByteBuffer byteBuffer, Session session) 方法用于处理二进制消息, 在方法中
- 先读取第一个字节的值, 根据不同的值对应不同的操作
- 1 表示文件传输前的准备
- 3 表示文件内容写入
- 5 表示文件结束
- 再读取后续的值
- 1 解析出文件元信息, 并创建文件通道
- 3 将内容写入文件
- 5 关闭文件通道, 清除buffer
- 回传ACK
- 1 ACK 2
- 3 不ACK
- 5 ACK 6
@Component
@ServerEndpoint("/websocket/server/{sessionId}")
public class SocketServer {
//...
@OnMessage
public void onMessage(ByteBuffer byteBuffer, Session session) throws IOException {
if (byteBuffer.limit() == 0) {
return;
}
byte mark = byteBuffer.get(0);
if (mark == 1) {
log.info("mark 1");
byteBuffer.get();
String info = new String(
byteBuffer.array(),
byteBuffer.position(),
byteBuffer.limit() - byteBuffer.position());
FileInfo fileInfo = new JsonMapper().readValue(info, FileInfo.class);
byteChannel = Files.newByteChannel(
Path.of("D:/data/" + fileInfo.getFileName()),
new StandardOpenOption[]{StandardOpenOption.CREATE, StandardOpenOption.WRITE});
//ack
ByteBuffer buffer = ByteBuffer.allocate(4096);
buffer.put((byte) 2);
buffer.put("receive fileinfo".getBytes(StandardCharsets.UTF_8));
buffer.flip();
session.getBasicRemote().sendBinary(buffer);
} else if (mark == 3) {
log.info("mark 3");
byteBuffer.get();
byteChannel.write(byteBuffer);
} else if (mark == 5) {
log.info("mark 5");
//ack
ByteBuffer buffer = ByteBuffer.allocate(4096);
buffer.clear();
buffer.put((byte) 6);
buffer.put("receive end".getBytes(StandardCharsets.UTF_8));
buffer.flip();
session.getBasicRemote().sendBinary(buffer);
byteChannel.close();
byteChannel = null;
}
}
//...
public static class FileInfo implements Serializable {
private String fileName;
private long fileSize;
public String getFileName() {return fileName;}
public void setFileName(String fileName) {this.fileName = fileName;}
public long getFileSize() {return fileSize;}
public void setFileSize(long fileSize) {this.fileSize = fileSize;}
}
}
SocketClient.java
client 测试类, 连接后可以在命令行向 server 发送消息
首先是消息处理中增加了 void onMessage(ByteBuffer bytes), 这个是用来接收服务端回传的ACK的, 根据第一个字节, 判断服务端的处理结果. 这里使用了一个 condition.notify() 用来通知发送线程继续发送
其次是消息发送中, 用输入的1触发文件发送. 文件发送在 void sendFile(WebSocketClient webSocketClient, Object condition) 方法中进行, 通过一个 condition 对象, 在文件开始传输和结束传输时控制线程的暂停和继续. byteBuffer.flip()用于控制 byteBuffer 从写状态变为读状态, 用于发送. flip is used to flip the ByteBuffer from "reading from I/O" (putting) to "writing to I/O" (getting).
public class SocketClient {
private static final Logger log = LoggerFactory.getLogger(SocketClient.class);
public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {
Object condition = new Object();
WebSocketClient wsClient = new WebSocketClient(new URI("ws://127.0.0.1:8763/websocket/server/10001")) {
//...
@Override
public void onMessage(ByteBuffer bytes) {
//To overwrite
byte mark = bytes.get(0);
if (mark == 2) {
synchronized (condition) {
condition.notify();
}
log.info("receive ack for file info");
} else if (mark == 6){
synchronized (condition) {
condition.notify();
}
log.info("receive ack for file end");
}
}
@Override
public void onClose(int i, String s, boolean b) {
log.info("On close: {}, {}, {}", i, s, b);
}
@Override
public void onError(Exception e) {
log.error("On error: {}", e.getMessage());
}
};
wsClient.connect();
log.info("Connecting ...");
while (!ReadyState.OPEN.equals(wsClient.getReadyState())) {
}
log.info("Connected");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String line = scanner.next();
if ("1".equals(line))
sendFile(wsClient, condition);
else
wsClient.send(line);
}
}
public static void sendFile(WebSocketClient webSocketClient, Object condition){
new Thread(() -> {
try {
SeekableByteChannel byteChannel = Files.newByteChannel(
Path.of("/home/milton/Backup/linux/apache-tomcat-8.5.58.tar.gz"),
new StandardOpenOption[]{StandardOpenOption.READ});
ByteBuffer byteBuffer = ByteBuffer.allocate(4*1024);
byteBuffer.put((byte)1);
String info = "{\"fileName\": \"greproto.tar.gz\", \"fileSize\":"+byteChannel.size()+"}";
byteBuffer.put(info.getBytes(StandardCharsets.UTF_8));
byteBuffer.flip();
webSocketClient.send(byteBuffer);
synchronized (condition) {
condition.wait();
}
byteBuffer.clear();
byteBuffer.put((byte)3);
while (byteChannel.read(byteBuffer) > 0) {
byteBuffer.flip();
webSocketClient.send(byteBuffer);
byteBuffer.clear();
byteBuffer.put((byte)3);
}
byteBuffer.clear();
byteBuffer.put((byte)5);
byteBuffer.put("end".getBytes(StandardCharsets.UTF_8));
byteBuffer.flip();
webSocketClient.send(byteBuffer);
synchronized (condition) {
condition.wait();
}
byteChannel.close();
} catch (InterruptedException|IOException e) {
log.error(e.getMessage(), e);
}
}).start();
}
}
运行示例
示例是一个普通的 Spring Boot jar项目, 可以通过mvn clean package进行编译, 再通过java -jar ws-demo01.jar运行, 启动后工作在8763端口
将 SocketClient.java 中的文件路径 D:/WorkJava/tmp/greproto.tar.gz 换成自己本地的文件路径, 运行 SocketClient, 可以观察到服务端接收到的消息. 如果输入1并回车, 就会触发客户端往服务端传输文件
参考
Java Websocket 02: 原生模式通过 Websocket 传输文件的更多相关文章
- 在java中使用SFTP协议安全的传输文件
本文介绍在Java中如何使用基于SSH的文件传输协议(SFTP)将文件从本地上传到远程服务器,或者将文件在两个服务器之间安全的传输.我们先来了解一下这几个协议 SSH 是较可靠,专为远程登录会话和其他 ...
- Java 学习笔记 网络编程 使用Socket传输文件 CS模式
Socket的简单认识 Socket是一种面向连接的通信协议,Socket应用程序是一种C/S(Client端/Server端)结构的应用程序 Socket是两台机器间通信的端点. Socket是连接 ...
- Java Socket实战之七 使用Socket通信传输文件
http://blog.csdn.net/kongxx/article/details/7319410 package com.googlecode.garbagecan.test.socket.ni ...
- Java后端WebSocket的Tomcat实现 html5 WebSocket 实时聊天
WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功能,使服务端也能主动向客户端发送数据.Tomcat7.0.47上才能运行. 需要添加Tomcat里lib目 ...
- java调用科大讯飞流式(websocket)语音识别接口
要使用讯飞的能力,需先注册讯飞开发平台账号(讯飞官网参见https://www.xfyun.cn/). 再创建应用,点击右上角的控制台 -> 创建新应用: 每个应用都有一个appId,由这个ap ...
- java设计模式之原型模式
原型模式概念 该模式的思想就是将一个对象作为原型,对其进行复制.克隆,产生一个和原对象类似的新对象.java中复制通过clone()实现的.clone中涉及深.浅复制.深.浅复制的概念如下: ⑴浅复制 ...
- java反射机制(工厂模式)
http://www.phpddt.com/dhtml/338.html java里面没有typeof,js有. 我终于实现了用反射机制编写的工厂模式.java反射在工厂模式可以体现. 包含产品接口类 ...
- 【WebSocket No.3】使用WebSocket协议来做服务器
写在开始 上面一篇写了一篇使用WebSocket做客户端,然后服务端是socke代码实现的.传送门:webSocket和Socket实现聊天群发 本来我是打算写到一章上的,毕竟实现的都是一样的功能,后 ...
- WebSocket原理与实践(二)---WebSocket协议
WebSocket原理与实践(二)---WebSocket协议 WebSocket协议是为了解决web即时应用中服务器与客户端浏览器全双工通信问题而设计的.协议定义ws和wss协议,分别为普通请求和基 ...
- websocket之三:Tomcat的WebSocket实现
Tomcat自7.0.5版本开始支持WebSocket,并且实现了Java WebSocket规范(JSR356 ),而在7.0.5版本之前(7.0.2版本之后)则采用自定义API,即WebSocke ...
随机推荐
- computed的setter妙用
使用场景:当我们用v-model绑定了一个计算属性,想直接设置计算属性时,就要利用到setter demo: <template> <div> <div>First ...
- react中的虚拟DOM,jsx,diff算法。让代码更高效
在react中当你的状态发生改变时,并不是所有组件的内容销毁再重建,能复用的就复用 react 组件其实 就是按照层级划分的 找到两棵任意的树之间最小的修改是一个复杂度为 O(n^3) 的问题. 你可 ...
- ChatGPT推荐最常用的自动化测试、性能、安全测试工具!
ChatGPT是一种当前被广泛关注的人工智能技术,它具备生成自然语言的能力,能够完成一些简单的文本生成.对话交互等任务.ChatGPT 算法的出现,打破了以前自然语言处理的瓶颈,使得机器具备了更加贴合 ...
- python类详解
类封装 继承 多态一静态属性1.静态变量和静态方法都属于类的静态成员,它们与普通的成员变量和成员方法不同,静态变量和静态方法只属于定义它们的类,而不属于某一个对象.2.静态变量和静态方法都可以通过类名 ...
- pandas之样本操作
随机抽样,是统计学中常用的一种方法,它可以帮助我们从大量的数据中快速地构建出一组数据分析模型.在 Pandas 中,如果想要对数据集进行随机抽样,需要使用 sample() 函数.sample() 函 ...
- 机器学习(五):混合高斯聚类(求聚类标签)+PCA降维(3维降2维)习题
使用混合高斯模型 GMM,计算如下数据点的聚类过程: \(Data = np.array([1,2,6,7])\) 均值初值为: \(\mu_1, \mu_2 = 1, 5\) 权重初值为: \(w_ ...
- 简单的cs修改器
目录 各个函数解析 main() GetPid() 无限子弹 无限血 无限金币 Patch() 无僵直 稳定射击 Depatch1 手枪连发 Depatch 源代码部分 各个函数解析 这是我根据b站上 ...
- 在英特尔 CPU 上加速 Stable Diffusion 推理
前一段时间,我们向大家介绍了最新一代的 英特尔至强 CPU (代号 Sapphire Rapids),包括其用于加速深度学习的新硬件特性,以及如何使用它们来加速自然语言 transformer 模型的 ...
- Podman Desktop安装与使用-Windows10
Podman Desktop安装与使用-Windows10 下载 containers/podman 地址 Podman Desktop Downloads 地址 我这里演示的是podman-v4.4 ...
- 【ACM组合数学 | 错排公式】写信
题目链接:https://ac.nowcoder.com/acm/contest/54484/B 题意很简单,但是数据范围偏大. 错排公式 首先来推导一下错排公式: \[D(n) = n!\sum_{ ...