ช่วงนี้งานไม่ยุ่งก็เลยมีเวลาลองเล่นอะไรไปเรื่อยเปื่อย งานที่ทำอยู่ก็มีแค่ Weblogic ไม่ได้ทำอะไรนอกจาก start stop ไม่ค่อยได้อะไรเท่าไหร่

นั่งเบื่อๆคิดไปคิดมา ยังไม่เคยลองใช้ Java EE 7 นี่หว่า  node.js ก็ไม่ค่อยได้เขียนสักเท่าไหร่ ก็เลยลองหยิบเอา WebSocket มาลองเล่นดู

Websocket (RFC 6455) เป็นโปรโตคอลที่ทำงานอยู่บน TCP โดยจะใช้ port เดียวกับ HTTP server (หรือไม่ก็ได้) ถูกออกแบบมาเพื่อรองรับการทำงานแบบ real-time (push) แทนที่จะต้องใช้ Ajax ในการเรียกไปยัง server การใช้ websocket ก็จะทำให้เราถูกเรียกจาก server แทน เหมือนกับการ implement listener interface ในโปรแกรมนั่นเอง

ในการเปรียบเทียบครั้งนี้ผมจะสร้าง website ง่ายๆขึ้นมาโดยเมื่อเปิดเข้าไปแล้วก็จะแสดงข้อความว่า เราต่อกับ server สำเร็จแล้วนะ แล้วมีคนต่ออยู่ทั้งหมดกี่คน และเมื่อมีคนใหม่ต่อเข้ามา ก็จะมีการอัพเดททุกๆคนด้วย

main1

Java EE 7

ผมจะใช้ Netbeans 8 + JDK7 + Wildfly + Netbeans Wildfly plugin บน windows ในการทดลองครั้งนี้

Websocket เพิ่งถูกนำมาใส่ใน Java EE7 ก็คราวนี้ละครับเป็น feature เด่นสำหรับ Java EE7

เริ่มแรกเราทำการสร้าง Maven project แบบ web แล้วให้ทำการ add beans.xml เข้าไปใน WEB-INF เพื่อทำให้ container รองรับกับ CDI (DI ของ Java EE) ในที่นี้คือ @Inject

จากนั้นก็สร้าง Websocket Endpoint WSEndpoint.java

@ServerEndpoint(“/endpoint”)
public class WSEndpoint {

@Inject
ClientSessions sessions;

@OnMessage
public String onMessage(String message) {
return null;
}

@OnOpen
public void onOpen(Session session) throws IOException {
sessions.addSession(session);
sessions.broadcastMessage(“Websocket response from Java EE 7, total client : ” + sessions.getSize());
}

@OnClose
public void onClose(Session session) {
sessions.removeSession(session);

}

}

ในสมัยนี้เค้านิยมใช้ annotation กัน (@….) เราก็ใช้ตามเค้า IoC, DI อะไรพวกนี้ จริงๆผมก็ทำไม่ค่อยเป็นเหมือนกัน เอาเป็นว่าเราประกาศ class WSEndpoint โดยใช้ @ServerEndpoint กำหนด path สำหรับ WebSocket Endpoint โดยเรามีสาม method หลักๆคือ

onMessage –  client ส่ง message มาหา server

onOpen – client ต่อกับ server สำเร็จ

onClose – client ปิดหน้าจอ

ที่นี้การใช้ annotation นั้น เราต้องทำการสร้าง method เอง ไม่เหมือนกับการ Implement interface ที่มีคนสร้างให้เสร็จ ดังนั้นเราจะต้องไปเปิดดูว่าใน annotation แต่ละตัวนั้นมี argument อะไรได้บ้าง (ไม่จำเป็นต้องใส่มาทั้งหมด)

หลังจากที่เราสร้าง class WSEndpoint เสร็จแล้ว ก็ให้กลับไปสร้าง class ClientSessions ซึ่งจะเป็น class ที่ทำหน้าที่เก็บ session ทั้งหมดที่ต่อเข้ามาใน server

โดยเราจะกำหนดให้ class นี้เป็น bean แบบ Singleton ซึ่งแปลว่าทั้งหมดใน container มีได้แค่ตัวเดียว (เหมือนกับ static class) และเป็นแบบ startup นั่นคือ container จะทำการ initial class นี้พร้อมกับการ start container เลย (เหมือนกับการ implement servletContextListener) ซึ่งจริงๆไม่จำเป็น แต่อยากลองใส่เฉยๆ

@Singleton
@Startup
public class ClientSessions {

private final ConcurrentHashMap<String, Session> sessions;

public ClientSessions() {
this.sessions = new ConcurrentHashMap<>();
}

public void addSession(Session s) {
System.out.println(s.getId());
sessions.put(s.getId(), s);

}

public void removeSession(Session s) {

sessions.remove(s.getId());
}

public int getSize(){
return sessions.size();
}
public void broadcastMessage(String msg) throws IOException{
for (Session s : sessions.values()) {
s.getAsyncRemote().sendText(msg);
}
}
}

ในคลาสนี้ไม่มีอะไรไปมากกว่า ConcurrentHashMap ที่ทำหน้าที่เก็บ session ใน server ไว้ แล้วก็มี method broadcastMessage สำหรับการส่ง message ไปหาทุกๆคน โดยในที่นี้เราจะใช้ AsyncRemote ซึ่งเป็นการส่งแบบ Asynchronous เพื่อให้แน่ใจว่าทุกคนควรจะได้รับ Message ในเวลาใกล้ๆกัน (ไม่รู้ว่าจะต่างกับ basic มากแค่ไหน )

ตอนที่รัน เจ้า class ClientSessions นี้จะถูก Inject หรือ assign เข้าไปให้กับตัวแปรนั้นโดยอัตโนมัติด้วยความสามารถของ CDI ซึ่งจะเกิดตอน runtime เลยมั้ง ถ้าเป็นสมัยก่อนผมก็คงสร้าง class ให้เป็น static แล้วก็เรียกใช้เลย ไม่มานั่ง Inject เหมือนกัน เค้าว่าการทำแบบนี้มันจะง่ายต่อการทำ test case ซึ่งก็ไม่เคยคิดจะทำอีกนั่นแหละ ไว้โอกาสหน้าๆ ละกันจะลองดูสักทีว่า TDD มันเป็นไง

จบแล้วสำหรับ Java EE7 ถามว่ายากมั้ยก็ไม่ยากมาก แต่ก็งม งงๆ ตั้งนานเหมือนกัน ชีวิตนี้ทำเป็นแค่ EE 5 แล้วก็ไม่ได้ใช้ EE6 เลยแถม Spring ก็ไม่เคยใช้

Node.js

จริงๆผมไม่ค่อยชอบเขียน node.js (javascript) สักเท่าไหร่ เพราะ IDE ไม่ค่อยมีดีๆ หรือมีเราก็ไม่รู้  แต่ให้เขียนมันก็เขียนได้ งมไปเรื่อยๆ คิดไรไม่ออกก็ print obj มันออกมาดู😛

ในการทำ websocket ด้วย node นี้เราจะใช้ Module ที่ขื่อว่า ws (websocket)

npm install ws

หลังจาก install module แล้วเราก็จะสร้าง server ของเราด้วย code ตัวนี้

var WebSocketServer = require(‘ws’).Server;
var wss = new WebSocketServer({
port: 8080
});
wss.on(‘connection’, function (ws) {
ws.on(‘message’, function (message) {
console.log(‘received: %s’, message);
});

for (var i in wss.clients)
this.clients[i].send(‘Websocket response from Node.js (ws module), total client:’ + wss.clients.length);

});

แค่นี้แหละครับสำหรับ Node.js เขียนซับซ้อนกว่านี้ไม่เป็น – -‘ ไว้ใน part ต่อๆไปจะลองทำให้มันเป็น OO ดูนะ

websocket

จะเห็นว่าแค่เราทำการสร้าง onConnection โดยใน onConnection นั้นก็จะมี onMessage (จริงๆไม่ต้องมีในตัวอย่างนี้)

หลังจากนั้นก็จะ loop array clients แล้วก็ส่งข้อความไปยังทุกๆ client ถามว่าทำให้ java สั้นเท่านี้ได้มั้ย ก็คงตอบว่าไม่ได้ แต่ก็ทำให้สั้นกว่าตัวอย่างข้างบนได้เยอะอยู่ แต่อยากลองใช้ Java EE 7 ดู

HTML

ด้วย HTML เดียวกันนี้เราสามารถคุย websocket ผ่าน java ee 7 และ node.js ได้รู้เรื่องทั้งคู่ (อาจจะต้องเปลี่ยน url เล็กน้อย)

หลักการทำงานคล้ายๆกันกับ java และ node ก็คือมีการเรียก onOpen onMessage มารอรับข้อความ เหมือนกัน

<body>
<h1>WebSocketTest</h1>
<div id=”output”></div>

<script>
var wsUri = “ws://” + document.location.host + document.location.pathname + “endpoint”;
var websocket = new WebSocket(wsUri);

websocket.onerror = function(evt) {
onError(evt)
};

function onError(evt) {
writeToScreen(‘<span style=”color: red;”>ERROR:</span> ‘ + evt.data);
}

// For testing purposes
var output = document.getElementById(“output”);
websocket.onopen = function(evt) {
onOpen(evt)
};

function writeToScreen(message) {
output.innerHTML += message + “<br>”;
}

function onOpen() {
writeToScreen(“Connected to ” + wsUri);
}
// End test functions

websocket.onmessage = function(evt) {
onMessage(evt)
};

function sendText(json) {
console.log(“sending text: ” + json);
websocket.send(json);
}

function onMessage(evt) {
console.log(“received: ” + evt.data);
writeToScreen(evt.data);
}
</script>
</body>

อ้างอิง

Java EE 7: EJB publishing CDI Events that are pushed over WebSocket to browser client

https://github.com/einaros/ws