go TCP聊天室

文章目录

楔子

菜鸟的学习笔记,[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)
	}

}

发布了308 篇原创文章 · 获赞 70 · 访问量 38万+

猜你喜欢

转载自blog.csdn.net/u012848709/article/details/104114685