Golang Cafe #15 まとめ netパッケージ
2014/02/02に開催された「Golang Cafe #15」についてのまとめです。
今回はnetパッケージ以下をみていきました。
所謂ソケット通信を行うためのパッケージです。今回は基本的なTCPとUDP関連のパッケージを見てみました。
+TakashiYokoyama氏が準備してくれたサンプルはこちらにあります。
私の書いたサンプルはGitHubに置いてあります。
TCP
特に難しいことはありません。C#やJava等でソケット通信をやったことがある方にとっては同じようなイメージです。
+TakashiYokoyama氏のサンプルを元にシンプルなエコーバックのサンプルを書いてみました。
サーバー側
func main() { runtime.GOMAXPROCS(3) listener, err := net.Listen("tcp", "localhost:8888") if err != nil { log.Fatalln(err) } for { conn, err := listener.Accept() if err != nil { log.Printf("Accept Error: %v\n", err) continue } log.Printf("Accept [%v]\n", conn.RemoteAddr()) go doProcess(conn) } } func doProcess(conn net.Conn) { var rlen int var err error tcpConn := conn.(*net.TCPConn) defer tcpConn.Close() err = tcpConn.SetDeadline(time.Now().Add(5 * time.Second)) if err != nil { log.Printf("[%v]: %v\n", tcpConn.RemoteAddr(), err) return } time.Sleep(time.Duration(rand.Intn(3)) * time.Second) buf := make([]byte, 1024) rlen, err = tcpConn.Read(buf) if err != nil { log.Printf("Receive Error [%v]: %v\n", tcpConn.RemoteAddr(), err) return } s := string(buf[:rlen]) log.Printf("Receive [%v]: %v\n", tcpConn.RemoteAddr(), s) s = "Hello! " + s rlen, err = tcpConn.Write([]byte(s)) if err != nil { log.Printf("Send Error [%v]: %v\n", tcpConn.RemoteAddr(), err) return } log.Printf("Send [%v]: %v\n", tcpConn.RemoteAddr(), s) }
クライアント側
func exec(ch chan<- int, pos int) { var rlen int var err error defer func() { ch <- pos }() conn, err := net.DialTimeout("tcp", "localhost:8888", 5*time.Second) if err != nil { log.Fatalln(err) } log.Printf("Connect[%d]: %v\n", pos, conn.RemoteAddr()) defer conn.Close() s := "user" + strconv.Itoa(pos) rlen, err = conn.Write([]byte(s)) if err != nil { log.Fatalf("Send Error[%d]: %v\n", pos, err) return } log.Printf("Send[%d]: %v\n", pos, s) buf := make([]byte, 1024) rlen, err = conn.Read(buf) if err != nil { log.Fatalf("Receive Error[%d]: %v\n", pos, err) return } log.Printf("Receive[%d]: %v\n", pos, string(buf[:rlen])) }
サーバー側の手順です。
- ネットワークとアドレス:ポートを指定してListenメソッドを呼ぶとListenerを取得できます。このサンプルではネットワークにtcpを指定しているので実態はTCPListenerになっています。ただしListenerはinterfaceですので、TCPListener固有のメソッドを実行するときはTypeAssertionする必要があります。
- Acceptメソッドでクライアントからの接続を待ちます。接続が完了した場合は、Connを返すので、あとはConnに対してReadまたはWriteの操作を行うだけです。Listenerと同じようにConnの実態はTCPConnですが、interfaceですので、TCPConn固有のメッソドを実行する場合はTypeAssertionする必要があります。
- 処理が完了したらCloseメソッドを呼びます。
クライアント側の手順です。
- DialメソッドまたはDialTimeメソッドで指定した接続先に接続します。DialTimeメソッドは名前の通りタイムアウトを同時に指定することができます。
- 接続が完了するとConnを取得できるので、あとはConnに対してReadまたはWriteの操作を行うだけです。サーバー側と同じようにConnはinterfaceで実態はTCPConnになっています。
- 処理が完了したらCloseメソッドを呼びます。
TCP接続なのでプロトコルさえ決めておけばやりとりはコネクションに対するReadとWriteだけです。
ちなみに実行結果はこのような感じです。
$ go run tcpserver.go 2014/02/11 00:01:08 Accept [127.0.0.1:1401] 2014/02/11 00:01:08 Accept [127.0.0.1:1402] 2014/02/11 00:01:08 Receive [127.0.0.1:1402]: user0 2014/02/11 00:01:08 Accept [127.0.0.1:1403] 2014/02/11 00:01:08 Send [127.0.0.1:1402]: Hello! user0 2014/02/11 00:01:08 Accept [127.0.0.1:1404] 2014/02/11 00:01:08 Accept [127.0.0.1:1405] 2014/02/11 00:01:09 Receive [127.0.0.1:1405]: user4 2014/02/11 00:01:09 Send [127.0.0.1:1405]: Hello! user4 2014/02/11 00:01:10 Receive [127.0.0.1:1401]: user3 2014/02/11 00:01:10 Send [127.0.0.1:1401]: Hello! user3 2014/02/11 00:01:10 Receive [127.0.0.1:1404]: user2 2014/02/11 00:01:10 Receive [127.0.0.1:1403]: user1 2014/02/11 00:01:10 Send [127.0.0.1:1404]: Hello! user2 2014/02/11 00:01:10 Send [127.0.0.1:1403]: Hello! user1
$ go run tcpclient.go 2014/02/11 00:01:08 Connect[3]: 127.0.0.1:8888 2014/02/11 00:01:08 Connect[2]: 127.0.0.1:8888 2014/02/11 00:01:08 Connect[1]: 127.0.0.1:8888 2014/02/11 00:01:08 Send[1]: user1 2014/02/11 00:01:08 Connect[4]: 127.0.0.1:8888 2014/02/11 00:01:08 Send[4]: user4 2014/02/11 00:01:08 Send[2]: user2 2014/02/11 00:01:08 Connect[0]: 127.0.0.1:8888 2014/02/11 00:01:08 Send[3]: user3 2014/02/11 00:01:08 Send[0]: user0 2014/02/11 00:01:08 Receive[0]: Hello! user0 2014/02/11 00:01:09 Receive[4]: Hello! user4 2014/02/11 00:01:10 Receive[3]: Hello! user3 2014/02/11 00:01:10 Receive[2]: Hello! user2 2014/02/11 00:01:10 Receive[1]: Hello! user1
UDP
基本的にはTCPと同じような処理になります。
大きつ異なるところはサーバー側の処理です。TCPではListenしてクライアントからの接続要求を待ち、Acceptするとクライアントと1対1のConnectionを取得することができましたが、UDPではその点が異なります。
簡単に説明すると、サーバー側がConnectionを用意していると、勝手に接続して処理されるイメージでしょうか。
TCPの様にAcceptのようなメソッドがないので接続されたタイミングが分かりません。
サーバー側
func main() { addr, err := net.ResolveUDPAddr("udp", "localhost:8888") if err != nil { log.Fatalln(err) } conn, err := net.ListenUDP("udp", addr) if err != nil { log.Fatalln(err) } defer conn.Close() buf := make([]byte, 1024) for { rlen, remote, err := conn.ReadFromUDP(buf) if err != nil { log.Fatalf("Error: %v\n", err) } s := string(buf[:rlen]) log.Printf("Receive [%v]: %v\n", remote, s) s = "Hello! " + s rlen, err = conn.WriteToUDP([]byte(s), remote) if err != nil { log.Printf("Receive Error [%v]: %v\n", remote, s) } log.Printf("Send [%v]: %v\n", remote, s) } }
クライアント側
func exec(ch chan<- int, pos int) { var rlen int var err error defer func() { ch <- pos }() remote, err := net.ResolveUDPAddr("udp", "localhost:8888") if err != nil { log.Fatalf("%v\n", err) } conn, err := net.DialUDP("udp", nil, remote) if err != nil { log.Fatalf("%v\n", err) } conn.SetDeadline(time.Now().Add(5 * time.Second)) defer conn.Close() s := "user" + strconv.Itoa(pos) rlen, err = conn.Write([]byte(s)) if err != nil { log.Printf("Send Error: %v\n", err) return } log.Printf("Send[%d]: %v\n", pos, s) buf := make([]byte, 1024) rlen, err = conn.Read(buf) if err != nil { log.Printf("Receive Error: %v\n", err) return } log.Printf("Receive[%d]: %v\n", pos, string(buf[:rlen])) }
ちなみに実行結果はこのような感じです。
$ go run udpserver.go 2014/02/11 00:02:10 Receive [127.0.0.1:60089]: user4 2014/02/11 00:02:10 Send [127.0.0.1:60089]: Hello! user4 2014/02/11 00:02:10 Receive [127.0.0.1:60088]: user1 2014/02/11 00:02:10 Send [127.0.0.1:60088]: Hello! user1 2014/02/11 00:02:10 Receive [127.0.0.1:60090]: user2 2014/02/11 00:02:10 Send [127.0.0.1:60090]: Hello! user2 2014/02/11 00:02:10 Receive [127.0.0.1:60092]: user3 2014/02/11 00:02:10 Send [127.0.0.1:60092]: Hello! user3 2014/02/11 00:02:10 Receive [127.0.0.1:60091]: user0 2014/02/11 00:02:10 Send [127.0.0.1:60091]: Hello! user0
$ go run udpclient.go 2014/02/11 00:02:10 Connect[4]: 127.0.0.1:8888 2014/02/11 00:02:10 Connect[1]: 127.0.0.1:8888 2014/02/11 00:02:10 Connect[2]: 127.0.0.1:8888 2014/02/11 00:02:10 Connect[3]: 127.0.0.1:8888 2014/02/11 00:02:10 Connect[0]: 127.0.0.1:8888 2014/02/11 00:02:10 Send[4]: user4 2014/02/11 00:02:10 Send[1]: user1 2014/02/11 00:02:10 Send[2]: user2 2014/02/11 00:02:10 Send[3]: user3 2014/02/11 00:02:10 Send[0]: user0 2014/02/11 00:02:10 Receive[4]: Hello! user4 2014/02/11 00:02:10 Receive[1]: Hello! user1 2014/02/11 00:02:10 Receive[2]: Hello! user2 2014/02/11 00:02:10 Receive[3]: Hello! user3 2014/02/11 00:02:10 Receive[0]: Hello! user0
サーバー側の手順です。
- ネットワークとアドレスを指定してListenUDPメソッドを呼んだ時点でコネクションが作成されます。これで待機完了です。
- クライアントからデータを読み込むにはReadFromUDPメソッドを使用します。このメソッドを使用することでどのクライアントからデータを受信したかがわかります。
- クライアントにデータを送信するにはWriteToUDPメソッドを使用して送信先を指定してデータを送信します。
- すべてが完了したら(終了時)Closeメソッドを呼びます。
クライアント側の手順です。