Go
golang
TCP

GoでTCPコネクションはるよー

More than 1 year has passed since last update.

はじめに

Go言語の勉強し始めて2・3日のインフラエンジニアがなんとなく監視をテーマに選んで遊んで見る会。

今回はGo言語を用いてTCPコネクションをざっくり張ってみます。
要はServer-Client型のぷろぐらむです。

ソースコードも公開しておきます。誰得かはしらない。
気が向いたらアップデートされたりもするかも。

Server コード

まずはnet.Listenを利用してListenポートを開きます。
既にこのポートが別のアプリケーションで利用されていると即死します。

func CreateListener() net.Listener {
    ln, err := net.Listen("tcp", ":8081")
    if err != nil {
        log.Fatal(err)
    }

    return ln
}

その後通信要求に応じてコネクション(net.Conn)を開きます。

func ListenWorker(ln net.Listener) {    conn, err := ln.Accept()
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

複数のコネクションを受ける場合はドキュメントにあるようにgoroutineに処理を放り投げるのが良いみたいですね。

deferしておくと終了時に勝手にコネクション閉じてくれるので、使えそうなら入れておきましょう。
今回は1対1の処理構造なので処理が済んだら終わりです。生成処理をfunctionにいれたりすると、function終わった瞬間に即死する気がする。

そしてメッセージの受信待ち、受信後の処理を定義します。
今回は改行を区切り文字として定義してるので複数行届いても1行ずつ処理されます。

    for {
        message, err := bufio.NewReader(conn).ReadString('\n')
        if err != nil {
            log.Fatal(err)
        }
        log.Print("Message Received:", string(message))
        newmessage := strings.ToUpper(message)
        conn.Write([]byte(newmessage + "\n"))
    }
}

受信したら大文字にしてClientに返すってだけの処理です。
現状コネクション制御が全く入っていないので、Clientが死ぬと暴走する危険なやつになってます。

あとはmainで動かして終わりです。
Workerを分離したのはgoroutine使って複数Clientからの接続を後々試すために分けておいただけでした。

func main() {

    // Create Listen port and accept to connection.
    log.Print("Launching server...")
    ln := CreateListener()

    ListenWorker(ln)

}

Client コード

ClientはServerへの発呼とデータの送受信を行う処理がメインですね。
自分がやりたいことだけ書けばいいのでサーバ側に比べると楽。

とりあえずコネクション貼っちゃいましょう。
net.Dialの時点でサーバに接続しに行くので、ここでエラー処理をしないと接続失敗しても動いている変なやつになります。

func MakeConnection(server, port string) net.Conn {
    conn, err := net.Dial("tcp", server+":"+port)
    if err != nil {
        log.Fatal(err)
    }
    return conn
}

ここでは標準入力から何らかの入力を得てそれをサーバへ流す仕組みにします。

func GetInput() string {
    for {
        reader := bufio.NewReader(os.Stdin)
        fmt.Print("Input text: ")
        text, _ := reader.ReadString('\n')

        if len(text) > 1 {
            return text
        }
    }
}

取得した入力を送信する処理と、終了用にexitコマンドを定義してあります。
もっといろいろ増えそうならコマンド処理の部分はFunctionにしてもいいかなって思ったけどサボった。

Serverからの応答待ちと表示処理も一緒にやっちゃってます。

func PublishMessage(conn net.Conn) {

Loop:
    for {
        // Read in input from stdin
        text := GetInput()

        // If the input is "exit", we will stop the loop and end client.
        switch {
        case text == "exit\n":
            log.Print("Entering exit command. EXIT.")
            break Loop
        }
        log.Print("Will send text: " + text)

        // Send to socket
        conn.Write([]byte(text + "\n"))

        // Listen for reply
        message, _ := bufio.NewReader(conn).ReadString('\n')
        log.Print("Message from server: " + message)
    }
}

実際にやりたい処理のための基盤は大体出来たので、粗も多いけど今回はこのくらいで。

func main() {

    // connect to this socket
    conn := MakeConnection("127.0.0.1", "8081")
    defer conn.Close()
    log.Print("Connected.")

    PublishMessage(conn)
}