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文の存在を知った。これは便利かもね。