[GO]併發實現聊天室服務器

package main

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

type Client struct {
C chan string //用戶發送數據的通道
Name string //用戶名
Addr string //網絡地址
}

//保存在線用戶 cliaddr ======> client
var onlineMap map[string]Client

var message = make(chan string)

func WriteMsgToClient(cli Client, conn net.Conn) {
for msg := range cli.C {
//給當前客戶端發送信息
conn.Write([]byte(msg + "\n"))
}
}

func MakeMsg(cli Client, msg string) (buf string) {
buf = "[" + cli.Addr + "]" + cli.Name + ": login"
return
}

func HandleConn(conn net.Conn) { //處理用戶鏈接
defer conn.Close()
//獲取客戶端的網絡地址
cliAddr := conn.RemoteAddr().String()
//建立一個結構體
cli := Client{make(chan string), cliAddr, cliAddr}
//把結構體添加到map
onlineMap[cliAddr] = cli

//新開一個協程,專門給當前客戶端發送信息
go WriteMsgToClient(cli, conn)

//廣播某我的在線
message <- MakeMsg(cli, "login")

//提示,我是誰
cli.C <- MakeMsg(cli, "I am here")

var isQuit = make(chan bool)

hasData := make(chan bool)

go func() {
buf := make([]byte, 2048)
for true {
n, err := conn.Read(buf)
if n == 0 { //對方斷開,或者出問題
isQuit <- true
fmt.Println("conn.read err = ", err)
return
}
msg := string(buf[:n-1])
if len(msg) == 3 && msg == "who" {
conn.Write([]byte("user list : \n"))
for _, tmp := range onlineMap {
msg = tmp.Addr + ":" + tmp.Name + "\n"
conn.Write([]byte(msg))
}
}else if len(msg) >= 8 && msg[:6] == "rename" {
//rename|mike
name := strings.Split(msg,"|")[1]
cli.Name = name
onlineMap[cliAddr] = cli
conn.Write([]byte("user list :\n"))
}else {
message <- MakeMsg(cli, msg)
}
}
}()

for true {
select {
case <- isQuit:
delete(onlineMap, cliAddr) //當前用戶從map移除
message <- MakeMsg(cli, "log out") //廣播誰下線了
return
case <- hasData:
//不作操做
case time.After(60*time.Second): //60秒都沒有操做了,超時
delete(onlineMap, cliAddr) //當前用戶從map移除
message <- MakeMsg(cli, "tiem out") //廣播誰下線了
return
}
}
}

func Manager() {
//給map分配空間map
onlineMap = make(map[string]Client)

for true {
msg := <-message //沒有消息前,這裏會阻塞
//遍歷map,給map每一個成員都發送此消息
for _, cli := range onlineMap{
cli.C <- msg
}
}

}

func main() {
//建立監聽
listener, err := net.Listen("tcp", ":8000")
if err != nil {
fmt.Println("net.Listen err = ", err)
return
}

//新開一個協程,用於轉發消息,只要有消息到達 ,那就遍歷map而後給map每一個成員都發送消息
go Manager()

//主協程,循環阻塞等待用戶鏈接
for true {
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener.Accept err = ", err)
continue //若是這個不發,説不定還有下個發呢
}
//處理用戶的鏈接
go HandleConn(conn)
}
}

執行的結果,當有任何一個新用戶登陸了,其餘全部的用戶都會收到登陸提醒,這裏以ip加端口號的試作爲一個惟一標識 網絡

相關文章
相關標籤/搜索