[Eject]VPS上にNode.jsでチャットサーバを作って自宅のRaspberryPiにEjectさせる
私が今使っている自宅のインターネット環境ではプロバイダからDHCPでIPアドレスをもらい、固定IPは有料となっています。
また、ポート等も上位のネットワークで管理されており、内向きのパケットは基本的にすべてが破棄されます。
固定IP取得ポート開放はいくらか支払えば可能ですが、月2000円くらいかかったりするので、できれば無料でやりたいですよね。
そこで考えたのが、以前の[Twitter]←(UserStrem監視)←[RaspberryPi]→(制御)→[Eject]だったわけですが、これではTwitterに依存することになります。
できればTwitterなどを使わずに自由にEjectしたいです。(UIも自分で作ったり)
というわけで今回はVPS上にNode.jsでチャットサーバを立てることで実現してみました。
※Ejectネタです。(☝ ՞ਊ ՞)☝ウィーン
構想
VPSにNode.jsを立ててブラウザとRaspberryPiとの中継をしてもらう感じにします。
ブラウザ→(接続)→[Node.js チャットサーバ]←(接続)←RaspberyyPi(Javaクライアント)
こんな感じにして、ブラウザからメッセージを送信したらRaspberryPiに中継します。
RaspberryPi側はJavaでクライアントを作成し、メッセージを受信したらEjectを行わせます。
そしてEjectの成功・失敗をサーバに送信し、サーバはブラウザに向けてメッセージを中継します。
シンプルですね。
Node.jsを選んだ理由は、以前少しだけ使ったことがあったのと、Socket.ioが簡単に利用できるためチャットサーバをすぐ立てられると考えたからです。
やっていることはIRCなどを利用したbot攻撃などとあまり変わりませんね。(攻撃指令をIRCサーバで中継させて、botに攻撃を開始させるというアレ)
正直IRCでやってもいいと思いますが、ブラウザからやるならNode.jsで直接JSとSocket.ioで通信できたほうがいいので今回はIRCは使いませんでした。
(IRCサーバとの通信プログラムなんて書いたことなかったし)
Node.jsサーバを立てる
Node.jsからダウンロードするか、apt-getとかyumとかでも入ると思います。
次に適当な場所にディレクトリを作ります。
1
2
|
# mkdir EjectChat
# cd EjectChat
|
Socket.ioを使うので、npmでダウンロードします。
私は最新版のsocket.ioではうまく動作しなかったので、少し古いバージョンを指定しました。
このあたりを参考にさせていただきました。
1 |
npm install socket.io@0.8.7
|
これでNode.jsの環境は整いました。
あとはサーバーサイドのコードを書いていきます。
とはいえ、Node.jsはほとんど触ったことないので一からコードは書けるはずもありません。
なので、やっぱり他の方が書かれたソースコードを参考に書いていくことにします。
今回はここを参考にしました。(ほぼコピペです)
[app.js]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
var http = require("http");
var socketio = require("socket.io");
var fs = require("fs");
var server =
http.createServer(function(req, res) {
res.writeHead(200, {"Content-Type":"text/html"});
var output = fs.readFileSync("./index.html", "utf-8");
res.end(output);
}).listen(process.env.VMC_APP_PORT || 3000);
var io = socketio.listen(server);
io.sockets.on("connection", function (socket) {
socket.on("toRPi", function(data){
io.sockets.emit("toRPi", {value:data.value});
});
socket.on("toClient", function(data){
io.sockets.emit("toClient", {value:data.value});
});
socket.on("disconnect", function () {
//io.sockets.emit("S_to_C_message", {value:"user disconnected"});
});
});
|
[index.html]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>node.js eject</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
// var s = io.connect(); //リモート
var s = io.connect('http://example.com:3000'); //ローカル
//サーバから受け取るイベント
s.on("connect", function () {}); // 接続時
s.on("disconnect", function (client) {}); // 切断時
s.on("S_to_C_message", function (data) {
addMessage(data.value);
});
s.on("toClient", function(data){
//alert(data);
//$("#msg_list").prepend("<div class='msg'>" + data.toString() + "</div>");
addMessage(data.value);
//alert(data);
});
//クライアントからイベント送信(イベント名は自由に設定できます)
function sendMessage() {
var msg = $("#message").val(); //取得
$("#message").val(""); //空白にする
s.emit("C_to_S_message", {value:msg}); //サーバへ送信
}
function sendBroadcast() {
var msg = $("#message").val(); //取得
$("#message").val(""); //空白にする
s.emit("C_to_S_broadcast", {value:msg}); // サーバへ送信
}
function sendRPi(){
var msg = "eject";
//$("#message").val($("message").val()+"AAA|");
s.emit("toRPi", {value:msg});
}
//jqueryでメッセージを追加
function addMessage (value,color,size) {
var msg = value.replace( /[!@$%<>'"&|]/g, '' ); //タグ記号とかいくつか削除
$("#msg_list").prepend("<div class='msg'>" + msg + "</div>");
}
</script>
<style>
*{
font-size:30px;
margin:0;
padding:0;
}
</style>
</head>
<body>
<div id="msg_list" style="height:300px; overflow:auto;"></div>
<form action="" method="post" onsubmit="return false;">
<!--
<input type="text" class="text" style="width:95%; padding:10px" id="message"/>
<input type="submit" class="button" style="padding:10px" onclick="sendMessage();" value="みんなに送信" />
<input type="submit" class="button" style="padding:10px" onclick="sendBroadcast();" value="自分以外に送信" />
-->
<input type="submit" class="button" onclick="sendRPi();" value="Eject" />
</form>
</div>
</body>
</html>
|
サーバを起動します。
1 |
node app.js
|
これでブラウザからアクセスしてみてサーバーに出力があれば成功です。
このコードでは3000ポートを指定しています。
RaspberryPi側を実装する
前回と同じようにJavaで実装します。
このライブラリを使いましょう。
antで.jarにして、classpathで指定してあげて使います。
(この辺りの説明は面倒なので、各自ググってください)
以下のコードでは他にも色々ライブラリを使ってますが、今回重要なのはsocket.ioなので他のライブラリの説明は省略します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
import io.socket.*;
import java.net.Socket.*;
import org.json.*;
import java.net.URL;
import org.apache.commons.io.IOUtils;
import java.io.*;
import java.util.*;
import java.text.*;
class EjectorSocketIO{
public static void main(String args[]){
SocketClient client = new SocketClient();
client.run();
}
}
class SocketClient{
public SocketClient(){};
public void run(){
try{
SocketIO socket = new SocketIO("http://example.com:3000/");
socket.connect(new IOCallback() {
@Override
public void onMessage(JSONObject json, IOAcknowledge ack) {
//System.out.println("Server said:" + json.toString(2));
}
@Override
public void onMessage(String data, IOAcknowledge ack) {
System.out.println("Server said: " + data);
}
@Override
public void onError(SocketIOException socketIOException) {
System.out.println("an Error occured");
socketIOException.printStackTrace();
}
@Override
public void onDisconnect() {
System.out.println("Connection terminated.");
}
@Override
public void onConnect() {
System.out.println("Connection established");
}
@Override
public void on(String event, IOAcknowledge ack, Object... args) {
//System.out.println("Server triggered event '" + event + "'");
try{
if(event.equals("toRPi")){
org.json.JSONObject jsonObject = new org.json.JSONObject(args[0].toString());
String command = jsonObject.getString("value");
if(command.equals("eject")){
eject_message();
}
}
}
catch(Exception e){
e.printStackTrace();
}
}
private int eject(){
try {
URL url = new URL("http://raspihost/eject/eject.php");
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"));
String contents =IOUtils.toString(reader);
org.json.JSONObject jsonObject = new org.json.JSONObject(contents);
return(jsonObject.getInt("code"));
} catch (Exception e) {
e.printStackTrace();
return(-1);
}
}
private void eject_message(){
int result = eject();
JSONObject obj = new JSONObject();
try{
if(result == 0){
obj.put("value", "TrayOpen : Eject Successful "+getDate());
}
else if(result == 1){
obj.put("value", "TrayClose : Eject Successful "+getDate());
}
else{
obj.put("value", "Eject Faild " +getDate());
}
socket.emit("toClient", obj);
}
catch(Exception e){
e.printStackTrace();
}
}
private String getDate(){
Date d = new Date();
SimpleDateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
return("["+df.format(d)+"]");
}
});
// This line is cached until the connection is establisched.
//socket.send("Hello Server!");
}
catch(Exception e){
System.out.println("[Error!] サーバーに接続できませんでした");
System.exit(1);
}
}
}
|
こんなかんじで適当にプログラムを組みます。
JavaでNode.jsに接続し、Socket.ioでメッセージのやりとりを行います。
ブラウザからNode.jsにEjectのメッセージが送られると、Node.jsはJavaのクライアントに向けてEjectのメッセージを送信します。
Javaクライアントは自分宛てのメッセージを受信すると、前回作ったPHPのAPIを呼び出しEjectを実行します。
そしてJavaクライアントはEjectが成功したか失敗したかをブラウザに向けたメッセージを送信します。
ブラウザからのメッセージをNode.jsが中継する感じですね。
結果
VPSにNode.jsでチャットサーバを立てることでEjectメッセージの中継に成功。
Socket.IOは便利ですね、簡単にチャットサーバが立ちました。Ejectだけじゃなくて色々使えそうです。
ただ今回の例ではEjectメッセージが短い間隔で連続で飛ばされると、Javaのクライアントが落ちたりしました。
何が原因なのか分かりませんが、今回はとりあえず「できる」ということがわかったのでこれから色々試してみたいと思います。
今回はNode.jsだったので次はIRCで中継させてからのEjectでもやろうかなぁとか考えてます。