IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> Golang/Gin-WebSocket实现实时消息推送 -> 正文阅读

[网络协议]Golang/Gin-WebSocket实现实时消息推送

前言

WebSocket在 HTML5 游戏和网页消息推送都使用比较多。

WebSocket 是 HTML5 的重要特性,它实现了基于浏览器的远程socket,它使浏览器和服务器可以进行全双工通信。

目前Go中用的比较多的WebSocket包是gorilla/websocket,本文将介绍如何使用gorilla/websocket,在Gin框架下编写WebSocket实时消息推送。

gorilla/websocket基础用法

gorilla/websocket相关链接:

Github地址:https://github.com/gorilla/websocket

文档:https://godoc.org/github.com/gorilla/websocket

gorilla/websocket的安装:

go get github.com/gorilla/websocket

Upgrader用于升级 http 请求,把 http 请求升级为长连接的 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://

在这里插入图片描述

实现实时消息推送

这部分我参考了GitHub上lwnmengjing的代码,自己做了一些修改以贴合自己的需求,大家也可以直接到https://github.com/lwnmengjing/pushMessage参考他的代码。

代码

以下是我自己修改后的代码:

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()
}

api

接口请求方式参数用途
/getPushNewsgetid,ws客户端链接标记websocket客户端连接
/deleteClient:iddelete:id为ws客户端链接标记销毁已经连接过的客户端

补充说明

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

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

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

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-09-12 13:30:30  更:2021-09-12 13:31:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年6日历 -2024/6/27 1:43:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码