一文让你看懂Golang如何打造实时聊天系统

2023-05-16

项目截图

在这里插入图片描述

简介

在本次课程中,我们来学习使用WebSocket来打造一个实时聊天系统。我们会从一下几个方面来进行学习:

什么是websocket;
Websocket与传统的HTTP协议有什么区别;
Websocket有哪些优点;
如何建立连接;
如何维持连接;
Golang实战项目—实时聊天系统;

总结;

什么是websocket?
WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

WebSocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范。

WebSocket协议支持(在受控环境中运行不受信任的代码的)客户端与(选择加入该代码的通信的)远程主机之间进行全双工通信。用于此的安全模型是Web浏览器常用的基于原始的安全模式。 协议包括一个开放的握手以及随后的TCP层上的消息帧。 该技术的目标是为基于浏览器的、需要和服务器进行双向通信的(服务器不能依赖于打开多个HTTP连接(例如,使用XMLHttpRequest或iframe和长轮询))应用程序提供一种通信机制。

Websocket与传统的HTTP协议有什么区别?
http,websocket都是应用层协议,他们规定的是数据怎么封装,而他们传输的通道是下层提供的。就是说无论是 http 请求,还是 WebSocket 请求,他们用的连接都是传输层提供的,即 tcp 连接(传输层还有 udp 连接)。只是说 http1.0 协议规定,你一个请求获得一个响应后,你要把连接关掉。所以你用 http 协议发送的请求是无法做到一直连着的(如果服务器一直不返回也可以保持相当一段时间,但是也会有超时而被断掉)。而 WebSocket 协议规定说等握手完成后我们的连接不能断哈。虽然 WebSocket 握手用的是 http 请求,但是请求头和响应头里面都有特殊字段,当浏览器或者服务端收到后会做相应的协议转换。所以 http 请求被 hold 住不返回的长连接和 WebSocket 的连接是有本质区别的。

WebSocket有哪些优点?
说到优点,这里的对比参照物是HTTP协议,概括地说就是:支持双向通信,更灵活,更高效,可扩展性更好。

支持双向通信,实时性更强。
更好的二进制支持。
较少的控制开销。连接创建后,ws客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有2~10字节(取决于数据包长度),客户端到服务端的的话,需要加上额外的4字节的掩码。而HTTP协议每次通信都需要携带完整的头部。
支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)
对于后面两点,没有研究过WebSocket协议规范的同学可能理解起来不够直观,但不影响对WebSocket

如何建立连接?
客户端通过HTTP请求与WebSocket服务端协商升级协议。协议升级完成后,后续的数据交换则遵照WebSocket的协议。

  1. 客户端:申请协议升级
    首先,客户端发起协议升级请求。可以看到,采用的是标准的HTTP报文格式,且只支持GET方法。
GET / HTTP/1.1
Host: localhost:8080
Origin: http://127.0.0.1:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==

重点请求首部意义如下:

  • Connection: Upgrade:表示要升级协议
  • Upgrade: websocket:表示要升级到websocket协议。
  • Sec-WebSocket-Version:
    13:表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。
  • Sec-WebSocket-Key:与后面服务端响应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意的连接,或者无意的连接。
    2.服务器:响应协议升级
    服务端返回内容如下,状态代码101表示协议切换。到此完成协议升级,后续的数据交互都按照新的协议来。
HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=

3. Sec-WebSocket-Accept的计算
Sec-WebSocket-Accept根据客户端请求首部的Sec-WebSocket-Key计算出来。

计算公式为:

  • 将Sec-WebSocket-Key跟258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接。
  • 通过SHA1计算出摘要,并转成base64字符串。

如何维持连接?

WebSocket为了保持客户端、服务端的实时双向通信,需要确保客户端、服务端之间的TCP通道保持连接没有断开。然而,对于长时间没有数据往来的连接,如果依旧长时间保持着,可能会浪费包括的连接资源。

但不排除有些场景,客户端、服务端虽然长时间没有数据往来,但仍需要保持连接。这个时候,可以采用心跳来实现。

发送方->接收方:ping

接收方->发送方:pong

ping、pong的操作,对应的是WebSocket的两个控制帧,opcode分别是0x9、0xA。

举例,WebSocket服务端向客户端发送ping,只需要如下代码(采用ws模块)

Golang实战项目—实时聊天系统

这里是使用Github上的一个开源项目作为案例。

获取Golang的websocket库

go get github.com/gorilla/websocket

获取测试程序

git clone https://github.com/scotch-io/go-realtime-chat.git

Server

package main

import (

    "log"

    "net/http"

    "github.com/gorilla/websocket"

)

var clients = make(map[*websocket.Conn]bool) // connected clients

var broadcast = make(chan Message)          // broadcast channel

// Configure the upgrader

var upgrader = websocket.Upgrader{

    CheckOrigin: func(r *http.Request) bool {

        return true

    },

}

// Define our message object

type Message struct {

    Email    string `json:"email"`

    Username string `json:"username"`

    Message  string `json:"message"`

}

func main() {

    // Create a simple file server

    fs := http.FileServer(http.Dir("../public"))

    http.Handle("/", fs)

    // Configure websocket route

    http.HandleFunc("/ws", handleConnections)

    // Start listening for incoming chat messages

    go handleMessages()

    // Start the server on localhost port 8000 and log any errors

    log.Println("http server started on :8000")

    err := http.ListenAndServe(":8000", nil)

    if err != nil {

        log.Fatal("ListenAndServe: ", err)

    }

}

func handleConnections(w http.ResponseWriter, r *http.Request) {

    // Upgrade initial GET request to a websocket

    ws, err := upgrader.Upgrade(w, r, nil)

    if err != nil {

        log.Fatal(err)

    }

    // Make sure we close the connection when the function returns

    defer ws.Close()

    // Register our new client

    clients[ws] = true

    for {

        var msg Message

        // Read in a new message as JSON and map it to a Message object

        err := ws.ReadJSON(&msg)

        if err != nil {

            log.Printf("error: %v", err)

            delete(clients, ws)

            break

        }

        // Send the newly received message to the broadcast channel

        broadcast <- msg

    }

}

func handleMessages() {

    for {

        // Grab the next message from the broadcast channel

        msg := <-broadcast

        // Send it out to every client that is currently connected

        for client := range clients {

            err := client.WriteJSON(msg)

            if err != nil {

                log.Printf("error: %v", err)

                client.Close()

                delete(clients, client)

            }

        }

    }

}

客户端

new Vue({

    el: '#app',

    data: {

        ws: null, // Our websocket

        newMsg: '', // Holds new messages to be sent to the server

        chatContent: '', // A running list of chat messages displayed on the screen

        email: null, // Email address used for grabbing an avatar

        username: null, // Our username

        joined: false // True if email and username have been filled in

    },

    created: function() {

        var self = this;

        this.ws = new WebSocket('ws://' + window.location.host + '/ws');

        this.ws.addEventListener('message', function(e) {

            var msg = JSON.parse(e.data);

            self.chatContent += '<div class="chip">'

                    + '<img src="' + self.gravatarURL(msg.email) + '">' // Avatar

                    + msg.username

                + '</div>'

                + emojione.toImage(msg.message) + '<br/>'; // Parse emojis

            var element = document.getElementById('chat-messages');

            element.scrollTop = element.scrollHeight; // Auto scroll to the bottom

        });

    },

    methods: {

        send: function () {

            if (this.newMsg != '') {

                this.ws.send(

                    JSON.stringify({

                        email: this.email,

                        username: this.username,

                        message: $('<p>').html(this.newMsg).text() // Strip out html

                    }

                ));

                this.newMsg = ''; // Reset newMsg

            }

        },

        join: function () {

            if (!this.email) {

                Materialize.toast('You must enter an email', 2000);

                return

            }

            if (!this.username) {

                Materialize.toast('You must choose a username', 2000);

                return

            }

            this.email = $('<p>').html(this.email).text();

            this.username = $('<p>').html(this.username).text();

            this.joined = true;

        },

        gravatarURL: function(email) {

            return 'http://www.gravatar.com/avatar/' + CryptoJS.MD5(email);

        }

    }

});

总结

具体使用什么技术是需要根据使用场景进行选择的,在这里我为大家总结了http和websocket不同的使用场景,请大家参考。

HTTP :
检索资源(Retrieve Resource)

高度可缓存的资源(Highly Cacheable Resource)

幂等性和安全性(Idempotency and Safety)

错误方案(Error Scenarios)

websockt
快速响应时间(Fast Reaction Time)

持续更新(Ongoing Updates)

Ad-hoc消息传递(Ad-hoc Messaging)

错误的HTTP应用场景

依赖于客户端轮询服务,而不是由用户主动发起。

需要频繁的服务调用来发送小消息。

客户端需要快速响应对资源的更改,并且,无法预测更改何时发生。

错误的WebSockets应用场景

连接仅用于极少数事件或非常短的时间,客户端无需快速响应事件。

需要一次打开多个WebSockets到同一服务。

打开WebSocket,发送消息,然后关闭它 - 然后再重复该过程。

消息传递层中重新实现请求/响应模式。

以上内容都是我自己的一些感想,分享出来欢迎大家指正,顺便求一波关注,有问题的或者更好的想法的小伙伴可以评论私信我哦或者点击[Java分享交流群](https://jq.qq.com/?_wv=1027&k=5Y9m53y)一起来聊天哦在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一文让你看懂Golang如何打造实时聊天系统 的相关文章

  • 两行命令解决ubuntu22.04安装网易云音乐后点击图标无反应的问题

    感谢知乎用户 64 拉布 xff1a https zhuanlan zhihu com p 518108518 1 终端中输入以下命令 xff1a span class token builtin class name cd span op
  • 数据库MVCC多版本并发控制原理

    MVCC实现原理 频繁的加锁会带来什么问题 xff1f 读数据的时候没办法修改 修改数据的时候没办法读取 xff0c 极大的降低了数据库性能 数据库是如何解决加锁后的性能问题的 xff1f MVCC 多版本控制实现读取数据不用加锁 xff0
  • 学习笔记-----ButterKnife

    ButterKnife是一个专注于Android系统的View注入框架 ButterKnife bind this 一切findViewById Fragment Adapter中同样适用 xff0c ButterKnife bind th
  • 【Linux】vim 中批量添加注释

    本期主题 xff1a vim 中批量添加注释博客主页 xff1a 小峰同学分享小编的在Linux中学习到的知识和遇到的问题小编的能力有限 xff0c 出现错误希望大家不吝赐 此文主要介绍两种方法 xff1a 方法一 xff1a 块选择模式
  • Exported service does not require permission警告

    很久没写过应用了 xff0c 今天写一个Service时 xff0c 在manifest文件的 lt service gt 标签发现了这个警告 lt service android name 61 34 SendService 34 gt
  • Windows服务器高并发处理IOCP(完成端口)详细说明

    本系列里完成端口的代码在两年前就已经写好了 xff0c 但是由于许久没有写东西了 xff0c 不知该如何提笔 xff0c 所以这篇文档总是在酝酿之中 酝酿了两年之后 xff0c 终于决定开始动笔了 xff0c 但愿还不算晚 这篇文档我非常详
  • linux下如何测试端口通不通(四种方法)

    一般情况下使用 34 telnet ip port 34 判断端口通不通 接下来通过本文给大家分享四种方法测试端口通不通 xff0c 感兴趣的朋友一起学习吧 一般情况下使用 34 telnet ip port 34 判断端口通不通 xff0
  • 教你Vim编辑器,如何删除一行或者多行内容

    如何从Vim中删除行 xff1f 如何删除多行 xff1f 本文介绍在Vim编辑器中删除行的不同方法文内含长段代码可复制可往左滑 xff0c 希望对大家有帮助 xff01 安装Vim 在Ubuntu Debian中的安装方式 sudo ap
  • VBoxManage常用命令用法

    VBoxManage命令常用用法 系统环境 xff1a CentOS 7 3 x86 64 VirtualBox版本 xff1a 6 1 22 VirtualBox扩展版本 xff1a 6 1 22 增加一个新的扩展包 VBoxManage
  • Centos7安装Redis

    一 安装gcc依赖 由于 redis 是用 C 语言开发 xff0c 安装之前必先确认是否安装 gcc 环境 xff08 gcc v xff09 xff0c 如果没有安装 xff0c 执行以下命令进行安装 root 64 localhost
  • webRTC中的coturn服务安装

    目录 1 先准备一台云主机 2 安装coturn的依赖 2 1 依赖软件准备 2 1 安装依赖组件 2 2 安装coturn的持久化保存用户信息库 3 安装coturn 4 coturn配置 4 1 创建用户 4 2 配置说明 4 3 收集
  • Git配置.gitignore忽略文件

    git设置忽略文件和目录有两种方式 xff0c 一种是项目所有人员共用的的 xff0c 一种是开发自己使用的 第一种 xff0c 所有开发者共用的需要把设置设定在 gitignore该文件中 第二种 xff0c 开发者个人使用的忽略配置 x
  • 线程池实现原理

    创建线程有哪几种方式 一 继承Thread类创建线程类 xff08 1 xff09 定义Thread类的子类 xff0c 并重写该类的run方法 xff0c 该run方法的方法体就代表了线程要完成的任务 因此把run 方法称为执行体 xff
  • linux查看cpu与内存

    查看cpu 第一种方法 xff1a top命令法 1 首先执行top命令 xff1b 2 在top命令的显示界面 xff0c 按数据键1 xff0c 即可查看到当前系统中的总cpu数 xff1b 第二种方法 xff1a 通过proc文件系统
  • Element 布局组件el-row和el-col 详解

    1 背景 element的布局方式与bootstrap原理是一样的 xff0c 将网页划分成若干行 xff0c 然后每行等分为若干列 xff0c 基于这样的方式进行布局 xff0c 形象的成为栅栏布局 区别是element可将每行划分为24
  • netbeans中配置maven

    deploy 发布到远程maven库 本节默认maven库为nexus netbeans中按ctrl 43 1 xff0c 打开Project窗口 xff1b 在Project窗口中找到相关的project或module 在项目名上点击鼠标
  • 订阅关系一致

    订阅关系一致指的是同一个消费者Group ID下所有Consumer实例所订阅的Topic Tag必须完全一致 如果订阅关系不一致 消息消费的逻辑就会混乱 甚至导致消息丢失 本文提供订阅关系一致的正确示例代码以及订阅关系不一致的可能原因 帮
  • java之PO,VO,TO,QO,BO等

    PO persistant object 持久对象 在 o r 映射的时候出现的概念 xff0c 如果没有 o r 映射 xff0c 没有这个概念存在了 通常对应数据模型 数据库 xff0c 本身还有部分业务逻辑的处理 可以看成是与数据库中
  • 多生产者多消费者问题的无锁队列实现

    背景 代码根据论文 Implementing Lock Free Queues 复现 背景知识博客 xff1a 左耳朵耗子博客 https coolshell cn articles 8239 html 代码地址 xff1a https g
  • Ubuntu18下Github+Hexo搭建博客教程

    我的博客 xff0c 欢迎来访 xff1a www zxwsbg cn 搭建 安装git nodejs sudo apt get install git sudo apt get install nodejs sudo apt get in

随机推荐

  • linux中提供了PF_PACKET接口可以操作链路层的数据

    http blog sina com cn s blog 82f2fc28010132og html sock raw xff08 注意一定要在root下使用 xff09 原始套接字编程可以接收到本机网卡上的数据帧或者数据包 对于监听网络的
  • 分享52个Java源码,总有一款适合您

    Java源码 分享52个Java源码 xff0c 总有一款适合您 下面是文件的名字 xff0c 我放了一些图片 xff0c 文章里不是所有的图主要是放不下 xff0c 大家下载后可以看到 源码下载链接 xff1a https pan bai
  • 抽象类中的方法该如何实现呢?

    本节通过一个案例来学习如何实现抽象类中的方法 xff0c 具体步骤如下 xff1a 1 创建Animal类 创建一个Animal抽象类 xff0c 并在类中定义一个抽象call 方法 xff0c 如文件3 25所示 文件3 25Animal
  • zset类型的底层数据结构的实现

    参考资料 xff1a redis中zset底层实现原理 渣渣 CSDN博客 zset底层数据结构 redis的zset数据结构 xff1a 跳表 知乎 zset类型的底层数据结构的实现 xff1f zset是Redis提供的一个非常特别的数
  • XD软件都有哪些基础操作?

    下面我们来学习一下XD软件的基础操作 xff0c 包括资产面板的功能 交互动作 一键切图等等 1 重复网格 xff08 1 xff09 重复网格可智能复制其选择对象 xff0c 并批量更换图片 修改文字 之间距离等 xff08 2 xff0
  • 3分钟掌握7个XD基础操作

    下面我们来学习一下XD软件的基础操作 xff0c 包括资产面板的功能 交互动作 一键切图等等 1 重复网格 xff08 1 xff09 重复网格可智能复制其选择对象 xff0c 并批量更换图片 修改文字 之间距离等 xff08 2 xff0
  • 目标跟踪常用算法——EKF篇

    目录 1 扩展卡尔曼滤波算法 1 1 扩展卡尔曼滤波算法简单介绍 1 2 扩展卡尔曼滤波算法流程 1 3 扩展卡尔曼滤波算法仿真分析 2 参考文献 1 扩展卡尔曼滤波算法 1 1 扩展卡尔曼滤波算法简单介绍 对于非线性滤波问题 xff0c
  • 人工智能概述

    目录 什么是人工智能实现人工智能的方法逻辑编程机器学习深度学习机器学习和深度学习的区别 人工智能的分类如何实现人工智能 什么是人工智能 人工智能 又被称为机器智能 xff0c 是一种综合计算机科学 统计学 语言学等多种学科 xff0c 使机
  • java注解(annotation)的执行顺序

    可以在切面上使用 64 Order注解 如 64 Component 64 Aspect 64 Order 1 public class Aspect1 64 Component 64 Aspect 64 Order 2 public cl
  • Eclipse 常用快捷键 (动画讲解)

    Eclipse 常用快捷键 动画讲解 Eclipse有强大的编辑功能 xff0c 工欲善其事 xff0c 必先利其器 xff0c 掌握Eclipse快捷键 xff0c 可以大大提高工作效率 小坦克我花了一整天时间 xff0c 精选了一些常用
  • javaweb三大框架SSH

    1 MVC三层架构 xff1a 模型层 xff0c 控制层和视图层 模型层 xff0c 用Hibernate框架让来JavaBean在数据库生成表及关联 xff0c 通过对JavaBean的操作来 对数据库进行操作 xff1b 控制层 xf
  • HTTP请求方式及区别

    GET 向特定的路径资源发出请求 xff0c 数据暴露在url中 POST 向指定路径资源提交数据进行处理请求 xff08 一般用于上传表单或者文件 xff09 xff0c 数据包含在请求体中 OPTIONS 返回服务器针对特定资源所支持的
  • C++实现邮件群发的方法

    这篇文章主要介绍了C 43 43 实现邮件群发的方法 较为详细的分析了邮件发送的原理与C 43 43 相关实现技巧 非常具有实用价值 需要的朋友可以参考下 本文实例讲述了C 43 43 实现邮件群发的方法 分享给大家供大家参考 具体如下 x
  • Asp.Net Core IIS发布后PUT、DELETE请求错误405.0 - Method Not Allowed 因为使用了无效方法(HTTP 谓词)

    Asp Net Core IIS发布后PUT DELETE请求错误405 0 Method Not Allowed 因为使用了无效方法 HTTP 谓词 一 在使用Asp net WebAPI 或Asp Net Core WebAPI 时 x
  • Java lambda表达式使用笔记

    package com allsaints music admin import com allsaints music admin service entrymgr bak Student import lombok Data impor
  • .NET Framework 与 .NET Core 的区别与联系

    当今 net 生态系统如下 xff1a 从上面图中我们可以看到 net 主要分为三个部分 net FrameWork net Core Xamarin XAMARIN 主要用来构建APP的 xff08 包括IOS xff0c Android
  • .net 代码命名规范

    CAST 源代码命名规范手册 v1 1 Pascal 命名 xff1a 每一个单词首字母必须大写 Camel 命名 xff1a 第一个单词首字母小写 xff0c 其余单词首字母必须大写 任何命名必须优先使用英文单词表达意思 xff0c 若不
  • SpringBoot项目配置文件编写方式参考

    背景 为防止出现各环境配置文件不同步的情况 xff0c 现根据实际开发情况 xff0c 制定该配置文件编写参考 介绍 SpringBoot使用一个全局的配置文件 xff0c 配置文件名是固定的 xff1b application prope
  • xshell 连接 Linux kvm图形界面报错问题

    如下的报错 zyq 64 zyq sudo virt manager zyq 64 zyq virt manager 18561 Gtk WARNING cannot open display localhost 10 0 以上前提是需先安
  • 一文让你看懂Golang如何打造实时聊天系统

    项目截图 简介 在本次课程中 xff0c 我们来学习使用WebSocket来打造一个实时聊天系统 我们会从一下几个方面来进行学习 xff1a 什么是websocket xff1b Websocket与传统的HTTP协议有什么区别 xff1b