功能简介:并发聊天室主要实现群聊天任务。功能包括:上下线提醒、群聊天、超时强制退出。
实现原理图:
实例代码:
package main
import (
"net"
"fmt"
"runtime"
"strings"
"time"
)
type Client struct {
name string
add string
C chan string
}
var Massage = make(chan string) //Massge 管道
var OnLineMap = make(map[string]Client) //定义用户列表
func Manger() {
for {
msg := <-Massage //读取Massage中的消息
for _, data := range OnLineMap {
data.C <- msg //将消息广播到列表中的每一项
}
}
}
/*
客户端 网络发送数据
*/
func WiriteToClient(conn net.Conn, client Client) {
/* msg := <-client.C
conn.Write([]byte(msg + "\n"))*/
for data := range client.C {
conn.Write([]byte(data + "\n"))
}
fmt.Println(client.name, "子go程结束!")
}
func MakeMessage(client Client, msg string) string {
return " Address:<" + client.add + ">" + "\tusname:<" + client.name + "> \tmessage:" + msg
}
func HandlerConnet(conn net.Conn) {
defer conn.Close()
clientAddr := conn.RemoteAddr().String() //获取网络地址
client := Client{clientAddr, clientAddr, make(chan string)} //创建客户端对应的地址
OnLineMap[clientAddr] = client //添加客户端到在线列表
go WiriteToClient(conn, client) //开启网络写 go程
msgOnline := MakeMessage(client, "login") //上线信息
Massage <- msgOnline //向Massage 全局变量写数据
OnLineState := make(chan bool) //在线状态
chatState := make(chan bool) //聊天状态
go func() {
for {
buffer := make([]byte, 4096)
n, err := conn.Read(buffer)
if n == 0 {
// fmt.Println("客户端已经关闭!")
OnLineState <- false
return
}
if err != nil {
fmt.Println(err)
return
}
recMsg := string(buffer[:n-1]) // nc 命令最末尾为: /n (其他测试根据实际情况)
//查询在线用户
if len(recMsg) == 3 && recMsg == "who" {
for _, value := range OnLineMap {
senClient := value.add + value.name + "\n"
conn.Write([]byte(senClient))
}
} else if len(recMsg) >= 7 && recMsg[:7] == "rename|" { //重命名
newName := strings.Split(recMsg, "|") //重命名以 |作为分割 格式:rename|xxx
client.name = newName[1] //获取 命名XXX
/*
以上代码简写:
client.name=strings.Split(recMsg,"|")[1]
*/
OnLineMap[clientAddr] = client //存储修改信息到用户列表
conn.Write([]byte("rename is OK!"))
} else {
sendMsg := MakeMessage(client, string(buffer[:n])) //发送数据信息
Massage <- sendMsg //执行发送操作
}
chatState <- true
}
}()
for {
runtime.GC()
select {
case <-OnLineState:
delete(OnLineMap, clientAddr)
Massage <- MakeMessage(client, " is Leaved")
close(client.C) //结束子go程: WiriteToClient
return
case <-time.After(time.Second * 20):
delete(OnLineMap, clientAddr)
Massage <- MakeMessage(client, " is outtime")
/*
在子go程中开辟的新go程,新go程不会随着子go程的结束而结束。
原因:go程共享堆,不共享栈。子go程退出,栈退出,但堆还在。
在主go程中开辟的新go程,新go程会随着主go程的结束而结束。
原因:go程共享堆,主go程退出,堆也会退出。所以新go程会被退出。
*/
close(client.C) //结束子go程: WiriteToClient channel被关闭,对应的 for range会被退出。
return
/*
//有用户信息 ,重置<-time.After(time.Second * 20)
主要是select特性:有一个case执行,即退出,再次进入,从新开始,
这时<-time.After(time.Second * 20)被重置
*/
case <-chatState:
}
}
}
func main() {
//主go程
listener, err := net.Listen("tcp", "127.0.0.1:8081")
if err != nil {
fmt.Println("net.Listen err:", err)
return
}
defer listener.Close()
go Manger()
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener.Accept err:", err)
continue
}
//defer conn.Close()
go HandlerConnet(conn)
}
}
测试: