楔子
菜鸟的学习笔记,[20小时快速入门go语言视频] (https://www.bilibili.com/video/av39054230?p=216) TCP并发聊天室
demo
package main
import (
"bytes"
"fmt"
"io"
"log"
"net"
"runtime"
"strings"
"time"
)
func init() {
log.Println(strings.Title("hello"))
log.SetPrefix("[")
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
}
type Client struct {
C chan string //用于发送数据的管道
Name string //用户名
Add string //地址
}
//在线用户 cliAddr --->Client
var onlineMap map[string]*Client
var message chan string = make(chan string)
/********************************************
理用户连接|退出使用 runtime.Goexit()|
--https://www.cnblogs.com/SpiderShrimp/p/11416208.html
立即终止当前协程,不会影响其它协程,且终止前会调用此协程声明的defer方法。由于Goexit不是panic,所以recover捕获的error会为nil
*********************************************/
func HandleConnDemo01(conn net.Conn) {
defer conn.Close()
defer fmt.Println("HandleConn 协程退出")
// 获取客户端的网络地址
cliAddr := conn.RemoteAddr().String()
cli := &Client{make(chan string), cliAddr, cliAddr}
// 把结构体添加入map
onlineMap[cliAddr] = cli
// 广播某个人在线
message <- MakeMsg(cli, "login")
//新开一个协程,给当前客户端发送信息
go WritrMsgToClient(cli, conn)
log.Println(MakeMsg(cli, "login"))
//是否退出,如果接受用户输入 字节数为n 则退出
isQuit := false
//新建一个协程,接受用户发送来的数据
go func() {
buf := make([]byte, 2048)
for {
n, err := conn.Read(buf)
//log.Println(n, "错误是:", err)
if n == 0 || nil == io.EOF { //对方断开 或者其他
fmt.Println(err)
isQuit = true
runtime.Goexit()
}
msg := buf[:n-1] //通过window nc测试会多一个换行 所有减1
if string(msg) == "who" {
var buffer bytes.Buffer
for _, tmp := range onlineMap {
buffer.WriteString(tmp.Name)
buffer.WriteString(" ")
}
conn.Write(buffer.Bytes())
} else {
message <- MakeMsg(cli, string(msg))
}
}
}()
//循环执行,避免连接断开
for {
if isQuit {
runtime.Goexit()
}
for _, r := range `-\|/` {
fmt.Printf("\r%c", r)
time.Sleep(100 * time.Millisecond)
}
}
}
// 处理用户连接|这个有问题,一旦输入就退出了?|使用一个for循环解决
func HandleConnDemo02(conn net.Conn) {
defer conn.Close()
defer fmt.Println("HandleConn 协程退出")
// 获取客户端的网络地址
cliAddr := conn.RemoteAddr().String()
cli := &Client{make(chan string), cliAddr, cliAddr}
// 把结构体添加入map
onlineMap[cliAddr] = cli
// 广播某个人在线
message <- MakeMsg(cli, "login")
//新开一个协程,给当前客户端发送信息
go WritrMsgToClient(cli, conn)
log.Println(MakeMsg(cli, "login"))
//是否主动退出,使用管道控制退出
isQuit := make(chan uint)
//是否有用户输入|定义一个空结构体 的chan
hasUserInput := make(chan uint)
//新建一个协程,接受用户发送来的数据
go func() {
defer log.Println("接受用户发送来的数据 处理程序退出")
buf := make([]byte, 2048)
for {
n, err := conn.Read(buf)
//log.Println(n, "错误是:", err)
if n == 0 || nil == io.EOF { //对方断开 或者其他
fmt.Println(err)
isQuit <- 1
return
}
msg := buf[:n-1] //通过window nc测试会多一个换行 所有减1
if string(msg) == "who" {
var buffer bytes.Buffer
for _, tmp := range onlineMap {
buffer.WriteString(tmp.Name)
buffer.WriteString(" ")
}
conn.Write(buffer.Bytes())
} else {
message <- MakeMsg(cli, string(msg))
}
hasUserInput <- 1
}
}()
//循环执行,避免连接断开
select {
case <-isQuit:
delete(onlineMap, cliAddr)
message <- MakeMsg(cli, cliAddr+"login out")
return
case <-hasUserInput:
case <-time.After(time.Second * 60):
delete(onlineMap, cliAddr)
message <- MakeMsg(cli, " time out leave")
return
}
}
// 处理用户连接|主动退出和超时机制
func HandleConn(conn net.Conn) {
defer conn.Close()
defer fmt.Println("HandleConn 协程退出")
// 获取客户端的网络地址
cliAddr := conn.RemoteAddr().String()
cli := &Client{make(chan string), cliAddr, cliAddr}
// 把结构体添加入map
onlineMap[cliAddr] = cli
// 广播某个人在线
message <- MakeMsg(cli, "login")
//新开一个协程,给当前客户端发送信息
go WritrMsgToClient(cli, conn)
log.Println(MakeMsg(cli, "login"))
//是否主动退出,使用管道控制退出
isQuit := make(chan uint)
//是否有用户输入|定义一个空结构体 的chan
hasUserInput := make(chan uint)
//新建一个协程,接受用户发送来的数据
go func() {
defer log.Println("接受用户发送来的数据 处理程序退出")
buf := make([]byte, 2048)
for {
n, err := conn.Read(buf)
//log.Println(n, "错误是:", err)
if n == 0 || nil == io.EOF { //对方断开 或者其他
fmt.Println(err)
isQuit <- 1
return
}
msg := buf[:n-1] //通过window nc测试会多一个换行 所有减1
if string(msg) == "who" {
var buffer bytes.Buffer
for _, tmp := range onlineMap {
buffer.WriteString(tmp.Name)
buffer.WriteString(" ")
}
conn.Write(buffer.Bytes())
} else {
//转发内容
message <- MakeMsg(cli, string(msg))
}
hasUserInput <- 1
}
}()
for {
//循环执行,避免连接断开
select {
case <-isQuit:
delete(onlineMap, cliAddr)
message <- MakeMsg(cli, cliAddr+"login out")
return
case <-hasUserInput:
case <-time.After(time.Second * 10):
delete(onlineMap, cliAddr)
message <- MakeMsg(cli, " time out leave")
return
}
}
}
func MakeMsg(cli *Client, msg string) (buf string) {
buf = fmt.Sprint("{", cli.Add, "}", cli.Name, ": ", msg)
return
}
//新开一个协程,给当前客户端发送信息
func WritrMsgToClient(cli *Client, conn net.Conn) {
for msg := range cli.C {
conn.Write([]byte(msg + "\n"))
}
}
//新开一个协程,转发消息|只要有消息来了,遍历map,给每个成员发送消息
func ManagerMsg() {
for {
msg := <-message //没有消息前会阻塞
//遍历map,给每个成员发送
for _, cli := range onlineMap {
//包括转发给自己
cli.C <- msg
}
}
}
func main() {
log.Println("服务器监听9050")
listen, err := net.Listen("tcp", "0.0.0.0:9050")
if err != nil {
log.Println("net listen err", err)
return
}
defer listen.Close()
//新开一个协程,转发消息|只要有消息来了,遍历map,给每个成员发送消息
go ManagerMsg()
//给map分派空间
onlineMap = make(map[string]*Client)
// 主协程,等待客户连接
for {
conn, err := listen.Accept()
if err != nil {
log.Println(err)
continue
}
//处理用户连接
go HandleConn(conn)
}
}