2021/01,江端さんの技術メモ

// simple_csv_1_socket.go
// 2021/01/14
// 対向サーバは、server22-1.go(変更なしで使えた)
// simple_cvs.go の場合、数万のエージェント全部とwebsocket通信することになるため、
// サーバとの通信用のソケットを1つに限定して、この問題を回避した。
// ただエージェントは、従来通り、全部の数(数万から十万くらい?)作成したがそれでも。動いている。
// golang まじ凄い
// 変更点は、mainルーチンで、エージェント用のソケットを作って、goroutineでエージェント用のスレッド作る時に、それを渡している点
// あと通信は、送信→受信で1セットになるように、ミューテックスロックで競合回避を行った点(まあ通信が混乱するのを回避するため)
// 普通なら、これで相当の実行速度の低下が発生するはずなんだけど、体感的には遅くなかった。
// 現状の問題点は、chromoの方が先に落ちる、ということかな。まあ、数万のオブジェクトを1秒以内に動かされたら、chromoも文句の一つも言いたかろう。
// この問題は、メモリが潤沢に搭載されているPCでなら回避できるような気がするので、当面は放置することにする
// simple_csv_1_socket.go

// 2021/01/14
// simple_cvs.go の場合、数万のエージェント全部とwebsocket通信することになるため、
// サーバとの通信用のソケットを1つに限定して、この問題を回避した。
// ただエージェントは、従来通り、全部の数(数万から十万くらい?)作成したがそれでも。動いている。
// golang まじ凄い
// 変更点は、mainルーチンで、エージェント用のソケットを作って、goroutineでエージェント用のスレッド作る時に、それを渡している点
// あと通信は、送信→受信で1セットになるように、ミューテックスロックで競合回避を行った点(まあ通信が混乱するのを回避するため)
// 普通なら、これで相当の実行速度の低下が発生するはずなんだけど、体感的には遅くなかった。
// 現状の問題点は、chromoの方が先に落ちる、ということかな。まあ、数万のオブジェクトを1秒以内に動かされたら、chromoも文句の一つも言いたかろう。
// この問題は、メモリが潤沢に搭載されているPCでなら回避できるような気がするので、当面は放置することにする

package main

import (
	"encoding/csv"
	"flag"
	"fmt"
	"log"
	"math"
	"net/url"
	"os"
	"strconv"
	"sync"
	"time"

	"github.com/gorilla/websocket"
)

// GetLoc GetLoc
type GetLoc struct {
	ID  int     `json:"id"`
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
	//Address string  `json:"address"`
}

// 構造体の作り方
type unmTbl struct {
	uniName string // User Name: Example  6ca....
	objType string // "Bus" or "User"
	simNum  int
	pmNum   int
	lon     float64
	lat     float64
}

var list = make([]unmTbl, 0)                                           // 構造体の動的リスト宣言
var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト

func main() {
	file, err := os.Open("1.csv")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	var wg sync.WaitGroup

	reader := csv.NewReader(file)
	var line []string

	// サーバとのコネクションを1つに統一

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

	// rand.Seed(time.Now().UnixNano())

	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)
	}

	for {

		time.Sleep(time.Millisecond * 1) // 0.001秒休む

		line, err = reader.Read()
		if err != nil {
			break
		}

		uniName := line[0]
		//fmt.Printf("%s\n", uniName)

		objType := line[9]
		//fmt.Printf("%s\n", objType)

		lon, _ := strconv.ParseFloat(line[8], 64)
		//fmt.Printf("%f\n", lon)

		lat, _ := strconv.ParseFloat(line[7], 64)
		//fmt.Printf("%f\n", lat)

		// 特定範囲に限定する
		//if lon > 139.744330 && lon < 139.866586 && lat > 35.574777 && lat < 35.694479 {
		if lon > 139.7583407156985 && lon < 139.81403350119444 && lat > 35.62835195825786 && lat < 35.66678018870369 {

			flag := 0

			for i := range list {
				if i != 0 && list[i].uniName == uniName { // 同一IDを発見したら
					list[i].lon = lon // 新しい経度情報の更新
					list[i].lat = lat // 新しい緯度情報の更新

					flag = 1
					break
				}
			}

			uniNum := len(list)

			if flag == 0 { // 新しいIDを発見した場合
				wg.Add(1) // goルーチンを実行する関数分だけAddする
				go movingObject(uniNum, uniName, objType, lon, lat, &wg, c)
			}
		}

	}
}

var mutex sync.Mutex

func movingObject(uniNum int, uniName string, objType string, lon float64, lat float64, wg *sync.WaitGroup, c *websocket.Conn) {

	fmt.Printf("start movingObject\n")

	defer wg.Done() // WaitGroupを最後に完了しないといけない。

	defer c.Close()

	// リストを作る前にテストをする
	//fmt.Printf("%s\n", objType)
	//fmt.Printf("%d\n", uniNum)
	//fmt.Printf("%f\n", lon)
	//fmt.Printf("%f\n", lat)

	ut := unmTbl{} // 構造体変数の初期化
	ut.uniName = uniName
	ut.objType = objType
	ut.simNum = uniNum
	ut.lat = lat
	ut.lon = lon

	gl := new(GetLoc)
	gl.ID = 0
	gl.Lat = ut.lat
	gl.Lng = ut.lon

	mutex.Lock()           // 送受信時にミューテックスロックしないと
	err := c.WriteJSON(gl) // PruneMobile登録用送信
	if err != nil {
		log.Println("write1:", err)
	}

	gl2 := new(GetLoc) // PruneMobile登録確認用受信
	err = c.ReadJSON(gl2)
	mutex.Unlock()

	ut.pmNum = gl2.ID // PrumeMobileから提供される番号

	//fmt.Printf("ut.objType=%v\n", ut.objType)
	list = append(list, ut) // 構造体をリストに動的追加

	// ここからは更新用のループ
	for {
		time.Sleep(time.Millisecond * 100) // 0.1秒休む

		// 前回との座標に差が認められれば、移動させる
		if math.Abs(list[uniNum].lat-gl.Lat) > 0.000000001 || math.Abs(list[uniNum].lon-gl.Lng) > 0.000000001 {

			fmt.Print("MOVING!\n")
			gl.Lat = list[uniNum].lat
			gl.Lng = list[uniNum].lon
			gl.ID = gl2.ID

			// 座標の送信

			mutex.Lock()
			err = c.WriteJSON(gl)
			if err != nil {
				log.Println("write2:", err)
			}

			// 応答受信
			gl3 := new(GetLoc)
			err = c.ReadJSON(gl3)
			mutex.Unlock()

		}

	}

}

2021/01,江端さんの技術メモ

// simple_csv.go

package main

import (
	"encoding/csv"
	"flag"
	"fmt"
	"log"
	"math"
	"net/url"
	"os"
	"strconv"
	"sync"
	"time"

	"github.com/gorilla/websocket"
)

// GetLoc GetLoc
type GetLoc struct {
	ID  int     `json:"id"`
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
	//Address string  `json:"address"`
}

// 構造体の作り方
type unmTbl struct {
	uniName string // User Name: Example  6ca90e
	objType string // "Bus" or "User"
	simNum  int
	pmNum   int
	lon     float64
	lat     float64
}

var list = make([]unmTbl, 0)                                           // 構造体の動的リスト宣言
var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト

func main() {
	file, err := os.Open("1.csv")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	var wg sync.WaitGroup

	reader := csv.NewReader(file)
	var line []string

	for {

		time.Sleep(time.Millisecond * 1) // 0.001秒休む

		line, err = reader.Read()
		if err != nil {
			break
		}

		uniName := line[0]
		//fmt.Printf("%s\n", uniName)

		objType := line[9]
		//fmt.Printf("%s\n", objType)

		lon, _ := strconv.ParseFloat(line[8], 64)
		//fmt.Printf("%f\n", lon)

		lat, _ := strconv.ParseFloat(line[7], 64)
		//fmt.Printf("%f\n", lat)

		// 特定範囲に限定する
		//if lon > 139.744330 && lon < 139.866586 && lat > 35.574777 && lat < 35.694479 {
		if lon > 139.7583407156985 && lon < 139.81403350119444 && lat > 35.62835195825786 && lat < 35.66678018870369 {

			flag := 0

			for i := range list {
				if i != 0 && list[i].uniName == uniName { // 同一IDを発見したら
					list[i].lon = lon // 新しい経度情報の更新
					list[i].lat = lat // 新しい緯度情報の更新

					flag = 1
					break
				}
			}

			uniNum := len(list)

			if flag == 0 { // 新しいIDを発見した場合
				wg.Add(1) // goルーチンを実行する関数分だけAddする
				go movingObject(uniNum, uniName, objType, lon, lat, &wg)
			}
		}

	}
}

func movingObject(uniNum int, uniName string, objType string, lon float64, lat float64, wg *sync.WaitGroup) {

	fmt.Printf("start movingObject\n")

	defer wg.Done() // WaitGroupを最後に完了しないといけない。

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

	// rand.Seed(time.Now().UnixNano())

	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()

	ut := unmTbl{} // 構造体変数の初期化
	ut.uniName = uniName
	ut.objType = objType
	ut.simNum = uniNum
	ut.lat = lat
	ut.lon = lon

	gl := new(GetLoc)
	gl.ID = 0
	gl.Lat = ut.lat
	gl.Lng = ut.lon

	err = c.WriteJSON(gl) // PruneMobile登録用送信
	if err != nil {
		log.Println("write:", err)
	}

	gl2 := new(GetLoc) // PruneMobile登録確認用受信
	err = c.ReadJSON(gl2)

	ut.pmNum = gl2.ID // PrumeMobileから提供される番号

	//fmt.Printf("ut.objType=%v\n", ut.objType)
	list = append(list, ut) // 構造体をリストに動的追加

	// ここからは更新用のループ
	for {
		time.Sleep(time.Millisecond * 100) // 0.1秒休む

		// 前回との座標に差が認められれば、移動させる
		if math.Abs(list[uniNum].lat-gl.Lat) > 0.000000001 || math.Abs(list[uniNum].lon-gl.Lng) > 0.000000001 {

			fmt.Print("MOVING!\n")
			gl.Lat = list[uniNum].lat
			gl.Lng = list[uniNum].lon
			gl.ID = gl2.ID

			// 座標の送信
			err = c.WriteJSON(gl)
			if err != nil {
				log.Println("write:", err)
			}

			// 応答受信
			gl3 := new(GetLoc)
			err = c.ReadJSON(gl3)
		}

	}

}

2021/01,江端さんの技術メモ

以下、上記記事のコピペ

Create new file をクリックして、ファイル名を path/to/.gitkeep という名前で作成します
(例えば、testsというディレクトリが必要であれば、"tests/.gitkeep"で作成できる)
すると、スラッシュで区切られたところはファイル名ではなくディレクトリ名として扱われ、path/to ディレクトリ内に .gitkeep というファイルが作成されます。このとき path/to ディレクトリがまだ存在しない場合は新しく作成されますのでこれを利用しているというわけです。
なんでディレクトリの作成をしたいだけなのに、わざわざこんなファイルを作るのかといいますと、git では空のディレクトリを add することはできないためです。 まだディレクトリに入れるファイルが何も無い場合でも何かしら入れておかないと add して git での管理ができません。

2021/01,江端さんの技術メモ

// udp_sendto.go
// go run udp_sendto.go
// golangによるudpの送信"だけ"するプログラム

package main

import (
	"fmt"
	"math/rand"
	"net"
	"time"
)

func random(min, max float64) float64 {
	return rand.Float64()*(max-min) + min
}

type unmTbl struct {
	uniNum  int
	objType string // "Bus" or "User"
	simNum  int
	pmNum   int
	lon     float64
	lat     float64
}

func main() {
	conn, err := net.Dial("udp4", "localhost:12345")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	// 初期化
	var ut [5]unmTbl
	for i, _ := range ut {
		ut[i].objType = "User"
		ut[i].uniNum = i
		ut[i].lat = 35.653976
		ut[i].lon = 139.796821
	}

	for {
		fmt.Println("Sending to server")
		for i, _ := range ut {
			ut[i].lat += random(0.5, -0.5) * 0.00001 * 10 * 5
			ut[i].lon += random(0.5, -0.5) * 0.00002 * 10 * 5

			str := ut[i].objType + "," + fmt.Sprint(ut[i].uniNum) + "," + fmt.Sprint(ut[i].lon) + "," + fmt.Sprint(ut[i].lat) + ","

			fmt.Println(str)

			_, err = conn.Write([]byte(str))
			if err != nil {
				panic(err)
			}
			time.Sleep(3 * time.Second) // 1秒休む

		}

	}

}

// udp_recvfrom.go
// go run udp_recvfrom.go
// golangによるudpの受信"だけ"するプログラム

package main

import (
	"fmt"
	"net"
)

func main() {
	addr, _ := net.ResolveUDPAddr("udp", ":12345")
	sock, _ := net.ListenUDP("udp", addr)

	i := 0
	for {
		i++
		buf := make([]byte, 1024)
		rlen, _, err := sock.ReadFromUDP(buf)
		if err != nil {
			fmt.Println(err)
		}
		fmt.Println(string(buf[0:rlen]))
		//fmt.Println(i)
		//go handlePacket(buf, rlen)
	}
}

2021/01,江端さんの技術メモ

package main


import (
    "fmt"
)


// 構造体の作り方
type unm_tbl struct {
    obj_type string // "Bus" or "User"
    sim_num  int
    pm_num   int
}


func main() {
    list := make([]unm_tbl, 0) // 構造体の動的リスト宣言


    ut := unm_tbl{} // 構造体変数の初期化
    ut.obj_type = "User"
    ut.sim_num = 1
    ut.pm_num = 0
    list = append(list, ut) // 構造体をリストに動的追加


    ut = unm_tbl{} // 構造体変数の初期化
    ut.obj_type = "Bus"
    ut.sim_num = 2
    ut.pm_num = 1
    list = append(list, ut) // 構造体をリストに動的追加


    ut = unm_tbl{} // 構造体変数の初期化
    ut.obj_type = "Taxi"
    ut.sim_num = 3
    //ut.pm_num = 3
    list = append(list, ut) // 構造体をリストに動的追加


    for i, _ := range list { // リスト分、ループする


        fmt.Println(list[i].obj_type)
        fmt.Println(list[i].sim_num)
        fmt.Println(list[i].pm_num)


    }


    fmt.Println(list)


}

 

新しい言語の勉強って、たいてい、文字列処理で挫折するよね。

2021/01,江端さんの技術メモ

https://www.openstreetmap.org/export#map=13/35.6367/139.8312 から、"You requested too many nodes (limit is 50000). Either request a smaller area, or use planet.osm"(ノード数が多すぎます(制限は50000)。もっと小さい領域を要求するか、 planet.osm を使用してください。) と言われた時の対応方法は以下の通りです。

(Step.1) 「ドラッグして別の領域を選択」を選択

(Step.2) 領域をマウスで拡大・変形した後、「Overpass API」を選択

(Step.3) 名前を付けて保存で、適当なファイル名(例 toyosu.osm)でセーブする

以上

2021/01,江端さんの技術メモ

最近、PC起動時に、

C:\Users\ebata>docker ps
Error response from daemon: open \\.\pipe\docker_engine_linux: The system cannot find the file specified.

などが出てきて、ドキっとさせられる症状が頻発している。

基本的には、Dockerアイコンを使って再起動(Restart docker...)で、改善するんだけど、心臓に悪い。

近い内に、何か起こりそうな気がします。

 

 

2021/01,江端さんの技術メモ

この環境↓を前提として、

(まとめ)地図DBの作り方

Dockerの環境にて、ca_sim2ののダンプによるsqlファイル(map5.sql)の作成に先程成功しました。

[江端メモ]
root@6432e639f678:/db_data#
>pg_dump -U postgres ca_sim2 > map5.sql
>mv map5.sql /db_data (これ、カレントディレクトリで作業しているなら不要です)

C:\Users\ebata\toyosu>docker cp toyosu_db_1:/db_data/map5.sql map5.sql

(まあ、私の環境でなければ、分からないとは思います)

2021/01,江端さんの技術メモ

QGIS3でosmファイルを地図表示する方法

いつも、osmファイルをドラッグする場所を忘れるので、メモ

すると、こうなる。

以上

2021/01,江端さんの技術メモ

0.追記(江端が、今、必要な情報をトップに)

### STLって面倒(strcpyでええやん)

// added by Ebata 2021/01/04
#include "simple_udp.h"

// Ebata added simple_udp 2021/01/05                                                                                    
simple_udp udp0("192.168.0.8",12345);

// added by Ebata 2021/01/04
std::cout << "Ebata:User:  " << user.id() << "," << user.current_xy().x() << "," << user.current_xy().y() << std::endl;
std::stringstream ss;
ss << "Ebata:User:  " << user.id() << "," << user.current_xy().x() << "," << user.current_xy().y() << std::endl;

// simple_udp udp0("192.168.0.8",12345);
udp0.udp_send("hello!\n");

//udp0.udp_send(ss);  エラー
//udp0.udp_send(ss.c_str()); エラー
udp0.udp_send(ss.str());

1.背景

LinuxのDockerコンテナの中から、一方的に、ホストOSにデータを放り投げる為のUDPプログラムです。

1行程度のデータをコンテナの外に出したいだけなのに、なかなか、良い方法が見つからず、結局UDP socketを使ってしまいました(websocket用にサーバを作るのも大袈裟で面倒でした(3~4行程度で記述したかった))。

ちなみに、UDPは、相手がいようがいまいが、一方的に送信し、一方的に受信待ちができる、とても便利な通信方式です。当然、送達保証はありません。

2. 環境

私のホストOSのIPアドレスは、192.168.0.8、ポートは、12345 と適当に選びました。

普通DockerにはUDPのポートオープンの設定が必要だと思いますが、今回は、テストプログラムでたまたまpingとUDPが通ってしまったので、何もやっていません(運が良かっただけかもしれません)。

テスト用のUDPプログラムは、こちら「UDP送受信プログラム」を使いました(何でも残しておくものです)

3.ソースコード

まるまる、こちらをコピペさせて頂きました → 「メカトロ講座03 c++でudp通信をする方法」

一行でUDP送信ができるようにしたかったもので。

3.1 共通のヘッダファイル(simple_udp.h)

// g++ -std=c++11 としないと動かない(ことがある)

// simple_udp.h

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string>  // "string.h"だと通らないことがあるので、注意のこと(私は、ここで2時間ほど嵌った)

class simple_udp{
  int sock;
  struct sockaddr_in addr;
public:
  simple_udp(std::string address, int port){
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(address.c_str());
    addr.sin_port = htons(port);
  }
  void udp_send(std::string word){
    sendto(sock, word.c_str(), word.length(), 0, (struct sockaddr *)&addr, sizeof(addr));
  }

  void udp_bind(){
    bind(sock, (const struct sockaddr *)&addr, sizeof(addr));

  }
  std::string udp_recv(){
            #define BUFFER_MAX 400
    char buf[BUFFER_MAX];
    memset(buf, 0, sizeof(buf));
    recv(sock, buf, sizeof(buf), 0);
    return std::string(buf);
  }
  void udp_recv(char *buf, int size){
    memset(buf, 0, size);
    recv(sock, buf, size, 0);
  }

  ~simple_udp(){
    close(sock);
  }
};

3.2 送信プログラム(udp_sendto.cpp)

// g++ udp_sendto.cpp -o udp_sendto で大丈夫だが、g++ -std=c++11としないと動かない(ことがある)

#include <stdio.h>
#include <string.h>
#include "simple_udp.h"

simple_udp udp0("192.168.0.8",12345); // ホストOSのUDPの受信側

int main(int argc, char **argv){
  udp0.udp_send("hello!");
  return 0;
}

3.3 受信プログラム(udp_recvfrom.cpp)

// g++ udp_recvfrom.cpp -o udp_recvfrom で大丈夫だが、g++ -std=c++11としないと動かない(ことがある)
#include <stdio.h>
#include <string.h>
#include "simple_udp.h"
simple_udp udp0("0.0.0.0",12345);

int main(int argc, char **argv){
  udp0.udp_bind();
  while (1){
    std::string rdata=udp0.udp_recv();
    printf("recv:%s\n", rdata.c_str());
  }
  return 0;
}

4.Windows10でMinGWのgccを使ってる私の場合のケース

以下のように変更した

4.1 共通のヘッダファイル(simple_udp.h)

// simple_udp.h

/* Windows版 */
#include <stdio.h>
#include <sys/types.h>
#include <winsock2.h>
#include <ws2tcpip.h>
//#include <sys/socket.h>
//#include <netinet/in.h>
//#include <arpa/inet.h>
//#include <netdb.h>
#include <string>  // "string.h"だと通らないことがあるので、注意のこと(私は、ここで2時間ほど嵌った)
#include <unistd.h> // error: 'close' was not declared in this scope; did you mean 'fclose'?


class simple_udp{
  int sock;
  struct sockaddr_in addr;

  // Windows専用おまじない(ここから)
  WSADATA wsaData;
  // Windows専用おまじない(ここまで)	  

 public:
  simple_udp(std::string address, int port){

	// Windows専用おまじない(ここから)
	WSAStartup(MAKEWORD(2,0), &wsaData);
	// Windows専用おまじない(ここまで)	  
	  
	sock = socket(AF_INET, SOCK_DGRAM, 0);
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(address.c_str());
	addr.sin_port = htons(port);
  }
  void udp_send(std::string word){
	sendto(sock, word.c_str(), word.length(), 0, (struct sockaddr *)&addr, sizeof(addr));
  }
  
  void udp_bind(){
	bind(sock, (const struct sockaddr *)&addr, sizeof(addr));
  }
  std::string udp_recv(){
#define BUFFER_MAX 400
	char buf[BUFFER_MAX];
	memset(buf, 0, sizeof(buf));
	recv(sock, buf, sizeof(buf), 0);
	return std::string(buf);
  }
  
  void udp_recv(char *buf, int size){
	memset(buf, 0, size);
	recv(sock, buf, size, 0);
  }
  
  ~simple_udp(){
	close(sock);
  }
};

4.2 送信プログラム(udp_sendto.cpp)

(T.B.D.)  # 現時点では、Windows10(ホストOS側は受信専用なので、不要だから)

4.3 受信プログラム(udp_recvfrom.cpp)

// g++ udp_recvfrom.cpp -o udp_recvfrom で大丈夫だが、g++ -std=c++11としないと動かない(ことがある)
// Windows の場合は、g++ -g udp_recvfrom.cpp -o udp_recvfrom -lwsock32 -lws2_32

#include <stdio.h>
#include <string.h>
#include "simple_udp.h"

simple_udp udp0("0.0.0.0",12345);

int main(int argc, char **argv){
  udp0.udp_bind();
  while (1){
    std::string rdata=udp0.udp_recv();
    printf("recv:%s\n", rdata.c_str());
  }
  return 0;
}