文档章节

WebSocket 和 Golang 实现聊天功能

waylau
 waylau
发布于 2014/11/19 08:20
字数 1485
阅读 2151
收藏 10
点赞 0
评论 2

本文同步至 http://www.waylau.com/go-websocket-chat/

这个示例应用程序展示了如何使用 WebSocket, GolangjQuery 创建一个简单的web聊天应用程序。这个示例的源代码在 https://github.com/waylau/goChat

##Running the example 运行示例

这个示例需要 Golang 开发环境。 该页面描述如何安装开发环境。

一旦你去启动和运行,您可以下载、构建和运行的例子, 使用命令:

go get gary.burd.info/go-websocket-chat
go-websocket-chat

在支持 websocket 的浏览器尝试打开 http://127.0.0.1:8080/ 启动应用

##Server 服务器

服务器程序实现了 http 包,包含了 Go 分发和 Gorilla 项目的 websocket 包.

应用程序定义了两种类型, connection 和 hub 。服务器为每个 webscocket 连接 创建的一个 connection 类型的实例 。 连接器扮演了 websocket 和 hub 类型单例 之间的媒介 。 hub 保持一组注册了的连接器 和 广播到连接器的信息。

程序运行了一个 goroutine 给 hub 和两个 goroutine 给每个连接器。 goroutine 通过 channel 和其他进行交流。 hub 拥有注册连接器、注销连接器和广播信息的 channel。一个连机器拥有缓存的发出信息的 channel 。其中一个 连接器的 goroutine 从这个 channel 中读信息 并把信息写入 webscoket。另外一个连接器 goroutine 从 websocket 读信息,并把信息发送到 hub。

下面是 hub 类型代码:

package main

type hub struct {
    // 注册了的连接器
    connections map[*connection]bool

    // 从连接器中发入的信息
    broadcast chan []byte

    // 从连接器中注册请求
    register chan *connection

    // 从连接器中注销请求
    unregister chan *connection
}

var h = hub{
    broadcast:   make(chan []byte),
    register:    make(chan *connection),
    unregister:  make(chan *connection),
    connections: make(map[*connection]bool),
}

func (h *hub) run() {
    for {
        select {
        case c := <-h.register:
            h.connections[c] = true
        case c := <-h.unregister:
            if _, ok := h.connections[c]; ok {
                delete(h.connections, c)
                close(c.send)
            }
        case m := <-h.broadcast:
            for c := range h.connections {
                select {
                case c.send <- m:
                default:
                    delete(h.connections, c)
                    close(c.send)
                }
            }
        }
    }
}

应用程序的 主要 函数启动 hub 以 goroutine 形式运行方法。连接器 发送请求到 hub 通过 注册、注销和广播 channel。

hub 注册连接器通过添加 connection 的指针作为 connections map 的主键。这个 map 的值通常是 true。

注销的代码有点复杂。除了从 connections map 删除连接器的指针外, hub 关闭了 connection 的发送,来标识没有信息再被发送到 connection了。

hub 通过循环注册连接器和发送信息到连接器的发送 channel 来控制信息。 如果连接器的发送缓冲区已经满了,那么 hub 假设 客户端已死或卡住了。这种情况下, hub 注销连接器 并关闭 websocket.

下面关于 connection 类型的代码:

package main

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

type connection struct {
    // websocket 连接器
    ws *websocket.Conn

    // 发送信息的缓冲 channel 
    send chan []byte
}

func (c *connection) reader() {
    for {
        _, message, err := c.ws.ReadMessage()
        if err != nil {
            break
        }
        h.broadcast <- message
    }
    c.ws.Close()
}

func (c *connection) writer() {
    for message := range c.send {
        err := c.ws.WriteMessage(websocket.TextMessage, message)
        if err != nil {
            break
        }
    }
    c.ws.Close()
}

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

func wsHandler(w http.ResponseWriter, r *http.Request) {
    ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        return
    }
    c := &connection{send: make(chan []byte, 256), ws: ws}
    h.register <- c
    defer func() { h.unregister <- c }()
    go c.writer()
    c.reader()
}

wsHandler 方法被主函数当做http handler注册。HTTP 连接到 WebSocket 协议的升级,创建一个连接对象,注册这个连接到 sub ,并通过 defer延迟语句 来控制 连接的注销。

接着,wsHandler 方法开启 连接器的写入方法作为一个 goroutine。 写入方法将信息从连接器的 channel 转入 websocket。当 hub 关闭 channel 或者 在写入 websocket 时出错,写入方法关闭。

最后,wsHandler 方法 调用连接器的 读 方法。 读方法将 入站消息 从 websocket 转到 hub。

这里是服务器的代码的其余部分:

package main

import (
    "flag"
    "go/build"
    "log"
    "net/http"
    "path/filepath"
    "text/template"
)

var (
    addr      = flag.String("addr", ":8080", "http service address")
    assets    = flag.String("assets", defaultAssetPath(), "path to assets")
    homeTempl *template.Template
)

func defaultAssetPath() string {
    p, err := build.Default.Import("gary.burd.info/go-websocket-chat", "", build.FindOnly)
    if err != nil {
        return "."
    }
    return p.Dir
}

func homeHandler(c http.ResponseWriter, req *http.Request) {
    homeTempl.Execute(c, req.Host)
}

func main() {
    flag.Parse()
    homeTempl = template.Must(template.ParseFiles(filepath.Join(*assets, "home.html")))
    go h.run()
    http.HandleFunc("/", homeHandler)
    http.HandleFunc("/ws", wsHandler)
    if err := http.ListenAndServe(*addr, nil); err != nil {
        log.Fatal("ListenAndServe:", err)
    }
}

应用主程序启动 hub goroutine。 接着 主程序 注册 主页 和 websocket 连接器的控制器N。最后主程序启动 HTTP 服务器。

##Client 客户端

客户端的实现是一个简单的 HTML 文件:

<html>
<head>
<title>Chat Example</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
    $(function() {

    var conn;
    var msg = $("#msg");
    var log = $("#log");

    function appendLog(msg) {
        var d = log[0]
        var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight;
        msg.appendTo(log)
        if (doScroll) {
            d.scrollTop = d.scrollHeight - d.clientHeight;
        }
    }

    $("#form").submit(function() {
        if (!conn) {
            return false;
        }
        if (!msg.val()) {
            return false;
        }
        conn.send(msg.val());
        msg.val("");
        return false
    });

    if (window["WebSocket"]) {
        conn = new WebSocket("ws://{{$}}/ws");
        conn.onclose = function(evt) {
            appendLog($("<div><b>Connection closed.</b></div>"))
        }
        conn.onmessage = function(evt) {
            appendLog($("<div/>").text(evt.data))
        }
    } else {
        appendLog($("<div><b>Your browser does not support WebSockets.</b></div>"))
    }
    });
</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"/>
</form>
</body>
</html>

客户端使用 jQuery

文档加载。脚本检查 websocket 的功能 。如果 WebSocket 功能 可以用,然后打开脚本与服务器的连接,并注册一个回调处理来自服务器的信息。回调使用 appendlog 方法将消息添加到聊天记录。

appendlog 方法检查在添加新的内容时的滚动位置,从而可以让用户手动滚动聊天记录而不会被新来的消息中断。如果聊天记录滚动至底部,那么新内容添加的到旧内容的后面。否则,滚动的位置不会改变。

表单处理器将用户的输入写入到 WebSocket 并且清除输入字段。

参考:http://gary.burd.info/go-websocket-chat

© 著作权归作者所有

共有 人打赏支持
waylau

waylau

粉丝 476
博文 86
码字总数 159744
作品 2
深圳
架构师
加载中

评论(2)

waylau
waylau

引用来自“wahahachuang4”的评论

我觉得这种东西自己开发太麻烦了,就别自己捣鼓了,找个第三方,方便,GoEasy就挺不错的,我昨天试了一下,代码简洁易懂,几分钟我就洗了一个自己的实时推送功能;官网: http://goeasy.io/
你说的这个是 收费的吧? 消息推送 商业产品确实很多~
w
wahahachuang4
我觉得这种东西自己开发太麻烦了,就别自己捣鼓了,找个第三方,方便,GoEasy就挺不错的,我昨天试了一下,代码简洁易懂,几分钟我就洗了一个自己的实时推送功能;官网: http://goeasy.io/
【WebSocket No.3】使用WebSocket协议来做服务器

写在开始 上面一篇写了一篇使用WebSocket做客户端,然后服务端是socke代码实现的。传送门:webSocket和Socket实现聊天群发 本来我是打算写到一章上的,毕竟实现的都是一样的功能,后来想了想...

YanBigFeg
前天
0
0
HTML5 WebSocket JavaWeb 实现简单的聊天室功能

本实例使用websocket实现了即时聊天系统,websocket技术可以在网页中使用js技术完成与后台的通讯, 而后台tomcat集成了websocket功能, 只需要很少的代码就可以完成很强大的功能, 基于此聊天系...

智慧点点
04/23
0
0
talent-tan/tio-websocket-showcase

tio-websocket-showcase 项目介绍 展示tio-websocket的用法,官方提供的唯一tio-websocket示范教程 包括wss和流量监控及处理等高级特性 还包括t-io作者写的一个用于连接websocket服务器的js小...

talent-tan
05/06
0
0
使用WebSocket制作简单的聊天室

使用WebSocket制作简单的聊天室 一、功能需求 ①第一次进入聊天室,输入用户的昵称,用于聊天消息显示名称 ②用户进入系统后,系统需要提示所有用户,有新用户进入系统了 ③用户发送消息时要...

JS_HCX
05/27
0
0
为Phonegap Android平台增加websocket支持,使默认成为socket.io首选通

为Phonegap Android平台增加websocket支持,使默认成为socket.io首选通道选择 广而告之 使用socket.io作为跨浏览器平台的实时推送首选,经测试在各个主流浏览器上测试都确实具有良好的下实时...

james_laughing
2014/12/17
0
2
spring boot websocket stomp 实现广播通信和一对一通信聊天

一、前言 玩.net的时候,在asp.net下有一个叫 SignalR 的框架,可以在ASP .NET的Web项目中实现实时通信。刚接触java寻找先关替代品,发现 java 体系中有一套基于stomp协议的websocket通信的框...

ejiyuan
07/11
0
0
WebSocket原理及技术简介

WebSocket用于在Web浏览器和服务器之间进行任意的双向数据传输的一种技术。WebSocket协议基于TCP协议实现,包含初始的握手过程,以及后续的多次数据帧双向传输过程。其目的是在WebSocket应用...

yaukie
07/02
0
0
45.Tornado 搭建基于 WebSocket 的聊天服务

这年头 Python web 框架是有点泛滥了. 下面要介绍的是 facebook 的开源框架 tornado. 这东西比较简单, 而且自带 WebSocket 支持, 可以用它做个简单的聊天室. 读者最好已具备 Javascript 与 ...

quanpower
2013/08/08
0
0
WebSocket使用教程 - 带完整实例

什么是WebSocket?看过html5的同学都知道,WebSocket protocol 是HTML5一种新的协议。它是实现了浏览器与服务器全双工通信(full-duplex)。HTML5定义了WebSocket协议,能更好的节省服务器资源...

james_laughing
2014/12/18
0
7
Websocket和PHP Socket编程

谈谈php中使用websocket-实例 http://blog.csdn.net/xueling022/article/details/52902358 本来是搜一些html5 websocket资料看的,结果被引去看了php的socket编程。下面是一些简单的例子,在...

mickelfeng
2014/10/28
0
2

没有更多内容

加载失败,请刷新页面

加载更多

下一页

CDH的坑之Sqoop导出数据到MySQL

CDH的坑之Sqoop导出数据到MySQL 最近使用Sqoop从Hive导出数据到MySQL中,出现了一系列的问题,下面将这个问题记录一下,避免再度踩坑! 导出语句 sqoop export --connect jdbc:mysql://192....

星汉
3分钟前
0
0
Hyperledger Fabric 客户端开发三

前面两篇文章介绍了Hyperledger Fabric SDK并使用一个实例介绍如何通过SDK和Hyperledger Fabric Blockchain交互, 现在详细分析相关的过程。 首先看 enroll (登录) admin 过程。 'use stric...

十一月不远
4分钟前
0
0
PowerDesigner连接MySQL和逆向工程图

最近想梳理公司项目的表间关系,从项目后台管理系统的操作入手,以及代码的hibernate注解入手,都不算特别尽人意,于是最后还是鼓捣了一下PowerDesigner的逆向工程图,这样更直观一些。 想着...

Oo若离oO
4分钟前
0
0
威胁web应用安全的错误

一般绝大部分的web应用攻击都是没特定目标的大范围漏洞扫描,只有少数攻击确实是为入侵特定目标而进行的针对性尝试。这两种攻击都非常频繁,难以准确检测出来,许多网站的web应用防火墙都无法...

上树的熊
7分钟前
2
0
pypy2 install crypto error

install pycryptodome instead pip install pycryptodome

coord
11分钟前
0
0
Service Mesh所应对的8项挑战

Lori Macvittie 微服务架构是把双刃剑,我们享受它带来的开发速度(development velocity),却也不得不面对服务间通讯带来的复杂性问题。 目前大多数扩展容器化微服务的架构多是基于proxy-b...

好雨云帮
20分钟前
0
0
时间复杂度

1. 维基上的定义 在计算机科学中,算法的时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低...

liuyan_lc
26分钟前
0
0
js中的~符

~是js里的按位取反操作符,~~就是执行两次按位取反,其实就是保持原值,但是注意虽然是原值,但是对布尔型变量执行这个操作,会转化成相应的数值型变量,也就是 ~~true === 1,~~false === 0...

JamesView
27分钟前
0
0
webpack安装

npm install --save-dev webpack-cli

Vincent-Duan
29分钟前
0
0
实时监听EditText内容变化

主要是addTextChangedListener方法的使用 aswerEdittext.addTextChangedListener(new TextWatcher() { //编辑框的内容发生改变之前的回调方法 @Override public void before...

王先森oO
32分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部