【GO】31. websocket实现

news2025/1/9 1:12:03

一. 引入gin、gorilla websocket包

go get github.com/gin-gonic/gin
go get github.com/gorilla/websocket

二. 服务端代码

package main

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

const (
	// Time allowed to write a message to the peer.
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the peer.
	pongWait = 60 * time.Second

	// Send pings to peer with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10

	// Maximum message size allowed from peer.
	maxMessageSize = 512
)

var (
	newline    = []byte{'\n'}
	space      = []byte{' '}
	broadcast  chan []byte
	register   chan *websocket.Conn
	conns      map[*websocket.Conn]bool
	unregister chan *websocket.Conn
)

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

func main() {
	// 广播内容管道
	broadcast = make(chan []byte)
	// 注册连接管道
	register = make(chan *websocket.Conn)
	// 取消注册连接管道
	unregister = make(chan *websocket.Conn)
	// 连接池
	conns = make(map[*websocket.Conn]bool)
	router := gin.Default()
	// home页面,有ws连接javascript代码
	router.GET("/", serveHome)
	// 服务端websocket连接
	router.GET("/ws", serveWs)

	// websocket广播
	go run()

	err := router.Run(":8080")
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
	return
}

// 主页
func serveHome(ctx *gin.Context) {
	w, r := ctx.Writer, ctx.Request

	log.Println(r.URL)
	if r.URL.Path != "/" {
		http.Error(w, "Not found", http.StatusNotFound)
		return
	}
	if r.Method != http.MethodGet {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	http.ServeFile(w, r, "home.html")
}

// websocket连接api
func serveWs(ctx *gin.Context) {
	conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
	if err != nil {
		fmt.Printf("create conn fail. | err: %s", err)
		return
	}
	register <- conn

	go readPump(conn)
	go writePump(conn)
}

// 监听读方法
func readPump(conn *websocket.Conn) {
	defer func() {
		unregister <- conn
		conn.Close()
	}()
	conn.SetReadLimit(maxMessageSize)
	conn.SetReadDeadline(time.Now().Add(pongWait))
	conn.SetPongHandler(func(string) error { conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
	for {
		_, message, err := conn.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				log.Printf("error: %v", err)
			}
			break
		}
		message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
		broadcast <- message
	}
}

// ping保持连接
func writePump(conn *websocket.Conn) {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		conn.Close()
	}()
	for {
		select {
		case <-ticker.C:
			conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}

// 广播方法
func run() {
	for {
		select {
		case conn := <-register:
			conns[conn] = true
		case conn := <-unregister:
			if _, ok := conns[conn]; ok {
				delete(conns, conn)
			}
		case message, ok := <-broadcast:
			for conn := range conns {
				conn.SetWriteDeadline(time.Now().Add(writeWait))
				if !ok {
					// The hub closed the channel.
					conn.WriteMessage(websocket.CloseMessage, []byte{})
					return
				}

				w, err := conn.NextWriter(websocket.TextMessage)
				if err != nil {
					return
				}
				w.Write(message)

				if err := w.Close(); err != nil {
					return
				}
			}
		}
	}
}

三. home页代码

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Chat Example</title>
    <script type="text/javascript">
        window.onload = function () {
            var conn;
            var msg = document.getElementById("msg");
            var log = document.getElementById("log");

            function appendLog(item) {
                var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1;
                log.appendChild(item);
                if (doScroll) {
                    log.scrollTop = log.scrollHeight - log.clientHeight;
                }
            }

            document.getElementById("form").onsubmit = function () {
                if (!conn) {
                    return false;
                }
                if (!msg.value) {
                    return false;
                }
                conn.send(msg.value);
                msg.value = "";
                return false;
            };

            if (window["WebSocket"]) {
                conn = new WebSocket("ws://" + document.location.host + "/ws");
                conn.onclose = function (evt) {
                    var item = document.createElement("div");
                    item.innerHTML = "<b>Connection closed.</b>";
                    appendLog(item);
                };
                conn.onmessage = function (evt) {
                    var messages = evt.data.split('\n');
                    for (var i = 0; i < messages.length; i++) {
                        var item = document.createElement("div");
                        item.innerText = messages[i];
                        appendLog(item);
                    }
                };
            } else {
                var item = document.createElement("div");
                item.innerHTML = "<b>Your browser does not support WebSockets.</b>";
                appendLog(item);
            }
        };
    </script>
    <style type="text/css">
        html {
            overflow: hidden;
        }

        body {
            overflow: hidden;
            padding: 0;
            margin: 0;
            width: 100%;
            height: 100%;
            background: gray;
        }

        #log {
            background: white;
            margin: 0;
            padding: 0.5em 0.5em 0.5em 0.5em;
            position: absolute;
            top: 0.5em;
            left: 0.5em;
            right: 0.5em;
            bottom: 3em;
            overflow: auto;
        }

        #form {
            padding: 0 0.5em 0 0.5em;
            margin: 0;
            position: absolute;
            bottom: 1em;
            left: 0px;
            width: 100%;
            overflow: hidden;
        }

    </style>
</head>
<body>
<div id="log"></div>
<form id="form">
    <input type="submit" value="Send" />
    <input type="text" id="msg" size="64" autofocus />
</form>
</body>
</html>

四. 运行多个页面可以看到消息同步类似聊天室

 

PS:

服务端代码为官方库example的简化版,可以直接看官方示例

websocket/examples/chat at master · gorilla/websocket · GitHub

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

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

相关文章

从字节出来的测试总监,让我们用这份《测试用例规范》,再也没加班过。

经常看到无论是刚入职场的新人&#xff0c;还是工作了一段时间的老人&#xff0c;都会对编写测试用例感到困扰&#xff1f;例如&#xff1a; 固然&#xff0c;编写一份好的测试用例需要&#xff1a;充分的需求分析能力 理论及经验加持&#xff0c;作为测试职场摸爬打滚的老人&…

RobotStudio教程:ABB机器人TCP路径轨迹跟踪功能介绍与使用方法

目录 功能介绍 机器人工作站创建 TCP路径轨迹全局跟踪 基于事件管理器的TCP路径轨迹局部跟踪 基于Smart组件的TCP路径轨迹局部跟踪 仿真运行 功能介绍 干涉检查是虚拟仿真工作中非常重要的一个步骤&#xff0c;尤其是机器人工具与工件、工装夹具之间的碰撞干涉&#xff…

Java高阶数据结构 图的最短路径问题

图的最短路径问题&#xff01; 文章目录 Java高阶数据结构 & 图的最短路径问题1. Dijkstra算法【单源最短路径】1.1 Dijkstra算法证明1.2 Dijkstra算法代码实现1.3 堆优化的Dijkstra算法1.4 堆优化Dijkstra算法代码实现 2. Bellman-Ford算法【单源最短路径】2.1 BF算法证明…

常见的基础模块电路,你都能看懂吗?

文章开始前&#xff0c;先来考考大家~ 下面的五副电路图&#xff0c;你能看懂几个&#xff1f; 目录 01.电源电路 02.运算放大器电路 03.信号产生电路 04.信号处理电路 05.传感器及其应用电路 06.显示电路 TDA2030电路图 34063电路图 555电路 TDA2030电路图 三极管分立元…

音视频八股文(11)-- ffmpeg 音频重采样

1重采样 1.1 什么是重采样 所谓的重采样&#xff0c;就是改变⾳频的采样率、sample format、声道数等参数&#xff0c;使之按照我们期望的参数输出。 1.2 为什么要重采样 为什么要重采样&#xff1f;当然是原有的⾳频参数不满⾜我们的需求&#xff0c;⽐如在FFmpeg解码⾳频…

从头开始学习Python接口自动化测试:编写测试用例,执行测试以及生成测试报告

Python接口自动化测试详解 随着Web服务和移动应用不断增多&#xff0c;以及对API和微服务的需求不断增加&#xff0c;API已成为现代应用程序中必不可少的组件。自动化测试框架可以大大简化API测试的过程&#xff0c;并确保其正确性和稳定性。Python是一种非常流行的编程语言&a…

洛谷B2100 同行列对角线的格

同行列对角线的格 题目描述 输入三个自然数 N N N&#xff0c; i i i&#xff0c; j j j&#xff08; 1 ≤ i ≤ n 1 \le i \le n 1≤i≤n&#xff0c; 1 ≤ j ≤ n 1 \le j \le n 1≤j≤n&#xff09;&#xff0c;输出在一个 N N N \times N NN 格的棋盘中&#xff08;行…

西门子1200PLC如何在威纶通HMI上进行时间显示

先生成定时器DB&#xff0c;然后在引脚绑定变量&#xff0c;在西门子PLC中&#xff0c;DINT和TIME之间可以隐含转化。 第一种方法&#xff1a;触摸屏元件设置成DINT类型 数值元件资料格式为32-bit Signed&#xff0c;对应PLC中即为DINT类型。小数点以下没有位数。这是我们测试…

Android RecyclerView实现侧滑删除,附 Demo

距上次写博客有半年多了&#xff0c;回忆起来都觉得不可思议&#xff0c;中间也想憋俩大招&#xff0c;总是被耽误&#xff0c;这俩月忙完之后&#xff0c;终于空下来了&#xff0c;恰好新项目我和UI俩人商量一下&#xff0c;用MD来实现app。中间有个需求是RecyclerView中侧滑显…

ch06-Pytorch的正则化与归一化

ch06-Pytorch的正则化与归一化 0.引言1.weight decay 和 dropout1.1.Regularization1.2.Dropout 2.Normalization2.1.Batch Normalization2.2.Batch Normalization in PyTorch2.2.1.nn.BatchNorm1d()2.2.2.nn.BatchNorm2d()2.2.3.nn.BatchNorm3d() 2.3.其他常见的Normalization…

java servlet jsp 农产品价格信息搜集系统 python开发mysql数据库web结构jsp编程计算机网页项目

一、源码特点 jsp 农产品价格信息搜集系统 python是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助 系统采用 serlvetdaobean 模式开发 利用python 进行网站爬取 &#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开…

Redis常见命令有哪些?怎么使用?

一、概述&#xff1a; 在了解Redis命令之前&#xff0c;我们要先了解Redis的数据结构&#xff0c;Redis是NoSQL数据库&#xff0c;采用了json存储模式&#xff0c;比MySQL等关系数据库更易于扩展&#xff0c;拥有丰富的数据类型&#xff0c;分基本类型与特殊类型。基本类型包括…

【Linux】网络---->套接字编程(TCP)

套接字编程TCP TCP的编程流程TCP的接口TCP的代码&#xff08;单线程、多进程、多线程代码&#xff09;单线程多进程多线程 TCP的编程流程 TCP的编程流程&#xff1a;大致可以分为五个过程&#xff0c;分别是准备过程、连接建立过程、获取新连接过程、消息收发过程和断开过程。 …

【youcans的深度学习 07】PyTorch入门教程:张量的基本操作 2

欢迎关注『youcans的深度学习』系列&#xff0c;持续更新中… 【youcans的深度学习 01】安装环境之 miniconda 【youcans的深度学习 02】PyTorch CPU版本安装与环境配置 【youcans的深度学习 03】PyTorch CPU版本安装与环境配置 【youcans的深度学习 04】PyTorch入门教程&#…

面向对象程序设计概述

&#x1f9d1;‍&#x1f4bb;CSDN主页&#xff1a;夏志121的主页 &#x1f4cb;专栏地址&#xff1a;Java核心技术专栏 目录 一、类 二、对象 三、识别类 四、类之间的关系 面向对象程序设计&#xff08;Object-Oriented Programming,OOP)是当今的主流程序设计范型&#x…

线段树详解

目录 线段树的概念 线段树的实现 线段树的存储 需要4n大小的数组 线段树的区间是确定的 线段树的难点在于lazy操作 代码样例 线段树的概念 线段树&#xff08;Segment Tree&#xff09;是一种平衡二叉树&#xff0c;用于解决区间查询问题。它将一个区间划分成若干个子区…

Android 车载值不值得入手学?

前言 随着智能车的不断普及和智能化程度的提高&#xff0c;车载系统也在逐步升级和演进&#xff0c;越来越多的汽车厂商开始推出采用Android系统的车载设备&#xff0c;这为Android车载开发提供了广泛的市场需求。 其次&#xff0c;随着人工智能技术的发展和应用&#xff0c;…

Linux : 安装源码包

安装源码包之前我们要准备好yum环境&#xff0c;或者使用默认上网下载的yum仓库或者查看&#xff1a;Linux&#xff1a;rpm查询安装 && yum安装_鲍海超的博客-CSDN博客 准备离线yum仓库 &#xff0c;默认的需要在有网环境下才能去网上下载 其次就是安装 gcc make 准…

UDP协议 sendto 和 recvfrom 浅析与示例

UDP&#xff08;user datagram protocol&#xff09;用户数据报协议&#xff0c;属于传输层。 UDP是面向非连接的协议&#xff0c;它不与对方建立连接&#xff0c;而是直接把数据报发给对方。UDP无需建立类如三次握手的连接&#xff0c;使得通信效率很高。因此UDP适用于一次传…

Kyligence Zen 一站式指标平台体验——“绝对实力”的指标分析和管理工具——入门体验评测

&#x1f996;欢迎观阅本本篇文章&#xff0c;我是Sam9029 文章目录 前言Kyligence Zen 是什么Kyligence Zen 能做什么Kyligence Zen 优势在何处 正文注册账号平台功能模块介绍指标图表新建指标指标模板 目标仪表盘数据设置 实际业务体验---使用官网数据范例使用流程归因分析指…