Golang websocket推送

在工作用主要使用的是Java,也做过IM(后端用的netty websocket)。最近想通过Golang重写下,于是通过websocket撸了一个聊天室。

项目地址

Github

依赖

golang.org/x/net下的websocket。

由于我使用的是golang版本是1.12,在国内访问golang.org/x需要借助代理,或者通过replace替换为github下的镜像。

module github.com/xuanbo/pusher

require golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3

replace (
golang.org/x/crypto => github.com/golang/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/net => github.com/golang/net v0.0.0-20190404232315-eb5bcb51f2a3
golang.org/x/sys => github.com/golang/sys v0.0.0-20190215142949-d0b11bdaac8a
golang.org/x/text => github.com/golang/text v0.3.0
)

即工程下的go.mod.cn文件。

websocket用法

核心就是for循环下的处理收到的消息逻辑,然后对消息进行处理(转发、广播等)。

// websocket Handler
// usage: http.Handle("/websocket", websocket.Handler(pusher.Handler))
func Handler(conn *websocket.Conn) {
// handle connected
var userId string
var err error
if userId, err = doConnected(conn); err != nil {
fmt.Println("Client connect error: ", err)
return
} fmt.Println("Client connected, userId: ", userId) for {
msg := new(Message) if err := websocket.JSON.Receive(conn, msg); err != nil {
fmt.Println("Can't receive, error: ", err)
break
} msg.UpdateAt = Timestamp() fmt.Println("Received from client: ", msg) // handle received message
if err := doReceived(conn, msg); err != nil {
fmt.Println("Received message error: ", err)
break
}
} // handle disConnected
if err := doDisConnected(userId, conn); err != nil {
fmt.Println("Client disconnected error: ", err)
return
} fmt.Println("Client disconnected, userId: ", userId)
}

连接管理

在IM中比较重要的点就是管理客户端连接,这样我们才能通过服务端转发消息给对应的用户。注意,下面没有考虑集群,只在单机中考虑。

// websocket connection manager
type ConnManager struct {
// websocket connection number
Online *int32
// websocket connection
connections *sync.Map
}

上面定义了一个连接管理结构体,Online为在线的人数,connections为客户端的连接管理(key为userId,value为websocket connection)。

下面为ConnManager添加一些方法来处理连接、断开连接、发送消息、广播等操作。

// add websocket connection
// online number + 1
func (m *ConnManager) Connected(k, v interface{}) {
m.connections.Store(k, v) atomic.AddInt32(m.Online, 1)
} // remove websocket connection by key
// online number - 1
func (m *ConnManager) DisConnected(k interface{}) {
m.connections.Delete(k) atomic.AddInt32(m.Online, -1)
} // get websocket connection by key
func (m *ConnManager) Get(k interface{}) (v interface{}, ok bool) {
return m.connections.Load(k)
} // iter websocket connections
func (m *ConnManager) Foreach(f func(k, v interface{})) {
m.connections.Range(func(k, v interface{}) bool {
f(k, v)
return true
})
} // send message to one websocket connection
func (m *ConnManager) Send(k string, msg *Message) {
v, ok := m.Get(k)
if ok {
if conn, ok := v.(*websocket.Conn); ok {
if err := websocket.JSON.Send(conn, msg); err != nil {
fmt.Println("Send msg error: ", err)
}
} else {
fmt.Println("invalid type, expect *websocket.Conn")
}
} else {
fmt.Println("connection not exist")
}
} // send message to multi websocket connections
func (m *ConnManager) SendMulti(keys []string, msg interface{}) {
for _, k := range keys {
v, ok := m.Get(k)
if ok {
if conn, ok := v.(*websocket.Conn); ok {
if err := websocket.JSON.Send(conn, msg); err != nil {
fmt.Println("Send msg error: ", err)
}
} else {
fmt.Println("invalid type, expect *websocket.Conn")
}
} else {
fmt.Println("connection not exist")
}
}
} // broadcast message to all websocket connections otherwise own connection
func (m *ConnManager) Broadcast(conn *websocket.Conn, msg *Message) {
m.Foreach(func(k, v interface{}) {
if c, ok := v.(*websocket.Conn); ok && c != conn {
if err := websocket.JSON.Send(c, msg); err != nil {
fmt.Println("Send msg error: ", err)
}
}
})
}

消息类型、格式

消息类型(MessageType)主要有单聊、群聊、系统通知等。

消息格式(MediaType)主要有文本格式、图片、文件等。

type MessageType int
type MediaType int const (
Single MessageType = iota
Group
SysNotify
OnlineNotify
OfflineNotify
) const (
Text MediaType = iota
Image
File
) // websocket message
type Message struct {
MessageType MessageType `json:"messageType"`
MediaType MediaType `json:"mediaType"`
From string `json:"from"`
To string `json:"to"`
Content string `json:"content,omitempty"`
FileId string `json:"fileId,omitempty"`
Url string `json:"url,omitempty"`
CreateAt int64 `json:"createAt,omitempty"`
UpdateAt int64 `json:"updateAt,omitempty"`
}

上面定义了一个统一的消息(Message)。

效果

前端的代码就不展示了,最终实现的聊天室效果如下:

补充

本例子没有涉及到用户认证、消息加密、idle、单聊、消息格式、消息持久化等等,只做了一个简单的群聊。

欢迎感兴趣的道友,基于此扩展出自己的推送系统、IM等。

说明

Just for fun!

Golang websocket推送的更多相关文章

  1. 用 Go 编写一个简单的 WebSocket 推送服务

    用 Go 编写一个简单的 WebSocket 推送服务 本文中代码可以在 github.com/alfred-zhong/wserver 获取. 背景 最近拿到需求要在网页上展示报警信息.以往报警信息 ...

  2. 模拟websocket推送消息服务mock工具二

    模拟websocket推送消息服务mock工具二 在上一篇博文中有提到<使用electron开发一个h5的客户端应用创建http服务模拟后端接口mock>使用electron创建一个模拟后 ...

  3. GoEasy实现websocket 推送消息通知到客户端

    最近在实现一个推送功能,用户扫描二维码签到,后台及时将签到成功信息推送到浏览器端.排除了前端ajax轮询的方式,决定采用websocket及时推送. 于是发现了第三方websocket推送库GoEas ...

  4. 小谢第37问:关于websocket推送进度,本地保存进度条,然后跳出页面进入后再显示的问题

    1.主要技术点:sessionStorage 会话存储进度 这里在使用之前,顺便说一下cookie.sessionStorage.localStorage 共同点:都是保存在浏览器端,且同源的. 区别 ...

  5. 利用奇偶数来获取websocket推送时间间隔(或者比较前一个数和下一个数的变化)

    利用奇偶数来获取websocket推送时间间隔(或者比较前一个数和下一个数的变化) 在vue中的 data () {     return { countTime: 0,         newDat ...

  6. 基于Java的WebSocket推送

    WebSocket的主动推送 关于消息推送,现在的解决方案如轮询.长连接或者短连接,当然还有其他的一些技术框架,有的是客户端直接去服务端拿数据. 其实推送推送主要讲的是一个推的概念,WebSocket ...

  7. web全栈应用【爬取(scrapy)数据 -> 通过restful接口存入数据库 -> websocket推送展示到前台】

    作为 https://github.com/fanqingsong/web_full_stack_application 子项目的一功能的核心部分,使用scrapy抓取数据,解析完的数据,使用 pyt ...

  8. 从构建分布式秒杀系统聊聊WebSocket推送通知

    秒杀架构到后期,我们采用了消息队列的形式实现抢购逻辑,那么之前抛出过这样一个问题:消息队列异步处理完每个用户请求后,如何通知给相应用户秒杀成功? 场景映射 首先,我们举一个生活中比较常见的例子:我们去 ...

  9. WebSocket推送

    本篇博客只是记录websocket在自己的项目中的应用,只是记录,不做说明(后来替换为GoEasy了). /** * 握手的设置,这其实是为了获取session */ public class Get ...

随机推荐

  1. Spring ES

    elasticsearchTemplate 和 ElasticsearchRepository JPA中有个ElasticsearchRepository可以做Elasticsearch的相关增删改查 ...

  2. 软件测试-Svn服务器搭建全过程-基于Centos6.7-64bit

    本文详细介绍了在CentOs6.7-64bit服务器上搭建SVN版本控制服务 工具准备 CentOs6.7-64bit操作系统:关闭linux防火墙:设置定固IP--DHCP(动态):安装系统过程中开 ...

  3. encode与decode

    import torch from torch import nn import numpy as np import matplotlib.pyplot as plt import torch.ut ...

  4. python学习第37天

    MySQL数据库 数据库的优势 什么是数据(Data) 什么是数据库(DataBase,简称DB) 什么是数据库管理系统(DataBase Managerment System 简称DBMS) 数据库 ...

  5. 迭代和JDB(课下作业,选做)

    迭代和JDB(课下作业,选做) 题目要求 1 使用C(n,m)=C(n-1,m-1)+C(n-1,m)公式进行递归编程实现求组合数C(m,n)的功能 2 m,n 要通过命令行传入 3 提交测试运行截图 ...

  6. git/gerrit的简介

    gerrit和git   1.git Git是什么? Git是目前世界上最先进的分布式版本控制系统. SVN是集中式版本控制系统. Git与svn比较 相同:能记录文件的所有更改记录.这样是为了大量更 ...

  7. Qt-c++桌面编程报错:qt.qpa.plugin: Could not find the Qt platform plugin "windows" in "",已解决

    语言:c++ 编译库:Qt GUI,qt5.12.1 软件类型:Qt application,qt桌面软件 运行平台:window 10 ?按照[https://www.devbean.net/201 ...

  8. eclipse导入maven时,pom文件的project一直报错(Failure to transfer org.apache.maven.plugins:maven-surefire-plugin:pom:2.12.)

    这里有两种解决办法. 一:右键项目->maven->update project勾选上Force Update of Snapshots/Releases然后ok就可以了. 二:如果第一种 ...

  9. IE8引用jQuery报$或者jQuery未定义

    最近公司做的项目要求兼容到IE8,结果在页面调试的时候出了个bug,在IE8上面一直报错$未定义,或者jQuery未定义,导致页面上面写的jQuery全部失效,在Chrome浏览器没有任何问题.很是头 ...

  10. Interactive map of Linux kernel

    Interactive map of Linux kernel 2.6.36  : http://www.makelinux.net/kernel_map/ 注: 图中函数名带连接