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"))
}
}
测试
用户一
用户二
用户三
控制台消息
后续再对聊天室进一步优化
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!