【GO语言】实现一个简单的并发聊天室,初级练手项目

【GO语言】实现一个简单的并发聊天室,初级练手项目,第1张

GO语言实现一个简单的并发聊天室 项目总览:一、项目开发日志二、知识补充与整体框架图1.TCP通信的基础知识2.聊天室实现框架图3.各个主要go程拆解 三、全部代码展示四、效果展示:

项目总览:

1.开发语言:GO语言
2.IDE:Goland
3.开发用时:半天
4.源码已上传到我的GitHub,链接:https://github.com/2394799692/IM-2022-3-13- 或点此跳转
5.项目主要用于练习go语言实现并发编程和网络编程。


以下是本篇文章正文内容,欢迎朋友们进行指正,一起探讨,共同进步。——来自考研路上的lwj。QQ:2394799692

一、项目开发日志
开发日志:
广播用户上线:
1.主go程中,创建监听套接字,记得defer
2.for循环监听客户端连接请求。Accept()函数
3.有一个客户端连接,创建新go程处理客户端数据 HandlerConnet(conn)defer
4.定义全局结构体类型 C,Name,Addr
5.创建全局map,channel
6.实现HandlerConnet,获取客户端IP+port——RemoteAddr().初始化新用户结构体信息。name==Addr
7.创建实现管理go程,在accept()之前。
8.实现Manager。初始化在线用户map。循环读取全局变量channel,如果无证据,阻塞。如果有证据,遍历在线用户map,将数据写到用户的C里
9.将新用户添加到 在线用户map中。key==IP+port value=新用户结构体
10.创建WriteMsgToClient go程,专门给当前用户写数据。——来源于用户自带的C中
11.创建WriteMsgToClient(clnt,conn)。遍历自带的C,读数据,conn.Write到客户端
12.HandlerConnet中,结束位置,组织用户上线信息写到全局channel——Manager的读就被激活(原来一直阻塞)
13.HandlerConnet中,结束加for{}

广播用户消息:
1.封装函数MakeMsg()来处理广播,用户消息
2.HandlerConnet中,创建匿名go程,读取用户socket上发送来的聊天内容,写到全局channel
3.for循环conn.Read n==0 err!=nil
4.写给全局message——后续的事,原来广播用户上线模块 完成。

查询在线用户:
1.将读取到的用户消息msg结尾的“\n”去掉
2.判断是否是“who”命令
3.如果是,遍历在线用户列表,组织显示信息,写到socket中、
4.如果不是,写给全局messge

修改用户名:
1.将读取到的用户消息msg判断是否包含“rename”
2.提取”|“后面的字符串,存入到Client的Name成员中
3.更新在线用户列表。onlineMap。Key——IP+prot
4.提示用户更新完成。conn.Write

用户退出:
1.在用户成功登陆之后,创建监听用户退出的channel——isQuit
2.当conn.Read==0,isQuit<-true
3.在HandlerConnet结尾for中,添加select监听<-isQuit
4.条件满足,将用户从在线列表移除。组织用户下线消息,写入message

超时强T:
1.在select中监听定时器。(time.After())计时到达。将用户从在线列表移除。组织用户下线消息,写入message(广播)
2.创建监听用户活跃的channel——hasData
3.只要用户在执行聊天,改名,who、任意 *** 作,都在hasdata中写数据
4.在select中添加监听hasdata,条件满足,不做任何事情,目的是重置计时器


主go程(服务器):负责监听,接收用户(客户端)连接请求,建立通信关系。同时启动相应的go程处理任务

二、知识补充与整体框架图 1.TCP通信的基础知识

【GO语言】实现TCP—C/S设计模式的服务器server端与客户client端即时通信
【GO语言】实现TCP—C/S设计模式的服务器server端与客户client端【并发】通信

2.聊天室实现框架图

3.各个主要go程拆解


三、全部代码展示
package main

import (
	"fmt"
	"net"
	"strings"
	"time"
)

//创建用户结构体类型!
type Client struct {
	C    chan string
	Name string
	Addr string
}

//创建全局map,存储在线用户
var onlineMap map[string]Client

//创建全局channel,用于传递用户消息
var message = make(chan string)

func WriteMsgToClient(clnt Client, conn net.Conn) {
	//监听用户自带channel上是否有消息
	for msg := range clnt.C {
		conn.Write([]byte(msg + "\n"))
	}
}
func MakeMsg(clnt Client, msg string) (buf string) {
	buf = "[" + clnt.Addr + "]" + clnt.Name + ":" + msg
	return
}
func HandlerConnect(conn net.Conn) {
	defer conn.Close()
	//创建channel判断,用户是否活跃
	hasData := make(chan bool)
	//获取用户网络地址 ip+port
	netAddr := conn.RemoteAddr().String()
	//创建用户的结构体信息
	clnt := Client{make(chan string), netAddr, netAddr}
	//将新链接用户,添加到在线用户map中
	onlineMap[netAddr] = clnt
	//发送用户上线消息到全局channel中
	go WriteMsgToClient(clnt, conn)
	//创建专门用来给当前用户发送消息的go程
	message <- MakeMsg(clnt, "login")
	//创建一个channel,用来判断退出状态
	isQuit := make(chan bool)
	//创建一个匿名go程,专门处理用户发送的消息。
	go func() {
		buf := make([]byte, 4096)
		for {
			n, err := conn.Read(buf)
			if n == 0 {
				isQuit <- true
				fmt.Printf("检测到客户端:%s退出", clnt.Name)
				return
			}
			if err != nil {
				fmt.Println("conn.Read err:", err)
				return
			}
			//将读到的用户消息,写入到message中
			msg := string(buf[:n-1]) //减去1的意思是去掉斜杆
			//提取在线用户列表
			if msg == "who" && len(msg) == 3 {
				conn.Write([]byte("online user list:\n"))
				//遍历当前map、获取在线用户
				for _, user := range onlineMap {
					userInfo := user.Addr + ":" + user.Name + "\n"
					conn.Write([]byte(userInfo))
				}
			} else if len(msg) >= 8 && msg[:6] == "rename" {
				newName := strings.Split(msg, "|")[1]
				clnt.Name = newName       //修改结构体成员name
				onlineMap[netAddr] = clnt //更新onlineMap
				conn.Write([]byte("rename successful\n"))
			} else {
				//将读到的用户消息,写入到message中
				message <- MakeMsg(clnt, msg)
			}
			hasData <- true
		}
	}()
	//保证不退出
	for {
		//监听channel上的数据流动
		select {
		case <-isQuit:
			delete(onlineMap, clnt.Addr)       //将用户从online移除
			message <- MakeMsg(clnt, "logout") //写入用户退出消息到全局channel
			return
		case <-hasData:
			//什么都不做,目的是重置下面case的计时器。
		case <-time.After(time.Second * 10):
			delete(onlineMap, clnt.Addr)       //将用户从online移除
			message <- MakeMsg(clnt, "logout") //写入用户退出消息到全局channel
			return
		}
	}
}
func Manager() {
	//初始化map
	onlineMap = make(map[string]Client)
	for { //循环从message中读取
		//监听全局channel中是否有数据,有数据存储至msg,无数据阻塞
		msg := <-message
		//循环发送消息给所有在线用户
		for _, clnt := range onlineMap {
			clnt.C <- msg
		}
	}
}
func main() {
	//创建监听套接字
	listener, err := net.Listen("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println("Listen err", err)
		return
	}
	defer listener.Close()
	//循环监听客户端连接请求
	//创建管理者go程,管理map和ch
	go Manager()
	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("Accept err", err)
			return
		}
		//启动go程处理客户端请求
		go HandlerConnect(conn)
	}
}



四、效果展示:

1)启动服务器后,开启3个客户端,同时发送数据,测试都能接收到:

2)使用rename|+新名字,实现改名功能

3)使用exit命令退出聊天室

4)超时自动下线


5)who展示所有在线人员

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/990284.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存