chatroom

本文最后更新于:1 年前

GO语言实现的聊天室

实现很简单,基于tcp socket,做这个小项目的目的是检测对channel的理解,channel在并发场景中真的好用,但是有时候也是真挺难理解的

准备

User

每个连接的用户需要对应一个账号,因此需要创建一个全局的struct,用户有名字,id,以及接受消息的管道

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

map&message

还需要一个全局的map保存所有的用户信息,以便服务器端向所有的用户转发消息

// 需要一个全局的map存储所有user信息
var allUsers = make(map[string] User)

转发消息时又需要一个管道接收用户发送的消息后转发给所有用户

// 需要一个全局的管道message 向所有用户发送消息
var message = make(chan string, 10)

功能实现

需要一个广播函数,开启该goroutine后可以一直监听message管道中的消息,然后向用户转发消息

需要一个业务处理函数,当用户发送消息后,通过该函数处理

需要一个消息反馈函数,将User.msg中的消息返回到客户端

broadcast

func broadcast(){
	fmt.Println("[+]:广播go协程启动成功...")
	for{
		// 从message中读取数据
		info := <- message
		// 将消息发送给所有用户
		if info != ""{
			for _, user := range allUsers{
				user.msg <- info
			}
		}
	}
}

handler

func handler(conn net.Conn) {
	for true {
		fmt.Println("[+]:启动业务...")
		// 每次建立新连接需要创建一个user
		clientAddr := conn.RemoteAddr().String()
		newUser := User{
			name : clientAddr,
			id : clientAddr,
			msg: make(chan string),  // 一定要使用make,否则没有空间写人
		}
		fmt.Println(clientAddr)

		_, ok := allUsers[clientAddr]
		if !ok{
			//将新创建的用户添加到map中
			allUsers[newUser.id] = newUser
			// 向广播中写入消息 通知其他人你已经上线
			loginInfo := fmt.Sprintf("[%s]:[%s] ====> online now!", newUser.name, newUser.id)
			message <- loginInfo
			time.Sleep(time.Second)
			go writeToClient(newUser, conn)
		}
		buf:= make([]byte, 1024)
		cnt, err := conn.Read(buf)
		if err!=nil{
			fmt.Println("conn.Read error: " , err)
		}
		//fmt.Println("服务器端接受的数据为:",  string(buf[:cnt]), ", cnt:", cnt)
		userInfo:= fmt.Sprintf("[%s] say:", clientAddr)
		message <- userInfo + string(buf[:cnt])
	}
}

writeToClient

func writeToClient(user User, conn net.Conn){
	for msg := range user.msg{
		conn.Write([]byte(msg + "\n"))
	}
}

完整程序

package main

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

type User struct {
	// 名字
	name string
	// id
	id string
	// msg管道
	msg chan string
}
// 需要一个全局的map存储所有user信息
var allUsers = make(map[string] User)
// 需要一个全局的管道message 向所有用户发送消息
var message = make(chan string, 10)
func main() {
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		fmt.Println("net.listen err: ", err)
	}
	fmt.Println("[+]:服务器监听成功")
	// 启动全局唯一的 广播协程
	go broadcast()
	for{
		accept, err := listen.Accept()
		if err != nil {
			fmt.Println("[+]:listen.accept err: ", err)
		}
		fmt.Println("[+]:建立连接成功...")
		go handler(accept)

	}
}

func handler(conn net.Conn) {
	for true {
		fmt.Println("[+]:启动业务...")
		// 每次建立新连接需要创建一个user
		clientAddr := conn.RemoteAddr().String()
		newUser := User{
			name : clientAddr,
			id : clientAddr,
			msg: make(chan string),  // 一定要使用make,否则没有空间写人
		}
		fmt.Println(clientAddr)

		_, ok := allUsers[clientAddr]
		if !ok{
			//将新创建的用户添加到map中
			allUsers[newUser.id] = newUser
			// 向广播中写入消息 通知其他人你已经上线
			loginInfo := fmt.Sprintf("[%s]:[%s] ====> online now!", newUser.name, newUser.id)
			message <- loginInfo
			time.Sleep(time.Second)
			go writeToClient(newUser, conn)
		}
		buf:= make([]byte, 1024)
		cnt, err := conn.Read(buf)
		if err!=nil{
			fmt.Println("conn.Read error: " , err)
		}
		//fmt.Println("服务器端接受的数据为:",  string(buf[:cnt]), ", cnt:", cnt)
		userInfo:= fmt.Sprintf("[%s] say:", clientAddr)
		message <- userInfo + string(buf[:cnt])
	}
}

// 向所有用户广播消息, 全局唯一
func broadcast(){
	fmt.Println("[+]:广播go协程启动成功...")
	for{
		// 从message中读取数据
		info := <- message
		// 将消息发送给所有用户
		if info != ""{
			for _, user := range allUsers{
				user.msg <- info
			}
		}
	}
}

func writeToClient(user User, conn net.Conn){
	for msg := range user.msg{
		conn.Write([]byte(msg + "\n"))
	}
}

测试

用户一

image-20221207103317397

用户二

image-20221207103251004

用户三

image-20221207103427557

控制台消息

后续再对聊天室进一步优化


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

 目录

Copyright © 2020 my blog
载入天数... 载入时分秒...