在客戶機和服務器之間建立單一的雙向連接,這就意味著客戶只需要發送一個請求到服務端,那麼服務端則會進行處理,處理好後則將其返回給客戶端,客戶端則可以在等待這個時間繼續去做其他工作,整個過程是異步的。在本系列教程中,將指導用戶如何在JAVA EE 7的容器GlassFish 4中,使用JAVA EE 7中的全新的解析Json API(JSR-353),以及綜合運用jQuery和Bootstrap。本文要求讀者有一定的HTML 5 Websocket的基礎原理知識。
效果圖
我們先來看下在完成這個教程後的效果圖,如下所示:
準備工作
我們使用的是JDK 7 和MAVN 3進行庫的構建工作,首先看pom.xml中關於Jave EE 7的部分:
<properties> <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> < dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.7</source> <target >1.7</target> <compilerArguments> <endorseddirs>${endorsed.dir}</endorseddirs> </compilerArguments> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId > <artifactId>maven-war-plugin</artifactId> <version>2.3</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> <plugin> <groupId>org.apache. maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.6</version> [..] </plugin> </plugins> </build>
同時,為了能使用GlassFish 4,需要增加如下的插件:
plugin> <groupId>org.glassfish.embedded</groupId> <artifactId>maven-embedded-glassfish-plugin</artifactId> <version>4.0</version> <configuration> <goalPrefix>embedded-glassfish</goalPrefix> < app>${basedir}/target/${project.artifactId}-${project.version}.war</app> <autoDelete>true</autoDelete> <port>8080</port> <name>${project .artifactId}</name> <contextRoot>hascode</contextRoot> </configuration> <executions> <execution> <goals> <goal>deploy</goal> </goals> </execution> </executions> </ plugin>
設置Websocket的Endpoint
我們先來看服務端Websocket的代碼如下,然後再做進一步解析:
package com.hascode.tutorial; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import javax.websocket.EncodeException; import javax.websocket.OnMessage; import javax.websocket .OnOpen; import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; @ServerEndpoint(value = "/chat/{room}", encoders = ChatMessageEncoder.class, decoders = ChatMessageDecoder.class) public class ChatEndpoint { private final Logger log = Logger.getLogger(getClass().getName()); @OnOpen public void open(final Session session, @PathParam("room") final String room) { log. info("session openend and bound to room: " + room); session.getUserProperties().put("room", room); } @OnMessage public void onMessage(final Session session, final ChatMessage chatMessage) { String room = ( String) session.getUserProperties().get("room"); try { for (Session s : session.getOpenSessions()) { if (s.isOpen() && room.equals(s.getUserProperties().get(" room"))) { s.getBasicRemote().sendObject(chatMessage); } } } catch (IOException | EncodeException e) { log.log(Level.WARNING, "onMessage failed", e); } } }
下面分析下上面的代碼:
使用@ ServerEndpoint定義一個新的endpoint,其中的值指定了URL並且可以使用PathParams參數,就像在JAX-RS中的用法一樣。
所以值“/chat/{room}”允許用戶通過如下形式的URL去連接某個聊天室:ws://0.0.0.0:8080/hascode/chat/java
在大括號中的值(即room),可以通過使用javax.websocket.server.PathParam,在endpoint的生命週期回調方法中以參數的方式註入。
此外,我們要使用一個編碼和解碼的類,因為我們使用的是一個DTO形式的類,用於在服務端和客戶端傳送數據。
當用戶第一次連接到服務端,輸入要進入聊天室的房號,則這個房號以參數的方式註入提交,並且使用session.getUserProperties將值保存在用戶的屬性map中。
當一個聊天參與者通過tcp連接發送信息到服務端,則循環遍歷所有已打開的session,每個session被綁定到指定的聊天室中,並且接收編碼和解碼的信息。
如果我們想發送簡單的文本信息或和二進制格式的信息,則可以使用session.getBasicRemote().sendBinary() 或session.getBasicRemote().sendText()
接下來我們看下用於代表信息傳遞實體(DTO:Data Transfer Object)的代碼,如下:
package com.hascode.tutorial; import java.util.Date; public class ChatMessage { private String message; private String sender; private Date received; // 其他getter,setter方法}
聊天消息的轉換
在這個應用中,將編寫一個編碼和解碼類,用於在聊天信息和JSON格式間進行轉換。
先來看下解碼類的實現,這將會把傳遞到服務端的聊天信息轉換為ChatMessage實體類。在這裡,使用的是Java API for JSON Processing(JSR353)規範去將JSON格式的信息轉換為實體類,代碼如下,其中重寫的willDecode方法,這裡默認返回為true。
package com.hascode.tutorial; import java.io.StringReader; import java.util.Date; import javax.json.Json; import javax.json.JsonObject; import javax.websocket.DecodeException; import javax.websocket.Decoder; import javax.websocket.EndpointConfig; public class ChatMessageDecoder implements Decoder.Text<ChatMessage> { @Override public void init(final EndpointConfig config) { } @Override public void destroy() { } @Override public ChatMessage decode(final String textMessage) throws DecodeException { ChatMessage chatMessage = new ChatMessage(); JsonObject obj = Json.createReader(new StringReader(textMessage)) .readObject(); chatMessage.setMessage(obj.getString("message")); chatMessage.setSender(obj.getString(" sender")); chatMessage.setReceived(new Date()); return chatMessage; } @Override public boolean willDecode(final String s) { return true; } }
同樣再看下編碼類的代碼,這個類相反,是將ChatMessage類轉換為Json格式,代碼如下:
package com.hascode.tutorial; import javax.json.Json; import javax.websocket.EncodeException; import javax.websocket.Encoder; import javax.websocket.EndpointConfig; public class ChatMessageEncoder implements Encoder.Text<ChatMessage> { @Override public void init(final EndpointConfig config) { } @Override public void destroy() { } @Override public String encode(final ChatMessage chatMessage) throws EncodeException { return Json.createObjectBuilder() .add("message", chatMessage.getMessage()) . add("sender", chatMessage.getSender()) .add("received", chatMessage.getReceived().toString()).build() .toString(); } }
這裡可以看到JSR-353的強大威力,只需要調用Json.createObjectBuilder就可以輕易把一個DTO對象轉化為JSON了。
通過Bootstrap、Javacsript搭建簡易客戶端
最後,我們綜合運用著名的Bootstrap、jQuery框架和Javascript設計一個簡易的客戶端。我們在src/main/weapp目錄下新建立index.html文件,代碼如下:
<!DOCTYPE html> <html lang="en"> <head> [..] <script> var wsocket; var serviceLocation = "ws://0.0.0.0:8080/hascode/chat/"; var $nickName; var $message; var $chatWindow; var room = ''; function onMessageReceived(evt) { //var msg = eval('(' + evt.data + ')'); var msg = JSON.parse(evt.data ); // native API var $messageLine = $('<tr><td>' + msg.received + '</td><td>' + msg.sender + '</td><td>' + msg .message + '</td></tr>'); $chatWindow.append($messageLine); } function sendMessage() { var msg = '{"message":"' + $message.val() + ' ", "sender":"' + $nickName.val() + '", "received":""}'; wsocket.send(msg); $message.val('').focus(); } function connectToChatserver() { room = $('#chatroom option:selected').val(); wsocket = new WebSocket(serviceLocation + room); wsocket.onmessage = onMessageReceived; } function leaveRoom() { wsocket.close(); $ chatWindow.empty(); $('.chat-wrapper').hide(); $('.chat-signin').show(); $nickName.focus(); } $(document).ready(function () { $nickName = $('#nickname'); $message = $('#message'); $chatWindow = $('#response'); $('.chat-wrapper').hide(); $nickName.focus(); $('#enterRoom').click(function(evt) { evt.preventDefault(); connectToChatserver(); $('.chat-wrapper h2').text('Chat # '+ $nickName.val() + "@" + room); $('.chat-signin').hide(); $('.chat-wrapper').show(); $message.focus(); } ); $('#do-chat').submit(function(evt) { evt.preventDefault(); sendMessage() }); $('#leave-room').click(function(){ leaveRoom() ; }); }); </script> </head> <body> <div> <form> <h2>Chat sign in</h2> <label for="nickname">Nickname</label> <input type ="text" placeholder="Nickname" id="nickname"> <div> <label for="chatroom">Chatroom</label> <select size="1" id="chatroom"> <option>arduino</ option> <option>java</option> <option>groovy</option> <option>scala</option> </select> </div> <button type="submit" id="enterRoom">Sign in< /button> </form> </div> <!-- /container --> <div> <form id="do-chat"> <h2></h2> <table id="response"></ table> <fieldset> <legend>Enter your message..</legend> <div> <input type="text" placeholder="Your message..." id="message" style="height:60px"/> <input type="submit" value="Send message" /> <button type="button" id="leave-room">Leave room</button> </div> </fieldset> </form> </ div> </body> </html>
在上面的代碼中,要注意如下幾點:
在Javascript端要調用websocket的話,要用如下的方式發起連接即可:ws://IP:PORT/CONTEXT_PATH/ENDPOINT_URL eg ws://0.0.0.0:8080/hascode/chat/java
創建一個Websocket連接的方法很簡單,使用的是var wsocket = new WebSocket('ws://0.0.0.0:8080/hascode/chat/java');
要獲得來自服務端返回的信息,只需要在回調函數wsocket.onmessage中設置對應的獲取返回信息的方法即可。
發送一個Websocket消息到服務端,使用的方法是wsocket.send(),其中可以發送的消息可以文本或者二進制數據。
關閉連接使用的是wsocket.close()。
最後,我們通過mvn package embedded-glassfish:run進行代碼的部署,然後就可以看到本文開始部分截圖的效果。
以上就是用JavaEE7、Websockets和GlassFish4實現的聊天室,希望對大家的學習有所幫助。