Go
golang

GO言語でTCP/IP通信をする

Goにおけるプロセス間通信

Go言語では, 言語レベルで用意されているgo構文によってプロセス(それともスレッド?)を立ち上げ
チャンネルを経由して互いに通信を行うことができます.
しかし, この方法で通信できるのはプログラム内部で生成したプロセス間のみで, それ以外の論理的あるいは物理的に
離れたプロセス間では通信したい場合は, TCP通信などの別の方法を使用しないといけないようです.
今回はクライアントからのメッセージをそのまま返すだけのエコープログラムを実装していこうと思います.

クライアント

コード

client.go
package main

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


func main() {
    connection, error := net.Dial("tcp", "localhost:10000");

    if error != nil {
        panic(error);
    }

    defer connection.Close()
    sendMessage(connection);
}

func sendMessage(connection net.Conn) {
    fmt.Print("> ");

    stdin := bufio.NewScanner(os.Stdin)
    if stdin.Scan() == false {
        fmt.Println("Ciao ciao!");
        return;
    }

    _, error := connection.Write([]byte(stdin.Text()));

    if error != nil {
        panic(error);
    }

    var response = make([]byte, 4 * 1024);
    _, error = connection.Read(response);

    if (error != nil) {
        panic(error);
    }

    fmt.Printf("Server> %s \n", response);

    sendMessage(connection)
}

解説

まずは簡単なクライアントから.
処理の流れは, まずnetパッケージで用意されているDial関数を使ってサーバとのコネクションを取得します

    connection, error := net.Dial("tcp", "localhost:10000");

次に, 取得したコネクションを使ってサーバにメッセージを送るのですが, プログラム終了時に確実に
コネクションが解放されるように defer構文を使ってコネクション開放命令を予約しておきます.

    defer connection.Close()
    sendMessage(connection);

メッセージ送信関数であるsendMessageでは, まず標準入力から一行受け取り

    stdin := bufio.NewScanner(os.Stdin)
    if stdin.Scan() == false {
        fmt.Println("Ciao ciao!");
        return;
    }

受け取った文字列をバイト列に変換して, コネクション経由で送信します

    _, error := connection.Write([]byte(stdin.Text()));

connection#Write 関数の一番目の返り値は送信したデータのバイト数のようですが, 今回は使わないので
_変数を使って捨てています.
データの送信の後, 今度はサーバからの受信を待ち, 結果を標準出力に出力します.


    var response = make([]byte, 4 * 1024);
    _, error = connection.Read(response);

    if (error != nil) {
        panic(error);
    }

    fmt.Printf("Server> %s \n", response);

最後に, 再びsendMessage関数を呼び出し入力待ちに戻ります.

    sendMessage(connection);

プログラムを終了したい場合は, コマンドラインからCtrl-Cを入力するとstdin#Scanがfalseを返すので
正常に終了します.

Server

コード


package main

import (
    "fmt"
    "net"
    "io"
)

func main() {

    listener, error := net.Listen("tcp", "localhost:10000");

    if error != nil {
        panic(error);
    }

    fmt.Println("Server running at localhost:10000");

    waitClient(listener);

}

func waitClient(listener net.Listener) {
    connection, error := listener.Accept();

    if error != nil {
        panic(error);
    }

    go goEcho(connection);

    waitClient(listener);
}

func goEcho(connection net.Conn) {
    defer connection.Close();
    echo(connection);
}

func echo(connection net.Conn) {

    var buf = make([]byte, 1024);

    n, error := connection.Read(buf);
    if (error != nil) {
        if error == io.EOF {
            return;
        } else {
            panic(error);
        }
    }

    fmt.Printf("Client> %s \n", buf);

    n, error = connection.Write(buf[:n])
    if error != nil {
        panic(error);
    }

    echo(connection)
}

解説

サーバ側のプログラムは, クライアントに比べるとちょっとだけ複雑になります

まず最初に, netパッケージのListen関数を使って通信用ポートを確保します.

    listener, error := net.Listen("tcp", "localhost:10000");

ポートの確保に成功したら, waitClient関数を呼び出しクライアントからの接続待ち状態にします

    if error != nil {
        panic(error);
    }

    fmt.Println("Server running at localhost:10000");

    waitClient(listener);

waitClient関数では, Listener#Accept関数でクライアントからの接続を受け付け,

    connection, error := listener.Accept();

その後, ゴールーチンを生成し, クライアントからのリクエスト処理を移譲します

    go goEcho(connection);

最後に, 再びwaitClient関数を呼び出し, クライアントからの接続待ち状態に戻ります

    waitClient(listener);

クライアントとの通信を移譲されたゴールーチンでは, コネクションからの入力を受け取り
同じ文字列をそのままコネクションに送信します.

func goEcho(connection net.Conn) {
    defer connection.Close();
    echo(connection);
}

func echo(connection net.Conn) {

    var buf = make([]byte, 1024);

    n, error := connection.Read(buf);
    if (error != nil) {
        if error == io.EOF {
            return;
        } else {
            panic(error);
        }
    }

    fmt.Printf("Client> %s \n", buf);

    n, error = connection.Write(buf[:n])
    if error != nil {
        panic(error);
    }

    echo(connection)
}

基本的な処理はクライアントと同じなので解説は省略します.
一点だけ異なるのは, クライアント側でコネクションを閉じた際, Conn#read関数のエラーとしてio.EOFが返されるようなので
これを受け取ったら処理のループを抜けるようにしています.

    n, error := connection.Read(buf);
    if (error != nil) {
        if error == io.EOF {
            return;
        } else {
            panic(error);
        }
    }

最後に

これでGo言語によるTPC通信の簡単な解説を終わります.
サンプルが単純すぎて実践的でないので, もう少し複雑なサンプルができたら
続きを書きたいと思います.