Yabu.log

色々勉強するブログです

分散処理本第 38 回に参加

Distributed Computing: Principles, Algorithms, and Systems を読む勉強会です。やはり難しいので少し予習していきました。アルゴリズムの分類までは一応軽く読んで概要をつかんでから臨みました

予習をしてブログの下書きを作っておくと、感想のポストがサクッとかけて良いですね。

Distributed Computing: Principles, Algorithms, and Systems

Distributed Computing: Principles, Algorithms, and Systems

分散システムに置けるデッドロックの検出アルゴリズムの分類

  • path-pushing
  • edge-chasing
  • diffusion computation
  • global state detection.

出典 :E. Knapp, Deadlock detection in distributed databases, ACM Computing Surveys, 19 (4), 1987, 303–328.

※詳しい方が概要を知っているがその分類名を知らない、ということが発生していたので、一般的な分類ではないのかもしれない。

path-pushing

各サイトが WFG を送り合いグローバルな WFG を構築する。 送信しながらどんどん WFG を更新してデッドロックの発生、検知を明らかにして行く

edge-chasing

グラフのエッジに剃って probes と呼ばれる特殊なメッセージを送信して行く。 以前送信した probe と同じものを送信した場合、それで cycle を検出できる

diffusion computation

deadlock が発生していれば帰ってくるようなエコーメッセージを送る。

よく解らん ? という話になった

参考にしてある論文は以下のもの

    1. Chandy, J. Misra, and L. M. Haas, Distributed deadlock detection, ACM Transactions on Computer Systems, 1 (2), 1983, 144–156.
  1. Herman and K. M. Chandy, A Distributed Procedure to Detect AND/OR Deadlock, Technical Report TR LCS-8301, Department of Computer Sciences, University of Texas, Austin, TX, 1983.

本書記載のアルゴリズム例は 2 つあり、

前者は Wikipedia の記事があった

https://en.wikipedia.org/wiki/Chandy-Misra-Haas_algorithm_resource_model

CMH アルゴリズムとしても有名らしい

global state detection.

分散システムの各スナップショットからデッドロック有無を確認する。しかし以下の条件が必要である

  • (I) a consistent snapshot of a distributed system can be obtained without freezing the underlying computation.

  • (II) a consistent snapshot may not represent the system state at any moment in time, but if a stable property holds in the system before the snapshot collection is initiated, this property will still hold in the snapshot.

10.6  Mitchell and Merritt’s algorithm for the single-resource model

アルゴリズムとしては public/privateなlabel を使う 文章化するのがちょっと難しいが一言で表すなら

  • 待ち状態に対して 2 種類 (private/public) のラベル作成し値 z を与える
    • 下の例は u-> v 間の待ちを表しているこの zはu+v より大きな値かつユニークでなければならない
    • この操作を待ちの連鎖が終わるところまで続けて行く

f:id:yuyubu:20180918234652j:plain

2 週することで図のDetectの状態が発生が把握できる (1 週しただけだと Deadlock かどうかわからない)

p360 の図は誤植

  • Figure 10.2 The four possible state transitions [22]

という図があるが元ネタが「 E. Knapp, Deadlock detection in distributed databases, ACM Computing Surveys, 19 (4), 1987, 303–328. 」という論文の図であり、引用するときに一部記号が間違っている。

また10.6は本書中の記載のみだと理解が辛く、引用元の論文をよく読み込まないと何を行っているかわからない。

余談:ACM Degital Libary は高い

500 ドルくらいするらしい。。。

次回は「 10.7  Chandy–Misra–Haas algorithm for the AND model 」から!

a tour of Go をやった

Go ならわかるシステムプログラミングで推奨していたのでとりあえず一周して見た*1

https://go-tour-jp.appspot.com/list

Go 言語の文法については、全くのプログラミング初心者でなければ、 tour of Go という公式のチュートリアルを一通りこなすだけで、本書の範囲なら十分理解できるでしょう

Go ならわかるシステムプログラミング p4 より抜粋

と書かれていたのでとりあえずやっておこう、というつもりで進めたのだが。。。 だらだらやったのもあってか 1.5 日ほどかかった。

とりあえず自分は日頃競プロ的な頭を使うプログラミングをやらないので、 プログラミング能力が劇的に落ちていることを実感した。

時間をかけて競プロを強めようとは思わないが、アルゴリズムとかのエンジニアとしての基礎的な部分を固める必要があると再度認識した。

コードはあまり人に見せられるような綺麗なものではないけど、時間がかかった課題のものを中心に公開しておきます。

フィボナッチ

https://go-tour-jp.appspot.com/moretypes/26

DRY にものすごく反しているような???

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
    count := 0
    v1 := 0
    v2 := 0
    value := 0
    return func() int {
        defer func() { count++ }()
        if count == 0 {
            return 0
        } else if count == 1 {
            v1 = 0
            v2 = 1
            return 1
        } else {
            value = v1 + v2
            v1 = v2
            v2 = value
            return value
        }
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 15; i++ {
        fmt.Println(f())
    }
}

ワードカウント ? 的なやつ。一応テストは全てパスした

https://go-tour-jp.appspot.com/moretypes/23

package main

import (
    "golang.org/x/tour/wc"
    "strings"
)

func WordCount(s string) map[string]int {
    wordlists := strings.Fields(s)
    countMap := make(map[string]int)
    for _, v := range wordlists {
        countMap[v]++
    }
    //return map[string]int{"x": 1}
    return countMap
}

func main() {
    wc.Test(WordCount)
}

なんか画像を作るやつ

https://go-tour-jp.appspot.com/moretypes/18

package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
    value := make([][]uint8, dy)
    for i := 0; i < dy; i++ {
        value[i] = make([]uint8, dx)
        for j:=0;j<dx;j++{
            value[i][j] = uint8(i^j)
        }
    }
    return value
}

func main() {
    pic.Show(Pic)
}

生成する画像は、好きに選んでください。例えば、面白い関数に、 (x+y)/2 、 x*y 、 xy などがあります。

f:id:yuyubu:20180918023900p:plain
(x+y)/2
f:id:yuyubu:20180918023856p:plain
x * y
f:id:yuyubu:20180918023902p:plain
x ^ y

ASCII 文字 ' A ' の無限ストリームを出力する Reader

package main

import "golang.org/x/tour/reader"

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.
func(r MyReader) Read(b []byte) (int ,error){
    b[0] = 'A'
    return 1,nil
}


func main() {
    reader.Validate(MyReader{})
}

io. Reader をラップした暗号復号処理 (rot13)

rot13Reader (io. Reader をラップして換字式暗号を標準入出力に変換してだす)

reader が最後に error として io. EOF を返すのが解らずに、 1 時間ほど試行錯誤した。

package main

import (
    "fmt"
    "io"
    "os"
    "strings"
)

type rot13Reader struct {
    r io.Reader
}

func (r2 rot13Reader) Read(p []byte) (n int, e error) {
    i := 0
    b := make([]byte, 1)
    for {
        _, err := r2.r.Read(b)
        //fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
        //fmt.Printf("b[:n] = %q(%v)\n", b[0], b[0])
        if err == io.EOF {
            break
        }

        value := b[0]
        //文字の場合のみ暗号置換を実施する
        if 64 < b[0] && b[0] < 91 {
            value = value + 13
            if value > 90 {
                value = value - 26
            }
        }

        if 96 < b[0] && b[0] < 123 {
            value = value + 13

            if value > 122 {
                value = value - 26
            }
        }

        //fmt.Printf("lot13 = %q(%v)\n", value, value)
        p[i] = value
        i++
    }

    return i, io.EOF
}

func main() {
    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    //b := make([]byte, 100);r.Read(b)
    io.Copy(os.Stdout, &r)
}

解読後は普通の文章になっているので多分あっていると思うが、 if 文の境界値条件はきちんと検査していないのでバッグっているかもしれない。

conncurenncy を使った二分木探索

https://go-tour-jp.appspot.com/concurrency/8

package main

import (
    "fmt"
    "golang.org/x/tour/tree"
)

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
    //fmt.Println(t)
    //左があるときはどんどん左に行っちゃう
    if t.Left != nil {
        Walk(t.Left, ch)
        ch <- t.Value
        if t.Right != nil {
            Walk(t.Right, ch)
        }
        //左がなくなったけど右がある場合はそっちに行く
    } else if t.Right != nil {
        ch <- t.Value
        Walk(t.Right, ch)
    } else {
        //葉
        ch <- t.Value
    }
}

// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
    ch1:=make(chan int,10)
    ch2:=make(chan int,10)
    Walk(t1,ch1)
    Walk(t2,ch2)

    for i:=0;i<10;i++{
        v1:= <-ch1
        v2:= <-ch2
        if(v1!=v2){
            return false
        }
    }
    return true

}

func main() {
    fmt.Println("test")
    /*

   ch := make(chan int)
   go Walk(tree.New(2), ch)
   count := 0
   for i := range ch {
       fmt.Println(i*10)
       count++
       if(count == 10){
           //closeするタイミングをどこに入れるか解らなかったので
           //呼び出し側にいれた。こういうプログラミングは良くないと思う
           close(ch)
       }
   }
   */
    b1:= Same(tree.New(1), tree.New(2))
    fmt.Println(b1)
}

mutex を使ったクローラー

https://go-tour-jp.appspot.com/concurrency/10

Crawl メソッドの呼び出しを並列に (goroutine) にしなければ動く。 並列にするとなぜか正しく動かない。これは自分が mutex とか並列プログラミングにまだまだ無知だからだと思う。 とりあえず私が並列プログラミングをやると deadlock だらけになることがわかった。

package main

import (
    "fmt"
    "sync"
)

type Fetcher interface {
    // Fetch returns the body of URL and
    // a slice of URLs found on that page.
    Fetch(url string) (body string, urls []string, err error)
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {
    // TODO: Fetch URLs in parallel.
    // TODO: Don't fetch the same URL twice.
    // This implementation doesn't do either:

    if depth <= 0 {
        return
    }

    mux.Lock()
    if check[url] == 1 {
        mux.Unlock()
        return
    }
    check[url] = 1
    body, urls, err := fetcher.Fetch(url)
    mux.Unlock()


    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("found: %s %q\n", url, body)
    for _, u := range urls {
        //go Crawl(u, depth-1, fetcher) //並列だと正しく動かない
    Crawl(u, depth-1, fetcher)

    }
    return
}

func main() {
    check = make(map[string]int)
    Crawl("https://golang.org/", 4, fetcher)
}

// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
    body string
    urls []string
}

var mux sync.Mutex
var check map[string]int

func (f fakeFetcher) Fetch(url string) (string, []string, error) {

    if res, ok := f[url]; ok {
        return res.body, res.urls, nil
    }
    return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{
    "https://golang.org/": &fakeResult{
        "The Go Programming Language",
        []string{
            "https://golang.org/pkg/",
            "https://golang.org/cmd/",
        },
    },
    "https://golang.org/pkg/": &fakeResult{
        "Packages",
        []string{
            "https://golang.org/",
            "https://golang.org/cmd/",
            "https://golang.org/pkg/fmt/",
            "https://golang.org/pkg/os/",
        },
    },
    "https://golang.org/pkg/fmt/": &fakeResult{
        "Package fmt",
        []string{
            "https://golang.org/",
            "https://golang.org/pkg/",
        },
    },
    "https://golang.org/pkg/os/": &fakeResult{
        "Package os",
        []string{
            "https://golang.org/",
            "https://golang.org/pkg/",
        },
    },
}

他: 違和感のある翻訳

呼び出す 具体的な メソッドを示す型がインターフェースのタプル内に存在しないため、 nil インターフェースのメソッドを呼び出すと、ランタイムエラーになります。

https://go-tour-jp.appspot.com/methods/13

編集履歴を辿って原文を確認すると以下のようであった

Calling a method on a nil interface is a run-time error because there is no type inside the interface tuple to indicate which concrete method to call.

https://github.com/atotto/go-tour-jp/commit/123f593e8270035b7f59a8d4eaa0e61bbee98239

具象メソッドを含む interface タプルが存在しないので nil インターフェースの呼び出しはエラーになります。くらいが妥当な気がする?

というか interface タプル、ってなに ? っていう感じで go をそこまでわかっていないので具体的に今どういう役が適切かは提案できない。

https://qiita.com/umisama/items/e215d49138e949d7f805

だた goのnilはJavaのnull とも違うし、 nil 自体が nil 型のような特性を持っているような振る舞いがあるようなので、 この辺りは今後注意して学習して行きたい。

感想

今までは写経中心だったり、ロジックというよりも API のドキュメントを調べて書かれている通りに実装することが中心だったが、 Go できちんとプログラミングを書くのは初めてだったのでいい経験になったと思う。

何よりも、 tour of Go をやらねばいけない、という憑き物のようなものが落とせてよかった。

*1: と行っても周回するつもりはあい r ません

GolangでUDP サーバー UND クライアントを作成して Wireshark で検証する

Go ならわかるシステムプログラミング第 7 章 UDP ソケットを使ったマルチキャスト通信に書かれている内容を Wireshark を使ったり、デバッグでライブラリ (net パッケージ中心) のソースを読んだりして検証してみました

サーバー側

  • net. ListenPacket () の戻り値の net. PacketConn インターフェースを使う
    • TCP の時の net. Listener インタフェースのようなクライアントを待つインタフェースではない
    • ReadFrom メソッドで受信内容を読み取る
      • 引数で渡している buffer に通信内容を格納している
      • remoteAddress に通信して来たクライアントのアドレスを受け取っている
      • buffer の中身はあくまで udp パケットの中身しか入らない
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 つづつしかパケットが飛んでいない。

f:id:yuyubu:20180917005441p:plain

UDP はとてもシンプルですが、 TCP が信頼性を担保するためにいかに頑張ってくれているか、というのが実感できるのではないでしょうか。

Goならわかるシステムプログラミング

Go ならわかるシステムプログラミング

Golang で書かれた Web サーバーで HTTP/1.1のkeep-alive を検証する

HTTP/1.1 には Keep-Alive という通信方法があります。 HTTP/1.0 の頃は 1 セットの通信が終わるたびに接続・切断処理が入っていたので非効率だったため、複数のリクエストが来た場合にコネクションを使い回す機能になります。 今回は Golang で書かれたサーバーを実行して Wireshark で通信の様子をキャプチャしています

コード

keep-alive の実装コードの一部です。 Request を終わりまで読み取り、コネクションを再利用します。

//Accept後のソケットでなんども応答を返すためにループ
for {
  //タイムアウトを設定
  conn.SetReadDeadline(time.Now().Add(5 * time.Second))
  //リクエストを読み込む
  request, err := http.ReadRequest(bufio.NewReader(conn))
  if err != nil {
    //タイムアウトもしくはソケットクローズ時は終了
    //それ以外はエラーにする
    neterr, ok := err.(net.Error) //ダウンキャスト
    if ok && neterr.Timeout() {
      fmt.Println("timeout")
      break
    } else if err == io.EOF {
      break
    }
    panic(err)
  }
  //リクエストを表示
  dump, err := httputil.DumpRequest(request, true)
  if err != nil {
    panic(err)
  }
  fmt.Println(string(dump))
  content := "Hello World\n"

  //レスポンスを書き込む
  //HTTP/1.1かつ,ContentLengthの設定が必要
  response := http.Response{
    StatusCode:    200,
    ProtoMajor:    1,
    ProtoMinor:    1,
    ContentLength: int64(len(content)),
    Body: ioutil.NopCloser(
      strings.NewReader(content)),
  }
  response.Write(conn)
}

通信内容

curlで回リクエストを投げています。成功すると 2 回hello worldが帰ってきます

$ curl localhost:8888 localhost:8888
hello world
hello world

キャプチャ結果

続いて Wireshark のキャプチャ結果を貼りますちょっと分かり辛いかもしれませんが、

  • 「接」が TCP の接続処理 (3Way HandShake) です
  • 「通」がデータの通信処理です
  • 「断」が TCP の切断処理です (FIN)

f:id:yuyubu:20180915230426p:plain
図1.keep alive なしの場合

f:id:yuyubu:20180915230429p:plain
図2.keep alive ありの場合

keep alive が無い方は 2 回のリクエストに対してそれぞれ接続、切断処理を行なっていますが、 keep alive がある方はコネクションを使いまわして全部で 1 度しか接続、切断処理をしていないことがわかります。

Golang で作成した WEB サーバーの TCP 通信 (HTTP GET) をWireshark で検証

ソースコード

サーバー側のソースコードです 8888 ポートでクライアントからの送信を待ち受けます

package main

import (
    "bufio"
    "fmt"
    "io/ioutil"
    "net"
    "net/http"
    "net/http/httputil"
    "strings"
)

func main() {
    listener, err := net.Listen("tcp", "localhost:8888")
    if err != nil {
        panic(err)
    }
    fmt.Println("Server is running at localhost:8888")
    for {
        conn, err := listener.Accept()
        if err != nil {
            panic(err)
        }
        go func() {
            //fmt.Printf("Accept %v\n",conn.RemoteAddr())
            //リクエストを読みこむ
            request, err := http.ReadRequest(
                bufio.NewReader(conn))
            if err != nil {
                panic(err)
            }
            dump, err := httputil.DumpRequest(request, true)
            if err != nil {
                panic(err)
            }
            fmt.Println(string(dump))

            //レスポンスを書き込む
            response := http.Response{
                StatusCode: 200,
                ProtoMajor: 1,
                ProtoMinor: 0,
                Body: ioutil.NopCloser(
                    strings.NewReader("hello world\n")),
            }
            response.Write(conn)
            conn.Close()
        }()
    }
}

クライアントの方は後述の curl を利用します

通信方法

サーバーの方で 8888 のポートで受信を受け付けているのでこちらに curl で通信を試みています

$ curl localhost:8888
hello world

サーバーの方でリクエストを受け取るとhello worldという文字列を返すようにしているのでコマンドを実行するとこちらの文字列が帰ります

localhost に対する送受信をキャプチャする準備

yuyubu.hatenablog.com

こちらの記事を参考に Loopback:Io0 を選択しています

netstat コマンドを利用した TCP コネクションの状態

以下のタイミングで netstat コマンドを実行してコネクションの状態を確認しました

サーバー起動時

サーバーは起動したタイミングではnetstatコマンドの出力に 8888port の情報は含まれていません。

listner. Accept () メソッドに置いたブレイクポイントで処理を止めたタイミング

tcp4      78      0  localhost.ddi-tcp-1    localhost.53315        ESTABLISHED
tcp4       0      0  localhost.53315        localhost.ddi-tcp-1    ESTABLISHED

クライアント側のポート番号として 53315 が選ばれていることがわかります

なおこの状態でパケットをキャプチャすると以下の情報が得られました。 サーバーが GET を受け取って OK を返す直前という感じです。

f:id:yuyubu:20180915165701p:plain

接続終了時

netstat コマンドに port8888 の出力は含まれませんでした

次の記事で HTTP/1.1のKeep-Alive を検証するのでこの netstat の情報の差分は興味深い結果になると思います

キャプチャ結果

Wireshark を起動しフィルタをtcp.port==8888に設定して通信内容を記録しました。すると下のような通信内容が得られました。

f:id:yuyubu:20180915165058p:plain

解説

  • 色枠 1 のところで TCPの3way HandShake をしています
  • 色枠 2 のところでデータを送受信しています
  • 色枠 3 のところで切断処理をしています

3Way HandShake

TCP の接続開始を示すこの 3 つのパケットのやりとりを 3 Way HandShake と言います。

f:id:yuyubu:20180915165237p:plain

データ送受信

f:id:yuyubu:20180915165250p:plain

  • データ送信直後に、受信側が送信するパケットの ACK がデータを受け取った分だけ増えていることが確認できます。
  • データ受信確認の後、送信側から送ったもので受信が確認できたオクテット数だけ SEQ が増えていることが確認できます

切断

f:id:yuyubu:20180915165307p:plain

サーバーから送った FIN のパケットに対してクライアントも FIN のパケットを送り返しています。 こちらは確認応答のたびに ACK を + 1 しています

感想

キャプチャ結果冒頭*1 の [SYN] パケットを送った後の [RST, ACK] が送り返されて再度 SYN パケットを送信している理由がわかりませんでした。 一般的な挙動には見えないのですが。。。これは GoのListner の実装などを読めばわかるのでしょうか。次回は Keep-Alive を実験してみたいと思います

参考

*1: ①の上に位置する 2 つのパケット

Wiresharkでlocalhost (127.0.0.1) 宛のパケット送受信をキャプチャする方法

「Go ならわかるシステムプログラミング」のネットワークプログラミングの章でプログラムの検証をやりたかったので Loopback アドレスに対するパケット送受信ができないのか調べてみたところ、日本語ではあまりズバリな解決策*1 が出なかったため英語で調べると解決した。

「wireshark how to capture localhost 」でググったところ以下の投稿を参考に wiresharkのwiki にたどり着いた

https://stackoverflow.com/questions/5847168/wireshark-localhost-traffic-capture

https://wiki.wireshark.org/CaptureSetup/Loopback

Summary: you can capture on the loopback interface on Linux, on various BSDs including Mac OS X, and on Digital/Tru64 UNIX, and you might be able to do it on Irix and AIX, but you definitely cannot do so on Solaris, HP-UX, or Windows.

上記引用にある通り、 WiresharkのNIC 選択一覧に出ているものの中には、 loopback のパケットをキャプチャできるものがある。

f:id:yuyubu:20180913133347p:plain
Loopback:Io0 を利用した

今回はこちらを選択することで無事 localhost 宛のパケットをキャプチャすることができた

f:id:yuyubu:20180913133516p:plain
TCP ハンドシェイクを含む HTTP 通信をキャプチャした様子

ただし macOS ではこの方法が使えたが

では使えないので該当 OS ユーザーの方は別の方法を模索してください

*1: ルーティングテーブルを書き換えろとか、別のツールを入れろとか

Effective SQL 読書会 (5) に参加

参加者が少なかったので木村さんの話がいつもより多く聞けました。 サポートやコンサル、 DBMS の制作などをされている方なのでその膨大な知見に毎度驚かされます。

今回は

  • (続き) 第2 章 プログラム可能性とインデックスの設計
    • 項目 17  計算値をインデックスで使用する状況を理解する
  • 第3 章 設計を変更できないときはどうするか

を読みました

Effective SQL

Effective SQL

過去の SQLServer では NULL が値として扱われていた。 そのためNULL is NULLじゃなくてNULL = NULLでもTrueとなる ANSI_NULLS オプションをオンにするとこのようなおかしな挙動を防げる 最近だとデフォルトで有効化されているので安心。

ラズパイは正直、あまり大規模ではない組み込み用途か教育用でしか利用できないと思っていたが、 分散システムのようなものを作ってパフォーマンスがシビアな状況でも利用しているらしい

次回は「項目 21 UNION を使って非正規化データをアンピボットする」から