2022/04,江端さんの技術メモ

https://teratail.com/questions/2m8dsx1urnslib

一応動いているソースコード

// main.go

package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"net/url"

	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{}

type GetLoc struct {
	ID    int     `json:"id"`
	Lat   float64 `json:"lat"`
	Lng   float64 `json:"lng"`
	TYPE  string  `json:"type"` // "PERSON","BUS","CONTROL
	POPUP int     `json:"popup"`
	//Address string  `json:"address"`
}

func echo2(w http.ResponseWriter, r *http.Request) {
	conn2, err := upgrader.Upgrade(w, r, nil) //conn2でwebsocketを作成
	if err != nil {
		log.Println("websocket connection err:", err)
		return
	}
	defer conn2.Close()

	for {
		gl2 := new(GetLoc)
		err := conn2.ReadJSON(&gl2)
		if err != nil {
			log.Println("ReadJSON:", err)
			break
		}
		fmt.Println("echo2:", gl2)
		//time.Sleep(time.Second * 10)
	}

}

var addr = flag.String("addr", "0.0.0.0:5000", "http service address") // テスト

func dummyClient() {
	_ = websocket.Upgrader{} // use default options

	flag.Parse()
	log.SetFlags(0)
	u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo2"}
	log.Printf("connecting to %s", u.String())

	c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
	if err != nil {
		log.Fatal("dial:", err)
	}
	defer c.Close()

	for {
		gl := new(GetLoc)

		gl.ID = 101
		gl.Lat = 181.0
		gl.Lng = 181.0
		gl.TYPE = "BUS"
		gl.POPUP = 101

		err := c.WriteJSON(gl)
		if err != nil {
			log.Println("write:", err)
			break
		}
	}

}

func main() {
	http.Handle("/", http.FileServer(http.Dir(".")))

	http.HandleFunc("/echo2", echo2)
	//go dummyClient()

	log.Println("server starting...", "http://localhost:5000")
	log.Fatal(http.ListenAndServe("localhost:5000", nil))
}
// index.html

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test</title>

</head>
<body>
    <form>
        <button id="open">open</button>
        <button id="send">send</button>
        <button id="close">close</button>
    </form>

    <script>  

        var ws;

        // websocketのオープン(この段階で接続完了)
        ws = new WebSocket('ws://localhost:5000/echo2')  // ユーザ登録画面

        // websocketのオープンイベントが発生したら
        ws.onopen = function(evt) {
            // 何もしない
            print("OPEN");
            console.log("OPEN");
            return false;
        }

        // "send"ボタンをクリックしたら
        document.getElementById("send").onclick = function(evt) {
            if (!ws) {
                return false;
            }
 
            var obj = {id:101,lat:181.0, lng:181.0,type:"BUS", popup:101}
            var json_obj = JSON.stringify(obj);            
            ws.send(json_obj);
            return false;
         };
        
        // "close"ボタンをクリックしたら
        document.getElementById("close").onclick = function(evt) {
            if (!ws) {
                return false;
            }
            // websocketを閉じる
                
            ws.close();
            console.log("CLOSE")
            return false;
        };
   
    </script>
</body>
</html>

2020/08,江端さんの技術メモ

https://github.com/gorilla/websocket/tree/master/examples/echo

この"server.go"は、client.goからの通信だけではなく、ブラウザの画面も提供します(スゴい!)。

というか、Goプログラムの中に、html書けるなんて知らんかった。

 
// server.go

// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build ignore
// +build ignore

package main

import (
	"flag"
	"html/template"
	"log"
	"net/http"

	"github.com/gorilla/websocket"
)

var addr = flag.String("addr", "localhost:8080", "http service address")

var upgrader = websocket.Upgrader{} // use default options

func echo(w http.ResponseWriter, r *http.Request) {
	c, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()
	for {
		mt, message, err := c.ReadMessage()
		if err != nil {
			log.Println("read:", err)
			break
		}
		log.Printf("recv: %s", message)
		err = c.WriteMessage(mt, message)
		if err != nil {
			log.Println("write:", err)
			break
		}
	}
}

func home(w http.ResponseWriter, r *http.Request) {
	homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}

func main() {
	flag.Parse()
	log.SetFlags(0)
	http.HandleFunc("/echo", echo)
	http.HandleFunc("/", home)
	log.Fatal(http.ListenAndServe(*addr, nil))
}

var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>  
window.addEventListener("load", function(evt) {

    var output = document.getElementById("output");
    var input = document.getElementById("input");
    var ws;

    var print = function(message) {
        var d = document.createElement("div");
        d.textContent = message;
        output.appendChild(d);
        output.scroll(0, output.scrollHeight);
    };

    document.getElementById("open").onclick = function(evt) {
        if (ws) {
            return false;
        }
        ws = new WebSocket("{{.}}");
        ws.onopen = function(evt) {
            print("OPEN");
        }
        ws.onclose = function(evt) {
            print("CLOSE");
            ws = null;
        }
        ws.onmessage = function(evt) {
            print("RESPONSE: " + evt.data);
        }
        ws.onerror = function(evt) {
            print("ERROR: " + evt.data);
        }
        return false;
    };

    document.getElementById("send").onclick = function(evt) {
        if (!ws) {
            return false;
        }
        print("SEND: " + input.value);
        ws.send(input.value);
        return false;
    };

    document.getElementById("close").onclick = function(evt) {
        if (!ws) {
            return false;
        }
        ws.close();
        return false;
    };

});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server, 
"Send" to send a message to the server and "Close" to close the connection. 
You can change the message and send multiple times.
<p>
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<p><input id="input" type="text" value="Hello world!">
<button id="send">Send</button>
</form>
</td><td valign="top" width="50%">
<div id="output" style="max-height: 70vh;overflow-y: scroll;"></div>
</td></tr></table>
</body>
</html>
`))
// client.go

// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build ignore
// +build ignore

package main

import (
	"flag"
	"log"
	"net/url"
	"os"
	"os/signal"
	"time"

	"github.com/gorilla/websocket"
)

var addr = flag.String("addr", "localhost:8080", "http service address")

func main() {
	flag.Parse()
	log.SetFlags(0)

	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, os.Interrupt)

	u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"}
	log.Printf("connecting to %s", u.String())

	c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
	if err != nil {
		log.Fatal("dial:", err)
	}
	defer c.Close()

	done := make(chan struct{})

	go func() {
		defer close(done)
		for {
			_, message, err := c.ReadMessage()
			if err != nil {
				log.Println("read:", err)
				return
			}
			log.Printf("recv: %s", message)
		}
	}()

	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-done:
			return
		case t := <-ticker.C:
			err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
			if err != nil {
				log.Println("write:", err)
				return
			}
		case <-interrupt:
			log.Println("interrupt")

			// Cleanly close the connection by sending a close message and then
			// waiting (with timeout) for the server to close the connection.
			err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
			if err != nil {
				log.Println("write close:", err)
				return
			}
			select {
			case <-done:
			case <-time.After(time.Second):
			}
			return
		}
	}
}

2022/04,江端さんの技術メモ

leafletの地図上に、マウスでクリックするとマーカーが出てきて、マーカーをクリックするとマーカーが消える、というJavaScript

var map = L.map("map", {
        attributionControl: false,
        zoomControl: false
        }).setView(new L.LatLng(35.654543, 139.795534), 18);
        // }).setView(new L.LatLng(35.598563, 139.475528), 18); 広袴

        L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {
         detectRetina: true,
         maxNativeZoom: 18
        }).addTo(map);

        var leafletView = new PruneClusterForLeaflet(1,1);  // (120,20)がデフォルト

        //地図のclickイベントでonMapClick関数を呼び出し
        map.on('click', onMapClick)
        
        function onMapClick(e) {
            //地図のclickイベント呼び出される
            //クリック地点の座標にマーカーを追加、マーカーのclickイベントでonMarkerClick関数を呼び出し
            var mk = L.marker(e.latlng).on('click', onMarkerClick).addTo(map);
            console.log("click:",e.latlng)
        }

        function onMarkerClick(e) {
            //マーカーのclickイベント呼び出される
            //クリックされたマーカーを地図のレイヤから削除する
            map.removeLayer(e.target);
            console.log("remove:",e.target)
        }

2022/04,江端さんの技術メモ

PrumeMobileのサーバ化

から、1日もあれば、動かせるだろうと思ったのですが、そこから、redisのパブサブ、Golangのmap、JavaScriptの改造、えらい目に会いましたが、ようやく目処が立ちました。

PrumeMobileは、Webのメモリを使うので、単なるWebサーバのようには取り扱えないので、色々工夫をしました。デバッグで苦労しました。

嬉しい気持ちは、直ぐに消えてしまうので、消える前に記録しておきました。

 

2022/04,未分類,江端さんの技術メモ

  1. golang内でredis経由でJSONを飛す時
    // パブリッシュ側
    
    type GetLoc struct {
        ID    int     `json:"id"`
        Lat   float64 `json:"lat"`
        Lng   float64 `json:"lng"`
        TYPE  string  `json:"type"` // "PERSON","BUS","CONTROL
        POPUP int     `json:"popup"`
        //Address string  `json:"address"`
    }
    
    		gl.TYPE = "BUS"
    		gl.Lat = 35.654543 + (rand.Float64()-0.5)*0.00001*20
    		gl.Lng = 139.795534 + (rand.Float64()-0.5)*0.00002*20
    		gl.ID = rand.Int() % 5
    
    		json_gl, _ := json.Marshal(gl)
    		r, err := redis.Int(conn.Do("PUBLISH", "channel_1", json_gl))
    
    // サブスクライブ側
    
    	for {
    		switch v := psc.Receive().(type) {
    		case redis.Message:
    			fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
    
    			var gl GetLoc
    			_ = json.Unmarshal(v.Data, &gl)
    			fmt.Println(gl.ID)
  2. golangから直接JavaScriptへJSONを飛す時
    golangから (上記の続き)

    var gl GetLoc
    			_ = json.Unmarshal(v.Data, &gl)
    			fmt.Println(gl.ID)
    
    			//conn.WriteJSON(v.Data)
    			conn.WriteJSON(gl)

    JavaScriptで受けとる

    <script>
            function obj(id, lat, lng, type, popup){
                this.id = id;
                this.lat = lat;
                this.lng = lng;
                this.type = type;
                this.popup = popup;
            }
    
    
            //ws.onmessage = e => console.log(e.data)
            ws.onmessage = function(event) {  // 受信したメッセージはここに飛んでくる
                console.log("RESPONSE",event.data)
                var obj = JSON.parse(event.data);
                console.log("after parse:",obj.id)
            }

ポイントは、Marshal、Unmarshal、parseの使い方

2022/04,江端さんの技術メモ

入力2、出力1のマップを取扱いたい場合のケースを調べてみました。

Golangは、2次元マップの取扱いは簡単なのですが、N次元は、かなり複雑になるようです。調べたところ、以下のような取扱いができることが分かりました。

package main

import "fmt"

func main() {

	// 2次元マップ
	m := map[string]int{}
	//m := make(map[string]int)

	m["apple"] = 150
	m["banana"] = 200
	m["lemon"] = 300

	fmt.Println("0:", m["apple"])

	// 多次元mapは、2次元マップのように簡単には使えない
	// 比較的面倒のないやり方は、以下のように構造体を使う方法(らしい)
	// https://qiita.com/ruiu/items/476f65e7cec07fd3d4d7

	type key struct {
		id  int
		att string
	}

	// 配列宣言
	m1 := make(map[key]int)

	// レコード追加
	m1[key{0, "BUS"}] = 1
	m1[key{0, "PERSON"}] = 1

	m1[key{1, "BUS"}] = 2
	m1[key{1, "PERSON"}] = 11

	// レコードの一つを表示
	fmt.Println("1:", m1[key{1, "BUS"}])
	// レコードの全部を表示
	fmt.Println("2:", m1)

	// 変数を使う方法
	id0 := 1
	att0 := "PERSON"
	fmt.Println("3:", m1[key{id0, att0}])

	// レコードの削除
	delete(m1, key{1, "BUS"})
	fmt.Println("4:", m1)

	for i := range m1 {
		fmt.Println("5:", i)
	}

	// 変数を使って、キーの存在を確認する
	id0 = 1
	att0 = "PERSON"
	value, isThere := m1[key{id0, att0}]
	fmt.Println("6:", value, isThere)

	id0 = 2
	att0 = "PERSON"

	value, isThere = m1[key{id0, att0}]
	fmt.Println("7:", value, isThere)

	// レコード追加の例

	id0 = 3
	att0 = "BUS"
	new_id0 := 20

	fmt.Println("8:", m1)

	_, isThere = m1[key{id0, att0}]
	if !isThere {
		m1[key{id0, att0}] = new_id0
	}

	fmt.Println("9:", m1)

}

出力結果は以下の通りです。

C:\Users\ebata\goga\1-11\test>go run main.go
0: 150
1: 2
2: map[{0 BUS}:1 {0 PERSON}:1 {1 BUS}:2 {1 PERSON}:11]
3: 11
4: map[{0 BUS}:1 {0 PERSON}:1 {1 PERSON}:11]
5: {1 PERSON}
5: {0 BUS}
5: {0 PERSON}
6: 11 true
7: 0 false
8: map[{0 BUS}:1 {0 PERSON}:1 {1 PERSON}:11]
9: map[{0 BUS}:1 {0 PERSON}:1 {1 PERSON}:11 {3 BUS}:20]

以上

 

 

 

 

 

2022/04,江端さんの技術メモ

江端家のテレビには、AmazonとNetFlixを視聴する為だけ用のPCが繋っています。
で、昨日、NetFlixを見ようとした嫁さんから「動かない」とクレームを受けました。

「あ、これ、電池切れだ」と当りをつけて、PCの中をのぞいてみました。

で、型番を調べたら、

CR2032でしたが、私の部屋には、その型番のものがなかったので(他の型番はあったのですが)、明日、100均で手に入れようと決めて、別の作業に入りました。
先ずは、Windows10のライセンスキーの購入でした。最近、正規版(リテール版も)が安くなっているようで、私は、https://www.gamivo.com/product/windows-10-professionalから、プロバイダーを一つ選びました。

1500円くらいでした。
VISAカードで支払いを終えたら、直ぐに、ライセンスキーが表示されました。
-----
で、今朝、100均にいってCR2032を3つ/100円を購入して、PCを再起動、
Windows10をインストールして、ライセンスキーを入力して、あとは、あれやこれややって、入れ替えを終えました。
で、そのなんやかんやには、「ライセンス認証を行って下さい」という表示が出てくるなどがありました。

で、電話による認証が必要となりました。
中古ライセンスのリセール品を売りつけられたのかもしれませんが、ともあれ認証されたようです。
以上

2022/03,江端さんの技術メモ

brian-goo/pubsub-go

(1)redisサーバを援用して、(2)golangをサーバにして、(3)websocketでブロードキャストが実現できて、(4)JavaScriptをクライアントとして使える、(5)OSSを捜し出しました。

理由は、複数のWebに同じ地図と車両の移動をリアルタイムを表示しなればならないのですが、Websocketはユニキャスト通信しかできないので、困っていました(UDPとか使えればいいんですが、スマホでUDPが通るとは思えないので)。

WebSocketでブロードキャストを作るのが辛いので、これを援用したいと思います。

これは、http://localhost:5000 でブラウザを立ち上げると、サーバを介してブラウザ→ブラウザにメッセージがechoされます。

redisサーバを立ち上げておいて、main.goと、js/index.htmlだけで動きます。

上記を改造すれば、サーバからパブ(pub)、webブラウザでサブ(sub)が可能になるはずです ―― 多分


実験した結果、subscribeしたオブジェクトに、過去のデータもpublishされることが分かりました。

subscribe以前のデータを取込むと逆に困る(過去のデータは不要な上に、データの表示もバラバラになる)ので、pubsub-goの採用は見送り、redigoの方を使うことにしました。

redisを前提として、golangでPubSubを実現するプログラム

JavaScriptを直接のクライアントとして使いたかったのですが、redigoと連携するJavaScriptが見つけられなかった(かなり捜したつもり)ので、Golangで立ち上げるハンドルの中で、個別に対処することにしました。

 

 

 

 

 

2022/03,江端さんの技術メモ

golangのプログラムの中でブロードキャストするのであれば、

https://github.com/MicrosoftArchive/redis/releases/tag/win-3.2.100

で、redisサーバをインスールして、

https://github.com/gomodule/redigoを使うという手もありますが、

Redigoを使う(6) パブリッシュ/サブスクライブ

のサンプルプログラムで簡単に試すことができます。

 

 

2022/03,江端さんの技術メモ

PrumeClusterは、Leafletをベースとして動く、スケーラブルオブジェクトビューアです。1万オブジェクトくらいなら軽く表示できます。

しかし、PrumeClusterは、クライアントのブラウザの中にアイコンのオブジェクトを直接作るので、基本的にはサーバとして使うことができません。クライアントとして使うものであて、描画画面は、常に"1つ"です。

で、私が作ったPrumeMobileもベースは、PrumeClusterなので、サーバとして使うことはできないのですが ―― 今週末、Vue.jpとかでスマホクライアント作ろうかと思ったのですが ――『もう新しいこと覚えるのは嫌だ』と思い知り、PrumeMobileのサーバ化を試みています ――ひとえに、考えうる限り、手を抜きたい、という一心からです。

複数のJavaScriptに対して、PrumeClusterがメッセージをブロードキャスト送付してくれるのか、くれないのか、明日調べよう。