尝试用Go goroutine实现一个简单的聊天服务

news2024/11/30 2:39:49

hello,大家好,我是张张,「架构精进之路」公号作者。

a835336624f9da6462d48f67593a0102.jpeg

对于聊天服务,想必大家都不会陌生,因为在我们的生活中经常会用到。

我们用 Go 并发来实现一个聊天服务器,这个程序可以让一些用户通过服务器向其它所有用户广播文本消息。

这个程序中有四种 goroutine。
main 和 broadcaster 各自是一个 goroutine 实例,每一个客户端的连接都会有一个handleConn 和 clientWriter 的 goroutine。
broadcaster 是 select 用法的不错的样例,因为它需要处理三种不同类型的消息。

下面我们来演示的 main goroutine 的工作,是 listen 和 accept (网络编程里的概念)从客户端过来的连接。对每一个连接,程序都会建立一个新的 handleConn 的 goroutine。

func main() {
    listener, err := net.Listen("tcp", "localhost:8000")
    if err != nil {
        log.Fatal(err)
    }
    go broadcaster()
    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Print(err)
            continue
        }
        go handleConn(conn)
    }
}

然后是broadcaster的goroutine。

他的内部变量clients会记录当前建立连接的客户端集合,其记录的内容是每一个客户端的消息发出channel的“资格”信息。

type client chan<- string // an outgoing message channel


var (
    entering = make(chan client)
    leaving  = make(chan client)
    messages = make(chan string) // all incoming client messages
)


func broadcaster() {
    clients := make(map[client]bool) // all connected clients
    for {
        select {
        case msg := <-messages:
            // Broadcast incoming message to all
            // clients' outgoing message channels.
            for cli := range clients {
                cli <- msg
            }
        case cli := <-entering:
            clients[cli] = true


        case cli := <-leaving:
            delete(clients, cli)
            close(cli)
        }
    }
}

broadcaster监听来自全局的entering和leaving的channel来获知客户端的到来和离开事件。

当其接收到其中的一个事件时,会更新clients集合,当该事件是离开行为时,它会关闭客户端的消息发送channel。broadcaster也会监听全局的消息channel,所有的客户端都会向这个channel中发送消息。当broadcaster接收到什么消息时,就会将其广播至所有连接到服务端的客户端。

现在让我们看看每一个客户端的goroutine。

handleConn函数会为它的客户端创建一个消息发送channel并通过entering channel来通知客户端的到来。然后它会读取客户端发来的每一行文本,并通过全局的消息channel来将这些文本发送出去,并为每条消息带上发送者的前缀来标明消息身份。当客户端发送完毕后,handleConn会通过leaving这个channel来通知客户端的离开并关闭连接。

func handleConn(conn net.Conn) {
    ch := make(chan string) // outgoing client messages
    go clientWriter(conn, ch)


    who := conn.RemoteAddr().String()
    ch <- "You are " + who
    messages <- who + " has arrived"
    entering <- ch


    input := bufio.NewScanner(conn)
    for input.Scan() {
        messages <- who + ": " + input.Text()
    }
    // NOTE: ignoring potential errors from input.Err()


    leaving <- ch
    messages <- who + " has left"
    conn.Close()
}


func clientWriter(conn net.Conn, ch <-chan string) {
    for msg := range ch {
        fmt.Fprintln(conn, msg) // NOTE: ignoring network errors
    }
}

另外,handleConn为每一个客户端创建了一个clientWriter的goroutine,用来接收向客户端发送消息的channel中的广播消息,并将它们写入到客户端的网络连接。客户端的读取循环会在broadcaster接收到leaving通知并关闭了channel后终止。

下面演示的是当服务器有两个活动的客户端连接,并且在两个窗口中运行的情况,使用netcat来聊天:

$ go build gopl.io/ch8/chat
$ go build gopl.io/ch8/netcat3
$ ./chat &
$ ./netcat3
You are 127.0.0.1:64208               $ ./netcat3
127.0.0.1:64211 has arrived           You are 127.0.0.1:64211
Hi!
127.0.0.1:64208: Hi!                  127.0.0.1:64208: Hi!
                                      Hi yourself.
127.0.0.1:64211: Hi yourself.         127.0.0.1:64211: Hi yourself.
^C
                                      127.0.0.1:64208 has left
$ ./netcat3
You are 127.0.0.1:64216               127.0.0.1:64216 has arrived
                                      Welcome.
127.0.0.1:64211: Welcome.             127.0.0.1:64211: Welcome.
                                      ^C
127.0.0.1:64211 has left”

当与n个客户端保持聊天session时,这个程序会有2n+2个并发的goroutine,然而这个程序却并不需要显式的锁。clients这个map被限制在了一个独立的goroutine中,broadcaster,所以它不能被并发地访问。

多个goroutine共享的变量只有这些channel和net.Conn的实例,两个东西都是并发安全的。

cc9169c99af81f035e7c19d7b8ba9718.png

·END·

相关阅读:

专注架构技术研究,一起跨越职业瓶颈!

关注公众号,免费领学习资料

如果您觉得还不错,欢迎关注和转发~     

91d7a9cc8ca1ffe482653c20130d0ba1.png

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

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

相关文章

总有人问我 Cookie 是什么?

苏生不惑第432 篇原创文章&#xff0c;将本公众号设为星标&#xff0c;第一时间看最新文章。 之前分享过我写的微博批量下载工具2023 更新版&#xff1a;苏生不惑开发过的那些原创工具和脚本 &#xff0c;因为要输入自己账号的cookie&#xff0c;总有人问我cookie是什么?今天写…

FPGA 的数字信号处理:重写 FIR 逻辑以满足时序要求

在上一篇文章中&#xff08;FPGA 的数字信号处理&#xff1a;Verilog 实现简单的 FIR 滤波器&#xff09;演示了在 Verilog 中编写自定义 FIR 模块的初始demo。该项目在行为仿真中正常&#xff0c;但在布局和布线时未能满足时序要求。 所以今天的文章让我们来看看当设计不能满足…

Nacos集群Raft反序列化漏洞-修复

近日&#xff0c;奇安信CERT监测到 Nacos 集群Raft反序列化漏洞(QVD-2023-13065)&#xff0c;在Nacos集群处理部分Jraft请求时&#xff0c;攻击者可以无限制使用hessian进行反序列化利用&#xff0c;最终实现代码执行。鉴于该漏洞仅影响集群间通信端口 7848(默认配置下)&#x…

计算机网络管理-实验6-使用SNMPc开展网管活动

一、实验目的 全面学习SNMPc网络管理软件业务服务监控功能&#xff0c;了解如何使用网管软件从事网络管理工作 二、实验内容与设计思想 1&#xff09;操作映射数据库。 2&#xff09;查看管理对象的MIB数据。 3&#xff09;创建、保存长期统计数据&#xff08;要求一定时长…

spring事务隔离

在数据库中读取数据时&#xff0c;可能会遇到以下三个常见的问题&#xff1a;脏读&#xff08;Dirty Read&#xff09;、不可重复读&#xff08;Non-repeatable Read&#xff09;和幻读&#xff08;Phantom Read&#xff09;。 这些问题主要涉及并发事务的隔离性和一致性。 脏…

ChatGPT 4 的 6 个最佳使用场景

作者&#xff1a;SYDNEY BUTLER 译者&#xff1a;明明如月 无论是在 ChatGPT 中还是通过 API&#xff0c;对 OpenAI 的 GPT-4 模型的访问比 GPT-3.5 限制更多。这意味着你需要慎重考虑在何种情况下使用 GPT-4&#xff0c;并选择性地将最适合的任务交给它&#xff0c;以便让其发…

来薅羊毛!阿里云 AI 神器公测了

阿里云 AI 神器「通义听悟」上线了&#xff0c;宣称是身边的 AI 学习助手。这名字听着挺玄乎的&#xff0c;老逛也去试了一下&#xff0c;确实解决了之前遇到的很多问题。 01 视频转文字 老逛是小破站的资深用户&#xff0c;喜欢几个不错的 UP 主&#xff0c;比如老蒋&#xff…

sklearn中的roc_auc_score(二分类或多分类)

官方API地址&#xff1a; sklearn.metrics.roc_auc_score — scikit-learn 1.2.2 documentationExamples using sklearn.metrics.roc_auc_score: Release Highlights for scikit-learn 0.22 Release Highlights for scikit-learn 0.22 Probability Calibration curves Probabi…

AI创作与大语言模型:2023亚马逊云科技中国峰会引领企业应用新潮流

川川出品&#xff0c;必属精品。 文章目录 CodeWhispere免费的代码生成器安装教程使用自动编码 2023亚马逊云科技中国峰会最后总结 CodeWhispere免费的代码生成器 这里我介绍亚马逊云科技的一个产品&#xff0c;那就是Amazon codewhisperer。大家肯定对AI各种产品的火爆已经有…

F/S系统分分钟系统秒变BS/CS,但共享文件夹上的DBF访问掉了个坑

接VFP MIX ALL社群狐友求助&#xff0c;说IIS访问共享文件夹的DBF出错了&#xff1a; 猫猫复现了一下错误: 错误号1705 不能访问DBF表 这个问题估计还是会有很多狐友会遇到这个问题&#xff0c;那么我们就来解决一下吧. 在服务器上面建好共享文件夹 \\newserver\dbf 里面放一个…

开源项目|EasyOCR一款实用的图片OCR文字识别项目

欢迎关注「全栈工程师修炼指南」公众号 点击 &#x1f447; 下方卡片 即可关注我哟! 设为「星标⭐」每天带你 基础入门 到 进阶实践 再到 放弃学习&#xff01; “ 花开堪折直须折&#xff0c;莫待无花空折枝。 ” 作者主页&#xff1a;[ https://www.weiyigeek.top ] 博客&…

一级建造师执业资格考试--工程经济--速学36记--联想法

第一记&#xff1a;利息的计算 第二记&#xff1a;等值计算 第三记&#xff1a;名义利率与有效利率 第四记&#xff1a;经济效果评价指标体系 第五记&#xff1a;静态投资回收期分析 第六记&#xff1a;静态投资回收期分析 第七记&#xff1a;预付款 第八记&#xff1a;施工索赔…

2023年深圳某互联网公司前端开发初级岗笔试真题(含解析和源码)

&#x1f4da;关于该专栏: 该专栏的发布内容是前端面试中笔试部分真题、答卷类、机试等等的题目&#xff0c;题目类型包括逻辑题、算法题、选择题、问答题等等&#xff0c;除了内容的分享&#xff0c;还有解析和答案。真实来自某些互联网公司&#xff0c;坐标广东广州、深圳。 …

如何使用Python Flask和MySQL创建管理用户的REST API

部分数据来源&#xff1a;ChatGPT 引言 在现代化的应用开发中&#xff0c;数据库是一个非常重要的组成部分。关系型数据库&#xff08;例如&#xff1a;MySQL、PostgreSQL&#xff09;在这方面尤其是很流行。Flask是一个Python的web框架&#xff0c;非常适合实现REST API。在…

NLP学习笔记六-lstm模型

NLP学习笔记六-lstm模型 上一篇我们讲的是simple RNN模型&#xff0c;那么其实lstm模型更像是simple RNN模型的改进或者变种。 对于lstm模型&#xff0c;我们先看下面一张图&#xff1a; 其实lstm模型的思想是建立在simple RNN模型上的&#xff0c;但是要更加贴近于显示&…

内网安全:内网渗透.(拿到内网主机最高权限 vulntarget 靶场 A)

内网安全&#xff1a;内网渗透.&#xff08;拿到内网主机最高权限&#xff09; 内网穿透又被称为NAT穿透&#xff0c;内网端口映射外网&#xff0c;在处于使用了NAT设备的私有TCP/IP网络中的主机之间建立连接的问题。通过映射端口&#xff0c;让外网的电脑找到处于内网的电脑。…

数据分析第19课pyecharts布局(基础图形绘制)

官网:https://pyecharts.org/#/zh-cn/global_options?id=legendopts%ef%bc%9a%e5%9b%be%e4%be%8b%e9%85%8d%e7%bd%ae%e9%a1%b9 不想每个属性方法的看,可以直接看gallery 官网的数据都是静态的,如果要做数据实时更新的,即做前后端结合时,会用到Vue框架,与后端连接,实现动…

Nacos架构与原理 - CAP一致性协议 ( Raft Distro)

文章目录 为什么 Nacos 需要⼀致性协议为什么 Nacos 选择了 Raft 以及 Distro从服务注册发现来看从配置管理来看为什么是 Raft 和 Distro &#xff1f;Raft (CP模式)Distro &#xff08;AP模式&#xff09; Nacos ⼀致性协议的演进早期的 Nacos ⼀致性协议当前 Nacos 的⼀致性协…

[Python图像处理] 基于离散余弦变换的安全扩频数字水印

基于离散余弦变换的安全扩频数字水印 数字水印基于离散余弦变换的安全扩频数字水印实现安全扩频数字水印相关链接 数字水印 数字水印是可见的或不可见的标识码&#xff0c;这种标识码被永久嵌入图像中&#xff0c;并且即使在解码过后后仍存在于图像中。为了保证有效性&#xf…

Jetpack Compose 中在屏幕间共享数据的 5 种方案

1. 路由传参 Jetpack Compose 中路由传参的方式有很多种&#xff0c;具体可以参考 Jetpack Compose 中的导航路由 以下是最简单的路由传参测试代码&#xff1a; import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.…