edo1z blog

プログラミングなどに関するブログです

Go - チャットアプリをつくってみた

コマンドライン上で、複数人でチャットできるクライアント・サーバ型のやつをつくってみた。まだとりあえず動くレベル。スレッドセーフ?な状態でクライアントをclientListから消す方法がわからなかったから消すのをやめた。あと、参考サイトだとチャンネルをたくさん使ってるので、自分がつくったチープなやつと比較して研究したいと思ってます。せっかくなので、暗号化とかしてみたい。あとはp2pバージョンもつくってみる。自動でpがpを発見できる仕組みをつくりたい。

参考サイト

8.1 Socketプログラミング チャットで学ぶ Go ネットワークプログラミング mshahriarinia/Golang

Github

コード(サーバ)

package main

import (
    "./util"
    "fmt"
    "io"
    "math/rand"
    "net"
    "os"
    "time"
)

type Client struct {
    name  []byte
    conn  net.Conn
    color int
}

var clientList []*Client
var colorList = [5]int{32, 33, 34, 35, 36}

func send(msg []byte) {
    for _, cl := range clientList {
        _, err := cl.conn.Write(msg)
        if err != nil {
            continue
        }
    }
}

func receiver(cl *Client) {
    buf := make([]byte, 560)
    for {
        n, err := cl.conn.Read(buf)
        if err != nil {
            go send(makeMsgForAdmin(string(cl.name) + " Quit."))
            break
        }
        go send(makeMsg(buf[:n], cl))
        buf = make([]byte, 560)
    }
}

func createClient(conn net.Conn) {
    name := getName(conn)
    color := getColor()
    cl := Client{
        name:  name,
        conn:  conn,
        color: color,
    }
    clientList = append(clientList, &cl)
    send(makeMsgForAdmin(string(name) + " joined!!"))
    go receiver(&cl)
}

func getName(conn net.Conn) []byte {
    buf := make([]byte, 560)
    n, err := conn.Read(buf)
    if err != nil {
        fmt.Println("Fail get name")
        Close(conn)
        os.Exit(1)
    }
    return buf[:n]
}

func getColor() int {
    rand.Seed(time.Now().UnixNano())
    return colorList[rand.Intn(5)]
}

func Close(c io.Closer) {
    err := c.Close()
    if err != nil {
        fmt.Fprintf(os.Stderr, err.Error())
    }
}

func SprintColor(msg string, color int) string {
    return fmt.Sprintf("\x1b[%dm%s\x1b[0m", color, msg)
}

func getTime() string {
    return time.Now().Format("15:04")
}

func makeMsg(msg []byte, cl *Client) []byte {
    new_msg := fmt.Sprintf("%s[%s] %s", getTime(), cl.name, string(msg))
    return []byte(SprintColor(new_msg, cl.color))
}

func makeMsgForAdmin(msg string) []byte {
    new_msg := fmt.Sprintf("(%s) %s", getTime(), msg)
    return []byte(SprintColor(new_msg, 31))
}

func main() {
    service := ":7777"
    tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
    util.ChkErr(err, "tcpaddr")
    li, err := net.ListenTCP("tcp", tcpAddr)
    util.ChkErr(err, "tcpaddr")
    for {
        conn, err := li.Accept()
        if err != nil {
            fmt.Println("Fail to connect.")
            continue
        }
        defer Close(conn)
        createClient(conn)
    }
}

コード(クライアント)

package main

import (
    "./util"
    "bufio"
    "fmt"
    "io"
    "net"
    "os"
    "time"
)

var running = true

func sender(conn net.Conn, name string) {
    reader := bufio.NewReader(os.Stdin)
    for {
        input, _, _ := reader.ReadLine()
        if string(input) == "\\q" {
            running = false
            break
        }
        _, err := conn.Write(input)
        util.ChkErr(err, "sender write")
    }
}

func receiver(conn net.Conn, name string) {
    buf := make([]byte, 560)
    for running == true {
        n, err := conn.Read(buf)
        util.ChkErr(err, "Receiver read")
        fmt.Println(string(buf[:n]))
        buf = make([]byte, 560)
    }
}

func Close(c io.Closer) {
    err := c.Close()
    if err != nil {
        fmt.Fprintf(os.Stderr, err.Error())
    }
}

func main() {
    fmt.Print("Please input your name: ")
    reader := bufio.NewReader(os.Stdin)
    name, _, err := reader.ReadLine()

    host := "127.0.0.1:7777"
    tcpAddr, err := net.ResolveTCPAddr("tcp4", host)
    util.ChkErr(err, "tcpAddr")

    conn, err := net.DialTCP("tcp", nil, tcpAddr)
    util.ChkErr(err, "DialTCP")
    defer Close(conn)

    _, err = conn.Write(name)
    util.ChkErr(err, "Write name")

    go receiver(conn, string(name))
    go sender(conn, string(name))

    for running {
        time.Sleep(1 * 1e9)
    }
}