前言
学过Java的同学在学习socket编程章节的时候,应该都有使用socket编码实现聊天室的经历,在我看了go的socket的编程的基础知识之后,回想着Java的聊天室的经历,同时看到网上有go语言实现的聊天室的资料,看着设计的功能,于是自己编码实现,里面加入了一些的自己的个人喜好。同学们看着代码,可以自行修改功能,仅作娱乐。
核心技术
- 需要对go语言对管道有比较熟练的操作与认识
- 需要对协程知识了解
- 需要了解socket编程
- 了解网页爬虫(可选)
- 需要了解正则表达式对使用
- 需要了解锁机制
- 了解io (可选)
注:可选部分如果你也了解,可以丰富所要使用的数据,对于主题功能影响不大
功能介绍
- 用户列表功能
用户上线之后,输入users命令,则会提示当前在线的用户列表,其中管理员为红色字体,用户自己则是红色背景白色字体

- 艾特某人功能
格式为@用户名+空格+消息体,例如用户输入@王语嫣 神仙姐姐表示@王语嫣这个用户,并且朝她发送了神仙姐姐的消息,这个消息所有在线用户都会看到,

- 用户上线提醒

- 私信用户功能
与艾特某人不同,私信某人的消息只能被被私信的人看到,命令格式为格式为@@用户名+空格+消息体

- 用户下线提醒

- 群发颜色变化

- 管理员踢出用户功能
实现策略是给每个用户分配一个阻塞通道,每当这个用户接收到被踢出的信号后,则朝这个用户的阻塞通道中写入数据,则监听这个通道的协程则会进行倒计时,然后关闭这个通道,从而实现用户被迫下线的效果
核心代码
var isKill = make(chan bool)
//每个用户对应一个协程,处理每个用户的状态,此处主要用来监控用户的活跃状态
go func() {
for {
select {
//如果能取到值,那么计时器应该重新计时,此时,走不到上面的倒计时,计时器的管道一直处于阻塞状态
case <-isKill:
<-time.After(5 * time.Second)
//只要能取出时间,就关闭链接
err := conn.Close()
if err != nil {
return
}
return
命令格式为tt用户名tt,例如tt段誉tt表示将段誉踢出聊天室

8. 被踢出倒计时与进度条的功能
之前写过一篇用来显示读取文章的进度条的功能,利用的思路是,写出的消息不换行,后面写入的消息会覆盖掉前面一行的内容,因此可以采用字符串不断拼接的策略,但是此处想要实现的功能是倒计时,如果还采用之前的思路,那么第一次写出的数据会把后面写出的覆盖,因为第一次输出的数据是最多的,因此需要换个思路,此处的思路是将后面的数据不断的倒数着进行变换,造成一种数据在不断后退的错觉,而实际上是每次输出的数据量是相同的,只不过每次输出的数据的末尾不通而已
效果见上图
核心代码
//此处设置类似进度条的功能,只不过此处的目的是想让进度条有中倒退的感觉
str := "##################################################"
for i := 50; i >= 0; i-- {
runes := []rune(str)
temp := string(runes[:i])
for j := 0; j < 50-i; j++ {
temp += "<"
}
_, _ = conn.Write([]byte(temp + "\r"))
time.Sleep(100 * time.Millisecond)
}
- 超时未发言自动被清理下线功能
此处大量采用了go语言中的管道知识,策略是为每个用户分配一个是否活跃的阻塞管道,每当当前用户发言,则向管道内写入一个数据,那么从管道中取出数据的代码将会解除阻塞,此时,倒计时的代码则没有机会执行,如果执行了,那么在没有倒计时结束时,每当用户发言后,计时器将会被重置。
核心代码
var isActive = make(chan bool)
//是否有被踢出信号
var isKill = make(chan bool)
//每个用户对应一个协程,处理每个用户的状态,此处主要用来监控用户的活跃状态
go func() {
for {
select {
case <-time.After(IdleTime):
err := conn.Close()
if err != nil {
return
}
return
case <-isActive:
//如果能取到值,那么计时器应该重新计时,此时,走不到上面的倒计时,计时器的管道一直处于阻塞状态
case <-isKill:
<-time.After(5 * time.Second)
//只要能取出时间,就关闭链接
err := conn.Close()
if err != nil {
return
}
return
}
}
}()
//如果发出的信号是踢出信号,则判断这个用户是否是管理员
//再来判断被踢出的用户是否还存在,如果存在则执行踢出操作
//假设被踢出的用户是xxx,需要通知用户你即将被踢出下线
if len(usersSlice) > 0 && user.Name == usersSlice[0] {
member := getMember(msg)
if member != "" {
flag := false
for _, s := range usersSlice {
if member == s {
flag = true
break
}
}
if flag {
//向全局管道发送消息
globalMsgChannel <- fmt.Sprintf("%s将被踢出聊天室", member)
continue
}
}
}

10. 用户上线后随机给予默认用户名
我从网上采集了 《三国演义》、《红楼梦》、歌曲名、中国所有地级市城市名、《奥特曼大全》、qq网名几种类型的昵称,每次服务器启动后,会随机从这些种类里面选取一种来作为当前聊天室的默认用户名库存。每当有用户上线,则会随机分配一个。并且保证不会重复
核心代码
//将所有的网名写入到map中
func init() {
path := "/Users/java0904/goProject/chathouse/json"
dir, err := ioutil.ReadDir(path)
if err != nil {
panic(err)
}
rand.Seed(time.Now().Unix())
info := dir[rand.Intn(len(dir))]
file, err := os.Open(path + "/" + info.Name())
if err != nil {
panic(err)
}
defer file.Close()
all, err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}
var names []string
err = json.Unmarshal(all, &names)
if err != nil {
panic(err)
}
for _, name := range names {
nicknameMap[name] = name
}
}
package main
import (
"bufio"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
)
func main() {
var nameSlice []string
open, err := os.Open("/Users/java0904/goProject/chathouse/nickname/hongloumeng.txt")
if err != nil {
fmt.Println(err)
}
defer open.

本文详细介绍了如何使用Go语言实现一个功能丰富的聊天室,涉及管道操作、协程应用、用户列表、艾特功能、私信、管理员权限、倒计时踢出及自动清理等。通过实例展示了如何结合并发编程和网络通信技术。
最低0.47元/天 解锁文章
2148

被折叠的 条评论
为什么被折叠?



