websocket的使用及建立连接

1. websocket

  • 1.客户端与服务端建连接
  • 2.客户端向服务端发送消息
  • 3.服务端在接受消息后以“response-接受的消息内容“的形式返回给客户端
  • 4.当服务端收到第5条信息的时候,主动关闭与客户端的连接

客户端代码

​private void clientWebSocket(String url) {OkHttpClient client = new OkHttpClient.Builder().build();//构造request对象Request request = new Request.Builder().url(url).build();//建立连接client.newWebSocket(request, new WebSocketListener() {//当远程对等方接受Web套接字并可能开始传输消息时调用。@Overridepublic void onOpen(WebSocket webSocket, Response response) {super.onOpen(webSocket, response);webSocket.send("发送消息");}//当收到文本(类型{@code 0x1})消息时调用@Overridepublic void onMessage(WebSocket webSocket, String text) {super.onMessage(webSocket, text);}//当收到二进制(类型为{@code 0x2})消息时调用。@Overridepublic void onMessage(WebSocket webSocket, ByteString bytes) {super.onMessage(webSocket, bytes);}//当远程对等体指示不再有传入的消息将被传输时调用。@Overridepublic void onClosing(WebSocket webSocket, int code, String reason) {super.onClosing(webSocket, code, reason);}//当两个对等方都表示不再传输消息并且连接已成功释放时调用。 没有进一步的电话给这位听众。@Overridepublic void onClosed(WebSocket webSocket, int code, String reason) {super.onClosed(webSocket, code, reason);}//由于从网络读取或向网络写入错误而关闭Web套接字时调用。// 传出和传入的消息都可能丢失。 没有进一步的电话给这位听众。@Overridepublic void onFailure(WebSocket webSocket, Throwable t, @Nullable Response response) {super.onFailure(webSocket, t, response);}});}​

2 客户端与服务端建连接

2.1创建一个 RealWebSocket 对象,然后执行其 connect() 方法建立连接。

​
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory@Overridepublic WebSocket newWebSocket(Request request, WebSocketListener listener) {RealWebSocket webSocket = new RealWebSocket(request, listener, new Random(), pingInterval);webSocket.connect(this);return webSocket;}
}​

2.2 真真的RealWebSocket

主要的是初始化了 key,以备后续连接建立及握手之用。Key 是一个16字节长的随机数经过 Base64 编码得到的。此外还初始化了 writerRunnable 等。

​
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallbackpublic RealWebSocket(Request request, WebSocketListener listener, Random random,long pingIntervalMillis) {//使用get的方式进行握手if (!"GET".equals(request.method())) {throw new IllegalArgumentException("Request must be GET: " + request.method());}this.originalRequest = request;this.listener = listener;this.random = random;this.pingIntervalMillis = pingIntervalMillis;//初始化key,以备后续连接建立及握手之用。byte[] nonce = new byte[16];random.nextBytes(nonce);this.key = ByteString.of(nonce).base64();//初始化了 writerRunnablethis.writerRunnable = new Runnable() {@Overridepublic void run() {try {while (writeOneFrame()) {}} catch (IOException e) {failWebSocket(e, null);}}};}
}​

2.3 握手建立连接

  • 1.创建一个 RealWebSocket 对象,然后执行其 connect() 方法建立连接。
  • 2.连接建立及握手的过程主要是向服务器发送一个HTTP请求。这个 HTTP 请求的特别之处在于,它包含了如下的一些Headers:
​
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Key: 7wgaspE0Tl7/66o4Dov2kw==
Sec-WebSocket-Version: 13​
  • 其中 Upgrade 和 Connection header 向服务器表明,请求的目的就是要将客户端和服务器端的通讯协议从 HTTP 协议升级到 WebSocket 协议,同时在请求处理完成之后,连接不要断开。
  • Sec-WebSocket-Key header 值正是我们前面看到的key,它是 WebSocket 客户端发送的一个 base64 编码的密文,要求服务端必须返回一个对应加密的 “Sec-WebSocket-Accept” 应答,否则客户端会抛出 “Error during WebSocket handshake” 错误,并关闭连接。
  • 3.来自于 HTTP 服务器的响应到达的时候,即是连接建立大功告成的时候
​
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {public void connect(OkHttpClient client) {client = client.newBuilder().eventListener(EventListener.NONE).protocols(ONLY_HTTP1).build();final Request request = originalRequest.newBuilder().header("Upgrade", "websocket").header("Connection", "Upgrade").header("Sec-WebSocket-Key", key).header("Sec-WebSocket-Version", "13").build();call = Internal.instance.newWebSocketCall(client, request);/*** WebSocket协议首先会通过发送一个http请求来完成一个握手的过程* 客户端发送一个请求协议升级的get请求给服务端* 服务端如果支持的话会返回http code 为101,表示可以切换到对应的协议*/call.enqueue(new Callback() {@Overridepublic void onResponse(Call call, Response response) {try {//第一步就是检查 HTTP 响应checkResponse(response);} catch (ProtocolException e) {failWebSocket(e, response);closeQuietly(response);return;}//将HTTP流推广到Web套接字流中StreamAllocation streamAllocation = Internal.instance.streamAllocation(call);streamAllocation.noNewStreams(); //阻止连接池//第二步:初始化用于输入输出的 Source 和 Sink。// Source 和 Sink 创建于之前发送HTTP请求的时候。这里会阻止在这个连接上再创建新的流。Streams streams = streamAllocation.connection().newWebSocketStreams(streamAllocation);// Process all web socket messages.try {//第三步是调用回调 onOpen()。listener.onOpen(RealWebSocket.this, response);//第四步是初始化 Reader 和 Writer:String name = "OkHttp WebSocket " + request.url().redact();initReaderAndWriter(name, streams);//第五步是配置socket的超时时间为0,也就是阻塞IO。streamAllocation.connection().socket().setSoTimeout(0);//第六步执行 loopReader()。这实际上是进入了消息读取循环了,也就是数据接收的逻辑了。loopReader();} catch (Exception e) {failWebSocket(e, null);}}@Overridepublic void onFailure(Call call, IOException e) {failWebSocket(e, null);}});}
}​

2.4 数据收发准备

  • HTTP 服务器的响应到达的时候,为数据收发做的准备,如:2.3代码onResponse;
  • 共有6步:
  • 第一步就是检查 HTTP 响应:
  • 第二步:初始化用于输入输出的 Source 和 Sink。
  • 第三步是调用回调 onOpen()。
  • 第四步是初始化 Reader 和 Writer。
  • 第五步是配置socket的超时时间为0,也就是阻塞IO。
  • 第六步执行 loopReader()。
2.4.1 第一步就是检查 HTTP 响应:
  • 根据 WebSocket 的协议,服务器端用如下响应,来表示接受建立 WebSocket 连接的请求:
    1. 响应码是 101。
    1. "Connection" header 的值为 "Upgrade",以表明服务器并没有在处理完请求之后把连接个断开。
    1. "Upgrade" header 的值为 "websocket",以表明服务器接受后面使用 WebSocket 来通信。
    1. "Sec-WebSocket-Accept" header 的值为,key + WebSocketProtocol.ACCEPT_MAGIC 做 SHA1 hash,然后做 base64 编码,来做服务器接受连接的验证。关于这部分的设计的详细信息,可参考 WebSocket 协议规范。
​
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {void checkResponse(Response response) throws ProtocolException {//响应码是 101。if (response.code() != 101) {throw new ProtocolException("Expected HTTP 101 response but was '"+ response.code() + " " + response.message() + "'");}//"Connection" header 的值为 "Upgrade",以表明服务器并没有在处理完请求之后把连接个断开。String headerConnection = response.header("Connection");if (!"Upgrade".equalsIgnoreCase(headerConnection)) {throw new ProtocolException("Expected 'Connection' header value 'Upgrade' but was '"+ headerConnection + "'");}//"Upgrade" header 的值为 "websocket",以表明服务器接受后面使用 WebSocket 来通信。String headerUpgrade = response.header("Upgrade");if (!"websocket".equalsIgnoreCase(headerUpgrade)) {throw new ProtocolException("Expected 'Upgrade' header value 'websocket' but was '" + headerUpgrade + "'");}//"Sec-WebSocket-Accept" header 的值为,key + WebSocketProtocol.ACCEPT_MAGIC 做 SHA1 hash,然后做 base64 编码,来做服务器接受连接的验证。String headerAccept = response.header("Sec-WebSocket-Accept");String acceptExpected = ByteString.encodeUtf8(key + WebSocketProtocol.ACCEPT_MAGIC).sha1().base64();if (!acceptExpected.equals(headerAccept)) {throw new ProtocolException("Expected 'Sec-WebSocket-Accept' header value '"+ acceptExpected + "' but was '" + headerAccept + "'");}}
}​
2.4.2 第二步:初始化用于输入输出的 Source 和 Sink。

Source 和 Sink 创建于之前发送HTTP请求的时候。这里会阻止在这个连接上再创建新的流。

​
public final class RealConnection extends Http2Connection.Listener implements Connection {public RealWebSocket.Streams newWebSocketStreams(final StreamAllocation streamAllocation) {return new RealWebSocket.Streams(true, source, sink) {@Overridepublic void close() throws IOException {streamAllocation.streamFinished(true, streamAllocation.codec(), -1L, null);}};}
}​
2.4.3 第三步是调用回调 onOpen()
2.4.4 第四步是初始化 Reader 和 Writer:
  • OkHttp使用 WebSocketReaderWebSocketWriter 来处理数据的收发。
  • 在发送数据时将数据组织成帧,在接收数据时则进行反向操作,同时处理 WebSocket 的控制消息。
  • WebSocket 的所有数据发送动作,都会在单线程线程池的线程中,通过 WebSocketWriter 执行。在这里会创建 ScheduledThreadPoolExecutor 用于跑数据的发送操作。
  • WebSocket 协议中主要会传输两种类型的帧,一是控制帧,主要是用于连接保活的 Ping 帧等;二是用户数据载荷帧
  • 在这里会根据用户的配置,调度** Ping 帧**周期性地发送。
  • 在调用 WebSocket 的接口发送数据时,数据并不是同步发送的,而是被放在了一个消息队列中。发送消息的 Runnable 从消息队列中读取数据发送。这里会检查消息队列中是否有数据,如果有的话,会调度发送消息的 Runnable 执行。
​
public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {public void initReaderAndWriter(String name, Streams streams) throws IOException {synchronized (this) {this.streams = streams;this.writer = new WebSocketWriter(streams.client, streams.sink, random);this.executor = new ScheduledThreadPoolExecutor(1, Util.threadFactory(name, false));if (pingIntervalMillis != 0) {executor.scheduleAtFixedRate(new PingRunnable(), pingIntervalMillis, pingIntervalMillis, MILLISECONDS);}if (!messageAndCloseQueue.isEmpty()) {runWriter(); // Send messages that were enqueued before we were connected.}}reader = new WebSocketReader(streams.client, streams.source, this);}
}​
2.4.5第五步是配置socket的超时时间为0,也就是阻塞IO。
2.4.6第六步执行 loopReader()。这实际上是进入了消息读取循环了,也就是数据接收的逻辑了。
本次分享到此结束,感谢大家的阅读,觉得有所帮助的朋友点点关注点点赞!
 

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

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

相关文章

声压级越大,STIPA 越好,公共广播就越清晰吗?

在公共广播中,有些朋友经常问到是不是声压越大,广播清晰度就越高,下面我从搜集了一些专业技术资料,供大家参考。 一、声压级越大,STIPA 越好吗? 不完全是。最初,人们认为当声压级达到 60 dBA 以…

氢燃料电池汽车行业发展

文章目录 前言 市场分布 整车销售 发动机配套 氢气供应 发展动能 参考文献 前言 见《氢燃料电池技术综述》 见《燃料电池工作原理详解》 见《燃料电池发电系统详解》 见《燃料电池电动汽车详解》 市场分布 纵观全球的燃料电池汽车市场,截至2022年底&#xff…

2024最新 Jenkins + Docker实战教程(一) - Jenkins介绍及安装

😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~ 🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Mi…

Android BACK键和HOME键应用差异详解

文章目录 1、应用层分析1.1 BACK键功能实现 1.2 HOME键功能实现 1.3 BACK键与HOME键的区别 2、系统层分析2.1 BACK键的处理2.2 HOME键的处理2.3 代码分析BACK键HOME键BACK键的系统代码分析HOME键的系统代码分析BACK键HOME键 3、优缺点分析3.1 BACK键3.2 HOME键 4、项目中的使用…

【ERNIE + PaddleOCR】 创建自己的论文字典,更好的写论文吧!

一、项目背景 在撰写论文的过程中,许多作者习惯先以中文完成初稿,随后再将其翻译为英文。然而,这种翻译过程往往伴随着一系列挑战。尤其是在词汇选择和语法结构上,很容易使用到一些在学术论文中不常用或不符合规范的表达。为了克服…

【学习笔记】Windows GDI绘图(七)图形路径GraphicsPath详解(下)

文章目录 前三篇回顾GraphicsPath方法Flatten压平(将曲线转成线段)GetBounds获取外接矩形GetLastPoint获取路径最后一个点IsOutlineVisibleIsVisiable是否在轮廓上或内部Reset重置Reverse逆转点的顺序Transform矩阵变换Wrap扭曲变换Widen将路径替换为指定画笔的填充区域 前三篇…

安卓手机APP开发__USB主机和配件概述

安卓手机APP开发__USB主机和配件概述 目录 概述 USB 配件模式 调试注意事项 选择合适的 USB 配件 API 支持 USB 主机模式 API 概览 清单和资源文件示例 概述 Android 通过 USB 配件和 USB 主机两种模式支持各种 USB 外围设备和 Android USB 配件(实现 Andr…

springboot学习记录--Maven setting配置

一、配置本地仓库 1 <!--自定义本地仓库路径--> Mirro&#xff1a;Maven 将从镜像仓库获取资源&#xff0c;不同的地理位置或具有更快的网络访问速度。 阿里云镜像&#xff1a; <mirror> <id>aliyunmaven</id> <mirrorOf>*</mirrorO…

TypeScript中的模块和命名空间:代码组织与封装

TypeScript中的模块和命名空间&#xff1a;代码组织与封装 引言 在TypeScript中&#xff0c;模块和命名空间是两种用于代码组织和封装的工具。模块用于将代码划分为独立的单元&#xff0c;而命名空间提供了一种将相关类型和值分组的方式。 基础知识 模块&#xff1a;通过文…

fastapi的简单实战,且用uvicorn将日志同时输出到控制台和日志文件中

简单描述 fastapi的简单实战&#xff0c;且用uvicorn将日志同时输出到控制台和日志文件中 main.py import signal import sys from contextlib import asynccontextmanagerfrom fastapi import FastAPI import uvicorn from fastapi.staticfiles import StaticFilesfrom set…

生成式AI导论2024-李宏毅

生成式AI导论2024-李宏毅 第0讲&#xff1a; 课程说明第1讲&#xff1a;生成式AI是什么第2講&#xff1a;今日的生成式人工智慧厲害在哪裡&#xff1f;從「工具」變為「工具人」 第0讲&#xff1a; 课程说明 生成式AI的入门课程 第1讲&#xff1a;生成式AI是什么 生成式人…

python调用阿里云通义千问(q-wen-max)API-创建智能体Agent

文章目录 Assistant API简介创建和使用Assistant API1、调用Assistant API夸克搜索回答问题2、Agent智能体构建Assistant API简介 百炼Assistant API能够让用户定制化构建一个assistant,这个assistant支持多种不同的指令(instruction)和描述(prompt),并且可以使用各类工…

AI预测福彩3D采取888=3策略+和值012路一缩定乾坤测试5月26日预测第2弹

昨天的8883大底成功命中&#xff0c;但是由于昨天杀了对子&#xff0c;结果昨天开了对子&#xff0c;导致最终与中奖号码擦肩而过。今天继续基于8883的大底&#xff0c;使用尽可能少的条件进行缩号&#xff0c;同时&#xff0c;今天将准备两套方案&#xff0c;一套是我自己的条…

队列——顺序存储

核心思路&#xff1a; 1、使用顺序存储的方式定义队列时&#xff0c;使用数组存储队列元素&#xff0c;然后声明两个int类型的指针——rear和front&#xff0c;分别指向队尾元素的下一个位置和队头元素的位置。 2、初始化队列时&#xff0c;队列的首尾指针都指向0 。 3、当队列…

2021 年 3 月青少年软编等考 C 语言二级真题解析

目录 T1. 与指定数字相同的数的个数思路分析 T2. 合法 C 标识符思路分析 T3. 计算鞍点思路分析 T4. 谁考了第 k 名思路分析 T5. 石头剪刀布思路分析 T1. 与指定数字相同的数的个数 输出一个整数序列中与指定数字相同的数的个数。 时间限制&#xff1a;1 s 内存限制&#xff1…

【CSS】计算属性 calc 函数

CSS 中的 calc() 函数是用于动态计算数值的函数。它可以在 属性的值中执行基本的数学运算&#xff0c;包括加法、减法、乘法和除法&#xff0c;以及使用 CSS 单位进行计算。 calc() 函数的语法如下&#xff1a; calc(expression) 其中 expression 是包含数学运算和 CSS 单位表…

英语学习笔记28——Where are they?

Where are they? 他们在哪里&#xff1f; 课文部分

【NumPy】关于numpy.median()函数,看这一篇文章就够了

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

贝叶斯算法:机器学习中的“黄金法则”与性能提升之道

&#x1f440;传送门&#x1f440; &#x1f50d;机器学习概述&#x1f340;贝叶斯算法原理&#x1f680;贝叶斯算法的应用✨文本分类✨医疗系统 &#x1f496;贝叶斯算法优化✨贝叶斯算法优化的主要步骤✨贝叶斯算法优化的优点✨贝叶斯算法优化的局限性 &#x1f697;贝叶斯算…

二维前缀异或和,1738. 找出第 K 大的异或坐标值

一、题目 1、题目描述 给你一个二维矩阵 matrix 和一个整数 k &#xff0c;矩阵大小为 m x n 由非负整数组成。 矩阵中坐标 (a, b) 的 值 可由对所有满足 0 < i < a < m 且 0 < j < b < n 的元素 matrix[i][j]&#xff08;下标从 0 开始计数&#xff09;执行…