用go语言实现简单并发聊天室

用go语言实现简单并发聊天室

1.可以支持多个客户端上线
2.服务器知道那个客户端上线
3.服务器可以将消息转发给所有的客户端
4.可以查看在线用户列表
5.可以实现用户重命名

并发聊天服务器原理分析

用go语言实现简单并发聊天室_第1张图片
用go语言实现简单并发聊天室_第2张图片

并发聊天服务器代码

package main

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

type Client struct {
     
	C chan string	//用户发送数据的管道
	Name string		//用户名
	Addr string		//网络地址
}
var onlineMap map[string]Client
var message = make(chan string)

func main(){
     
	//监听
	listener,err := net.Listen("tcp","127.0.0.1:8080")
	if err != nil{
     
		fmt.Println("net.Listen err:",err)
		return
	}
	defer listener.Close()
	//新开一个协程,用来转发消息,只要有消息,遍历map,给每个成员发送消息
	go Manager()
	//主协程,循环堵塞等待用户连接
	for{
     
		conn,err1 := listener.Accept()
		if err1 != nil {
     
			fmt.Println("listener Accept err:",err1)
			continue
		}

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

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)
	//广播某个在线
	//message <- "[" + cli.Addr + "]" + cli.Name + ":login"
	message <- MakeMsg(cli,"login")
	//提示当前用户是谁
	cli.C <- MakeMsg(cli,"I`m here")
	isQuit := make(chan bool)	//对方是否主动退出
	hasDate := make(chan bool)	//对方是否有数据发送
	//新建一个协程,接受用户发送过来的数据
	go func() {
     
		buf := make([]byte,2048)
		for{
     
			n,err2 := conn.Read(buf)
			if n == 0 {
     	//对方断开或者出问题
				isQuit <- true
				fmt.Println("conn.Read err:",err2)
				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) >= 3 && msg[:6] == "rename"{
     	//更改用户名
				name := strings.Split(msg,"|")[1]
				cli.Name = name
				onlineMap[cliAddr] = cli
				conn.Write([]byte("rename ok\n"))
			} else {
     
				//转发此内容
				message <- MakeMsg(cli,msg)
			}
			hasDate <- true	//代表有数据

		}
	}()
	for {
     
		//通过select检测channel的流动
		select {
     
		case <- isQuit:
			delete(onlineMap,cliAddr)	//当前用户从map移除
			message <- MakeMsg(cli,"login out\n")	//广播谁下线了
			return
		case <- hasDate:
		case <- time.After(60*time.Second):	//60秒后超时
			delete(onlineMap,cliAddr)	//当前用户从map移除
			message <- MakeMsg(cli,"time out leave out\n")	//广播谁下线了
		}
	}
}

//新开一个协程,用来转发消息,只要有消息,遍历map,给每个成员发送消息
func Manager(){
     
	//给map分配空间
	onlineMap = make(map[string]Client)
	for{
     
		msg := <-message
		for _,cli := range onlineMap{
     
			cli.C <- msg
		}

	}
}

func WriteMsgToClient(cli Client,conn net.Conn){
     
	for msg := range cli.C{
     		//给当前客户端发送信息
		conn.Write([]byte(msg + "\n"))
	}
}

你可能感兴趣的:(作业,笔记,go)