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