Scala에서 Socket.IO

배경



Socket.IO로 pub/sub를 실현하고 싶다면 Node.js로 서버를 세우는 것이 철판? 라고 생각합니다만, 다른 시스템간에 제휴하고 싶은 일도 있지요.

Scala에서 Socket.IO




그림과 같이 센서로 데려온 데이터를 Scala로 작성된 서버에 보내고 있습니다만, 갱신 정보를 서버로부터 클라이언트측에 리얼타임으로 전하고 싶은 경우, Scala의 서버와 클라이언트간에 websocket를 사용하면 됩니다만 , 클라이언트측의 브라우저의 대응 유무등을 신경쓰고 싶지 않기 때문에 이번은 Socket.IO를 사용합니다.

Scala에서 Socket.IO와 호환되는 라이브러리가 있는지 찾았지만 Play Framework의 모듈로 존재하는 것을 확인할 수 있었지만 범용 라이브러리를 찾을 수 없습니다.

이번 서버측은 AkkaHTTP로 쓰고 있어, 다른 방법으로 실현할 필요가 나왔습니다.

Redis pub/sub 활용하기




그림과 같이 Redis를 중개역으로 이용하는 방법을 생각합니다.

Node.js측은, 이하와 같이 조금 코드를 더하는 것만으로 대응할 수 있습니다.
const io = require('socket.io')(3000);
const redisAdapter = require('socket.io-redis');
io.adapter(redisAdapter({ host: 'localhost', port: 6379 }));

Scala 측은 그렇다면 Redis에 직접 쓰러 가야합니다.
Scala용 Redis 클라이언트에 대해 정리해 주었던 기사를 참고로 선정합니다.
타케조 빈사 블로그

Sedis는 차단만이므로 제외. 남은 'scala-redis' 또는 'rediscala'가 후보가 됩니다.

redis에 쓰는 데이터의 형식은 다음과 같습니다.
여기서는 data 배열의 두 번째 요소로 보낼 데이터를 설정합니다.
[
    "emitter",
    {
        "type": 2,
        "data": [
            "イベント名",
            {
                "message": "Hello World!"
            }
        ],
        "nsp": "namespace"
    },
    {
        "rooms": ["ルーム名"],
        "flags": []
    }
]

이것을 MessagePack로 직렬화한 바이너리 데이터를 redis에 기입합니다.
여기서 이전의 라이브러리 중 「scala-redis」는 바이너리 기입의 메소드를 가지고 있지 않기 때문에, 「rediscala」일택이 됩니다.

Scala에서 MessagePack의 라이브러리는 MessagePack 본가가 내고 있는 "msgpack-scala"를 이용합니다.
msgpack-scala

이 라이브러리가 조금 곡자로 위의 JSON을 표현하는 코드는 다음과 같습니다.
다른 언어의 라이브러리라면 메소드 일발로 바이너리로 해주거나 하는 것 같습니다만, 이 라이브러리에는 보이지 않았습니다...
    val out = new ByteArrayOutputStream
    val packer = MessagePack.newDefaultPacker(out)

    packer.packArrayHeader(3)

    // set emitter
    packer.packString("emitter")

    // set type 2
    packer.packMapHeader(3)
    packer.packString("type")
    packer.packString("2")

    // set data
    packer.packString("data")
    packer.packArrayHeader(2)

    // set event name
    packer.packString("イベント名")

    // set data body
    packer.packMapHeader(1)
    packer.packString("message")
    packer.packString("Hello World!")

    // set namespace
    packer.packString("nsp")
    packer.packString("/")

    packer.packMapHeader(2)
    packer.packString(roomName)
    packer.packArrayHeader(1)
    packer.packString("ルーム名")
    packer.packString("flags")
    packer.packMapHeader(0)

    packer.flush()
    packer.close()

    out.toByteArray

여기에서 얻은 바이너리 데이터를 rediscala를 사용하여 Redis에 씁니다.
※RedisClient의 생성자는 ActorSystem을 implict로 받게 되어 있으므로, 각각의 환경에 따라 준비해 주세요.
   val redis = RedisClient(server,port)
   redis.publish("チャンネル名", バイナリデータ)

데이터 수신



클라이언트 측에서는 설정한 이벤트 이름으로 데이터를 수신할 수 있습니다.
socketio.on('イベント名', function(msg){
        console.log('receive:' +  JSON.stringify(msg));
    });

요약



Scala에서 소개했지만 다른 언어에서도 같은 구조를 이용하여 Redis에 쓸 수 있으면 제휴를 할 수 있을까 생각합니다.

※이번 참고로 한 사이트



이 사람은 비슷한 것을 파이썬으로 실현되었습니다.
파이썬을 사용하여 socket.io-redis에 이벤트를 emit

좋은 웹페이지 즐겨찾기