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強制轉換之後,得到字符串類型。