golang 聊天室學習筆記

1聊天室服務器端

package main

import (
    "fmt"
    "net"
    "strings"
    "time"
)

//定義的此結構體爲全局map的value值,包括每一個用戶的姓名,ip地址和私人管道
type client struct {
    name string
    addr string
    C    chan string
}

/*這個函數是將私人管道中的內容發送給用戶,配合全局管道Message使用可以實現廣播的功能,
單獨使用可以實現私聊的功能*/
func writemsg2client(clinet client, conn net.Conn) {
    for m := range clinet.C {
        conn.Write([]byte(m + "\n"))
    }
}

//這只是一個封裝好用來統一(發送信息格式)的小函數,不用在意
func makemsg(name string, addr string, s string) string {
    return "[" + addr + "]" + name + ":" + s
}

//每一個進入聊天室的用戶都將啓動一個handleconn的go程來處理事件
func handleconn(conn net.Conn) {
    defer conn.Close()
    /*用戶連接進來以後要初始化全局map,把自己的信息加入到字典裏,相當於進到聊天室裏之前要登
      記一下個人信息,注意姓名初始爲ip地址。*/
    addr := conn.RemoteAddr().String()
    fmt.Printf("用戶%s進入了房間\n", addr)
    client := client{addr, addr, make(chan string)}
    //在這裏啓動子go程,功能上面已經提及,具體就是會寫信息給自己連接的客戶端
    go writemsg2client(client, conn)
    onlinemap[addr] = client
    //登錄進來一切準備就緒後就給所有人廣播上線信息啦
    Message <- makemsg(client.name, addr, "login")
    //下面這三個變量服務於下面一些小功能
    var haschat = make(chan bool)
    var ifquit = make(chan bool)
    var flag bool
    //從這單獨開啓一個go程來讀取用戶輸入的信息
    go func() {
        buf := make([]byte, 4096)
        for {
            n, _ := conn.Read(buf)
            if n == 0 {
                fmt.Printf("%s離開了房間\n", client.name)
                ifquit <- true
                return
            }
            //改名功能的實現
            if string(buf[:7]) == "Rename|" {
                client.name = strings.Split(string(buf[:n]), "|")[1]
                onlinemap[addr] = client
                conn.Write([]byte("rename success\n"))
            } else if string(buf[:n-1]) == "/who" {
                //查詢在線用戶信息的功能
                for _, s := range onlinemap {
                    conn.Write([]byte(s.name + "online\n"))
                }
            } else if string(buf[:2]) == "m|" && strings.Count(string(buf[:n]), "|") == 2 {
                /*私聊功能的實現,其實私聊功能就是跳過了往全局Message裏傳輸信息,
                  改爲直接向私人管道里傳輸信息*/
                flag = true
                slice := strings.Split(string(buf[:n]), "|")
                fmt.Println(slice[1])
                for _, a := range onlinemap {
                    //遍歷所有在線用戶,向指定的用戶管道中發送信息
                    if a.name == slice[1] {
                        flag = false
                        a.C <- makemsg(client.name, addr, slice[2])
                        conn.Write([]byte("send success"))

                    }
                }
                if flag {
                    conn.Write([]byte("no such man or not online"))
                }
            } else {
                Message <- makemsg(client.name, addr, string(buf[:n]))
            }
            haschat <- true
        }
    }()
    for {
        select {
        case <-haschat:
        //超時強踢
        case <-time.After(time.Minute * 100):
            delete(onlinemap, addr)
            Message <- makemsg(client.name, addr, "out time to leave")
            close(client.C)
            return
        case <-ifquit:
            //退出處理
            delete(onlinemap, addr)
            Message <- makemsg(client.name, addr, "out time to leave")
            close(client.C)
            return
        }
    }
}

//這個函數用來將全局Message中的內容全部塞到私人管道C裏,實現上下線廣播和羣聊的功能
func Manager() {
    for {
        msg := <-Message
        for _, s := range onlinemap {
            s.C <- msg
        }
    }
}

var Message = make(chan string)
var onlinemap map[string]client = make(map[string]client)

//主函數
func main() {
    listener, _ := net.Listen("tcp", "127.0.0.1:9876")
    defer listener.Close()
    //提前開啓全局Message的go程,防止被阻塞
    go Manager()
    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("accept err", err)
            continue
        }
        //每一個連接進來的用戶都會被分配進入一個子go程,用來處理上面我們提到的各種功能
        go handleconn(conn)
    }
}

/*備註
1、  listener, _ := net.Listen("tcp", "127.0.0.1:9876")
監聽啓動

2、  go Manager()開啓全局Message的go程,防止被阻塞,沒有消息便被阻塞,有消息便會被喚起,
消息發送完畢後重新等待消息,有消息變發送沒消息便阻塞等待(Message 是一個字符串channel )。

接收到消息後,遍歷所有在線人員,並把消息發送給client的私人通道。

func Manager() {
    for {
        msg := <-Message
        for _, s := range onlinemap {
            s.C <- msg
        }
    }
}

3、私人通道消息處理
這個函數是將私人管道中的內容發送給用戶,配合全局管道Message使用可以實現廣播的功能。
單獨使用可以實現私聊的功能(m|客戶端連接ip加端口|發送消息)(m|127.0.0.1:59700|hello)。
這個函數也是等待消息,收到消息後被喚醒執行,消息執行完畢後等待新消息,沒有阻塞,有就處理
func writemsg2client(clinet client, conn net.Conn) {
    for m := range clinet.C {
        conn.Write([]byte(m + "\n"))
    }
}


*/

2、聊天室客戶端

package main

import (
    "bufio"
    "fmt"
    "net"
    "os"
    "strings"
)

func readFromServer(conn net.Conn) {
    buf := make([]byte, 4096)
    for {
        n, err := conn.Read(buf)
        if err != nil {
            fmt.Println(err)
            os.Exit(1)

        }
        defer conn.Close()
        fmt.Println("接收到消息:", string(buf[:n]))
        fmt.Println("請輸入要發送的消息:")
    }
}

func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:9876")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer conn.Close()
    go readFromServer(conn)
    //fmt.Println("請輸入要發送的消息:")

    for {

        //strs :=""
        // fmt.Scanln(&strs) 空格有問題
        //strs := make([]byte, 4096)

        //n, err := os.Stdin.Read(strs)
        str, err := bufio.NewReader(os.Stdin).ReadString('\n')
        if err != nil {
            fmt.Println(err)
        }
        str = strings.TrimSpace(str)
        //fmt.Println("發送前", , "展示")
        //fmt.Println("a", str, "b")
        if str == "Q" {
            fmt.Println("接收到退出命令,退出客戶端")
            break
        }
        conn.Write([]byte(str))

    }

}


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