Go语言之进阶篇实现并发聊天功能

1、并发聊天服务器原理分析

 

2、并发聊天室

功能: 

广播消息、广播上线、 查询在线用户、修改用户名、用户主动退出、超时处理

示例:

package main

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

type Client struct {
	C    chan string //用户发送数据的管道
	Name string      //用户名
	Addr string      //网络地址
}

//保存在线用户   cliAddr =====> Client
var onlineMap map[string]Client

var messaage = make(chan string)

//新开一个协程,转发消息,只要有消息来了,遍历map, 给map每个成员都发送此消息
func Manager() {
	//给map分配空间
	onlineMap = make(map[string]Client)

	for {
		msg := <-messaage //没有消息前,这里会阻塞

		//遍历map, 给map每个成员都发送此消息
		for _, cli := range onlineMap {
			cli.C <- msg
		}
	}
}

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 + ": " + msg

	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)
	//广播某个在线
	//messaage <- "[" + cli.Addr + "]" + cli.Name + ": login"
	messaage <- MakeMsg(cli, "login")
	//提示,我是谁
	cli.C <- MakeMsg(cli, "I am here")

	isQuit := make(chan bool)  //对方是否主动退出
	hasData := make(chan bool) //对方是否有数据发送

	//新建一个协程,接收用户发送过来的数据
	go func() {
		buf := make([]byte, 2048)
		for {
			n, err := conn.Read(buf)
			if n == 0 { //对方断开,或者,出问题
				isQuit <- true
				fmt.Println("conn.Read err = ", err)
				return
			}

			msg := string(buf[:n-1]) //通过windows nc测试,多一个换行
			if len(msg) == 3 && msg == "who" {
				//遍历map,给当前用户发送所有成员
				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("rename ok\n"))

			} else {
				//转发此内容
				messaage <- MakeMsg(cli, msg)
			}

			hasData <- true //代表有数据
		}

	}() //别忘了()

	for {
		//通过select检测channel的流动
		select {
		case <-isQuit:
			delete(onlineMap, cliAddr)            //当前用户从map移除
			messaage <- MakeMsg(cli, "login out") //广播谁下线了

			return
		case <-hasData:

		case <-time.After(30 * time.Second): //60s后
			delete(onlineMap, cliAddr)                     //当前用户从map移除
			messaage <- MakeMsg(cli, "time out leave out") //广播谁下线了
			return
		}
	}
}

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

	defer listener.Close()

	//新开一个协程,转发消息,只要有消息来了,遍历map, 给map每个成员都发送此消息
	go Manager()

	//主协程,循环阻塞等待用户链接
	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("listener.Accept err = ", err)
			continue
		}

		go HandleConn(conn) //处理用户链接
	}

}

执行结果:

 

 

posted @ 2019-01-22 17:35  努力哥  阅读(802)  评论(0编辑  收藏  举报