go开发的聊天室

88 阅读7分钟

前言

以前从来没有开发过像聊天室这样的通讯项目,所以刚开始写的时候有点茫然,在百度上搜了大量的博客,我不太喜欢看视频。发现很多博主写的都太简单了,功能只有群发消息,私发。还有一些写的很好大事涉及到了go的框架,还有就是使用了redis,不是我说很少见写聊天室使用mysql的,我的百度搜的就是这些。最终结合视频(还是要看视频)以及自己的筛选,写了这个聊天室。我准备分两篇来写。

功能介绍

1.服务端添加了管理员,管理员可以屏蔽某个人,给某个人私发消息,群发通知,把某人踢出群聊 2.客户端主要是群发,私发, 3.还有管理员,用户的登录注册,这里没用mysql,用的是txt文件存的注册用户。

服务端运行

在这里插入图片描述 在这里插入图片描述 上面三幅图是我服务端以及两个客户端的运行界面,主要的运行效果都在上面 咱们先说说客户端的功能实现 数据的输入

func inputMessage() {

	var input, objUser string

	for {

		fmt.Scanln(&input)

		switch input {

		// 用户退出
		case "/quit":
			fmt.Println("退出聊天室,欢迎下次使用!")
			os.Exit(0)

		// 向单个用户发送消息
		case "/to":
			fmt.Println("请输入聊天对象:")
			fmt.Scanln(&objUser)
			fmt.Println("请输入消息:")
			fmt.Scanln(&input)
			if len(input) != 0 {
				input = "To-" + uname + "-" + objUser + "-" + input
			}

			// 默认群发
		default:
			if len(input) != 0 {
				input = uname + " : " + input
			}
		}

		// 发送数据
		if len(input) != 0 {

			if !isSheild {

				sentData <- input

			} else {

				fmt.Println("你已被管理员屏蔽,无法发言!")
			}

			input = ""

		}

	}

}

登录的实现

func clientLog() {

LOOP:
	for {
		var username, password string
		fmt.Println("请输入用户名:")
		fmt.Scanln(&username)
		if username == "" {
			goto LOOP
		}
		fmt.Println("请输入密码:")
		fmt.Scanln(&password)

		sentData <- "Log-" + username + "-" + password

		res := <-recvData

		if res == "success" {

			fmt.Println("登录成功,你已进入聊天室!")

			uname = username // 登录成功保存用户名

			go showMassage() // 启动聊天内容显示协程

			break

		} else {

			fmt.Println(res)
		}
	}

	inputMessage() // 启动聊天内容输入

}

注册的实现

func clientReg() {

	for {

		var username, pwd, pwdCheck string
		fmt.Println("请输入用户名:")
		fmt.Scanln(&username)
		fmt.Println("请输入密码:")
		fmt.Scanln(&pwd)
		fmt.Println("请确认密码:")
		fmt.Scanln(&pwdCheck)

		if pwdCheck != pwd {

			fmt.Println("两次输入密码不一致,请重新注册!")

		} else {

			sentData <- ("Reg-" + username + "-" + pwd) // 发送用户名和密码

			res := <-recvData // 等待登录结果

			if res == "success" {
				fmt.Println("注册成功,请登录")
				break
			} else {
				fmt.Println(res)
			}
		}
	}

	clientLog() // 转至登录
}

创建tcp链接 tcp链接是客户端与服务端相连的一种形式,通过两者相同的端口号来进行交互

func createTCP(tcpaddr string) net.Conn {

	tcpAddr, err := net.ResolveTCPAddr("tcp4", tcpaddr)

	checkError(err, "ResolveTCPAddr")

	conn, err := net.DialTCP("tcp", nil, tcpAddr)

	checkError(err, "DialTCP")

	fmt.Println("连接服务器成功!请输入将要进行的操作:1、登录  2、注册")

	return conn
}

客户端的启动程序

func startClient(tcpAddr string) {

	conn = createTCP(tcpAddr) // 创建TCP连接

	go recv(recvData, conn) // 启动数据接收协程

	go sent(sentData, conn) // 启动数据发送协程

LOOP:
	{

		var input string

		fmt.Scanln(&input)

		if input == "1" {

			clientLog() // 登录

		} else if input == "2" {

			clientReg() // 注册

		} else {

			fmt.Println("输入有误,请重新输入!")
			goto LOOP
		}

	}

}

数据接收和数据发送

func recv(recvData chan string, conn net.Conn) {

	for {

		buf := make([]byte, 1024)

		n, err := conn.Read(buf)

		checkError(err, "Connection")

		recvData <- string(buf[:n])

	}

}

/**
 * 数据发送
 * sentData  接收数据Channel
 * conn      TCP连接对象
 **/
func sent(sentData chan string, conn net.Conn) {

	for {

		data := <-sentData

		_, err := conn.Write([]byte(data))

		checkError(err, "Connection")

	}
}

主程序

func main() {

	if len(os.Args) == 2 {

		startClient(os.Args[1]) // 启动客户端

	} else {

		fmt.Println("输入错误!")

	}

}

客户端引用的标准库

import (
	"fmt"
	"net"
	"os"
	
)

客户端的代码实现就是以上部分,具体的解释都在注释里

前言

前一篇说了聊天室客户端的实现,这一篇就接着上篇讲服务端,与很多聊天室不同,我在服务端里加入了管理员,管理员与普通用户的功能相比多了屏蔽发言,以及踢出群聊的功能

数据的接收与发送 和客户端的没什么不同

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"
)

总结 终于写完了,啊啊啊! 源码地址 gitee.com/qian-xiawud…