服務器端代碼解析

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






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章