GolangでUDPサーバー&クライアントを作成してWiresharkで検証する
Goならわかるシステムプログラミング第7章 UDPソケットを使ったマルチキャスト通信に書かれている内容をWiresharkを使ったり、デバッグでライブラリ(netパッケージ中心)のソースを読んだりして検証してみました
サーバー側
- net.ListenPacket()の戻り値のnet.PacketConnインターフェースを使う
package main import ( "fmt" "net" ) func main() { fmt.Println("Server is runnign at localhost:8888") conn, err := net.ListenPacket("udp", "localhost:8888") if err != nil { panic(err) } defer conn.Close() buffer := make([]byte, 1500) for { length, remoteAddress, err := conn.ReadFrom(buffer) if err != nil { panic(err) } fmt.Printf("Received from %v: %v\n",remoteAddress, string(buffer[:length])) _, err = conn.WriteTo([]byte("Hello from Server"), remoteAddress) if err != nil { panic(err) } } }
ReadFrom関数の実装が気になったので確認してみた
//udpsock.go func (c *UDPConn) ReadFrom(b []byte) (int, Addr, error) { if !c.ok() { return 0, nil, syscall.EINVAL } n, addr, err := c.readFrom(b) if err != nil { err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} } if addr == nil { return n, nil, err } return n, addr, err }
//udpsock_posix.go func (c *UDPConn) readFrom(b []byte) (int, *UDPAddr, error) { var addr *UDPAddr n, sa, err := c.fd.readFrom(b) switch sa := sa.(type) { case *syscall.SockaddrInet4: addr = &UDPAddr{IP: sa.Addr[0:], Port: sa.Port} case *syscall.SockaddrInet6: addr = &UDPAddr{IP: sa.Addr[0:], Port: sa.Port, Zone: zoneCache.name(int(sa.ZoneId))} } return n, addr, err }
//fd_unix.go func (fd *netFD) readFrom(p []byte) (n int, sa syscall.Sockaddr, err error) { n, sa, err = fd.pfd.ReadFrom(p) runtime.KeepAlive(fd) return n, sa, wrapSyscallError("recvfrom", err) }
//syscall_unix.go func Recvfrom(fd int, p []byte, flags int) (n int, from Sockaddr, err error) { var rsa RawSockaddrAny var len _Socklen = SizeofSockaddrAny if n, err = recvfrom(fd, p, flags, &rsa, &len); err != nil { return } if rsa.Addr.Family != AF_UNSPEC { from, err = anyToSockaddr(&rsa) } return }
//zsyscall_darwin_amd64.go // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func recvfrom(fd int, p []byte, flags int, from *RawSockaddrAny, fromlen *_Socklen) (n int, err error) { var _p0 unsafe.Pointer if len(p) > 0 { _p0 = unsafe.Pointer(&p[0]) } else { _p0 = unsafe.Pointer(&_zero) } r0, _, e1 := Syscall6(SYS_RECVFROM, uintptr(fd), uintptr(_p0), uintptr(len(p)), uintptr(flags), uintptr(unsafe.Pointer(from)), uintptr(unsafe.Pointer(fromlen))) n = int(r0) if e1 != 0 { err = errnoErr(e1) } return }
このSyscall6
の中身は見れなかった。名前的にもこれはネットワーク系のシステムコールなのでしょうか。
という訳で、コールスタックは私の場合は
syscall.recvfrom (/usr/local/go/src/syscall/zsyscall_darwin_amd64.go:146) syscall.Recvfrom (/usr/local/go/src/syscall/syscall_unix.go:262) internal/poll.(*FD).ReadFrom (/usr/local/go/src/internal/poll/fd_unix.go:215) net.(*netFD).readFrom (/usr/local/go/src/net/fd_unix.go:208) net.(*UDPConn).readFrom (/usr/local/go/src/net/udpsock_posix.go:47) net.(*UDPConn).ReadFrom (/usr/local/go/src/net/udpsock.go:121) main.main (/User/Study/go-sys/chap7/udp_server/udpServer.go:18)
となった。
クライアント側
package main import ( "fmt" "net" ) func main() { conn, err := net.Dial("udp4", "localhost:8888") if err != nil { panic(err) } defer conn.Close() fmt.Println("Sending to server") _, err = conn.Write([]byte("Hello from client")) if err != nil { panic(err) } fmt.Println("Receiving from server") buffer := make([]byte, 1500) length, err := conn.Read(buffer) if err != nil { panic(err) } fmt.Printf("Received: %s\n", string(buffer[:length])) }
net.Dial
でnet.Connインターフェース(実装はnet.UDPConn)を取得してそちらにWrite,Readで送受信ができる。
とてもシンプルですね。
キャプチャ結果
この2つのソースで送受信それぞれ1つづつしかパケットが飛んでいない。
UDPはとてもシンプルですが、TCPが信頼性を担保するためにいかに頑張ってくれているか、というのが実感できるのではないでしょうか。
- 作者: 渋川よしき
- 出版社/メーカー: Lambda Note
- 発売日: 2017/10/19
- メディア: テキスト
- この商品を含むブログを見る