服务器端代码解析

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强制转换之后,得到字符串类型。
 
 
 
 
 

                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值