Goでdial
現実逃避気味にGoで簡単なエコーサーバ/クライアントを書いてみた。さすがソケットをそのままユーザに見せるのではなく、Plan9のdialライクなAPIになっている*1。こまかい文法についてはGoのページを参照してもらうとして、ちょっとコードを見ていこう。Plan 9版は第二回探検隊の資料に書いたので、興味があればそちらを参照してほしい。
ネットワーク関連のパッケージはnetになる。ソースコードはsrc/pkg/net以下にあるので、詳しい動作が知りたければそちらを参照したくなるだろう。
まずはクライアントから。net.Dialの引数にはホスト名を書いてもOKなので、netmkaddrは不要である。エラーコードを返すために、multiple return valuesを積極的に使っている。これはありかな。配列のスライスも使える。
package main
import (
"fmt";
"os";
"net";
)
func main() {
var buf [512]byte;
s := "localhost";
var rhost *string = &s;
if len(os.Args) == 2 {
rhost = &os.Args[1];
}
conn, err := net.Dial("tcp", "", *rhost + ":8007");
defer conn.Close();
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.String());
os.Exit(1);
}
for {
nr, er := os.Stdin.Read(&buf);
if er != nil {
fmt.Fprintf(os.Stderr, "%s\n", er.String());
break;
}
nw, ew := conn.Write(buf[0:nr]);
if ew != nil {
fmt.Fprintf(os.Stderr, "%s\n", ew.String());
break;
}
ne, ee := conn.Read(&buf);
if ee != nil {
fmt.Fprintf(os.Stderr, "%s\n", ee.String());
break;
}
os.Stdout.Write(buf[0:ne]);
}
}一方、サーバ側にannounceはない。いきなりnet.Listenでlistenerを得て、net.Acceptで待ち受けする。Plan 9ではlistenで待って、accept/hangupという流れで、ソケットよりもわかりやすい気がしたんだけど、なんでPlan 9と変えたんだろう?
acceptに成功したらワーカをgoroutineで動かす。go文はLimboのspawnに相当する。forkやpthreadよりもお気楽。
package main
import (
"fmt";
"os";
"net";
)
func goecho(conn net.Conn) {
var buf [512]byte;
conn.Close();
for {
nr, er := conn.Read(&buf);
if er != nil {
if er != os.EOF {
fmt.Fprintf(os.Stderr, "%s\n", er.String());
}
break;
}
nw, ew := conn.Write(buf[0:nr]);
if nw != nr {
fmt.Fprintf(os.Stderr, "%s\n", ew.String());
break;
}
}
}
func main() {
lis, err := net.Listen("tcp", ":8007");
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.String());
os.Exit(1);
}
for {
conn, err := lis.Accept();
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.String());
os.Exit(1);
}
go goecho(conn);
}
}(追記:2009-11-22)「Goでエコーサーバーを書く」経由でdefer文の存在を知った。これは便利かもね。