一、概述

实现一个网络聊天室(群),

功能分析:

  1. 上线下线
  2. 聊天,其他人、自己都可以看到聊天消息
  3. 查询当前聊天室用户名字 who
  4. 可以修改自己名字rename|Duke
  5. 超时踢出

效果展示:

技术点分析:

  1. sockt tcp编程

  2. map结构,

    1. 存储所有的用户
    2. map遍历
    3. map删除
  3. go程、channel

  4. select(超时退出,主动退出)

  5. timer 定时器

二、实现基础

第一阶段:

  1. 思路分析

1.tcp socket,建立多个连接

package mainimport ("fmt""net"
)//将所有的代码写在一个文件中,不做代码整理func main() {//创建服务器listener, err := net.Listen("tcp", ":8080")if err != nil {fmt.Println("net.Listen err:", err)return}fmt.Println("服务器启动成功!")for {fmt.Println("=====> 主go程监听中...")//监听conn, err := listener.Accept()if err != nil {fmt.Println("listener.Accept err:", err)return}//建立连接fmt.Println("建立连接成功!")//启动处理业务的go程go handler(conn)}
}//处理具体业务
func handler(conn net.Conn) {for {fmt.Println("启动业务...")//TODO  //代码这里以后再具体实现,当前保留buf := make([]byte, 1024)//读取客户端发送过来的请求数据cnt, err := conn.Read(buf)if err != nil {fmt.Println("conn.Read err:", err)return}fmt.Println("服务器接收客户端发送过来的数据为: ", string(buf[:cnt-1]), ", cnt:", cnt)}
}

go run chatroom.go

启动nc

2.分析

数据流向:

3. 定义User/map结构

type User struct {//名字name string//唯一的idid string//管道msg chan string
}//创建一个全局的map结构,用户保存所有的用户
var allUsers = make(map[string]User)

在handler中调用(建立连接之后)

4. 定义message通道

创建监听广播go程函数

启动,全局唯一:

写入上线数据:

bug修复:

效果:

5. user监听通道go程

每个用户应该还有一个用来监听自己msg管道的go程,负责将数据返回给客户端


//每个用户应该还有一个用来监听自己msg管道的go程,负责将数据返回给客户端
func writeBackToClient(user *User, conn net.Conn) {fmt.Printf("user : %s 的go程正在监听自己的msg管道:\n", user.name)for data := range user.msg {fmt.Printf("user : %s 写回给客户端的数据为:%s\n", user.name, data)//Write(b []byte) (n int, err error)_, _ = conn.Write([]byte(data))}
}

效果:

当前整体代码:

package mainimport ("fmt""net"
)//将所有的代码写在一个文件中,不做代码整理type User struct {//名字name string//唯一的idid string//管道msg chan string
}//创建一个全局的map结构,用户保存所有的用户
var allUsers = make(map[string]User)//定义一个message全局通道,用于接收任何人发送过来消息
var message = make(chan string, 10)func main() {//创建服务器listener, err := net.Listen("tcp", ":8080")if err != nil {fmt.Println("net.Listen err:", err)return}//启动全局唯一的go程,负责监听message通道,写给所有的用户go broadcast()fmt.Println("服务器启动成功!")for {fmt.Println("=====> 主go程监听中...")//监听conn, err := listener.Accept()if err != nil {fmt.Println("listener.Accept err:", err)return}//建立连接fmt.Println("建立连接成功!")//启动处理业务的go程go handler(conn)}
}//处理具体业务
func handler(conn net.Conn) {fmt.Println("启动业务...")//客户端与服务器建立连接的时候,会有ip和port ==> 当成user的idclientAddr := conn.RemoteAddr().String()fmt.Println("clientAddr:", clientAddr)//创建usernewUser := User{id:   clientAddr,            //id,我们不会修改,这个作为在map中的keyname: clientAddr,            //可以修改,会提供rename命令修改,建立连接时,初始值与id相同msg:  make(chan string, 10), //注意需要make空间,否则无法写入数据}//添加user到map结构allUsers[newUser.id] = newUser//启动go程,负责将msg信息返回给客户端go writeBackToClient(&newUser, conn)//向message写入数据, 当前用户上线的消息,用于通知所有人(广播)loginInfo := fmt.Sprintf("[%s]:[%s] ===> 上线了login!!", newUser.id, newUser.name)message <- loginInfofor {//具体业务逻辑buf := make([]byte, 1024)//读取客户端发送过来的请求数据cnt, err := conn.Read(buf)if err != nil {fmt.Println("conn.Read err:", err)return}fmt.Println("服务器接收客户端发送过来的数据为: ", string(buf[:cnt-1]), ", cnt:", cnt)}
}//向所有的用户广播消息,启动一个全局唯一go程
func broadcast() {fmt.Println("广播go程启动成功...")defer fmt.Println("broadcast 程序退出!")for {//1. 从message中读取数据fmt.Println("broadcast监听message中...")info := <-messagefmt.Println("message 接收到消息:", info)//2. 将数据写入到每一个用户的msg管道中for _, user := range allUsers {//如果msg是非缓冲的,那么会在这里阻塞user.msg <- info}}
}//每个用户应该还有一个用来监听自己msg管道的go程,负责将数据返回给客户端
func writeBackToClient(user *User, conn net.Conn) {fmt.Printf("user : %s 的go程正在监听自己的msg管道:\n", user.name)for data := range user.msg {fmt.Printf("user : %s 写回给客户端的数据为:%s\n", user.name, data)//Write(b []byte) (n int, err error)_, _ = conn.Write([]byte(data))}
}

三、增加功能

1. 查询用户

查询命令:who ==》 将当前所有的登陆的用户,展示出来, id,name , 返回给当前用户

在handler中,处理业务逻辑:

     fmt.Println("服务器接收客户端发送过来的数据为: ", string(buf[:cnt-1]), ", cnt:", cnt)//-------- 业务逻辑处理  开始----------//1. 查询当前所有的用户 who//    a. 先判断接收的数据是不是who  ==> 长度&&字符串userInput := string(buf[:cnt-1]) //这是用户输入的数据,最后一个是回车,我们去掉它if len(userInput) == 3 && userInput == "who" {//  b. 遍历allUsers这个map (key : userid  value: user本身),将id和name拼接成一个字符串,返回给客户端fmt.Println("用户即将查询所有用户信息!")//这个切片包含所有的用户信息var userInfos []string//[]string{"userid:z3, username:z3", "userid:l4, username:l4","userid:w5, username:w5"}for _, user := range allUsers {userInfo := fmt.Sprintf("userid:%s, username:%s", user.id, user.name)userInfos = append(userInfos, userInfo)}//最终写到管道中,一定是一个字符串r := strings.Join(userInfos, "\n") //连接数字切片,生成字符串//strings.Split() //分割字符串//`// "userid:z3, username:z3"//    "userid:l4, username:l4"//    "userid:w5, username:w5"//`//将数据返回给查询的客户端newUser.msg <- r} else {//如果用户输入的不是命令,只是普通的聊天信息,那么只需要写到广播通道中即可,由其他的go程进行常规转发message <- userInput}//-------- 业务逻辑处理  结束----------

2. 重命名

规则: rename|Duke

  1. 读取数据判断长度7,判断字符是rename
  2. 使用|进行分割,获取|后面的部分,作为名字
  3. 更新用户名字newUser.name = Duke
  4. 通知客户端,更新成功
else if len(userInput) > 9 && userInput[:7] == "\\rename" {//[:3]  // 0, 1, 2  ==> 左闭右开//    规则:  rename|Duke//1. 读取数据判断长度7,判断字符是rename//2. 使用|进行分割,获取|后面的部分,作为名字//func Split(s, sep string) []string//arry := strings.Split(userInput, "|")//name := arry[1]//3. 更新用户名字newUser.name = DukenewUser.name = strings.Split(userInput, "|")[1]allUsers[newUser.id] = newUser //更新map中的user//4. 通知客户端,更新成功newUser.msg <- "rename successfully!"} else {//如果用户输入的不是命令,只是普通的聊天信息,那么只需要写到广播通道中即可,由其他的go程进行常规转发message <- userInput}

效果:

3. 主动退出

两种形式:==》 quit,ctrl+c

用户退出:清理工作

  1. 从map中删除
  2. 对应的conn要close

每个用户都有自己的watch go程,负责监听退出信号

func watch(user *User, conn net.Conn, isQuit <-chan bool) {fmt.Println("222222 启动监听退出信号的go程....")defer fmt.Println("watch go程退出!")for {select {case <-isQuit:logoutInfo := fmt.Sprintf("%s exit already!", user.name)fmt.Println("删除当前用户:", user.name)delete(allUsers, user.id)message <- logoutInfoconn.Close()return}}}

在handler中启动go watch,同时传入相应信息:

在read之后,通过读取的cnt判断用户退出,向isQuit中写入信号:

测试结果:

4. 超时退出

使用定时器来进行超时管理

如果60s每没有发送任何数据,那么直接将这个连接关闭掉:

<- time.After(60 *time.second)   //chan time
更新watch函数//i,j int
//启动一个go程,负责监听退出信号,触发后,进行清零工作: delete map, close conn都在这里处理
func watch(user *User, conn net.Conn, isQuit, restTimer <-chan bool) {fmt.Println("222222 启动监听退出信号的go程....")defer fmt.Println("watch go程退出!")for {select {case <-isQuit:logoutInfo := fmt.Sprintf("%s exit already!\n", user.name)fmt.Println("删除当前用户:", user.name)delete(allUsers, user.id)message <- logoutInfoconn.Close()returncase <-time.After(5 * time.Second):logoutInfo := fmt.Sprintf("%s timeout exit already!\n", user.name)fmt.Println("删除当前用户:", user.name)delete(allUsers, user.id)message <- logoutInfoconn.Close()returncase <-restTimer:fmt.Printf("连接%s 重置计数器!\n", user.name)}}
}

创建并传入restTimer管道,

效果:

5. map

package mainimport ("fmt""sync""time"
)var idnames = make(map[int]string)
var lock sync.RWMutex//map不允许同时读写,如果有不同go程同时操作map,需要对map上锁
// concurrent map read and map write
func main() {go func() {for {fmt.Println("111111")lock.Lock()fmt.Println("2222222")idnames[0] = "duke"fmt.Println("3333333")lock.Unlock()}}()go func() {for {fmt.Println("aaaaaaa")lock.Lock()fmt.Println("bbbbbb")name := idnames[0]fmt.Println("name :", name)lock.Unlock()}}()for {fmt.Println("OVER")time.Sleep(1 * time.Second)}
}

go语言微服务项目,基础篇--go4-聊天室相关推荐

  1. go语言微服务项目,高级篇--03go-mirco框架-gin框架-mvc-REST-Session

    go-Micro 框架 创建 micro 服务 命令:micro new --type srv test66 框架默认自带服务发现:mdns. 使用consul服务发现: 1. 初始consul服务发 ...

  2. 微服务架构·基础篇,傻瓜看了都会

    转自:cyfonly 看到最近"微服务架构"这个概念这么火,作为一个积极上进的程序猿,成小胖忍不住想要学习学习.而架构师老王(不是隔壁老王)最近刚好在做公司基础服务的微服务化研究和 ...

  3. 成小胖学习微服务架构·基础篇

    看到最近"微服务架构"这个概念这么火,作为一个积极上进的程序猿,成小胖忍不住想要学习学习.而架构师老王(不是隔壁老王)最近刚好在做公司基础服务的微服务化研究和落地,对此深有研究. ...

  4. 谷粒商城微服务分布式基础篇二—— Spring Cloud Alibaba、Nacos注册与发现

    文章目录 Spring Cloud Alibaba--微服务开发 Spring Cloud Alibaba是什么?  主要功能  组件 为什么不使用Spring Cloud 使用 Nacos Disc ...

  5. go语言微服务项目,基础篇--go2-高级语法

    一.基础语法补充 1. switch package mainimport ("fmt""os" )//从命令输入参数,在switch中进行处理func mai ...

  6. go语言微服务项目,基础篇--02-shell

    一.输入输出 echo 回声,用于打印输出工作 -n: 可以不换行 -e:对字符进行转义,\t ===> enable printf printf "%d,%s\n" 10 ...

  7. java微服务开发(基础环境篇)

    java微服务开发(基础环境篇) 我们的目标是~~_浩瀚的宇宙 _~~全栈开发 俗话说的好 _工欲善其事 必先利其器 _对于一个开发者来说 一个好的开发环境可以带来的收益是巨大的 本篇的重点主要是li ...

  8. Docker学习篇——使用Docker部署账单微服务项目

    Docker概念 Docker 是一个开源的应用容器引擎 诞生于 2013 年初,基于 Go 语言实现, dotCloud 公司出品(后改名为Docker Inc) Docker 可以让开发者打包他们 ...

  9. .Net Core 商城微服务项目系列(一):使用IdentityServer4构建基础登录验证

    这里第一次搭建,所以IdentityServer端比较简单,后期再进行完善. 1.新建API项目MI.Service.Identity,NuGet引用IdentityServer4,添加类InMemo ...

最新文章

  1. POJ1611基础带权并查集
  2. Spring boot的properties文件编码设置
  3. 提高速度 - MyEclipse配置
  4. Java中实现定时任务的3种方法!
  5. DLL程序组件Microsoft Reporting Services Barcode Custom Report Item
  6. rocketmq新扩容的broker没有tps_揭秘 RocketMQ 新特性以及在金融场景下的实践
  7. 通过键盘事件执行查询与回填数据
  8. wsdl2java生成服务端_根据wsdl,apache cxf的wsdl2java工具生成客户端、服务端代码
  9. 固体火箭发动机三维装药逆向内弹道计算
  10. 自己写微信小程序MQTT模拟器
  11. 美通社:2018年全球企业品牌影响力调查报告
  12. HTML基本知识学习 2021-04-08
  13. windows防火墙是干什么的_我可以用windows防火墙做什么
  14. STM32单片机串口发送ASCII码
  15. 实战案例汇总,Java架构师实战视频教程
  16. 微信小程序简单实现两列瀑布流布局页面
  17. mysql中ddl和ddm_对话DDM:分布式数据库中间件全解析
  18. 计算机网络基础知识 学习笔记
  19. 陶瓷电容的ESR-谐振频率去哪儿查?
  20. win10系统下GTX1080TI显卡配置tensorflow运行环境(都是泪系列)

热门文章

  1. java-php-python-ssm校园疫情防控管理系统计算机毕业设计
  2. B/S三层架构[转载]
  3. [工具使用]Wireshark
  4. 万亿平安,如何继续高速增长?
  5. 基于openCV的形状模版匹配
  6. 千万级巨型汉语词库分享
  7. c语言中的英语缩写,英文短信缩写:超实用总结(C开头)
  8. 在Linux服务器运行jar包并一直处于后台执行
  9. 出现Presentation Error的解决方法
  10. [转] 明年,你还爱我吗?