golang中的websocket,使用wireshark抓包

news2024/11/27 5:36:20

websocket 是一个长连接协议,全双工通信,主要应用在及时通信:实时聊天,游戏,在线文档等等。

简单示例

客户端

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <input id="input" type="text" />
    <button onclick="send()">Send</button>
    <pre id="output"></pre>

    <script>
        var input = document.getElementById("input");
        var output = document.getElementById("output");

        //调用websocket对象建立连接:
        //参数:ws/wss(加密)://ip:port (字符串)
        var websocket = new WebSocket("ws://xxx.xxx.xxx.xxx:8080/echo");
        console.log(websocket.readyState) // 0 
        // readyState 
        // 0 链接还没有建立(正在建立链接)
        // 1 链接建立成
        // 2 链接正在关闭
        // 3 链接已经关闭

        // 监听链接开启事件
        websocket.onopen = function () {
            output.innerHTML += "Status: " + websocket.readyState + "\n";
            console.log(websocket.readyState)
        }

        // 监听服务端消息推送事件
        websocket.onmessage = function (back) {
            output.innerHTML += "Server: " + back.data + "\n";
            console.log(back.data)
        }

        // 监听连接错误信息
        websocket.onerror = function (evt, e) {
            console.log('Error occured: ' + evt.data);
        };

        //监听连接关闭
        websocket.onclose = function (evt) {
            console.log("Disconnected");
        };

        // 绑定按钮点击事件
        function send() {
            websocket.send(input.value)
            input.value = "";
        }

    </script>

</body>

</html>

服务端

package main

import (
	"fmt"
	"net/http"

	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

func main() {
	http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
		conn, _ := upgrader.Upgrade(w, r, nil)

		for {
             // messageType is either TextMessage == 1 or BinaryMessage == 2
			msgType, msg, err := conn.ReadMessage()
			if err != nil {
				return
			}

			fmt.Printf("%s sent: %s\n", conn.RemoteAddr(), string(msg))

			if err = conn.WriteMessage(msgType, msg); err != nil {
				return
			}
		}
	})

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "client.html")
	})

	fmt.Println("server is running on :8080...")

	http.ListenAndServe(":8080", nil)
}

运行 go run server.go

访问 http://xxx.xxx.xxx.xxx:8080/

请求头

GET ws://localhost:8080/echo HTTP/1.1
Host: localhost:8080
Origin: http://localhost:8080
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: h8MnjGsXMtnoAcyCAn+V5Q==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

响应头

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: n+hJTwLJbZTrnaXspOIBJZYIDh8=

websocket 是在 http 的基础上改造而成的,首先客户端改造成上面的请求头,服务端先要构建http服务,然后针对路由/echo做改造,分析请求头,构建响应头,另外普通的http请求在响应完就会关掉,此时需要改造成不关掉,也就是长连接,至此一个websocket连接就建立了。

因为ws是基于http之上的,所以默认情况下ws的端口是80,安全协议wss端口是443。

WebSocket协议的数据可以是普通的文本数据,也可以是二进制数据,数据量相对较大时,还可以分片多帧进行数据发送与接收。

请求头中的Sec-WebSocket-Key和响应头中的Sec-WebSocket-Accept是对应的,提供基本的防护,Sec-WebSocket-Accept的计算公式为:将Sec-WebSocket-Key258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接,通过SHA1计算出摘要,并转成base64字符串

websocket RFC标准 https://www.rfc-editor.org/rfc/rfc6455.txt

数据帧格式

WebSocket客户端、服务端通信的最小单位是帧(frame),由1个或多个帧组成一条完整的消息(message)。

  • 发送端:将消息切割成多个帧,发送给服务端;
  • 服务端:接收消息帧,并将关联的帧重新组装成完整的消息数据;

在这里插入图片描述

  • FIN (Final):是否为最后一个分片(占1比特位)
    如果是1,表示这是消息(message)的最后一个分片(fragment);
    如果是0,表示不是是消息(message)的最后一个分片(fragment)。

  • RSV1, RSV2, RSV3 (Reserved):扩展字段(共占3比特位)
    一般情况下全为0。当客户端、服务端协商采用WebSocket扩展时,这三个标志位可以非0,且值的含义由扩展进行定义。
    对于 Reserved 字段,这里不做详细说明,如有扩展需求,可自行查阅相关国际标准:
    RFC6455: WebSocket协议
    https://www.rfc-editor.org/rfc/rfc6455.txt

  • Opcode:操作码(占4比特位)
    Opcode的值决定了应该如何解析后续的数据载荷(data payload)。
    可选的操作代码如下:

    Opcode含义
    0x0表示一个延续帧,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片
    0x1表示这是一个文本帧(frame)
    0x2表示这是一个二进制帧(frame)
    0x3-7保留的操作代码,用于后续定义的非控制帧
    0x8表示连接断开
    0x9表示这是一个ping操作
    0xA表示这是一个pong操作
    0xB-F保留的操作代码,用于后续定义的控制帧
  • Mask:掩码(占1比特位)
    表示是否要对数据载荷进行掩码操作。
    从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。
    如果Mask是1,那么在Masking-key中会定义一个掩码键,并用这个掩码键来对数据载荷进行反掩码。

  • Payload length:数据载荷的长度,单位是字节(占 7、7+16 或7+64 比特位)
    假设 Payload length 中前7 个比特位的值为x
    如果 x 为 0~125,则 x 即为载荷数据的有效长度;
    如果 x 为 126,则后续16比特位代表一个16位的无符号整数,该无符号整数的值为载荷数据的有效长度;
    如果 x 为 127,则后续64个比特位代表一个64位的无符号整数(最高位为0),该无符号整数的值为载荷数据的有效长度。

  • Masking-key:掩码键(占0或32比特位)
    所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作(Mask值为1),会携带4字节的Masking-key。
    对于 Masking-key 掩码算法,这里不做详细说明,如需了解,可自行查阅相关国际标准:
    RFC6455: WebSocket协议
    https://www.rfc-editor.org/rfc/rfc6455.txt

  • Payload data:载荷数据(占x+y字节)
    载荷数据有两部分组成:扩展数据占 x 字节,应用数据占 y 字节
    扩展数据:如果没有协商使用扩展的话,扩展数据数据为0字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。
    应用数据:任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。

如何使用wireshark抓取websocket协议

1、wireshark无法抓取本地服务的包,也就是流量必须经过网卡才行。

2、分析 --> 启用的协议 --> websocket

3、必须在建立ws连接之前就开始抓,这一点的确容易忽略。

4、显示过滤器输入websocket,回车。

正常的效果如下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可以看到,客户端发给服务端的信息被MASK了,而服务端发给客户端的信息是明文的。

如果是在连接之后才开始抓,那么是抓不到websocket协议记录的,于是我使用IP地址来过滤,只能抓到一些TCP的记录,因为wireshark并没有按照ws协议来解析

在这里插入图片描述

客户端发送的是wer,服务端会返回wer

在这里插入图片描述

发出去的载荷是经过Mask转换的,此时wireshark并不知道如何解析它,而服务端返回的载荷确实明文的

在这里插入图片描述

关于心跳检测ping/pong

websocket协议中,Opcode为0x9为ping消息,0xA为pong消息,因此我们使用的websocket第三方库都会在底层处理掉ping/pong的发送和响应,

比如github.com/gorilla/websocket,在收到ping消息时,会自行处理掉,也就是说conn.ReadMessage()不会有返回,而是直接去读取下一条消息了,使用者无感。而作为客户端,应该要每隔一段时间向服务端发送ping消息。或者使用自定义的方式来实现心跳检测。

另外使用github.com/gorilla/websocket包需要注意的是,同一个连接上,如果一个消息还没read完新消息就来了,它会重置reader,这会导致正在读的消息丢失,直接开始读取新来的消息。

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

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

相关文章

分支语句和循环语句

控制语句&#xff1a;用于控制程序的执行流程&#xff0c;以实现程序的各种结构方式&#xff0c;它们由特定的语句定义符组成&#xff0c;C语言有9种控制语句&#xff0c;可分为三类&#xff1a; 条件判断语句也叫分支语句&#xff1a;if语句&#xff0c;switch语句&#xff1b…

【基础算法】贪心算法基础

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了秋招算法的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于代码随想录进行的&#xff0c;每个算法代码参考leetcode高赞回答和…

对SRC逻辑漏洞挖掘的思考

对SRC逻辑漏洞挖掘的思考 1.限制购买逻辑漏洞一人一单限制差价活动购买限制 2.支付类逻辑漏洞3.接口未授权逻辑漏洞4.越权类逻辑漏洞5.修改返回包进入后台6.任意用户注册7.重置任意用户 1.限制购买逻辑漏洞 一人一单限制 很多厂商都会搞一些活动&#xff0c;在享受优惠的时候…

微服务架构及工作原理!

在移动计算时代&#xff0c;应用程序开发人员应该能够快速部署操作并进行更改&#xff0c;而无需重新部署整个应用程序。结果&#xff0c;它导致了一种称为“微服务”的构建软件的新方法。 微服务是应用程序中独立的小部分&#xff0c;每个部分都完成自己的工作并通过API相互通…

Dubbo学习笔记

目录 简介 Dubbo高可用 集群容错 服务治理 Dubbo线程IO模型 源码层面 Java SPI 的问题 源码解析 简介 Apache Dubbo是一款高性能的Java RPC框架。其前身是阿里巴巴公司开源的一个高性能、轻量级的开源Java RPC框架&#xff0c;可以和Spring框架无缝集成。 Dubbo提供了…

主流总线通信和系统接口技术

一、关于现场控制总线 现场总线是自动控制领域的计算机局域网&#xff0c;应用在生产现场&#xff0c;在微机测控设备之间实现双向、串行、多节点数字通信&#xff0c;是一种开放式、数字化、多点通信的底层控制网络。 现场总线具有较高的测控能力指数 得益于仪表的微机化&am…

C++实现闭散列/开放定址法

前言 哈希冲突是无法避免的&#xff0c;只能尽可能的减少冲突的可能性&#xff0c;通常我们可以设计适合的哈希函数。但是&#xff0c;哈希冲突还是会发生&#xff0c;那我们如何解决呢&#xff1f; 我们可以使用闭散列/开放定址法的方法&#xff0c;解决哈希冲突 文章目录 前…

世界超高清大会发布重大技术成果:博冠自主创新推动8K摄像机攻关

一、世界超高清大会背景介绍&#xff1a; 近日&#xff0c;由工业和信息化部、国家广播电视总局、中央广播电视总台、广东省人民政府主办的2023世界超高清视频产业发展大会在广州越秀国际会议展览中心盛大召开。自2018年创办以来&#xff0c;大会已成功举办四届&#xff0c;成…

第08讲:搭建 SkyWalking 源码环境,开启征途

搭建 SkyWalking 源码环境 下载 SkyWalking 源码 执行 git clone 命令从 GitHub下载 SkyWalking 源码&#xff0c;如下所示 &#xff1a; git clone gitgithub.com:apache/skywalking.git 切换分支 等待 clone 完成之后&#xff0c;我们通过命令行窗口进入 SkyWalking 源码根…

SSM 三大框架原理、核心技术,运行流程讲解

作者:arrows 来源:https://www.cnblogs.com/arrows/p/10537733.html 一、Spring部分 1、 Spring的运行流程 第一步&#xff1a;加载配置文件ApplicationContext ac new ClassPathXmlApplicationContext(“beans.xml”); &#xff0c;ApplicationContext接口&#xff0c;它由…

存储卡目录变成未知文件?这些技巧能让你恢复数据!

当存储卡的目录变成未知文件时&#xff0c;我们无法直接访问存储卡中的数据。但是&#xff0c;这并不意味着这些数据永远无法恢复。以下是几种可能恢复存储卡数据的方法&#xff1a; 使用数据恢复软件。从互联网上下载并安装专业的数据恢复软件这些软件可以扫描存储卡&#xf…

分布式接口幂等性设计实现

面对分布式架构和微服务复杂的系统架构和网络超时服务器异常等带来的系统稳定性问题&#xff0c;分布式接口的幂等性设计显得尤为重要。本文简要介绍了几种分布式接口幂等性设计实现&#xff0c;包括Token去重机制、乐观锁机制、数据库主键和状态机实现等&#xff0c;以加深理解…

面板安全增强,网站支持反向代理设置,1Panel开源面板v1.2.0发布

2023年5月15日&#xff0c;现代化、开源的Linux服务器运维管理面板1Panel正式发布v1.2.0版本。 在这一版本中&#xff0c;1Panel着重增强了安全方面的功能&#xff0c;包括安全入口访问、面板SSL设置、网站密码访问等&#xff0c;同时网站新增支持反向代理设置&#xff0c;并带…

JVM学习(三)

1. JAVA 四中引用类型 1.1. 强引用 在 Java 中最常见的就是强引用&#xff0c; 把一个对象赋给一个引用变量&#xff0c;这个引用变量就是一个强引 用。当一个对象被强引用变量引用时 &#xff0c;它处于可达状态&#xff0c;它是不可能被垃圾回收机制回收的&#xff0c;即…

Java阶段二Day21

Java阶段二Day21 文章目录 Java阶段二Day21整合Lombok基础组件1 Lombok简介2 安装和配置 Lombok3 Lombok 注解及其用法3.1 Getter 和 Setter3.2 ToString3.3 AllArgsConstructor 和 NoArgsConstructor3.4 Data 4. 总结5 微博项目优化 Knife4j1 Knife4j的优点2 Knife4j快速上手2…

使用Docker构建的MySQL主从架构:高可用性数据库解决方案

前言 MySQL主从架构&#xff0c;我们已经在vmware虚拟机上实践过了&#xff0c;接下来我们一起探讨在docker中如何使用MySQL主从架构。 &#x1f3e0;个人主页&#xff1a;我是沐风晓月 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是沐风晓月&#xff0c;阿里云社…

《论文阅读》基于提示的知识生成解决对话情感推理难题

《论文阅读》基于提示的知识生成解决对话情感推理难题 前言摘要作者新观点问题定义模型框架Global ModelLocal ModelPrompt Based Knowledge Generation分类器实验结果问题前言 你是否也对于理解论文存在困惑? 你是否也像我之前搜索论文解读,得到只是中文翻译的解读后感到失…

openEuler 成功适配 LeapFive InFive Poros 开发板

近日&#xff0c;openEuler RISC-V 23.03 创新版本在跃昉科技的 Poros 开发板上成功运行。 openEuler 在 Poros 上适配成功&#xff0c;XFCE 桌面启动正常&#xff0c;文件系统、终端模拟器和输入法等相关 GUI 应用也运行流畅&#xff0c;Chromium 浏览器和 LibreOffice 等应用…

【Pm4py第三讲】关于Output

本节用于介绍pm4py中的输出函数&#xff0c;包括日志输出、模型输出、面向对象日志输出等。 1.函数概述 本次主要介绍Pm4py中一些常见的输入函数&#xff0c;总览如下表&#xff1a; 函数名说明write_bpmn()用于写入bpmn模型write_dfg()用于写入dfg模型write_pnml() 用于写入p…

面试之高手回答

1.int与Integer的区别 int与Integer的区别有很多&#xff0c;我简单罗列三个方面 第一个作为成员变量来说Integer的初始值是null&#xff0c;int的初始值是0&#xff1b; 第二个Integer存储在堆内存&#xff0c;int类型是在直接存储在栈空间&#xff1b; 第三个integer是个对象…