十三、网络聊天室

  • 服务端
package main

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

//创建用户结构体类型
type Client struct {
	C    chan string
	Name string
	Addr string
}

//创建全局map,存储在线用户
var onlineMap map[string]Client

//创建全局channel,传递用户消息。
var message = make(chan string)

func Manager() {
	//初始化onlineMap
	onlineMap = make(map[string]Client)
	fmt.Println("全局 onlineMap 初始化完成。")

	//循环监听全局channel中是否有数据
	for {
		msg := <-message
		fmt.Println("当前在线用户数 -> ", len(onlineMap))

		//循环发送消息给所有用户
		for _, cli := range onlineMap {
			cli.C <- msg
		}
	}
}

func HandlerConnect(conn net.Conn) {
	defer conn.Close()

	//创建channel判断,用户是否活跃
	hasData := make(chan bool)

	//获取用户网络地址
	cliAddr := conn.RemoteAddr().String()

	//创建新连接用户的结构体信息,默认用户名是IP+Port
	cli := Client{make(chan string), cliAddr, cliAddr}

	//将新连接用户添加到在线用户map中
	onlineMap[cliAddr] = cli

	//创建专门用来给当前用户发送消息的go程
	go WriteMagToClient(cli, conn)

	// 发送用户上线消息到全局channel
	msg := MakeMsg(cli, "login")
	message <- msg
	fmt.Println(msg)

	//创建一个channel,用来判断用户退出状态
	isQuit := make(chan bool)

	//创建一个匿名go程,专门处理用户发送的消息
	go func() {
		for {
			buf := make([]byte, 4096)
			n, err := conn.Read(buf)
			if n == 0 {
				fmt.Printf("服务器检查到客户端[%s]退出。\n", cli.Name)
				isQuit <- true //打开用户退出开关
				return
			} else if err != nil {
				fmt.Println("conn.Read", err)
				return
			}
			//读到用户的消息保存到msg中,去掉最后2个\r\n
			msg := string(buf[:n-2])
			fmt.Println(cliAddr, " -> ", msg)

			//提取在线用户列表
			if strings.HasPrefix(strings.ToLower(msg), "who") && strings.HasSuffix(strings.ToLower(msg), "who") {
				conn.Write([]byte("Online User List:\n"))
				//遍历当前map,获取在线用户
				for _, cli := range onlineMap {
					userInfo := cli.Addr + " -> " + cli.Name + "\n"
					conn.Write([]byte(userInfo))
				}
			} else if strings.HasPrefix(strings.ToLower(msg), "rename|") {
				//在线用户改名
				name := strings.Split(msg, "|")[1]
				cli.Name = name
				onlineMap[cliAddr] = cli
				conn.Write([]byte("User Rename Success\n"))
			} else {
				//将读到的用户消息,广播给所有在线用户
				message <- MakeMsg(cli, msg)
			}
			hasData <- true
		}
	}()

	//保存不退出
	for {
		//监听channel上的数据流动
		select {
		case <-isQuit:
		    close(cli.C)               //结束子go程(WriteMagToClient函数)
			delete(onlineMap, cliAddr) //将用户从在线用户列表移除
			message <- MakeMsg(cli, "logout")
			return
		case <-hasData:
			//什么都不做,目的是重置下面case的计时器
		case <-time.After(60 * time.Second):
		    close(cli.C)               //结束子go程(WriteMagToClient函数)
			delete(onlineMap, cliAddr) //超时退出
			message <- MakeMsg(cli, "time out")
			return
		}
	}
}

func WriteMagToClient(cli Client, conn net.Conn) {
	//监听用户自带channel上是否有消息
	for msg := range cli.C {
		conn.Write([]byte(msg + "\n"))
	}
}

func MakeMsg(cli Client, msg string) (str string) {
	str = "[" + cli.Addr + "]" + cli.Name + " -> " + msg
	return str
}

func main() {
	//创建监听套接字
	listener, err := net.Listen("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println("net.Listen err:", err)
		return
	}
	defer listener.Close()
	fmt.Println("TCP服务器 Listen 创建完成。")

	//创建管理者go程,管理map和全局channel
	go Manager()

	//循环监听客户端连接请求
	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("listener.Accept err:", err)
			return
		}
		//启动go程处理客户端数据请求
		go HandlerConnect(conn)
	}

}
  • 客户端
package main

import (
	"os"
	"fmt"
	"net"
)

func main() {
	//指定服务器通信协议、IP地址、端口号
	conn, err := net.Dial("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer conn.Close() //关闭

	//获取用户键盘输入数据
	go func() {
		buf := make([]byte, 4096)
		for {
			n, err := os.Stdin.Read(buf)
			if err != nil {
				fmt.Println("os.Stdin.Read err:", err)
				continue
			}
			//主动写数据给服务器
			conn.Write(buf[:n])
		}
	}()

	buf := make([]byte, 4096)
	for {
		//接收服务器回发的数据
		n, err := conn.Read(buf)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println(string(buf[:n]))
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值