読者です 読者をやめる 読者になる 読者になる

初老のボケ防止日記

おっさんのひとりごとだから気にしないようにな。

スポンサーリンク

いまからのGo(5)

Go言語


Yes! Go! Go! キモチをひとつに!
本を読んでも実際に何か作ってみないと全く理解が進まないのでサンプルを組みながら理解していくコーナー最終回。

前回迄のあらすじ

osa030.hatenablog.com

前回はECHOクライアント/ビッグECHOサーバをパッケージ化したのだが、今回は汎用性を追加する。

環境

前回通り。

OS Windwos 10 Pro
Go 1.6.2
IntelliJ IDEA 2016.2
Goプラグイン 0.12.1724
GB 0.4.x

構成

今迄の通り、IDEAのプロジェクトフォルダを「pachimongo」とする。

$ cd pachimongo/
$ tree -d
.
`-- src
    |-- cmd
    |   |-- client
    |   `-- server
    `-- common
        `-- net
            `-- echo

IDEAで補完を有効化するために「pachimongo/src」を「Go Libraries」に追加するのを忘れずに。

手順は以下参照

osa030.hatenablog.com

問題点

前回までのコードの問題点をあげると…サーバがビッグECHOしか対応してない。

仮にスモールECHOにしたり○○ECHOを実装するにはパッケージを改修しないといけないのはイケてない。

ということでリファクタリングしよう。

「common/net/echo」パッケージ

共通処理

要求メッセージを引数として応答メッセージを返すインタフェースを定義する。

  • common.go
type Responder interface {
	MakeResponse(req string) string
}

type ResponderFunc func(req string) string

func (f ResponderFunc) MakeResponse(req string) string {
	return f(req)
}

インタフェースを実装するのは構造体とは限らないので、関数型も定義しておく。

ビッグECHOサーバ改め汎用ECHOサーバ

Responderを使ってECHOサーバを汎用ECHOサーバに修正する。

  • EchoServer構造体
type EchoServer struct {
	listener  *net.TCPListener
	close     chan struct{}
	closed    chan struct{}
	responder Responder // ☆追加
}

構造体のメンバにResponderを追加する。

  • コンストラクタ
func NewEchoServer(service string, r Responder /* ☆追加 */) (*EchoServer, error) {
	// 省略
	return &EchoServer{
		listener: tcpListener,
		close:    make(chan struct{}, 1),
		closed:   make(chan struct{}, 1),
		responder: r, // ☆追加
	}, nil
}

コンストラクタの引数にResponderを指定するように変更。

  • サーバメインルーチン
func (es *EchoServer) handle() {
	// 省略
ACCEPT_LOOP:
	for {
		select {
		// 省略
		default:
			// 省略
			session := newEchoSession(conn, closed)
			sessions[session.id] = session
			log.Println("new session:", session.id)
			wg.Add(1)
			go session.handle(&wg, es.responder) // ☆変更
		}
	}
}

ECHOセッションのメインルーチンであるhandle()メソッドの引数にResponderを渡すように修正。

ECHOセッション

最後はECHOセッションをResponderに対応させる。

  • セッションメインルーチン
func (es *EchoSession) handle(wg *sync.WaitGroup, r Responder /* ☆追加 */) {
	// 省略
MESSAGE_LOOP:
	for {
		select {
		// 省略
		default:
			// 省略
			log.Printf("RECV[%s]\n", msg)

			resp := r.MakeResponse(msg) // ☆変更

			err = writeMessage(es.conn, resp)
			// 省略
			log.Printf("SEND[%s]\n", resp)
		}
	}
}

応答メッセージの生成はhandleメソッド起動時に指定されたResponderインタフェースを実装した何かを使う。このようにインタフェースを定義して用いればコードを抽象化することができる。

ビッグECHOサーバ(main)

汎用ECHOサーバを用いたビッグECHOサーバ実装。

package main

import (
	"common/net/echo"
	"log"
	"os"
	"os/signal"
	"strings"
)

func makeBigEchoServer( service string ) (*echo.EchoServer, error){

	responder := echo.ResponderFunc(
		func( req string ) string {
			return  strings.ToUpper(req)
	})

	return echo.NewEchoServer(service, responder)
}

func main() {

	service := ":5555"

	log.Println("start server", service)
	defer log.Println("stop server")

	echoServer, err := makeBigEchoServer(service)
	if err != nil {
		log.Fatal(err)
	}

	echoServer.Start()
	defer echoServer.Stop()

	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt)
	<-sig
}

Responderインタフェースを実装したecho.ResponderFuncに対して、ビッグECHO化の関数クロージャを渡すことでResponderを定義している。で、そのResponderを汎用ECHOサーバのコンストラクタに渡すことでビッグECHOサーバとして動作する。

新たなECHOサーバ(main)

ビッグECHOだけだと汎用化した成果がわかりにくいので、もう一つ別のECHOサーバを実装する。

  • PatternResponse構造体
type PatternResponse struct {
	re *regexp.Regexp
	response string
}

func newPatternResponse( re *regexp.Regexp, response string) (*PatternResponse) {
	return &PatternResponse{
		re: re,
		response:response,
	}
}

正規表現パターンと、そのパターンにマッチした時に返却する文字列を定義した構造体とコンストラクタ。

  • RegexpResponder構造体
type RegexpResponder struct {
	patterns []*PatternResponse
}

func newRegexpResponder() *RegexpResponder{
	return &RegexpResponder{
		patterns: make([]*PatternResponse, 0, 100),
	}
}

func (rr *RegexpResponder)add( pattern, response string) error{
	re, error := regexp.Compile(pattern)
	if error != nil {
		return error
	}

	pr := newPatternResponse(re, response)
	rr.patterns = append( rr.patterns, pr)
	return nil
}

func (rr *RegexpResponder) MakeResponse( req string ) string {

	for _, pr := range rr.patterns {
		if pr.re.MatchString(req) {
			return pr.response
		}
	}
	return req
}

PatternResponse構造体を配列として保持する構造体とコンストラクタ。PatternResponseを追加する為のadd()メソッドと、Responderインタフェースを実装する為にMakeResponse()メソッドを定義している。

  • main.go
func main() {

	service := ":5555"

	log.Println("start server", service)
	defer log.Println("stop server")

	regexpResponder := newRegexpResponder()
	regexpResponder.add("あきらめたら","そこで試合終了ですよ")

	if err != nil {
		log.Fatal(err)
	}

	echoServer.Start()
	defer echoServer.Stop()

	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt)
	<-sig
}

PatternResponseによるECHOサーバの実装例。

  • 実行結果
$ bin/client.exe
2016/08/12 10:53:00 start client 127.0.0.1:5555
こんにちわ
2016/08/12 10:53:09 SEND[こんにちわ]
2016/08/12 10:53:09 RECV[こんにちわ]
あきらめたら
2016/08/12 10:53:17 SEND[あきらめたら]
2016/08/12 10:53:17 RECV[そこで試合終了ですよ]
本当にあきらめたら
2016/08/12 10:53:24 SEND[本当にあきらめたら]
2016/08/12 10:53:24 RECV[そこで試合終了ですよ]
あきらめたら駄目なんでしょうか?
2016/08/12 10:53:36 SEND[あきらめたら駄目なんでしょうか?]
2016/08/12 10:53:36 RECV[そこで試合終了ですよ]
ありがとうございました
2016/08/12 10:53:46 SEND[ありがとうございました]
2016/08/12 10:53:46 RECV[ありがとうございました]
2016/08/12 10:53:48 getInput ERROR: EOF
2016/08/12 10:53:48 disconnect from: 127.0.0.1:5555
2016/08/12 10:53:48 stop client

安西ECHOサーバの完成。

ということで、全5回とだらだらとGo言語に関する記事を書いてみた。とりあえず基本はマスター出来た気がするので

努力したらHappy Come! 奇跡起こせ Go!

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

スターティングGo言語

スターティングGo言語

プログラミング言語Goフレーズブック

プログラミング言語Goフレーズブック

  • 作者: David Chisnall,デイビッド・チズナール,柴田芳樹
  • 出版社/メーカー: ピアソン桐原
  • 発売日: 2012/10/04
  • メディア: 単行本(ソフトカバー)
  • 購入: 1人 クリック: 5回
  • この商品を含むブログを見る

スポンサーリンク