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

@(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程
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容