美文网首页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