Go_实现网络聊天室

需求:

1、支持多用户聊天

2、用户上线、退出要在(聊天室通知)

3、修改昵称(不在聊天室通知)

4、规定时间内不活跃的用户强制踢出(聊天室通知)

5、查询在线用户列表(不在聊天室通知)

服务端:

package main

import (
	"fmt"
	"log"
	"net"
	"time"
)

func main() {
	// 1、监听地址的连接请求,当有新地连接请求到来时,创建Accept实现连接
	listen, err := net.Listen("tcp", "127.0.0.1:8080")
	ErrorFunc("net.Listen:", err)
	defer listen.Close()

	// 创建管理者go程,管理map和全局channel,这个是负责广播的,所以要在用户连接前创建好
	go Message()

	// 2、循环监听客户端连接请求
	for {
		accept, err := listen.Accept()
		ErrorFunc("listen.Accept:", err)
		// 启动Go程处理客户端数据请求
		go HandlerConnect(accept)
	}
}

// 负责读channel的数据,通知所有在线用户***用户上线信息

func Message() {
	// 初始化map
	OnlineMap = make(map[string]User)

	// 循环监听全局channel,读不到就阻塞,不循环的话就只能读一遍,后续的用户上线就读不到了
	for {
		msg := <-MessageChannel

		// 遍历在线用户,通知所有在线用户  clnt:Map的value,也就是User结构体
		for _, clnt := range OnlineMap {
			clnt.ch <- msg
		}
	}
}

// 3、创建用户结构体,存储登录用户信息
type User struct {
	ch   chan string //
	Name string      // 用户名
	Addr string      // 用户ip、端口
}

// 4、创建全局map,存储在线的用户	key:用户的ip、端口号	value:用户信息
var OnlineMap map[string]User

// 5、每个用户连接成功后向通道发送数据,  由message函数负责广播上线用户消息
var MessageChannel = make(chan string)

/*
存储用户信息、读取用户信息、修改名称、退出、异常(超时)处理
起个go程 将用户上线信息写到Message通道中
*/

func HandlerConnect(accept net.Conn) {
	defer accept.Close()

	// 检测用户是否活跃
	hasData := make(chan bool)

	// 6、获取用户ip、端口
	addr := accept.RemoteAddr().String()

	// 走到这里说明用户已经连接,因为下面要给子go程写用户信息,所以要初始化user结构体
	user := User{make(chan string), "", addr}

	// 将新连接的用户添加到在线的map,创建Map由Message处理,key:ip+端口	value:User结构体
	OnlineMap[addr] = user

	// 创建用来给当前用户发送消息的go程,并负责读取通道中的数据(在线用户)
	go WriteMessage(accept, user)

	// 发送用户消息到全局channel
	MessageChannel <- MakeMessage(user, "上线了")

	// 判断用户退出的channel
	isQuit := make(chan bool)

	// 创建一个go程,读取用户发送的聊天消息,写到全局channel
	go func() {
		bytes := make([]byte, 4096)
		for {
			n, err := accept.Read(bytes)
			ErrorFunc("accept.Read:", err)
			if n == 0 {
				// 用户退出后写到channel
				isQuit <- true
				fmt.Printf("检测到客户端:%s 已退出\n", user.Name)
				return
			}
			// 如果读到用户消息,就广播给所有在线用户,-1是因为使用了netcat模拟用户端,所有的指令都是string,后面会带一个\n,-1是把后面的\n删除掉
			msg := string(bytes[:n-1])

			// 判断用户端发的消息是否为查询在线用户列表指令,且仅有用户指令,不带有其它字符
			if msg == "查询在线用户" && len(msg) == 18 {
				accept.Write([]byte("在线用户列表为:\n"))

				// 遍历当前OnlineMap
				for _, user := range OnlineMap {
					userInfo := user.Addr + ":" + user.Name + "\n"
					accept.Write([]byte(userInfo))
				}
				// 有\n 要再加两个字节
			} else if len(msg) >= 14 && msg[:12] == "修改昵称" {
				user.Name = msg[13:]
				// 修改昵称后更新到在线Map
				OnlineMap[user.Addr] = user
				accept.Write([]byte("修改昵称成功\n"))
			} else {
				MessageChannel <- MakeMessage(user, msg)
			}

			// 只要执行到这里,就说明用户活跃
			hasData <- true
		}
	}()

	for {
		select {
		case <-isQuit:
			// 将当前用户从在线列表中移除,要先删除用户,不然会广播给自己
			delete(OnlineMap, user.Addr)
			// 广播给在线用户**用户退出
			MessageChannel <- MakeMessage(user, "退出了")
			return

		case <-hasData:
		// 读到数据什么都不做,重置下面的计时器
		case <-time.After(time.Second * 1000):
			// 将当前用户从在线列表中移除,要先删除用户,不然会广播给自己
			delete(OnlineMap, user.Addr)
			// 广播给在线用户**用户退出
			MessageChannel <- MakeMessage(user, "退出了")
			return
		}
	}
}

func MakeMessage(u User, msg string) (str string) {
	str = "【" + u.Addr + "】" + u.Name + " : " + msg
	return
}

// 读取用户上线信息
func WriteMessage(accept net.Conn, u User) {
	// 监听用户channel上是否有消息
	for msg := range u.ch {
		// 不加\n容易阻塞,写的消息发不出去
		accept.Write([]byte(msg + "\n"))
	}
}

func ErrorFunc(info string, err error) {
	if err != nil {
		log.Println(info, err)
		return
	}
}

客户端:

客户端不写了,使用Netcat模拟就行了

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

itzhuzhu.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值