0
点赞
收藏
分享

微信扫一扫

Go 实现一个简单的聊天室

干自闭 2022-04-19 阅读 80

先在main方法中创建一个sorcket服务端,这个服务端就像一个酒店

func main() {
	listen, err := net.Listen("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println("listen err", err)
		return
	}
	defer listen.Close()
}

之后就要开始监听下一个客户端的呼叫,也就是等待下一位客人
Accept方法会等待下一个呼叫,并返回一个该呼叫的Conn接口。这时便拿到了客人的信息

func main() {
	// 创建服务端
	listen, err := net.Listen("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println("listen err", err)
		return
	}
	defer listen.Close()
	
	// 等待客户端呼叫
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("accept err", err)
			return
		}
	}
}

创建客户端结构体, 每个服务端是一个用户

type Client struct{
	c chan string // 消息接受管道
	Name string   // 昵称
	Addr string	  // ip与端口
}

收到呼叫拿到conn后,就需要为这个conn来创建一个协程
也就是得到客人的信息后,专门派一个服务员只为该客户服务

func main() {
	// 创建服务端
	listen, err := net.Listen("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println("listen err", err)
		return
	}
	defer listen.Close()
	
	go Manager() // 这个暂时忽略掉,下面有
	
	// 等待客户端呼叫
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("accept err", err)
			return
		}
		go HandleConnect(conn) // 这里加了一句代码,启动一个go协程来为conn服务
	}
}

服务员要干的第一件是就是带客户到房间,也就是为这个conn创建一个Client,并将其放进在线用户Map,netAddr是网络地址,以网络地址为键,client为值

全局变量 在线用户map

var onlineUsersMap map[string]Client

到这里就出现问题了,那在线用户map在哪里初始化使用啊?

我们创建一个专门来管理在线用户与消息群发的管理方法,这里暂时先初始化在线用户

func Manager(){
	// 初始化一下在线用户map
	onlineUsersMap = make(map[string]Client)
}

初始化一定要在赋值前,因此Manager协程一定要在HandleConnect前启动,现在翻到上面的main方法中,看一下位置

func HandleConnect(conn net.Conn){
	defer conn.Close()
	netAddr := conn.RemoteAddr().String()
	client := Client{make(chan string), netAddr, netAddr}
	onlineUsersMap[netAddr] = client
	
	for{} // 先整个空循环放这里,目的是别让这个go程结束断开conn连接
}

房间开好了,现在需要开始服务!
聊天室的服务是什么?当然是聊天啊!聊天室是什么,当然是群聊啊,每个用户的消息,所有用户都能看的到

说先我们的先保证用户能看的到其他人说话,我们在定义Client时,同时定义了一个消息接收通道,用户收到的消息会先发到这个消息接受通道里,然后从通道取出然后打印出来

func writeMsgToClient(client Client, conn net.Conn) {
	for msg := range client.c {
		conn.Write([]byte(msg))
	}
}

HandleConnect()中为服务的用户开一个go协程

go writeMsgToClient(client, conn)

“听”有了,该“说”了

定义一个全局变量 消息通道 该通道内的消息会被发到聊天室内

var messageChan make(chan string)

我们为服务的用户开一个go协程来发送消息,就直接写在HandleConnect()中了
我们的消息都先发送到全局messageChan通道中了

go func(){
	buf := make([]byte, 4096)
	for{
		n, err := conn.Read(buf) // 从链接中读取消息,读到buf中,返回长度n
		if n == 0{ // 如果消息长度为0,则用户已退出
			fmt.Printf("用户[%s]:%s已退出\n", client.Addr, client.Name)
			return
		}
		if err != nil {
				fmt.Println("read err", err)
				return
		}
		// string(buf[:n-1])中的n-1,主要是为了不读取末尾的换行符
		msg := "[" + client.Addr + "]" + client.Name + ": " + string(buf[:n-1]) + "\n"
		messageChan <- msg
	}
}()

通过将 全局消息通道 中的消息发送到 每个用户的消息接收通道中 来实现消息群发
哪里有全部用户呢?当然是已经初始化onlineUsersMapManager()中了!

func Manager(){
	// 初始化一下在线用户map
	onlineUsersMap = make(map[string]Client)
	
	// 消息群发
	for{
		// 如果全局消息通道中没有消息会阻塞,直到有消息
		msg := <-messageChan
		for _, client := range onlineUsersMap{
			client.c <- msg
		}
	}
}

进入聊天室前时先说一句,我来了,要不然什么提示都没有,感觉跟还没进聊天室一样
完整的HandleConnect()代码:

func HandleConnect(conn net.Conn){
	defer conn.Close()
	netAddr := conn.RemoteAddr().String()
	onlineUsersMap[netAddr] = client
	go writeMsgToClient(client, conn) // "听"消息
	
	loginMsg := "[" + client.Addr + "]" + client.Name + " login!\n"
	messageChan <- loginMsg
	
	// 发消息
	go func(){
		buf := make([]byte, 4096)
		for{
			n, err := conn.Read(buf) // 从链接中读取消息,读到buf中,返回长度n
			if n == 0{ // 如果消息长度为0,则用户已退出
				fmt.Printf("用户[%s]:%s已退出\n", client.Addr, client.Name)
				return
			}
			if err != nil {
					fmt.Println("read err", err)
					return
			}
			// string(buf[:n-1])中的n-1,主要是为了不读取末尾的换行符
			msg := "[" + client.Addr + "]" + client.Name + ": " + string(buf[:n-1]) + "\n"
			messageChan <- msg
		}
	}()
	
	for{} // 先整个空循环放这里,目的是别让这个go程结束断开conn连接
}

至此,简单的聊天功能实现了!

那,要如何验证呢?
这里我们需要用到一个工具:“netcat”

下载地址:https://eternallybored.org/misc/netcat/

在这里插入图片描述

下载后解压出来,注意,这一步杀毒会报黑客工具,然后自动删掉解压的东西,建议先关闭杀毒软件,然后再解压,最后打开杀毒软件,此时大概率解压出来的东西已经又被杀毒软件删了,先将解压目录添加到杀毒白名单(信任区)中,再到隔离区将前两个文件恢复即可

最后将目录添加到系统的环境变量path中即可

打开两个cmd窗口

两个cmd窗口都输入

nc 127.0.0.1 8000

再按回车

之后便可以互相发消息测试了!

cmd1
cmd2
明天继续更新 改名,查询在线用户,私聊功能

举报

相关推荐

0 条评论