package main
import (
"bufio"
"database/sql"
"fmt"
"net"
"os"
"strconv"
"time"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
func checkErr(err error) {
if err != nil {
fmt.Println(err)
}
}
func setupDB() {
var err error
rootDbPwd := "000000"
connStr := "root:" + rootDbPwd + "@/mysql?charset=utf8&loc=Local&parseTime=true"
//连接数据库
db, err = sql.Open("mysql", connStr)
// 还有以下几种格式
// user:password@tcp(localhost:5555)/dbname?charset=utf8
// user:password@/dbname
// user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname
checkErr(err)
//真正连接,如果不成功,有错误处理
err = db.Ping()
checkErr(err)
cr_db := "CREATE DATABASE IF NOT EXISTS qnearBE DEFAULT CHARSET utf8 COLLATE utf8_general_ci;"
//准备一个数据库query操作,返回一个指针
stmt, err := db.Prepare(cr_db)
checkErr(err)
//执行一个query语句
_, err = stmt.Exec()
checkErr(err)
//释放指针资源
stmt.Close()
//权限设置
grantSQL := "grant all on qnearBE.* to cstAdmin identified by 'cstDb4ever';"
stmt, err = db.Prepare(grantSQL)
checkErr(err)
_, err = stmt.Exec()
checkErr(err)
stmt.Close()
//权限设置
grantSQL = "grant all on qnearBE.* to cstAdmin@'' identified by 'cstDb4ever';"
stmt, err = db.Prepare(grantSQL)
checkErr(err)
_, err = stmt.Exec()
checkErr(err)
stmt.Close()
//权限设置
grantSQL = "grant all on qnearBE.* to cstAdmin@'localhost' identified by 'cstDb4ever';"
stmt, err = db.Prepare(grantSQL)
checkErr(err)
_, err = stmt.Exec()
checkErr(err)
stmt.Close()
//权限设置
grantSQL = "grant all on qnearBE.* to cstAdmin@'127.0.0.1' identified by 'cstDb4ever';"
stmt, err = db.Prepare(grantSQL)
checkErr(err)
_, err = stmt.Exec()
checkErr(err)
stmt.Close()
//关闭数据库
db.Close()
//进行数据库其他用户的登录准备
dbPwd := "cstDb4ever"
connStr = "cstAdmin:" + dbPwd + "@/qnearBE?charset=utf8&loc=Local&parseTime=true"
//其他用户登录数据库
db, err = sql.Open("mysql", connStr)
checkErr(err)
err = db.Ping()
checkErr(err)
cr_table := "create table if not exists t_msg(msg_id int auto_increment primary key, peer varchar(64),msg varchar(128),recvTime datetime not null default 0)"
stmt, err = db.Prepare(cr_table)
checkErr(err)
defer stmt.Close()
_, err = stmt.Exec()
checkErr(err)
}
func keepMsg(arg_msg string, arg_peer string) {
//准备,执行,插入
sql := "insert into t_msg(peer,msg) values(?,?)"
stmt, err := db.Prepare(sql)
checkErr(err)
defer stmt.Close()
_, err = stmt.Exec(arg_peer, arg_msg)
checkErr(err)
}
func queryMsg(arg_peer string) string {
//准备从t_msg中读取 msg,recvTIme的信息
sql := "select msg,recvTime from t_msg"
stmt, err := db.Prepare(sql)
checkErr(err)
defer stmt.Close()
checkErr(err)
//执行操作:查询
//将获得的指针赋值给rows
rows, err := stmt.Query()
checkErr(err)
defer rows.Close()
msg := ""
//获得系统时间
recvTime := time.Now()
msgList := ""
//逐条遍历
for rows.Next() {
//浏览获得的信息和世间
rows.Scan(&msg, &recvTime)
//最后一个是回车符
msgList = msgList + recvTime.Format("15:04:05 ") + msg + "\r\n"
}
return msgList
}
var clnOnLineChannel chan net.Conn
var clnOffLineChannel chan net.Conn
var msgChannel chan string
func showOnLines(arg_conns map[string]net.Conn) {
//len(arg_conns)表示map数组的长度,
//Itoa将int转换成string类型
//所以这行显示第x个(非指定)网络流有状态发生
显示当前在线人数
fmt.Println("Online Number: " + strconv.Itoa(len(arg_conns)))
}
//这个函数主要处理3个channel的消息读取
func clnMgr() {
//有多余定义的嫌疑
clnOnLineChannel = make(chan net.Conn)
clnOffLineChannel = make(chan net.Conn)
msgChannel = make(chan string)
connList := make(map[string]net.Conn)
for {
select {
//客户端上线处理
case clnSck := <-clnOnLineChannel:
//获取客户端地址信息
clnSap := clnSck.RemoteAddr().String()
//提示某个客户端(ip+端口)上线
fmt.Println(clnSap + " online")
//创建map数组(地址为索引,客户端为值)
connList[clnSap] = clnSck
//显示当前在线人数,调用显示有一个客户端上线
showOnLines(connList)
//客户端掉线处理
case clnSck := <-clnOffLineChannel:
//获取客户端地址信息
clnSap := clnSck.RemoteAddr().String()
//提示某个客户端(ip+端口)掉线
fmt.Println(clnSap + " offline")
//删除这个map(数组名称+字符串索引)
delete(connList, clnSap)
//关闭这个客户端
clnSck.Close()
//显示当前在线人数,调用显示有一个客户端下线
showOnLines(connList)
//从服务端发送到客户端的消息处理
case msg := <-msgChannel:
//将msg的信息传递给bmsg切片
bMsg := []byte(msg)
//for range的使用,应用与map数组中
//将每一个map(客户端)数组赋值给v
for _, v := range connList {
//将信息发送给客户端
_, err := v.Write(bMsg)
//发送消息失败
if err != nil {
fmt.Println(err)
//客户端掉线处理
clnOffLineChannel <- v
}
}
}
}
}
func recv(clnSck net.Conn) {
//but定义成了一个slice切片(定义方式是make,所以每个源数组元素都是0,一共1024个),
//长度容量均是1024,可以读取足够长(1024个)字符信息。
//其中的byte是指int8 2的8次方个字符(0~255个ASCLL2码)
buf := make([]byte, 1024)
for {
//clnSck.Read()其中变量clnSck(conn类型)定义了Read()方法,
//其方法的参数是一个切片slice(buf),返回值包括一个int类型表示字节长度(赋值给了dataLen),
//另一个error类型赋值给了err。
//读取到数据流结尾时(err==io.EOF),break;
dataLen, err := clnSck.Read(buf)
//无法读取到客户端信息
if err != nil {
fmt.Println(err)
//客户端掉线处理
clnOffLineChannel <- clnSck
return
}
//从but切片的0位置一直到dataLen数字的前一个,
//注意这里显示的都是数字,0~255的数字中的一个。
//而string()将buf切片的元素全部转换成字符格式。
//最后,从clnSck得到的信息不包括最后一个字符(一般是回车),从dataLen中可以知道。
msg := string(buf[:dataLen])
//fmt.Println自带回车
fmt.Println(msg)
//获取ip+端口信息
sap := clnSck.RemoteAddr().String()
//通过数据库保存信息(收到信息内容+地址信息)
keepMsg(msg, sap)
//鉴别消息类型,若是query,进行查询操作
if len(msg) == 5 && msg[0:5] == "query" {
msgList := queryMsg(sap)
//同read(conn.read()这个是conn.write())参数也是一个切片slice
//msgList是一个string类型,通过[]byte强制转换成切片slice
//将获得的消息发送给客户端
clnSck.Write([]byte(msgList))
}
}
}
func getMsg() (msg string) {
//bufio.NewReader返回一个指针*Reader
//下面式子表示创建了一个读取器
reader := bufio.NewReader(os.Stdin)
//ReadString(delim byte)是一个方法
//读取到delim字符后结束,并且返回error=nil给err
//所以msg得到的是os.Stdin输入的字符串加上('\n')换行。(切记)
//最后返回字符串msg,对应函数名称,获得了Msg+('\n')的信息。
msg, err := reader.ReadString('\n')
checkErr(err)
return
}
func msgToCln() {
for {
msg := getMsg()
//将键盘输入的字符串写入到msgchanel中
//发送消息处理
msgChannel <- msg
}
}
func main() {
//数据库系统操作
setupDB()
//socket
srvSck, err := net.Listen("tcp", ":6666")
if err != nil {
fmt.Println(err)
return
}
defer srvSck.Close()
//同步对客户端信息(channel)管理
go clnMgr()
//同步发送信息
go msgToCln()
//用for一直在监听端口
for {
clnSck, err := srvSck.Accept()
if err != nil {
fmt.Println(err)
return
}
//客户端上线处理
clnOnLineChannel <- clnSck
//同步接受信息
go recv(clnSck)
}
}
//小结
//1.向conn类型进行读写操作时,read,write,函数的参数都是切片,显示信息一般用字符串.
//向客户端发送信息也就是,从键盘输入得到字符串,经过[]byte强制转换之后,得到切片类型,
//然后,客户端那边经过又将切片类型,经过string强制转换之后,得到字符串类型。