前言
前一篇说了聊天室客户端的实现,这一篇就接着上篇讲服务端,与很多聊天室不同,我在服务端里加入了管理员,管理员与普通用户的功能相比多了屏蔽发言,以及踢出群聊的功能
数据的接收与发送
和客户端的没什么不同
func recv(recvData chan string, conn net.Conn) {
for {
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
connection <- conn
return
}
recvData <- string(buf[:n])
}
}
/**
* 数据发送
* sentData 接收数据Channel
* conn TCP连接对象
**/
func sent(sentData chan string, conn net.Conn) {
for {
data := <-sentData
_, err := conn.Write([]byte(data))
if err != nil {
connection <- conn
return
}
}
}
客户端的连接管理
func connManager(connection chan net.Conn) {
for {
conn := <-connection
username := ipToUname[conn.RemoteAddr().String()]
notesInfo(messages, "用户:"+username+"已退出聊天室!")
conn.Close()
delete(conns, username)
delete(ipToUname, conn.RemoteAddr().String())
}
}
数据解析与封装
func dataSent(conns *map[string]clientInfo, messages chan string) {
for {
msg := <-messages
fmt.Println(msg)
data := strings.Split(msg, "-") // 聊天数据分析:
length := len(data)
if length == 2 { // 管理员单个用户发送控制命令
(*conns)[data[0]].sentData <- data[1]
} else if length == 3 { // 用户列表
(*conns)[data[1]].sentData <- data[2]
} else if length == 4 { // 向单个用户发送数据
msg = data[1] + " say to you : " + data[3]
(*conns)[data[2]].sentData <- msg
} else {
// 群发
for _, value := range *conns {
value.sentData <- msg
}
}
}
}
管理员注册处理
/**
* 获取全部已注册用户的数据,用于登录注册时验证
*/
func getAllUser(filename string) map[string]userData {
buf := make([]byte, dataSize)
udata := make(map[string]userData)
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0766) //打开保存用户的文件
defer file.Close() //保证文件关闭
errorExit(err, "getAllUser")
n, _ := file.Read(buf)
json.Unmarshal(buf[:n], &udata) //将流文件转成json并赋值给udata这个map
return udata
}
/**
* 添加新注册用户数据
*/
func insertNewUser(filename, username, password string) {
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0766)
defer file.Close()
checkError(err, "insertNewUser")
uData[username] = userData{password, 1}
data, _ := json.MarshalIndent(uData, "", " ")
file.WriteString(string(data))
}
func regProcess(username, password string) (status bool, info string) {
if len(uData) == maxReg {
return false, "注册人数已满!"
}
if _, ok := uData[username]; ok {
return false, "用户已存在,请更换注册名!"
} else {
insertNewUser(dataFileName, username, password)
return true, success
}
}
管理员登录处理
func logProcess(username, password string) (status bool, info string) {
if len(conns) == maxLog {
return false, "当前登录人数已满,请稍后登录"
}
if user, ok := uData[username]; !ok {
return false, "用户名或密码错误!"
} else {
if user.Password == password {
if _, ok := conns[username]; ok {
return false, "用户已登录!"
} else {
return true, success
}
} else {
return false, "用户名或密码错误!"
}
}
}
用户进入聊天室资格认证
func userAuth(conn *net.Conn, recvData, sentData chan string) {
for {
data := strings.Split(<-recvData, "-") // 等待用户发送登录或注册数据
flag, username, password := data[0], data[1], data[2]
if flag == "Reg" {
_, info := regProcess(username, password)
sentData <- info
} else {
status, info := logProcess(username, password)
sentData <- info
if status == true {
messages <- ("用户:" + username + "进入聊天室") // 用户登录成功,发送系统通知
conns[username] = clientInfo{*conn, sentData} // 记录用户登录状态信息
ipToUname[(*conn).RemoteAddr().String()] = username // 记录IP与用户名对应信息
messages <- "List-" + username + "-" + userList() // 向用户发送已在线用户列表
go dataRec(&conns, username, recvData, messages) // 开启服务器对该客户端数据接收线程
break
}
}
}
}
服务端的tcp连接创建
func createTCP(tcpaddr string) *net.TCPListener {
tcpAddr, err := net.ResolveTCPAddr("tcp4", tcpaddr)
errorExit(err, "ResolveTCPAddr")
l, err := net.ListenTCP("tcp", tcpAddr)
errorExit(err, "DialTCP")
return l
}
管理员的登录注册
func intoManager() {
fmt.Println("请输入将要进行操作:1、管理员注册 2、管理员登录")
var input string
LOOP:
{
fmt.Scanln(&input)
switch input {
case "1":
adminReg()
case "2":
adminLog()
default:
goto LOOP
}
}
admimManager(messages)
}
/**
* 管理员登录程序
**/
func adminLog() {
for {
var adminname, password string
fmt.Println("请输入管理员用户名:")
fmt.Scanln(&adminname)
fmt.Println("请输入管理员密码:")
fmt.Scanln(&password)
if pwd, ok := adminList[adminname]; !ok {
fmt.Println("用户名或者密码错误")
} else {
if pwd != password {
fmt.Println("用户名或者密码错误!")
} else {
fmt.Println("登录成功!")
break
}
}
}
}
/**
* 管理员注册程序
**/
func adminReg() {
var adminname, password string
fmt.Println("请输入管理员用户名:")
fmt.Scanln(&adminname)
fmt.Println("请输入管理员密码:")
fmt.Scanln(&password)
adminList[adminname] = password //将注册的管理员姓名密码保存到adminList中,单次启动有效
fmt.Println("注册成功!请登录")
adminLog() //跳转到登录
}
管理员一系列功能实现
func admimManager(messages chan string) {
for {
var input, objUser string
fmt.Scanln(&input)
switch input {
case "/to":
fmt.Println(userList())
fmt.Println("请输入聊天对象:")
fmt.Scanln(&objUser)
if _, ok := conns[objUser]; !ok {
fmt.Println("不存在此用户!")
} else {
fmt.Println("请输入消息:")
fmt.Scanln(&input)
notesInfo(messages, "To-Manager-"+objUser+"-"+input)
}
case "/all":
fmt.Println("请输入消息:")
fmt.Scanln(&input)
notesInfo(messages, "Manager say : "+input)
case "/shield":
fmt.Println(userList())
fmt.Println("请输入屏蔽用户名:")
fmt.Scanln(&objUser)
notesInfo(messages, objUser+"-/shield")
notesInfo(messages, "用户:"+objUser+"已被管理员禁言!")
case "/remove":
fmt.Println(userList())
fmt.Println("请输入踢出用户名:")
fmt.Scanln(&objUser)
notesInfo(messages, "用户:"+objUser+"已被管理员踢出聊天室!")
if _, ok := conns[objUser]; !ok {
fmt.Println("不存在此用户!")
} else {
conns[objUser].conn.Close() // 删除该用户的连接
delete(conns, objUser) // 从已登录的列表中删除该用户
}
}
}
}
服务端启动程序
func StartServer(port string) {
l := createTCP(":" + port)
fmt.Println("服务端启动成功,正在监听端口!")
go dataSent(&conns, messages) // 启动服务器广播线程
go intoManager() // 启动管理模块
go connManager(connection) // 启动连接管理线程
for {
conn, err := l.Accept()
if checkError(err, "Accept") == false {
continue
}
fmt.Println("客户端:", conn.RemoteAddr().String(), "连接服务器成功!")
var recvData = make(chan string)
var sentData = make(chan string)
go recv(recvData, conn) // 开启对客户端的接受数据线程
go sent(sentData, conn) // 开启对客户端的发送数据线程
go userAuth(&conn, recvData, sentData) // 用户资格认证
}
}
服务端主程序
func main() {
if len(os.Args) == 2 {
uData = getAllUser(dataFileName) // 用户登录、注册数据初始化
StartServer(os.Args[1]) // 启动客户端
} else {
fmt.Println("输入错误!")
}
}
用到的标准库
import (
"encoding/json"
"fmt"
"net"
"os"
"strings"
)
总结
终于写完了,啊啊啊!
源码地址
https://gitee.com/qian-xiawudi/test1