基于Go goroutine实现一个简单的聊天服务

 更新时间:2023年06月08日 08:50:55   作者:架构精进之路  
对于聊天服务,想必大家都不会陌生,因为在我们的生活中经常会用到,本文我们用 Go 并发来实现一个聊天服务器,这个程序可以让一些用户通过服务器向其它所有用户广播文本消息,文中通过代码示例介绍的非常详细,需要的朋友可以参考下

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

我们用 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的实例,两个东西都是并发安全的。

到此这篇关于基于Go goroutine实现一个简单的聊天服务的文章就介绍到这了,更多相关Go goroutine 聊天服务内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go语言使用AES加密解密的示例代码

    Go语言使用AES加密解密的示例代码

    这篇文章主要介绍了Go语言使用AES加密解密的示例代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-09-09
  • Go语言单线程运行也会有的并发问题解析

    Go语言单线程运行也会有的并发问题解析

    这篇文章主要为大家介绍了Go语言单线程运行的并发问题解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • 深入浅析Go中三个点(...)用法

    深入浅析Go中三个点(...)用法

    这篇文章主要介绍了深入浅析Go中三个点(...)用法,需要的朋友可以参考下
    2021-10-10
  • golang开启mod后import报红的简单解决方案

    golang开启mod后import报红的简单解决方案

    这篇文章主要给大家介绍了关于golang开启mod后import报红的简单解决方案,文中通过图文将解决的办法介绍的非常详细,对大家的学习或者工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2024-01-01
  • 使用Gomock进行单元测试的方法示例

    使用Gomock进行单元测试的方法示例

    这篇文章主要介绍了使用Gomock进行单元测试的方法示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-11-11
  • GO中公平锁和非公平锁的具体使用

    GO中公平锁和非公平锁的具体使用

    公平锁和非公平锁是计算机科学中的两种锁机制,它们主要用于多线程编程,以控制对共享资源的访问,本文主要介绍了GO中公平锁和非公平锁的具体使用,感兴趣的可以了解一下
    2024-08-08
  • Golang利用Template模板动态生成文本

    Golang利用Template模板动态生成文本

    Go语言中的Go Template是一种用于生成文本输出的简单而强大的模板引擎,它提供了一种灵活的方式来生成各种格式的文本,下面我们就来看看具体如何使用Template实现动态文本生成吧
    2023-09-09
  • Go读取配置文件的方法总结

    Go读取配置文件的方法总结

    我们常见的配置文件的格式一般有:XML、JSON、INI、YAML、env和.properties,本文小编为大家整理了Go语言读取这些格式的配置文件的方法,希望对大家有所帮助
    2023-10-10
  • 深入探究Golang中log标准库的使用

    深入探究Golang中log标准库的使用

    Go 语言标准库中的 log 包设计简洁明了,易于上手,可以轻松记录程序运行时的信息、调试错误以及跟踪代码执行过程中的问题等。本文主要来深入探究 log 包的使用和原理,帮助读者更好地了解和掌握它
    2023-05-05
  • golang开发go包依赖管理godep使用教程

    golang开发go包依赖管理godep使用教程

    这篇文章主要为大家介绍了golang开发go包依赖管理godep使用教程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2021-11-11

最新评论