Go语言练习:聊天室(二)——在线用户查询、修改用户名、超时强踢、私聊功能

在线用户查询

 

先查询map中的用户,将信息写到OnlineMsg中

再将OnlineMsg写到user管道中,由user的监听管道功能发送给对应的client。

总结一下handler函数中现有的功能:

1、用户上线,将用户加入map中

2、广播用户上线消息

3、循环接收用户消息,并判断是不是“who”,是就行查询,不是就广播。

单独和广播的区别在于一个在for循环外,一个在for循环里

修改用户名

超时强踢

 

笔记:

select格式:

select {
    case <-ch1:
        // 如果从 ch1 信道成功接收数据,则执行该分支代码
    case ch2 <- 1:
        // 如果成功向 ch2 信道成功发送数据,则执行该分支代码
    default:
        // 如果上面都没有成功,则进入 default 分支处理流程
}

1、select里的case后面不带判断条件,而是一个信道的操作。

2、select的case条件是并发执行的,select会选择先操作成功的那个case条件去执行,如果多个同时返回,则随机选择一个执行,此时将无法保证执行顺序。

3、如果有超时条件语句,判断逻辑为如果在这个时间段内一直没有满足条件的case,则执行这个超时case。如果此段时间内出现了可操作的case,则直接执行这个case。一般用超时语句代替了default语句。

1、实际超时使用:

select {
    case str := <- ch1:
        fmt.Println("receive str", str)
    case <- time.After(time.Second * 5): 
        fmt.Println("timeout!!")
}

2、阻塞main函数

让main函数阻塞不退出,使用空的select{}来阻塞main函数。

私聊功能

现将处理msg的函数单独拿出来

加入对私聊的判断:

if len(msg) > 4 && msg[:3] == "to|" {...}

调试:

 最后附上现有代码:

其中client.go和user.go没有变化和上一版一样Go语言练习:聊天室(一)——server搭建、client搭建、用户上线提醒_you_you_lu的博客-CSDN博客

只有server.go进行改动,除了加入新功能外,将一些代码(上线加入map,下线从map删掉,发送消息)进行封装,使得代码更加简洁。

package main

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

// 创建server类型
type Server struct {
	Ip   string
	Port int
	//增加onlineMap
	OnlineMap map[string]*User //map是哈希列表,键是字符串类型,值是指向User类型对象的指针
	mapLock   sync.RWMutex     //读写锁,用于保护map

	//消息广播的channel
	Message chan string
}

// 创建server的接口,用于创建一个server类型的实例
func NewServer(ip string, port int) *Server {
	server := &Server{
		Ip:        ip,
		Port:      port,
		OnlineMap: make(map[string]*User),
		Message:   make(chan string),
	}
	return server
}

// 监听Message广播消息channel的goroutine,一旦有消息就发送给全部的在线User
func (s *Server) ListenMessager() {
	for {
		msg := <-s.Message //从管道中读数据
		//将msg发送给全部的在线User
		s.mapLock.Lock()
		for _, cli := range s.OnlineMap { //key不关心,只关心value,value就是指向User的指针
			cli.C <- msg

		}

		s.mapLock.Unlock()

	}

}

// 广播消息的方法 将上线消息写到Message通道中
func (s *Server) Broadcast(user *User, msg string) {
	sendMsg := "[" + user.Name + "]" + ":" + msg
	//将sendMsg写到通道中
	s.Message <- sendMsg

}

// 给当前user管道发消息
func (s *Server) sendMessage(user *User, msg string) {
	user.C <- msg
}

// 对msg进行处理
func (s *Server) handleMessage(user *User, msg string) {

	//查询功能
	if msg == "who" {
		s.mapLock.RLock()
		OnlineMsg := "Online Users:"
		for _, cli := range s.OnlineMap {
			OnlineMsg += "[" + cli.Name + "]"

		}
		user.C <- OnlineMsg
		s.mapLock.RUnlock()

	} else if len(msg) > 7 && msg[:7] == "rename:" { //修改名称

		//消息格式rename:张三
		Newname := msg[7:] //获取名字
		//判断名字是否被占用
		_, ok := s.OnlineMap[Newname]
		if ok {
			s.sendMessage(user, "The name is already taken")
		} else {
			s.mapLock.Lock()
			delete(s.OnlineMap, user.Name)
			s.OnlineMap[Newname] = user
			s.mapLock.Unlock()

			user.Name = Newname
			s.sendMessage(user, "You have updated your username:"+Newname)
		}
	} else if len(msg) > 4 && msg[:3] == "to|" {
		//to|zhangsan|...

		//获取名字
		Getname := strings.Split(msg, "|")[1]
		if Getname == "" {
			s.sendMessage(user, "The message format is incorrect")
			return

		}
		//根据名字获取对方User对象,利用map
		Getuser, ok := s.OnlineMap[Getname]
		if !ok {
			s.sendMessage(user, "The user does not exist")
			return
		}
		//获取消息
		content := strings.Split(msg, "|")[2]
		if content == "" {
			s.sendMessage(user, "There is no message content, please resend it")
			return
		}
		s.sendMessage(Getuser, user.Name+" say: "+content)

	} else {

		//将消息广播
		s.Broadcast(user, msg)
	}
}

// 用户上线,将用户加入map中
func (s *Server) Online(user *User) {
	//用户上线,将用户加入map中

	s.mapLock.Lock()
	s.OnlineMap[user.Name] = user
	s.mapLock.Unlock()

}

// 用户下线,将用户从map中删掉
func (s *Server) offline(user *User) {
	s.mapLock.Lock()
	delete(s.OnlineMap, user.Name)
	s.mapLock.Unlock()
}

func (s *Server) Handler(conn net.Conn) {
	user := NewUser(conn)

	//用户上线,将用户加入map中
	s.Online(user)
	//广播当前用户上线消息
	s.Broadcast(user, "user is online")

	//监听用户是否活跃的channel
	isLive := make(chan bool)

	//接受客户端发送的消息
	go func() {
		buf := make([]byte, 4096)
		for {
			n, err := conn.Read(buf)
			if n == 0 {

				s.offline(user)
				s.Broadcast(user, "offline")
				return

			}
			if err != nil && err != io.EOF {
				fmt.Println("服务端的Read err=", err)
				return //这个很重要

			}
			//提取用户消息(去除'\n')
			msg := string(buf[:n-1])

			s.handleMessage(user, msg)

			isLive <- true //能收到用户消息,说明活跃
		}

	}()

	for {
		select {
		case <-isLive: //说明用户是活跃的,应该重置定时器

		case <-time.After(time.Minute * 10): //说明超时
			s.sendMessage(user, "You've been kicked out")
			//销毁资源
			close(user.C)
			conn.Close()
			//从map中删掉
			s.mapLock.Lock()
			delete(s.OnlineMap, user.Name)
			s.mapLock.Unlock()

			return //退出hanlder

		}
	}

}

// 启动服务器的端口
func (s *Server) Start() {
	//socket listen
	fmt.Println("服务端开始监听...")
	listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Ip, s.Port))
	if err != nil {
		fmt.Println("listen err=", err)
		return
	}

	defer listener.Close() //close

	//启动监听Message的goroutine
	go s.ListenMessager()

	//accept
	for {

		//等待客户端连接
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("Accept err=", err)

		} else {
			fmt.Printf("连接到客户端ip=%v\n", conn.RemoteAddr().String()) //查看conn
		}

		//do handler
		go s.Handler(conn)

	}

}

func main() {
	server := NewServer("127.0.0.1", 8888) //创建实例
	server.Start()
}

注:server.go和client.go不要放在同一个文件夹里。 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值