WebSocket 和 Golang 实现聊天功能

本文同步至 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

时间: 2024-10-03 13:44:45

WebSocket 和 Golang 实现聊天功能的相关文章

【WebSocket】---实现一对一聊天功能

实现一对一聊天功能 功能介绍:实现A和B单独聊天功能,即A发消息给B只能B接收,同样B向A发消息只能A接收. 本篇博客是在上一遍基础上搭建,上一篇博客地址:[WebSocket]---实现游戏公告功能.源码会在其它案例全部写完,在上传到gitHub,到时候会补源码地址. 先看演示效果: 一.案例解析 1.PTPContoller /** * 功能描述:简单版单人聊天 * 这里没有用到@SendTo("/topic/game_chat")来指定订阅地址,而是通过SimpMessaging

javaweb webSocket 实现简单的点对点聊天功能

本文依据 http://redstarofsleep.iteye.com/blog/1488639?page=4  内容修改完成,实现点对点聊天 需要 jdk 7 , tomcat需要支持websocket的版本 1.InitServlet 该类主要是用来初始化构造将来存储用户身份信息的map仓库,利用其初始化方法Init 初始化仓库, 利用其静态方法getSocketList 获得对应的用户身份信息. webSocket ,我认为MessageInbound 用来识别登录人的信息,用它来找到对

集成websocket即时通讯 java聊天源码 IM SSM

获取[下载地址]   QQ: 313596790   [免费支持更新]支持三大数据库 mysql  oracle  sqlsever   更专业.更强悍.适合不同用户群体[新录针对本系统的视频教程,手把手教开发一个模块,快速掌握本系统]A 代码生成器(开发利器);      增删改查的处理类,service层,mybatis的xml,SQL( mysql   和oracle)脚本,   jsp页面 都生成   就不用写搬砖的代码了,生成的放到项目里,可以直接运行B 阿里巴巴数据库连接池druid

微信小程序实现即时通信聊天功能的实例代码

项目背景:小程序中实现实时聊天功能 一.服务器域名配置 配置流程 配置参考URL:https://developers.weixin.qq.com/miniprogram/dev/api/api-network.html 二.nginx中配置反向代理加密websocket(wss) ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 3

springmvc(18)使用WebSocket 和 STOMP 实现消息功能

[0]README 1)本文旨在 介绍如何 利用 WebSocket 和 STOMP 实现消息功能: 2)要知道, WebSocket 是发送和接收消息的 底层API,而SockJS 是在 WebSocket 之上的 API:最后 STOMP(面向消息的简单文本协议)是基于 SockJS 的高级API (干货--简而言之,WebSocket 是底层协议,SockJS 是WebSocket 的备选方案,也是 底层协议,而 STOMP 是基于 WebSocket(SockJS) 的上层协议) 3)b

[Asp.net 开发系列之SignalR篇]专题二:使用SignalR实现酷炫端对端聊天功能

一.引言 在前一篇文章已经详细介绍了SignalR了,并且简单介绍它在Asp.net MVC 和WPF中的应用.在上篇博文介绍的都是群发消息的实现,然而,对于SignalR是为了实时聊天而生的,自然少了不像QQ一样的端对端的聊天了.本篇博文将介绍如何使用SignalR来实现类似QQ聊天的功能. 二.使用SignalR实现端对端聊天的思路 在介绍具体实现之前,我先来介绍了使用SignalR实现端对端聊天的思路.相信大家在前篇文章已经看到过Clients.All.sendMessage(name,

我们一起学习WCF 第九篇聊天功能

说到聊天,那么其实就是传输数据,把自己写的东西传给自己想发送的那么人.我总结一下传输有三种方式 1:就是我们常见的数据库传输 2:就是文件(流)传输 3:就是socket传输 今天我们说的wcf实现聊天其实是基于socket的聊天功能(QQ聊天发展到今天肯定是很牛的了,但是最初肯定也是这样的思想) 今天我先说说基于WCF聊天的原理 1:需要一个回调函数(当用户发送的时候会吧信息回调给客户端本身) 2:需要一个委托(把服务器传来的信息显示给前台) 3:需要一个触发点击事件(目的是为了触发把发送信息

安卓平台基于SIP协议实现注册,聊天功能

============问题描述============ 不涉及音频,视频发送,只要实现注册,和聊天功能就行, 网上下了sipdroid的源码,但是XML中配置的<uses-sdk android:minSdkVersion="3" android:targetSdkVersion="4"/> 这段没明白,资料里说安卓最低支持SIP协议的API level是9,但是这版本不匹配呀,有大牛指导下么 ============解决方案1============

Java UDP实现聊天功能代码

我以前经常写的是基于TCP的网络编程,由于TCP建立连接鼻血要经过三次握手连接,服务器端需要阻塞式等待客户端的连接.而UDP则是可以直接向目的地址的目的端口上发送数据包,由于它只负责发送出去就好,不管对方是否正确接受到与否,所以当网络性能不好时它容易出现丢包的问题.(注意:UDP是基于数据报为单位进行传输的,而TCP是一种基于流进行传输的) 但是UDP很好的模拟了我们呢平时聊天的方式,可以很好的实现连续多次发送和接受,也就是简单的QQ聊天的功能. 现在来简要介绍Java中有关UDP编程相关的类: