2020-06-20

Node.jsからVue.jsクライアントへSocket.ioを通して画像を送信して表示

httpサーバーを通さず純粋にSocket.ioだけで画像を送信して表示する方法を記載する。

Article Image

今回は「こんなこともできるよ」的な例

今回は画像ファイルをExpressを使わずSocket.ioだけでクライアントに送信して表示する。

やりたいことは

Nodeサーバーから画像切り替えのメッセージ → Vueクライアントの画像を切り替え」

実はこれは難しいことを考えずにExpressを導入してhttpサーバー越しに画像のURLだけSocket.ioで送信すれば、画像をメッセージで切り替えることができるのだが

少し高度なテクニックとして今回はこれをSocket.ioだけで再現する。

Expressを使った時のデメリット

一応Express+Socket.ioで動的に画像を切り替えるシステムのデメリットを挙げてみた。

  • HTTPサーバを立てるためSocket.ioとは別のポートが解放される

  • 送信する画像ファイルはURL(ファイル名)さえわかれば誰でもアクセスできてしまう

  • Expressのインストール+HTTPサーバー用コードを追加する必要がある

  • ファイルをフォルダではなく個別に管理する場合ルーティングをする必要がある。 (逆を言えばこの手間からstaticでフォルダごと公開することになる可能性大)

私自身Expressにそれほど詳しくなくもし間違っていたら申し訳ないが、多かれ少なかれデメリットはある。

Socket.ioで画像送信

以上のデメリットが致命的になることはさほどないが、そもそもExpressを必要としないシステムなのであればわざわざExpressをインストールして画像を送信するhttpサーバを追加する必要はない。

Socket.ioの基本的なコーディングに関しては他のサイト参照のこと。

今回は「画像」と書いたがSocket.ioではこの方法でバイナリファイルでもなんでも送信できる。

動作例

こんな感じで動作する。

  1. ボタンをおしてサーバーに画像をリクエスト
  2. サーバーから画像を受信
  3. 画面に表示
動作例

画面は一切遷移していない。

コード

Vue

※いつも通りVuetifyを使っているがv-btnbuttonで、v-imgimgタグでも置き換え可能。Vuetifyを使うとcssによる整形が不要になるのでオススメ。

<template>
  <v-container>
    <v-btn @click="getImg()">get img</v-btn>
    <v-img :src="imgDataBase64" v-if="imgDataBase64" />
  </v-container>
</template>

<script>
import io from "socket.io-client";
export default {
  name: "HelloWorld",
  data: () => ({
    socket: io("localhost:7000"),
    imgDataBase64:null, 
  }),
  mounted() {
    this.socket.on("connect", () => {
      console.log("connected");
      this.socket.on("img_data",imgDataBase64=>{
        this.imgDataBase64 = imgDataBase64
      })
    });
  },
  methods: {
    getImg() {
      this.socket.emit("get_img");
    }
  }
};
</script>

Node.js側

import io from 'socket.io'
import fs from 'fs'

const socket = io(7000)

socket.on('connection', function (socket) {
    console.log(`a user connected[id:${socket.id}]`)

    socket.on("get_img", () => {
        const filePath = "./images/test_img.jpg"
        const base64JpgHeader = "data:image/jpeg;base64,"
        const img = fs.readFileSync(filePath,'base64')
        const img_base64 = base64JpgHeader + img
        socket.emit("img_data",(img_base64))
        console.log("a image was sent")
    })
})

base64

画像はfsbase64でエンコードされた形式で読み込む。これによりバイナリファイルも読み込みが可能。

このbase64形式はバイナリファイルをテキストファイルに変換するので少々長いデータにはなるがバイナリが扱えない通信システムに流すことができる。

vue側のv-imgに入っているsrcを見てみると画像URLではなくこのbase64が入ったデータを直接引き渡している。

Vueimgタグはこのbase64データをそのまま「画像」として表示することができるのでデコードが必要ない。これが便利。

<v-img :src="imgDataBase64" v-if="imgDataBase64" />

ただし条件があるので次に書く

ヘッダ

Node.jsのエンコードしている部分で文字列によるヘッダを付与している。

const base64JpgHeader = "data:image/jpeg;base64,"
const img = fs.readFileSync(filePath,'base64')
const img_base64 = base64JpgHeader + img

ブラウザでデコードなしでダイレクトにbase64画像を表示する場合、このヘッダがないとエラーとなり表示されない。

Socket.ioのメリット・デメリット

Socket.ioだけで配信する場合のデメリットももちろんある。

メリット

Expressはディレクトリごと公開となるが、こちらはファイル単体ごとで制御が可能。

接続してきているユーザーごとに表示される画像を切り替えられ許可のない画像は配信されない。

ファイルとして出力しないので、ディスクのIOが減るなどのメリットもある。

ディスクIOを消費する可能性がある動的なサムネイル生成なんかはメモリだけで完結できるので得意そうだ。

Expressであればファイルが生成される前にURLを配信してしまいブラウザ側でエラーとなる可能性もある。

デメリット

Expressのようにディレクトリまるごと送信(たくさんの画像を一度に送信)しようとした場合、複数のファイルを明示的に読み込んで処理し一括で送信するコードを実装する必要があるのでコード量で言えば手間。

また画像の種類によって付与するヘッダを分ける必要があるかもしれない ※十分にテストしていないがpngなどもimage/jpegで表示されるのは確認

base64変換する必要ある?

そもそも古い習慣でテキスト(base64)にエンコードしているが、わざわざ変換しなくてもバイナリで送ったほうが良いのではと記事を執筆していて思ってしまった。

socket.ioでバイナリが送れるはずなのでbase64にエンコードすることは不要だ(通信量が30%削減できるらしい)

次回はこの手法に対応した記事を執筆予定。

2020/06/21:続きを執筆した

Socket.ioでバイナリ画像を受信してVue.jsで表示

さいごに

Socket.io+Vue.jsを使った可能性は無限だ。

ユーザーの操作に関係なくサーバー側からユーザー側に表示される画像をメッセージでコントロールできるので簡単なゲーム等も作れそうだ。

とはいえ今回の記事のようにSocket.ioだけで画像送信が出来たらなにか得か?といわれたらそれほどメリットがあるわけではない。

よほど画像の送信先を厳密に管理したい時だけだろう。

ニッチな需要ではあるが機会があれば使ってみてほしい。



この記事のタグ

この記事をシェア


謎の技術研究部 (謎技研)