エコモット 技術ブログ アドベントカレンダー 16日目です。
こんにちわ。開発部の堀野です。
最近は端末とお話をする仕事が多いです。
というわけでプロトコルの記事です。
SCTPとは
通信プロトコルの一種。お馴染みTCP、UDPなどなどのお仲間で、トランスポート層のプロトコルです。
少なくともIT業界関連の仕事をしている人であればTCP、UDPを聞いたことが無い人は居ないと思いますが、SCTPを聞いたことがある人は少数派ではないかと思いますがいかがでしょうか?
実は皆さん意識はしてませんが毎日のように使っているはずで、携帯電話の基地局間の通信に使われているようです。
なぜSCTP?
弊社はIoTを生業とした会社ですから、端末-サーバ間の通信は日々大量に発生しておりますが、そこで使われている通信プロトコルも様々です。
様々ではありますが大別して、TCP上のHTTP、FTPに代表されるウェルノンポートを使った既存のプロトコルか又は無手順/有手順の独自プロトコルを使用しております。
さて、また独自プロトコルの設計と実装をする機会が与えられ、色々と調査しているなかで興味を持ったのがSCTPです。
通信プロトコルはそれぞれ特徴がありますが、その特徴の中で今回、特に注目したのがフレーミング(データを受け渡す単位)です。
TCPはストリーム、UDPとSCTPはメッセージ単位です。 ※次章でご説明致します。
端末からデータを受けたり送ったりするときに、メッセージ単位というは魅力を感じました。
(他にもTCPのように順番の保証や再送制御をプロトコルがやってくれるのも良い)
ストリーム(TCP)
ストリームはメッセージの境界をアプリケーション側で判定します。
どいうことかというと…(ほんの少し懐かしい感じですみません。先頭と終端がわかりやすい例が他に思い浮かばず)
【送信側】
1 2 3 4 |
send:”拙者26先日自転車のxxxxxxxですから!残念!〇〇斬り!” send:”拙者36本日電車でxxxESC拙者xxESC斬り!xxなりますから!残念!〇〇斬り!” send:”拙者31明日飛行機がxxっていうじゃないxxxxxxx残念!〇〇斬り!” |
【受信側】
1 2 3 |
recv:”拙者先日自転車のxxxxxxxですから!残念!〇〇斬り!拙者本日電車でxxxxxESC斬り!” recv:”xxなりますから!残念!〇〇斬り!拙者41明日飛行機がxxっていうじゃないxxxxxxx残念!〇〇斬り!” |
そうです。データとしては全部届きますが、「送信」した回数と「受信」した回数や一回の文字数が違います。
プログラムとしては受信側では以下のような取決めが必要になります。
・「拙者」を先頭、「斬り!」を終端として一文字単位で判断する
・先頭に終端まであと何文字かを入れる
・「拙者」「斬り!」を途中に入れたいときは前に”ESC”を付ける(sendの2行目)
この取り決めを元に、送信したときのメッセージの単位で解釈する必要があります。
・送信側と受信側でESCの付け外しの手間がかかる
・受信処理時、何かの拍子にズレが生じると、少なくとも次のデータも破棄せざるを得ない
(多くのアプリケーションがTCPを使用していることからもわかる通り、決してTCPが悪いわけではなく、このような特性があるとご理解下さい。)
メッセージ(UDP)
【送信側】
1 2 3 4 |
send:”1/3先日自転車のxxxxxxxですから” send:”2/3本日電車でxxx拙者xx斬り!xxなりますから” send:”3/3明日飛行機がxxっていうじゃないxxxxxxx残念無念” |
【受信側】
1 2 3 4 |
recv:”2/3本日電車でxxx拙者xx斬り!xxなりますから” recv:”1/3先日自転車のxxxxxxxですから” recv:”3明日飛行機がxxっていうじゃないxxxxxxx残念無念” |
送信したデータと同じ文字数で受信できます。
なので、かならずネタの最初と最後に同じ言葉を入れる必要が無く、ESCのような処理も不要です。
しかし、順番は入れ替わっているため、順番を示す番号を入れて順番を管理する必要があります。
他にもデータ抜けをどう担保するかでタイマを張ったり、一度に送れるデータがとても少ないなどの特性があります。
SCTPはデータ抜けの再送や順番の保証をしてくれつつ、メッセージ単位で通信ができ、TCPとUDPのいいとこ取りのようです!
SCTPやってみた
まず実行環境にsctpが入っているか確認
find /usr/lib64/ -name "*sctp*"
見つからない場合はインストール
yum install lksctp-tools.i686
EC2のインバウンドの設定
タイプ:カスタムプロトコル
プロトコル:SCTP
ポート範囲:すべて(入力不可)
ソース:任意
今回はjavaで作ってみました。
【サーバ側】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
SctpServerChannel ssc = SctpServerChannel.open(); InetSocketAddress serverAddr = new InetSocketAddress(SERVER_PORT); ssc.bind(serverAddr); while (true) { buf.clear(); SctpChannel sc = ssc.accept(); MessageInfo mi = sc.receive(buf, null, null); buf.flip(); FileOutputStream output = new FileOutputStream(PATH); byte[] fileByte = new byte[buf.limit() - buf.position()]; buf.get(fileByte); utput.write(fileByte); output.close(); } |
【クライアント側】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
ByteBuffer sendBBuf = ByteBuffer.allocate(SIZE_BUF); InetSocketAddress serverAddr = new InetSocketAddress(serverIp, SERVER_PORT); FileInputStream input = new FileInputStream(FROM_PATH + filename); byte readBufs[] = new byte[SIZE_BUF]; int len; SctpChannel sc = SctpChannel.open(serverAddr, 0, 0); sc.configureBlocking(false); MessageInfo messageInfo = MessageInfo.createOutgoing(null, US_STREAM); while ((len = input.read(readBufs)) != -1) { sendBBuf.clear(); sendBBuf.put(readBufs, 0, len); sendBBuf.flip(); sc.send(sendBBuf, messageInfo); Thread.sleep(1); } input.close(); sc.close(); |
最後に
SCTPのサポート状況を感じて頂けたでしょうか?netstat(–sctp)もtcpdumpも使えるようです(-l sctp)し、普通に使えそうな雰囲気です。
コードが簡潔になりそうな雰囲気を感じられたかと思います。
簡潔になればより沢山の端末のデータを裁くことができることが期待できます。
いつもとは違うプロトコルに目を向けることで、TCP、UDPの特性もより理解が深まりますね。