Go项目实战:01-聊天室

news2024/11/25 4:51:17

实现一个聊天室(群):

功能分析:

  • 1、上线下线
  • 2、聊天:其他人和自己都可以看到聊天消息
  • 3、查询当前的聊天室用户所有人的名字
  • 4、可以修改自己的名字
  • 5、超时潜水踢出机制

技术点分析:

  • 1、socket tcp编程
  • 2、map结构,存储所有的用户
    • 1、存储所有的用户
    • 2、map遍历
    • 3、map删除
  • 3、go 程、channel
  • 4、select (超时退出、主动退出)
  • 5、timer 定时器

效果展示

请添加图片描述

具体实现

1、tcp socket,建立多个连接

package main

import (
	"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)
	}
}

2、思路分析

思路分析图

请添加图片描述

数据流分析

请添加图片描述

3、定义User/map结构

type User struct {
	//名字
	name string
	//唯一的id
	id string
	//管道
	msg chan string
}

//创建一个全局的map结构,用户保存所有的用户
var allUsers = make(map[string]User)

在handler中调用

请添加图片描述

4、定义message通道

请添加图片描述

创建监听广播go程函数
请添加图片描述

启动:读全局唯一
请添加图片描述

写入上线数据
请添加图片描述

bug修复
请添加图片描述
效果:
请添加图片描述

5、user监听通道go程

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

func writeBackToClient(user *User, conn net.Conn){
	//TODO
	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 main

import (
	"fmt"
	"net"
)

//课程将所有代码写在一个文件中,没有做代码整理了

type User struct {
	//名字
	name string
	//唯一的id
	id 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("启动业务...")

		clientAddr := conn.RemoteAddr().String()
		fmt.Println("clientAddr:",clientAddr)
		//创建user
		newUser := User{
			id: clientAddr, //id,我们不会修改。这个作为map中的key
			name: 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 <- loginInfo

		for {
		//具体业务逻辑
		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 := <- message
		 //2、将数据写入到每一个用户的msg管道中
		 for _, user := range allUsers{
			 //如果msg是非缓冲的,将会在这里阻塞
			 user.msg <- info
		 }
	 }
}
//每个用户应该还有一个用来监听自己msg管道的go程,负责将数据返回给客户端
func writeBackToClient(user *User, conn net.Conn){
	//TODO
	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 := user   id value:user本身,将id和name拼接程一个字符串,返回给客户端
				fmt.Println("用户即将查询所有用户信息!")

				//这个切片包含所有的用户信息
				var userInfotable []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)
					userInfotable = append(userInfotable, userInfo)
				}

				//最终写道管道中,一定是一个字符串,现在相当于是n个字符串
				r := strings.Join(userInfotable,"\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、读取数据判断长度是否>8,并且判断字符是否为rename
  • 2、使用|进行分割,获取|后面的部分,作为名字
  • 3、update 用户名字 newUser = Duike
  • 4、通知客户端,更新成功
else if len(userInput) > 9 && userInput[:7] == "\\rename" {
				//[:3] //0, 1, 2 ==> 左闭右开


				//规则: rename|Duke
				//- 1、读取数据判断长度是否>8,并且判断字符是否为rename

			    //- 2、使用|进行分割,获取|后面的部分,作为名字

				//arry := strings.Split(userInput,"|")
			    //name := array[1]

				//- 3、update 用户名字 newUser = Duke
				newUser.name = strings.Split(userInput,"|")[1]
				allUsers[newUser.id] = newUser //更新为map中的user
			    //- 4、通知客户端,更新成功
				newUser.msg <- "rename successfuly!"

			} 

效果:
请添加图片描述

3、主动退出

quit && contro + c

用户退出:清理工作
1、从map中删除
2、对应的conn要close

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

//启动一个go程只负责用来监听退出信号isQuit,触发后,进行清零工作:delete map,close conn
func watch(user *User, conn net.Conn, isQuit <-chan bool) {
	fmt.Println("2222启动监听退出信号的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 <-logoutInfo
			conn.Close()
			return
		}
	}
}

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

		//启动go程负责监听退出信号
		go watch(&newUser, conn, isQuit)

请添加图片描述

在read之后,通过读取的cnt判断用户退出,向isQuit中写入信号:
请添加图片描述

测试结果:
请添加图片描述

4、超时退出

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

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

<- time.After(60 *time.second) //chan time
func watch(user *User, conn net.Conn, isQuit, restTimer <- chan bool) {
	//func watch(user *User, conn net.Conn, isQuit <- chan bool, restTimer <- chan bool)   |   i, j int
	fmt.Println("2222启动监听退出信号的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 <-logoutInfo//消息告诉所有人

			conn.Close()
			return
		case <- time.After(10 *  time.Second):
			logoutInfo := fmt.Sprintf("%s timeout exit already\n", user.name)
			fmt.Println("删除当前用户",user.name)
			delete(allUsers, user.id)
			message <-logoutInfo//消息告诉所有人

			conn.Close()
			return
		case <- restTimer:
			fmt.Sprintf("连接%s,重置计数器\n", user.name)
		}
	}
}

创建并传入restTimer管道
请添加图片描述
请添加图片描述
效果:
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/90706.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ADI Blackfin DSP处理器-BF533的开发详解51:图像处理专题-CMOS摄像头采集图像(含源码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 功能介绍 板卡上设计了一个摄像头接口&#xff0c;可以连接与板卡匹配的 ADSP-EDU-CMOS 子卡板。摄像头接口采用 20PIN 插针接入&#xff0c;将插…

基于android的二手车交易系统app-计算机毕业设计

项目介绍 该二手车信息软件采用JSP Tomcat MysqlAndroid开发环境开发&#xff0c;是一款展示二手车信息&#xff0c;并提供卖方联系方式&#xff0c;让买家更方便地找到合适的二手车主的软件&#xff0c;系统分为前端和后端&#xff0c;前端采用了安卓技术开发&#xff0c;在…

jfinal整合IJPay paypal支付

这里写目录标题环境准备1.注册paypal账户2.登录开发者中心3.创建沙箱账户4.在沙箱模式创建一个app&#xff0c;平台默认创建好了一个4.1填写信息4.2绑定某个沙箱账号4.3Client ID及Secret支付代码1.pom2.实例化配置3.支付订单3.1用sandbox的personal的账号登录&#xff0c;并支…

你说精通MySQL其实很菜jī(1):你不一定会的基本技巧或知识点(值得一看)

你说精通MySQL其实很菜jī&#xff08;1&#xff09;&#xff1a;你不一定会的基本技巧或知识点&#xff08;值得一看&#xff09;《你说精通MySQL其实很菜jī》系列文章&#xff08;持续更新&#xff09;一、前言二、技巧或知识点汇总1、MySQL客户端&#xff08;Client&#x…

HID设备的报告描述符

HID设备类定义文档中明确指出&#xff0c;一个报告描述符必须包含但不仅限于以下数据项&#xff1a; 输入&#xff08;输出或特征&#xff09; 指明了报告的类型&#xff0c;其中隐含了报告的传输方向以及报告数据所具有的数学特性。 用法&#xff08;也可用“用法最小值与最大…

发现oracle10gSYSAUX空间没有了进行处理

今天用户反馈sap有些因为有问题&#xff0c;所以寻找一下原因&#xff1a; 1、用db02看空间情况 看这里好像空间是没有了&#xff0c;99%使用掉了 2、用toad看空间也是类型情况&#xff0c;所以确实这个表空间没有了 3、检查SYSAUX项目空间占比 SELECT occupant_name"…

Spring Boot 3.x微服务升级经历

前言 Spring Boot 3.0.0 GA版已经发布&#xff0c;好多人也开始尝试升级&#xff0c;有人测试升级后&#xff0c;启动速度确实快了不少&#xff0c;如下为网络截图&#xff0c;于是我也按捺不住的想尝试下。 历程 首先就是要把Spring Boot、Spring Cloud 相关的依赖升一下 …

用Python画一棵分形树

文章目录画一棵分形树加入随机量的分形树加入点缀的圣诞树画一棵分形树 分形树&#xff0c;就是用分形的逻辑去画一棵树&#xff0c;所谓分形&#xff0c;若从编程的角度去理解&#xff0c;其实就是简单规则的反复迭代。 例如&#xff0c;现在有一条线段&#xff0c;长为LLL&…

【微服务远程调用】基于RestTemplate发送HTTP请求实现微服务远程调用

本期目录1. 情景2. 远程调用方式分析3. 远程调用步骤3.1 注册 RestTemplate3.2 修改订单业务层3.3 测试4. 总结1. 情景 我编写的订单微服务查询订单时&#xff0c;无法跨越数据库查询订单所关联的用户 user 。因此下图中订单数据的 user 字段为 null 。 本节&#xff0c;我们…

【LSS: Lift, Splat, Shoot】代码的复现与详细解读

文章目录一、代码复现1.1 环境搭建1.2 数据集下载1.3 Evaluate a model1.4 Visualize Predictions1.5 Visualize Input/Output Data1.6 Train a model二、代码理解main.pyexplore.pydata.pymodels.pytools.pytrain.py原论文&#xff1a;https://arxiv.org/pdf/2008.05711v1.pdf…

Charles下载安装与手机调试教程

Charles下载与安装教程 1、安装目的&#xff1a;Charles需要收费&#xff0c;fiddler工具免费&#xff0c;想对比一下Charles抓包和fiddler抓包的异同 2、官网下载 https://www.charlesproxy.com/latest-release/download.do 3、选择合适的版本进行安装 ----------以下内容有参…

PyQt5 QtChart-曲线图

PyQt5 QtChart-QSplineSeries曲线图QSplineSeriesQSplineSeries QSplineSeries类将数据序列显示为曲线图。核心代码: spline QSplineSeries() spline.append(0, 23) spline.append(1, 56) … chart.addSeries(lineSeries) 常用方法&#xff1a; setPointsVisible(True) &am…

[附源码]Python计算机毕业设计高校教室管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

Stm32旧版库函数11——串口发送数据 使用中断接收

#include "stm32f10x_lib.h" #include "usart.h" #include "delay.h" /* ******************************************************************************** ** 函数名称 &#xff1a; USART1_Configuration(void) ** 函数功能 &#xff1a;…

城市消费券之地理位置攻防

近期&#xff0c;顶象发布了《城市消费券安全调研报告》&#xff08;以下简称《调研报告》&#xff09;。《调研报告》从城市消费券的发放规模、核销情况、风险出发&#xff0c;进一步分析除了黑灰产的作弊手段以及作弊工具。 其就作弊手段而言&#xff0c;黑灰产哄抢城市消费…

软件工程毕业设计题目100例

文章目录0 简介1 如何选题2 最新软件工程毕设选题3 最后0 简介 学长搜集分享最新的软件工程业专业毕设选题&#xff0c;难度适中&#xff0c;适合作为毕业设计&#xff0c;大家参考。 学长整理的题目标准&#xff1a; 相对容易工作量达标题目新颖 1 如何选题 最近非常多的学…

3个条件,筛选出最趁手的财务分析工具

市面上的财务分析工具很多&#xff0c;但究竟哪一种更好用&#xff0c;更趁手&#xff1f;这就需要看哪个财务分析工具可做到以下3大要求。 1&#xff1a;是否能快速计算财务分析指标 在财务分析中&#xff0c;存在一个需要在行与行之间进行计算分析需求&#xff0c;比如在利…

[附源码]Python计算机毕业设计高校教室申请管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

网络原理 一

网络原理 本文主要是介绍TCP/IP协议这里面的核心内容,还是很重要的 TCP/IP协议的层级 应用层 传输的数据如何去使用 传输层 起点和终点的传输 网络层 中间传输过程中的路径规划 数据链路层 相邻节点的传输 物理层 这是最底层的,相当于基础设施 应用层 不同的应用程序,涉及到…

「Electron|快速开始」来写个Hello World吧

本文主要介绍如何快速使用Electron生成一个Hello World应用 文章目录主要步骤一、准备工作创建项目安装electron二、编写electron应用所需的基本内容首先&#xff0c;我们需要给electron应用一个入口创建窗口往窗口里面放一个HTML界面&#xff0c;写上"Hello World!"…