WebSocket 快速入门

news2024/9/19 21:32:02

WebSocket是什么

WebSocket 是基于 TCP 的一种新的应用层网络协议。它实现了浏览器与服务器全双工通信,即允许服务器主动发送信息给客户端。因此,在 WebSocket 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输,客户端和服务器之间的数据交换变得更加简单。

WebSocket的真正美妙之处在于它们总共使用了 1 个 TCP 连接,并且所有通信都是通过这个单一的长寿命 TCP 连接完成的。这大大减少了使用 WebSockets 构建实时应用程序所需的网络开销,因为不需要对 HTTP 端点进行持续轮询。

关键词:应用层协议、基于 TCP、全双工通信、一次握手、持久连接、双向数据传输

WebSocket与http之间的区别

相同点: 

都是一样基于TCP的,都是可靠性传输协议。都是应用层协议。

联系:

 WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的。

下面一张图说明了 HTTP 与 WebSocket的主要区别:

  1. WebSocket 是双向通信协议,模拟Socket协议,可以双向发送或接受信息,而HTTP是单向的
  2. WebSocket 是需要浏览器和服务器握手进行建立连接的,而http是浏览器发起向服务器的连接。

注意:虽然HTTP/2也具备服务器推送功能,但HTTP/2 只能推送静态资源,无法推送指定信息。

WebSocket使用场景

如何建立连接

在 WebSocket 开始通信之前,通信双方需要先进行握手WebSocket 复用了 HTTP 的握手通道,即客户端通过 HTTP 请求与 WebSocket 服务端协商升级协议。协议升级完成后,后续的数据交换则遵照 WebSocket 的协议

利用HTTP完成握手有什么好处

  1. 让 WebSocket 和 HTTP 基础设备兼容(运行在 80 端口 或 443 端口)
  2. 可以复用 HTTP 的 Upgrade 机制,完成升级协议的协商过程。

WebSocket连接的过程

  1. 客户端发起http请求,经过3次握手后,建立起TCP连接;http请求里存放WebSocket支持的版本号等信息,如:Upgrade、Connection、WebSocket-Version等
  2. 服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据
  3. 客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信。

如何维持连接

如果我们使用 WebSocket 进行通信,建立连接之后怎么判断连接正常没有断开或者服务是否可用

可以通过建立心跳机制,所谓心跳机制,就是定时发送一个数据包,让对方知道自己在线且正常工作,确保通信有效。如果对方无法响应,便可以弃用旧连接,发起新的连接了。

需要重连的场景可能包括:网络问题或者机器故障导致连接断开、连接没断但不可用了或者连接对端的服务不可用了等等。

实时通信的基本步骤

  1. 引入gorilla/websocket:首先需要安装这个库,可以通过go get命令安装:

    go get github.com/gorilla/websocket
  2. 设置WebSocket升级处理:使用gorilla/websocketUpgrader结构来升级HTTP连接为WebSocket连接。可以设置读取和写入缓冲区大小,以及检查请求来源的函数。

  3. 创建WebSocket处理函数:编写一个处理函数,该函数使用Upgrader来升级连接,并处理WebSocket消息的接收和发送。

  4. 注册WebSocket路由:在Gin的路由中注册一个路径,当客户端请求这个路径时,调用上面创建的WebSocket处理函数。

  5. 编写业务逻辑:在WebSocket处理函数中,编写业务逻辑,例如接收消息、发送消息、连接管理等。

  6. 测试WebSocket服务:启动服务后,可以使用WebSocket客户端工具(如浏览器的开发者工具或专用的WebSocket测试工具)来测试WebSocket服务是否能够正常通信。

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"net/http"
)

// 定义Upgrader用于升级HTTP连接为WebSocket连接
var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		// 允许所有CORS请求
		return true
	},
}

func main() {
	r := gin.Default()

	// 定义WebSocket路由
	r.GET("/ws", func(c *gin.Context) {
		serveWebSocket(c.Writer, c.Request)
	})

	// 启动Gin服务器
	r.Run(":8080")
}

// serveWebSocket处理WebSocket连接
func serveWebSocket(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return // 错误处理
	}
	defer conn.Close()

	// 处理WebSocket消息
	for {
		_, message, err := conn.ReadMessage()
		if err != nil {
			return // 错误处理
		}
		// 将接收到的消息发送回客户端
		if err := conn.WriteMessage(websocket.TextMessage, message); err != nil {
			return // 错误处理
		}
	}
}

在这个示例中,我们创建了一个WebSocket服务端,它监听/ws路径的WebSocket请求。当客户端连接时,服务器将使用serveWebSocket函数来处理连接。服务器接收消息并直接将相同消息发送回客户端,这是一个基本的WebSocket回声服务器的实现。

hello world 示例

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
	"time"
)

var upgrader = websocket.Upgrader{
  // 这个是校验请求来源
  // 在这里我们不做校验,直接return true
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

func main() {
	engine := gin.Default()

	engine.GET("/helloWebSocket", func(context *gin.Context) {
    // 将普通的http GET请求升级为websocket请求
		client, _ := upgrader.Upgrade(context.Writer, context.Request, nil)
		for {
			// 每隔两秒给前端推送一句消息“hello, WebSocket”
			err := client.WriteMessage(websocket.TextMessage, []byte("hello, WebSocket"))
			if err != nil {
				log.Println(err)
			}
			time.Sleep(time.Second * 2)
		}
	})

	err := engine.Run(":8090")
	if err != nil {
		log.Fatalln(err)
	}
}

可以用websocket在线测试工具代码:http://coolaf.com/tool/chattest。

注意:请求url前面的http://记得换成ws://

实现实时消息推送 示例

package module

import (
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
	"sync"
	"time"
)

var (
  // 消息通道
	news = make(map[string]chan interface{})
  // websocket客户端链接池
	client = make(map[string]*websocket.Conn)
  // 互斥锁,防止程序对统一资源同时进行读写
	mux sync.Mutex
)

// api:/getPushNews接口处理函数
func GetPushNews(context *gin.Context)  {
	id := context.Query("userId")
	log.Println(id + "websocket链接")
  // 升级为websocket长链接
	WsHandler(context.Writer, context.Request, id)
}

// api:/deleteClient接口处理函数
func DeleteClient(context *gin.Context)  {
	id := context.Param("id")
  // 关闭websocket链接
	conn, exist := getClient(id)
	if exist {
		conn.Close()
		deleteClient(id)
	} else {
		context.JSON(http.StatusOK, gin.H{
			"mesg": "未找到该客户端",
		})
	}
  // 关闭其消息通道
	_, exist =getNewsChannel(id)
	if exist {
		deleteNewsChannel(id)
	}
}

// websocket Upgrader
var wsupgrader = websocket.Upgrader{
	ReadBufferSize:   1024,
	WriteBufferSize:  1024,
	HandshakeTimeout: 5 * time.Second,
	// 取消ws跨域校验
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

// WsHandler 处理ws请求
func WsHandler(w http.ResponseWriter, r *http.Request, id string)  {
	var conn *websocket.Conn
	var err error
	var exist bool
  // 创建一个定时器用于服务端心跳
	pingTicker := time.NewTicker(time.Second * 10)
	conn, err = wsupgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}
	// 把与客户端的链接添加到客户端链接池中
	addClient(id, conn)

	// 获取该客户端的消息通道
	m, exist := getNewsChannel(id)
	if !exist {
		m = make(chan interface{})
		addNewsChannel(id, m)
	}

	// 设置客户端关闭ws链接回调函数
	conn.SetCloseHandler(func(code int, text string) error {
		deleteClient(id)
		log.Println(code)
		return nil
	})

	for {
		select {
		case content, _ := <- m:
      // 从消息通道接收消息,然后推送给前端
			err = conn.WriteJSON(content)
			if err != nil {
				log.Println(err)
				conn.Close()
				deleteClient(id)
				return
			}
		case <- pingTicker.C:
      // 服务端心跳:每20秒ping一次客户端,查看其是否在线
			conn.SetWriteDeadline(time.Now().Add(time.Second * 20))
			err = conn.WriteMessage(websocket.PingMessage, []byte{})
			if err != nil {
				log.Println("send ping err:", err)
				conn.Close()
				deleteClient(id)
				return
			}
		}
	}
}

// 将客户端添加到客户端链接池
func addClient(id string, conn *websocket.Conn) {
	mux.Lock()
	client[id] = conn
	mux.Unlock()
}

// 获取指定客户端链接
func getClient(id string) (conn *websocket.Conn, exist bool) {
	mux.Lock()
	conn, exist = client[id]
	mux.Unlock()
	return
}

// 删除客户端链接
func deleteClient(id string) {
	mux.Lock()
	delete(client, id)
	log.Println(id + "websocket退出")
	mux.Unlock()
}

// 添加用户消息通道
func addNewsChannel(id string, m chan interface{}) {
	mux.Lock()
	news[id] = m
	mux.Unlock()
}

// 获取指定用户消息通道
func getNewsChannel(id string) (m chan interface{}, exist bool) {
	mux.Lock()
	m, exist = news[id]
	mux.Unlock()
	return
}

// 删除指定消息通道
func deleteNewsChannel(id string) {
	mux.Lock()
	if m, ok := news[id]; ok {
		close(m)
		delete(news, id)
	}
	mux.Unlock()
}

补充说明

当你要给某个用户推送消息时,你只需要使用getNewsChannel()方法获取该用户的消息通道,然后把消息送入通道就可以了。

若用户离线,你可以把消息直接存到用户所有消息中,或者设置一个消息队列,把消息放到用户未读消息队列中,下次用户上线时再一次性推送给用户。

服务端心跳:服务端每隔20秒回ping一下用户,查看其是否还在线,若ping不到,则服务端自动关闭websocket链接。

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

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

相关文章

Linux系统中安装Git(详细教程)

在Linux系统中安装Git&#xff0c;可以通过多种方式来实现&#xff0c;主要包括使用包管理器安装和从源代码编译安装。以下是详细的安装步骤&#xff1a; 一、使用包管理器安装&#xff08;不建议该方式&#xff09; 大多数Linux发行版都提供了包管理器&#xff0c;如Debian/…

90%的人都在用这7个图片转pdf技巧,转换速度很快!

图片怎么转换成pdf格式&#xff1f;图片和pdf格式是两种完全不一样的格式&#xff0c;但是如果想要将图片转换成pdf格式还是蛮容易的&#xff0c;常见的方法就有数十种了。 本文整理了几种常见的图片转pdf的方法&#xff0c;包括图片转pdf在线方法&#xff0c;有需要的朋友可以…

取证工具 ElcomSoft iOS Forensics Toolkit: 在 Windows 中加载 HFS 镜像

天津鸿萌科贸发展有限公司是 ElcomSoft 系列取证软件的授权代理商。 Elcomsoft iOS Forensics Toolkit 功能简介 Elcomsoft iOS Forensics Toolkit 软件工具包适用于取证工作&#xff0c;对 iPhone、iPad 和 iPod Touch 设备执行完整文件系统和逻辑数据采集。对设备文件系统制…

【Linux操作系统】基础IO

目录 一、接口使用1.1 铺垫知识1.2 C接口使用1.3 系统接口使用 二、认识fd三、缓冲区四、文件系统五、软硬连接六、动静态库6.1 静态库的制作和使用6.1 动态库的制作和使用 七、理解动态库加载 一、接口使用 1.1 铺垫知识 文件文件内容文件属性 。一个文件如果它的文件内容为…

AI产品经理如何入门?零基础入门到精通,收藏这一篇就够了

现在做产品经理&#xff0c;真的挺累的。 现在产品越来越难做&#xff0c;晋升困难&#xff0c;工资迟迟不涨……公司裁员&#xff0c;产品经理首当其冲&#xff01;&#xff01; 做产品几年了&#xff0c;还没升职&#xff0c;就先到了“职业天花板”。 想凭工作几年积累的…

linux被植入木马排查思路

linux被植入木马排查思路 一、是否侵入检查 1&#xff09;检查系统登录日志 last命令 2&#xff09;检查系统用户 1、检查是否有异常用户 cat /etc/passwd 2、查看是否产生了新用户、uid和gid为0的用户 grep "0" /etc/passwd 3、查看passwd的修改时间&#xf…

CY7C68000 实现High Speed USB2 UART

已经在Malogic PFGA Board 上用CY7C68000 实现High Speed USB2UART&#xff0c;店铺还上架了 ULPI &#xff08;USB3300&#xff09;的 TestBench 和ModelSim 验证环境&#xff0c;刚刚写出来的&#xff0c;其实效果和UTMI差不多&#xff0c; 比UTMI麻烦一些&#xff0c;需要写…

k8s篇之kubectl安装命令自动补全插件

1. 简介 常见情况&#xff1a;在部署生产环境或者测试环境的k8s集群时&#xff0c;常因输入命令繁琐&#xff0c;使得执行相关k8s操作排错时麻烦&#xff0c;以下自动补全插件即可解决这一问题。 以下安装亲测有效 2. 安装 安装bash completion yum install -y bash-comple…

车载以太网交换机入门基本功(3)—VLAN 转发

在《车载以太网交换机入门基本功&#xff08;2&#xff09;》中提到&#xff0c;报文通过携带Tag字段&#xff0c;表明报文所属的VLAN。本文将介绍携带Tag报文在VLAN下的转发过程。而在实际转发过程中&#xff0c;交换机的端口属性起到关键作用。 交换机端口属性 交换机的端口…

计算机毕业设计选题推荐-地震数据分析与预测-Python爬虫可视化

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

Java语言程序设计——篇十五(2)

&#x1f33f;&#x1f33f;&#x1f33f;跟随博主脚步&#xff0c;从这里开始→博主主页&#x1f33f;&#x1f33f;&#x1f33f; 欢迎大家&#xff1a;这里是我的学习笔记、总结知识的地方&#xff0c;喜欢的话请三连&#xff0c;有问题可以私信&#x1f333;&#x1f333;&…

如何在C++ QT 程序中集成cef3开源浏览器组件去显示网页?

目录 1、问题描述 2、为什么选择cef3浏览器组件 3、cef3组件的介绍与下载 4、将cef3组件封装成sdk 5、如何使用cef3组件加载web页面 5.1、了解CefApp与CefClient 5.2、初始化与消息循环 5.3、如何创建浏览器 5.4、重载CefClient类 6、在qt客户端集成cef组件 7、最后…

国内外大模型汇总:Open AI大模型、Google大模型、Microsoft大模型、文心一言大模型、通义千问大模型、字节豆包大模型、智普清言大模型

Open AI大模型 特点&#xff1a; 多模态能力&#xff1a;如GPT-4o&#xff0c;能接受文本、音频、图像作为组合输入&#xff0c;并生成任意形式的输出。 情感识别与回应&#xff1a;具备情感识别能力&#xff0c;能根据对话者的情绪做出有感情的回应。 几乎无延迟&#xff…

python中sum是什么意思

在开发语言中&#xff0c;sum函数是求和函数&#xff0c;用于求多个数据的和。而在python中&#xff0c;虽然也是求和函数&#xff0c;但稍微有些差别&#xff0c;sum()传入的参数得是可迭代对象&#xff08;比如列表就是一个可迭代对象&#xff09;&#xff0c;返回这个被传入…

webflux源码解析(3)-reactor netty

目录 1.连接的状态2.reactor netty中的连接状态3. webflux中的io处理4.总结 为什么webflux在io密集型的场景能有效的提升系统吞吐量呢&#xff1f; 是因为它使用的是响应式编程&#xff0c;使用的是NIO&#xff0c;但这里的响应式、nio到底是怎么样的呢&#xff1f;响应式编程上…

自动化智能立体库验收报告

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 这份文件是一份关于自动化智能立体库的验收报告&#xff0c;它包含了以下几个核心部分&#xff1a; 到货验收表&#xff1a;列出了自动化智能立体…

dubbo:dubbo整合nacos实现服务注册中心、配置中心(二)

文章目录 0. 引言1. nacos简介及安装2. 注册中心实现3. 配置中心实现4. 源码5. 总结 0. 引言 之前我们讲解的是dubbozookeeper体系来实现微服务框架&#xff0c;但相对zookeeper很多企业在使用nacos, 并且nacos和dubbo都是阿里出品&#xff0c;所以具备一些天生的契合性&#…

黑神话:悟空四年前就布局商标,多个名称申请全类!

近日黑神话&#xff1a;悟空上线&#xff0c;预售超4亿元&#xff0c;普推知产商标老杨经检索发现&#xff0c;背后的主体游科互动早在三年前就布局商标&#xff0c;申请了多个核心名称的45类全类的商标。 背后的游科互动名下申请了245件商标&#xff0c;其“黑悟空”是2021年申…

如何通过观测云实现AIOps突破?

在当今信息技术迅猛发展的浪潮中&#xff0c;企业正置身于一个日益复杂化的 IT 环境&#xff0c;并面临着数据量的爆炸性增长。智能运维&#xff08;AIOps&#xff09;&#xff0c;作为 IT 运维管理领域的革新者&#xff0c;融合了大数据和机器学习技术&#xff0c;致力于对 IT…

全场景——(四)Modbus 通讯协议

文章目录 一、学习Modbus的快速方法1.1 寄存器速记1.2 协议速记 二、初识Modbus2.1 背景2.2 什么是Modbus&#xff1f;2.2.1 Modbus简介2.2.2 Modbus特点2.2.3 Modbus常用术语2.2.4 Modbus事务处理 三、Modbus软件与使用3.1 Modbus软件简介3.2 Modbus Poll&#xff08;主站设备…