Hatena::ブログ(Diary)

taknb2nchのメモ

2014-09-15

Golang Cafe #47 まとめ WebSocketを試す。

| 16:00

2014/09/014に開催された「Golang Cafe #47」についてのまとめです。

今回はGo言語でWebSocketサーバー側)を試してみる会となりました。


Go言語のWebSocket実装で有名どころ(検索でヒットしそうなの)は、このあたりでしょうか。

ほかにもGoDocで検索するとたくさん出てきます。


その中なら準標準パケージ(?)のgo.net/websocketを試してみることにしました。


準備

お約束ですが、go getしておきます。

$ go get code.google.com/p/go.net/websocket

サンプル

クライアント側は、昨年前半にHTML5のお勉強会をしていた時のサンプルを使用します。HTML5のお勉強会の時はサーバー側はnode.jsでwsモジュールを使用していました。

今回はその部分をGoに置き換えます。

こちらの中から

sample01.html、sample01.js、sample.css

を使用します。

sample01.jsの15行目辺りを

ws = new WebSocket('ws://localhost:8080/echo');

に修正してGoのソースコードと同じディレクトリに配置します。


WebSocketサーバー側の実装はこちらを参考にします。

func echoHandler(ws *websocket.Conn) {
    io.Copy(ws, ws)
}

func main() {
    http.Handle("/echo", websocket.Handler(echoHandler))
    http.Handle("/", http.FileServer(http.Dir(".")))
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        panic("ListenAndServe: " + err.Error())
    }
}

クライアントのページを読み込み、接続ボタンでWebSocketサーバーに接続し、送信ボタンをクリックするとテキストボックスに入力された文字列がエコーバックされるだけのサンプルです。


上記サンプルでは、WebSocketで接続された際、echoHandler関数が呼び出されます。

引数にはwebsocket.Connが指定されているので、このコネクションに対して、Read、Writeするだけです。

読み書きする方法、内容については個々で調べてください。


ただ、気をつけなくてはならないのは、echoHandler関数を抜けた時点でコネクションが破棄されてしまうということです。

goroutine等で処理していても同じみたいです。


サンプルコードのio.Copyの実装を確認してもらえば分かると思いますが、forで無限ループしています。

独自の実装をする場合もコネクションを破棄しないようにする必要があります。

この辺りはnode.jsのwsのモジュールのon使った書き方の方が直感的で分かりやすいかも知れません。

(別にGoでも同じように実装できると思いますが)


Go言語のWebSocketに限ったことではないですが、まだすべてのブラウザWebSocketをサポートしているわけではないし、ネットワーク的に使用できない場合もあると思うので、go-socket.ioなるものを試してみるのもいいかもしれません。

ただ、yosuke-furukawaの記事にあるようにパフォーマンスはまだよろしくないようです。

(後に試してみたいと思います)


というわけでWebSocket使いたければ、そこだけnode.jsでもいいんじゃないの?という気もしてきました。

2014-09-14

Golang Cafe #46 まとめ

| 11:48

2014/09/07に開催された「Golang Cafe #46」についてのまとめです。


先週話題にも上がったpanicとrecoverの話。

no titleにもまとめていただいたが、やはりpanicが発生した場合、以降の処理は何もしない、終了コードも期待できない、プロセスが落ちるだけという考え方がよさそうです。

recoverで拾うことでできますが、何もせず落とすだけというのがよいような気がします。

Golang Cafe #45 まとめ

| 11:40

2014/08/31に開催された「Golang Cafe #45」についてのまとめです。


今回は、ruiuさんの投稿から気になったものを読んでディスカッションとなりました。

気になったところと言うか覚え書き程度にまとめておきます。


int型のサイズ

http://qiita.com/ruiu/items/28c77ed483cec365fe84

これは全くのノーマークでした。

Go言語はクロスコンパイルだできるとはいっても落とし穴になりそうです。


goroutineの最大数の制御

http://qiita.com/ruiu/items/d4ef16981b870a1b85af

書かれてあるとおり、バッファサイズを指定したchannelを使用すれば簡単です。どの処理についてchannelで制限するかがポイント。


timeパッケージのフォーマット

http://qiita.com/ruiu/items/5936b4c3bd6eb487c182

長い間"yyyy/MM/dd HH:mm:dd"でやってきたので"Mon, 02 Jan 2006 15:04:05 -0700"にはまだなれません。


三項演算子

http://qiita.com/ruiu/items/61236f09b3469a679eb9

個人的には多用するので、現時点でこの書き方ができないのは残念ですが、仕方ありません。


※9/1以降も新しい投稿があるようです。


残りの時間を利用した私が作ってみたツールをレビューしてもらいました。

過去のGolangCafeで取り上げたパッケージ等を利用して作ってみました。

github.com/mattn/go-sqlite3

github.com/coopernurse/gorp

github.com/gorilla/mux

github.com/PuerkitoBio/goquery

2014-09-12

Golang Cafe #44 まとめ

| 16:23

2014/08/24に開催された「Golang Cafe #44」についてのまとめです。


今回は「インタフェースの実装パターン」を読み進めました。

Go言語を一通り勉強した方なら目新しいものはあまりないかもしれませんが、ハマリどころや、気をつけなくてはならないポイントがまとめられています。

(サンプルはスライド内のものを参考にさせていただいてます)


type宣言で既存の型を再定義、拡張することができます。

C#の拡張メソッドのような感じで、あたかもint型にStringメソッドを追加したように振舞えます。

type Hex int;

func (h Hex) doSomething() { }

type宣言で関数型にも名前を付けることができます。

type HandlerFunc func(w ResponseWriter, r *Request)

func (h *HandlerFunc) doSomething() { }

Go言語でのinterface宣言ではメソッドしか宣言できません。C#のようにプロパティを宣言することはできません。

interfaceを実装する側は、implementや:などで明示的にinterfaceの実装を宣言する必要はありません。

逆に言うと、明示的な宣言がないので、interfaceが実装されていることに気が付きにくいです。

type Hex int;

func (h Hex) String() string { return "some" }

このサンプルはStringerインターフェースを実装していることになります。

typeで宣言された関数型も同じです。


Go言語では継承はサポートされていませんが、構造体の匿名フィールドとして別の型(構造体)を埋め込むことで、継承に”似た”振る舞いをすることができます。

type Name struct {
    FirstName  string
    FamilyName string
}

func (n Name) String() {
    fmt.Printf("%s %s\n", n.FirstName, n.FamilyName)
}

type Person struct {
    Name
    Age int
}

func main() {
    p := Person { Name{ "Taro", "Yamada" }, 20 }

    p.String()
    // p.Name.String() と同じ
}

ただ複数の型を埋め込んだ場合、両方の型に同じ名前のメソッドが存在している場合は、明示的に呼び出す必要があります。

埋め込んだ型があるinterfaceを実装している場合、埋め込み先の方もinterfaceを実装したことになります。


複数の型を埋め込んだ場合、複数の型に存在するメソッドを合わせてinterfaceに宣言されているメソッドを満たせば、埋め込み先の方もinterfaceを実装したことになります。


埋め込み先はinterfaceで宣言しておき、実行時にinterfaceを実装した型を代入することもできます。


「知らない間に実装されている」というのに気づくようになれば、気にせず使えるようになるのではないかと思います。

ただ通常の型のように単純なキャストができない点だけ気をつけなくてはなりません。

2014-09-04

C#によるマルチコアのための非同期-並列処理プログラミング 読みました。

| 16:16

メインは前半。ThreadPoolを使い方からTaskの使い方までそれぞれの場合で比較できます。

2014-08-19

Go言語で複数戻り値の受け取りで既存変数の再宣言?

| 13:15

非常に分かりにくいタイトルになっていますが、Go言語では変数の宣言を行うには、

var i int
var i = 0
i := 0

など複数の宣言方法があります。当然同じスコープ上で複数回宣言しようとすると、

no new variables on left side of :=

i redeclared in this block 
previous declaration at .\error_sample.go:11 // 丁寧に既に宣言されている場所を教えてくれる

のように怒られます。


普通にコードを書かれている方々にとっては当たり前のことだと思うのですが、ずっと気になっていたことがあったので確かめてみました。


Go言語では複数戻り値を使用できるので、関数メソッド戻り値として、

func function1() (int, error) {
    return 1, errors.New("エラーです")
}

のような書き方をすることがよくあります。呼び出し側は、

i, err := function1()

のように書いたりします。

では上の呼び出しを行った後に、

j, err := function1()

と呼び出したらどうなのでしょう?

jは新規の変数なので問題なし、errは直前でも使用されているので再宣言なのでエラーになるのかというとエラーにはなりません。

ということは、jだけ新規に宣言されて、errは使いまわされているのか、それとも以前のことはなかったことにしてerrも新規に宣言されていることになるのか。


という訳で検証してみたのが以下のコードです。

package main

import (
    "fmt"
)

func main() {
    err := function1()
    fmt.Println(err, &err)

    i, ii, err := function2()
    fmt.Println(i, &i, ii, &ii, err, &err)

    i, ii, err = function3()
    fmt.Println(i, &i, ii, &ii, err, &err)

    j, ii, err := function4()
    fmt.Println(j, &j, ii, &ii, err, &err)

    j, jj, err := function4()
    fmt.Println(j, &j, jj, &jj, err, &err)

    fmt.Println(err, &err)
}

func function1() error {
    return fmt.Errorf("error1")
}

func function2() (int, int, error) {
    return 2, 20, fmt.Errorf("error2")
}

func function3() (int, int, error) {
    return 3, 30, fmt.Errorf("error3")
}

func function4() (int, int, error) {
    return 4, 40, nil
}

実行結果は以下のとおりです。

error1 0xc0820001e0
2 0xc082000230 20 0xc082000238 error2 0xc0820001e0
3 0xc082000230 30 0xc082000238 error3 0xc0820001e0
4 0xc082000290 40 0xc082000238 <nil> 0xc0820001e0
4 0xc082000290 40 0xc082000298 <nil> 0xc0820001e0
<nil> 0xc0820001e0

結果を見て分かるように新規変数とともに既存変数を宣言していますが、既存変数のアドレスは同じです。

どうやら := 演算子で受け取る複数戻り値のうち1つでも新しい変数が含まれていたらエラーにはならず、既存の変数は使いまわされるようです。


追記

@_makoto_sato_ さんからご指摘を受けました(サンプルソース)が、あくまで同じスコープ上での話です。

異なるスコープでは別の変数として宣言されます。

Connection: close