美文网首页GO语言系列
GO语言初级学习之代码案例13 (QQ群聊)

GO语言初级学习之代码案例13 (QQ群聊)

作者: 大爬虫Shalom | 来源:发表于2018-09-07 12:34 被阅读45次

    @(go语言 黑马)[GO语言]

    并发聊天室

    • 题目:利用Go语言高并发的特性,编写一个类似QQ群聊功能的并发聊天服务器
    • 主要知识点:TCP通信,channel的使用
    • 逻辑思路(详细的步骤在代码注释中):

    _1. 建立TCP通信连接,并且循环监听,成功连接一个客户端就启动一个go程
    _2. 所有消息都会由 message 通道发给用户自带的 channel ,最终由 WriteMsgToClient 方法发送到客户端

    • 要想试试该服务器的效果,需要安装客户端:这里下载,然后命令行输入命令:nc 127.0.0.1 8001 ,即可连接该服务器;每个cmd终端相当于一个客户端,可以多开几个客户端模拟群聊

    服务器代码如下:

    package main
    
    //并发聊天服务器
    
    import (
        "net"
        "fmt"
        "strings"
        "time"
    )
    
    type Client struct {
        //用户有三个属性:用户名,用户地址,C
        C    chan string // 该通道是用来接收需要发送给客户端的数据
        Name string      // 用户名
        Addr string      // 客户端地址
    }
    
    //  存储在线用户的键值数据库,用map模拟
    var onlin_client_Map = make(map[string]Client)
    
    //  用于广播消息给用户的通道
    var message = make(chan string)
    
    //  广播消息给所有在线用户的方法
    func Messager() {
        for {
            //  循环监听读取message中的数据
            msg := <-message
    
            //  遍历所有在线的用户,再将消息写入用户用于接收消息的通道中
            for _, cli := range onlin_client_Map {
                cli.C <- msg
            }
        }
    }
    
    //  用户将自己通道的消息写给用户客户端的方法
    func WriteMsgToClient(cli Client, conn net.Conn) {
        for msg := range cli.C { // 遍历出通道中的信息
            conn.Write([]byte(msg + "\n")) //   利用通信socket,将信息传输给客户端
        }
    }
    
    //  消息生产方法
    func MakeMessage(cli Client, msg string) string {
        str := "[" + cli.Addr + "] " + cli.Name + ": " + msg
        return str
    }
    
    //  这是程序的核心,上线提醒、发送消息、更换用户名、查看所有在线用户列表 的功能都在这个函数实现
    func HandleConnect(conn net.Conn) {
        defer conn.Close() //不要忘记关闭
    
        //  获取 客户端 地址
        cli_addr := conn.RemoteAddr().String() //“.String()” 作用是:转成string类型
        //  初始化新用户
        cli := Client{make(chan string), cli_addr, cli_addr}
    
        //  添加新上线用户到Map中
        onlin_client_Map[cli_addr] = cli
    
        //  往全局通道中写入 登录信息
        message <- MakeMessage(cli, "login")
    
        //  用户将信息发送到客户端的go程
        go WriteMsgToClient(cli, conn)
    
        //  这两个通道是用来控制客户端的在线时间的
        isQuit := make(chan bool)
        hasData := make(chan bool)
    
        go func() {
            buf := make([]byte, 4096)
    
            for {
                n, err := conn.Read(buf) // 读取客户端发来的信息
                if n == 0 {
                    fmt.Printf("客户端%s断开\n", cli.Name)
                    isQuit <- true
                    return
                }
                if err != nil {
                    fmt.Println("conn.Read err:", err)
                    return
                }
                msg := string(buf[:n-1]) //去掉空格行
    
                if msg == "who" && len(msg) == 3 { //   查看在线用户列表
                    //  不需要广播,所以不用message,直接往conn中写数据
                    conn.Write([]byte("user list:\n"))
                    for _, cli := range onlin_client_Map { //   遍历map中所有在线客户,将信息组织好后再发送
                        msg = cli.Addr + ":" + cli.Name + "\n" //   这里没有使用MakeMessage,直接自己生成
                        conn.Write([]byte(msg))
                    }
                } else if len(msg) >= 8 && msg[:7] == "rename|" { //    更换用户名功能
    
                    cli.Name = strings.Split(msg, "|")[1]
                    onlin_client_Map[cli_addr] = cli
                    conn.Write([]byte("rename success!\n"))
                } else {
                    message <- MakeMessage(cli, msg)
                }
                hasData <- true
            }
        }()
        for {
            select {
            case <-isQuit:
                delete(onlin_client_Map, cli.Addr)
                message <- MakeMessage(cli, "log out")
            case <-hasData:
    
            case <-time.After(time.Second * 30)://  如果30s不发言,系统将强制将用户退出
                delete(onlin_client_Map, cli.Addr)
                message <- MakeMessage(cli, "time out leave")
                return
            }
        }
    }
    func main() {
        //创建与客户端的连接地址
        listener, err := net.Listen("tcp", "127.0.0.1:8001") //利用的是tcp通信
        if err != nil {
            fmt.Println("net.Listen err:", err)
            return
        }
        defer listener.Close()
    
        //  启动go程 广播方法,等待接收数据,并广播   到每个客户
        go Messager()
    
        //  循环监听客户端连接
        for {
            conn, err := listener.Accept()
            if err != nil {
                fmt.Println("Accept err:", err)
                return
            }
            go HandleConnect(conn) //收到一个客户连接,启动一个go程
        }
    }
    

    相关文章

      网友评论

        本文标题:GO语言初级学习之代码案例13 (QQ群聊)

        本文链接:https://www.haomeiwen.com/subject/qsqcgftx.html