Go语言应用——并发聊天室

功能简介:并发聊天室主要实现群聊天任务。功能包括:上下线提醒、群聊天、超时强制退出。

实现原理图:

实例代码:

package main

import (
	"net"
	"fmt"
	"runtime"
	"strings"
	"time"
)
type Client struct {
	name string
	add  string
	C    chan string
}
var Massage = make(chan string)         //Massge 管道
var OnLineMap = make(map[string]Client) //定义用户列表
func Manger() {
	for {
		msg := <-Massage //读取Massage中的消息
		for _, data := range OnLineMap {
			data.C <- msg //将消息广播到列表中的每一项
		}
	}
}
/*
  客户端 网络发送数据
*/
func WiriteToClient(conn net.Conn, client Client) {
	/*	msg := <-client.C
		conn.Write([]byte(msg + "\n"))*/
	for data := range client.C {
		conn.Write([]byte(data + "\n"))
	}
	fmt.Println(client.name, "子go程结束!")
}
func MakeMessage(client Client, msg string) string {
	return " Address:<" + client.add + ">" + "\tusname:<" + client.name + "> \tmessage:" + msg
}
func HandlerConnet(conn net.Conn) {
	defer conn.Close()
	clientAddr := conn.RemoteAddr().String()                    //获取网络地址
	client := Client{clientAddr, clientAddr, make(chan string)} //创建客户端对应的地址
	OnLineMap[clientAddr] = client                              //添加客户端到在线列表
	go WiriteToClient(conn, client)                             //开启网络写 go程
	msgOnline := MakeMessage(client, "login")                   //上线信息
	Massage <- msgOnline                                        //向Massage 全局变量写数据
	OnLineState := make(chan bool)                              //在线状态
	chatState := make(chan bool)                                //聊天状态
	go func() {
		for {
			buffer := make([]byte, 4096)
			n, err := conn.Read(buffer)
			if n == 0 {
				//	fmt.Println("客户端已经关闭!")
				OnLineState <- false
				return
			}
			if err != nil {
				fmt.Println(err)
				return
			}
			recMsg := string(buffer[:n-1]) // nc 命令最末尾为: /n  (其他测试根据实际情况)
			//查询在线用户
			if len(recMsg) == 3 && recMsg == "who" {
				for _, value := range OnLineMap {
					senClient := value.add + value.name + "\n"
					conn.Write([]byte(senClient))
				}
			} else if len(recMsg) >= 7 && recMsg[:7] == "rename|" { //重命名
				newName := strings.Split(recMsg, "|") //重命名以 |作为分割  格式:rename|xxx
				client.name = newName[1]              //获取 命名XXX
				/*
				  以上代码简写:
				client.name=strings.Split(recMsg,"|")[1]
				*/
				OnLineMap[clientAddr] = client //存储修改信息到用户列表
				conn.Write([]byte("rename is OK!"))
			} else {
				sendMsg := MakeMessage(client, string(buffer[:n])) //发送数据信息
				Massage <- sendMsg                                 //执行发送操作
			}
			chatState <- true
		}
	}()
	for {
		runtime.GC()
		select {
		case <-OnLineState:
			delete(OnLineMap, clientAddr)
			Massage <- MakeMessage(client, " is Leaved")
			close(client.C) //结束子go程: WiriteToClient
			return
		case <-time.After(time.Second * 20):
			delete(OnLineMap, clientAddr)
			Massage <- MakeMessage(client, " is outtime")
			/*
			在子go程中开辟的新go程,新go程不会随着子go程的结束而结束。
			原因:go程共享堆,不共享栈。子go程退出,栈退出,但堆还在。

			在主go程中开辟的新go程,新go程会随着主go程的结束而结束。
			原因:go程共享堆,主go程退出,堆也会退出。所以新go程会被退出。
			*/
			close(client.C) //结束子go程: WiriteToClient   channel被关闭,对应的 for range会被退出。
			return
			/*
			//有用户信息 ,重置<-time.After(time.Second * 20)
			主要是select特性:有一个case执行,即退出,再次进入,从新开始,
			这时<-time.After(time.Second * 20)被重置
			*/
		case <-chatState:
		}
	}
}
func main() {
	//主go程
	listener, err := net.Listen("tcp", "127.0.0.1:8081")
	if err != nil {
		fmt.Println("net.Listen err:", err)
		return
	}
	defer listener.Close()
	go Manger()
	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("listener.Accept err:", err)
			continue
		}
		//defer conn.Close()
		go HandlerConnet(conn)
	}
}

测试:

猜你喜欢

转载自blog.csdn.net/weixin_42117918/article/details/82353923
今日推荐