It is a well known fact that HTTP(Hypertext Transfer Protocol) is a stateless request-response protocol. This simple design of the HTTP protocol makes it very scalable but inefficient and unsuitable for highly interactive real-time web applications. HTTP was designed for document sharing and not for building today's highly interactive web applications. HTTP is bit chatty in nature, as for each http request/response, a lot of headers need to be transmitted over the wire. Before the HTTP 1.1 version, every request made to the server resulted in a new connection. This was improved in HTTP 1.1 with the introduction of HTTP persistence connections. Persistent connections allowed web browsers to reuse the same connection for fetching images, scripts, etc. HTTP was designed to be half-duplex which means it allows transmission of data in just one direction at a time. A Walkie-talkie is an example of a half duplex device because only one person can speak at a time. Developers have created some workarounds or hacks to overcome this HTTP shortcoming. Some of these workarounds are polling, long polling, andstreaming. | 已有1人翻译此段我来翻译 |
With polling, the client makes synchronous calls to get information from the server. If the server has new information available it will send back data in the response. Otherwise, no information will be sent to the client and the client will again make a new connection after sometime. This is very inefficient but a very simple way to achieve real-time behavior. Long polling is another workaround in which the client will make a connection to the server and the server will hold the connection until data is available or a designated timeout is achieved. Long polling is also known as comet. Because of the mismatch between synchronous HTTP and these asynchronous applications, it tends to be complicated, non-standard and inefficient. Over time, the need for a standards-based, bidirectional and full-duplex channel between clients and a server have increased. In this blog, we will look at how WebSockets can help address these problems and then learn how to use JSR 356 API to build WebSocket based applications in Java. Please note that this blog will not talk about OpenShift WebSocket support. If you want to learn about OpenShift WebSocket support, please refer to this blog by Marek Jelen. | 已有1人翻译此段我来翻译 |
What is a WebSocket?A WebSocket is asynchronous, bidirectional, full-duplex messaging implementation over a single TCP connection. WebSockets are not a HTTP connection , but use HTTP to bootstrap a WebSocket connection. A full-duplex system allows communication in both directions simultaneously. Land-line telephones are an example of a full-duplex device, since they allow both callers to speak and be heard at the same time. It was initially proposed as part of the HTML5 specification which promises to bring ease of development and network efficiency to modern, interactive web applications, but was later moved to a separate standards document to keep the specification focused only on WebSockets. It consists of two things -- the WebSocket protocol specification i.e. RFC 6455 which was published in December 2011 and WebSocket JavaScript API. The WebSocket protocol leverages the HTTP upgrade header to upgrade an HTTP connection to a WebSocket connection. HTML5 WebSockets address many problems that make HTTP unsuitable for real time applications and make application architecture simple by avoiding complex workarounds. WebSockets are supported by all the latest browsers as shown below in an image. The information is taken from http://caniuse.com/#feat=websockets. | 已有1人翻译此段我来翻译 |
How Does a WebSocket Work?Every WebSocket connection begins its life as an HTTP request. The HTTP request is much like another request, except that it has an Upgrade header. The Upgrade header indicates that a client would like to upgrade the connection to different protocol. For WebSockets it will upgrade to the WebSocket protocol. The WebSocket connection is established by upgrading from HTTP protocol to the WebSockets protocol during the initial handshake between the client and server over the same underlying connection. Once the WebSocket connection is established, messages can be sent back and forth between the client and server. Efficiency, Simplicity and Less Bandwidth with WebSocketsWebSockets are more efficient and performant than other workarounds like polling. They require less bandwidth and reduce latency. WebSockets simplify real-time application architectures. WebSockets do not require headers to send messages between peers. This considerably lowers the required bandwidth.
WebSocket Use CasesSome of the possible use-cases of WebSockets are : - Chat applications
- Multiplayer games
- Stock trading or financial applications
- Collaborative document editing
- Social networking applications
| 已有1人翻译此段我来翻译 |
Working with WebSockets in JavaAs it normally happens in the Java community, different vendors or developers write libraries to use a technology and then after sometime when the technology matures, it is standardized so that developers can interoperate between different implementations without the danger of vendor lock in. There were more than 20 different Java WebSocket implementations when JSR 356 was started. Most of them had different APIs. JSR 356 is an effort to standardize a WebSocket API for Java. Developers can use JSR 356 API for creating WebSocket applications independent of the implementation. The WebSocket API is purely event driven. | 已有1人翻译此段我来翻译 |
JSR 356 -- Java API for WebSocketsJSR 356, Java API for WebSocket, specifies Java API that developers can use to integrate WebSockets into their applications — both on the server side as well as on the Java client side. JSR 356 is part of the upcoming Java EE 7 standard. This means all Java EE 7 compliant application servers will have an implementation of the WebSocket protocol that adheres to the JSR 356 standard. Developers can also use JSR 356 outside Java EE 7 application server as well. Current development version of Apache Tomcat 8 will be adding WebSocket support based on JSR 356 API. A Java client can use JSR 356 compliant client implementation to connect to a WebSocket server. For web clients, developers can use WebSocket JavaScript API to communicate with WebSocket server. The difference between a WebSocket client and a WebSocket server lies only in the means by which the two are connected. A WebSocket client is a WebSocket endpoint that initiates a connection to a peer. And a WebSocket server is a WebSocket endpoint that is published and awaits connections from peers. There are callback listeners on both sides -- clients and server -- onOpen , onMessage , onError, onClose. We will look at these in more detail when we will build an application. | 已有1人翻译此段我来翻译 |
Tyrus -- JSR 356 Reference ImplementationTyrus is the reference implementation for JSR 356. We will be using Tyrus in standalone mode to develop a simple application in the next section. All Tyrus components are built using Java SE 7 compiler. It means, you will also need at least Java SE 7 to be able to compile and run this example application. It can't be used with Apache Tomcat 7 because it depends on servlet 3.1 specification upgrade. Developing a WordGame Using WebSocketsNow we will build a very simple word game. The user will get a scrambled word and he/she has to unscramble it. We will use a single WebSocket connection per game. Source code of this application is available on github https://github.com/shekhargulati/wordgame | 已有1人翻译此段我来翻译 |
Step 1 : Create a Template Maven ProjectWe will start by creating a template Java project using Maven archetype. Execute the command shown below to create Maven based Java project. 1 | $ mvn archetype:generate -DgroupId=com.shekhar -DartifactId=wordgame -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode= false |
Step 2 : Update pom.xml with Required DependenciesAs mentioned in previous section, you need Java SE 7 to build applications using Tyrus. To use Java 7 in your maven project , add the maven compiler plugin with configuration to use Java 7 as mentioned below. 04 | < groupId >org.apache.maven.plugins</ groupId > |
05 | < artifactId >maven-compiler-plugin</ artifactId > |
06 | < version >3.1</ version > |
08 | < compilerVersion >1.7</ compilerVersion > |
Next, add the dependency for JSR 356 API. The current version of javax.websocket-api is 1.0. 2 | < groupId >javax.websocket</ groupId > |
3 | < artifactId >javax.websocket-api</ artifactId > |
Then we will add Tyrus JSR 356 implementation related dependencies. The tyrus-server provides JSR 356 server side WebSocket API implementation and tyrus-client provides JSR356 client side WebSocket API implementation. 02 | < groupId >org.glassfish.tyrus</ groupId > |
03 | < artifactId >tyrus-server</ artifactId > |
04 | < version >1.1</ version > |
07 | < groupId >org.glassfish.tyrus</ groupId > |
08 | < artifactId >tyrus-client</ artifactId > |
09 | < version >1.1</ version > |
Finally we will add the tyrus-container-grizzly dependency to our pom.xml. This will provide a standalone container to deploy WebSocket applications. 2 | < groupId >org.glassfish.tyrus</ groupId > |
3 | < artifactId >tyrus-container-grizzly</ artifactId > |
You can view the full pom.xml here. | 已有1人翻译此段我来翻译 |
Step 3 : Write the First JSR 356 WebSocket Server EndpointNow that we have done setup for our project, we will start writing the WebSocket server endpoint. You can declare any Java POJO class WebSocket server endpoint by annotating it with @ServerEndpoint. Developers can also specify URI where endpoints will be deployed. The URI is relative to the root of WebSocket container and must begin with "/". In the code shown below, we have created a very simple WordgameServerEndpoint. 01 | package com.shekhar.wordgame.server; |
03 | import java.io.IOException; |
04 | import java.util.logging.Logger; |
06 | import javax.websocket.CloseReason; |
07 | import javax.websocket.OnClose; |
08 | import javax.websocket.OnMessage; |
09 | import javax.websocket.OnOpen; |
10 | import javax.websocket.Session; |
11 | import javax.websocket.CloseReason.CloseCodes; |
12 | import javax.websocket.server.ServerEndpoint; |
14 | @ServerEndpoint (value = "/game" ) |
15 | public class WordgameServerEndpoint { |
17 | private Logger logger = Logger.getLogger( this .getClass().getName()); |
20 | public void onOpen(Session session) { |
21 | logger.info( "Connected ... " + session.getId()); |
25 | public String onMessage(String message, Session session) { |
29 | session.close( new CloseReason(CloseCodes.NORMAL_CLOSURE, "Game ended" )); |
30 | } catch (IOException e) { |
31 | throw new RuntimeException(e); |
39 | public void onClose(Session session, CloseReason closeReason) { |
40 | logger.info(String.format( "Session %s closed because of %s" , session.getId(), closeReason)); |
The @OnOpen annotation is used to annotate a method which will be called after WebSocket connection is opened. Every connection has a session associated with it. In the code shown above, we printed the session id when onOpen() method is called. The method annotated with @OnOpen will be invoked only once per WebSocket connection. The @OnMessage annotation is used to annotate a method which will be called each time a message is received. This is the method where all the business code will be written. In the code shown above, we will close the connection when we receive "quit" message from client , else we will just return the message back to the client. So, a WebSocket connection will be open till we don't receive "quit" message.On receiving quit message we call the close method on session object giving it the reason for closing the session. In the code sample above, we say that it is a normal closure as game has ended. The @OnClose annotation is used to annotate a method which will be called when WebSocket connection is closed. | 已有1人翻译此段我来翻译 |
Step 4 : Write the First JSR 356 WebSocket Client EndpointThe @ClientEndpoint annotation is used to mark a POJO WebSocket client. Similar to javax.websocket.server.ServerEndpoint, POJOs that are annotated with @ClientEndpoint annotation can have methods that using the web socket method level annotations, are web socket lifecycle methods. 01 | package com.shekhar.wordgame.client; |
03 | import java.io.BufferedReader; |
04 | import java.io.IOException; |
05 | import java.io.InputStreamReader; |
07 | import java.net.URISyntaxException; |
08 | import java.util.concurrent.CountDownLatch; |
09 | import java.util.logging.Logger; |
11 | import javax.websocket.ClientEndpoint; |
12 | import javax.websocket.CloseReason; |
13 | import javax.websocket.DeploymentException; |
14 | import javax.websocket.OnClose; |
15 | import javax.websocket.OnMessage; |
16 | import javax.websocket.OnOpen; |
17 | import javax.websocket.Session; |
19 | import org.glassfish.tyrus.client.ClientManager; |
22 | public class WordgameClientEndpoint { |
24 | private Logger logger = Logger.getLogger( this .getClass().getName()); |
27 | public void onOpen(Session session) { |
28 | logger.info( "Connected ... " + session.getId()); |
30 | session.getBasicRemote().sendText( "start" ); |
31 | } catch (IOException e) { |
32 | throw new RuntimeException(e); |
37 | public String onMessage(String message, Session session) { |
38 | BufferedReader bufferRead = new BufferedReader( new InputStreamReader(System.in)); |
40 | logger.info( "Received ...." + message); |
41 | String userInput = bufferRead.readLine(); |
43 | } catch (IOException e) { |
44 | throw new RuntimeException(e); |
49 | public void onClose(Session session, CloseReason closeReason) { |
50 | logger.info(String.format( "Session %s close because of %s" , session.getId(), closeReason)); |
In the code shown above, we send a "start" message to the server when WebSocket connection is opened. The onMessage method annotated with @OnMessage is called each time a message is received from server. It first logs the message and then wait for user input. The user input is then sent to the server. Finally, onClose() method annotated with @OnClose annotation is called when WebSocket connection is closed. As you can see, the programming model for both client and server side code is same. This eases the development of writing WebSocket applications using JSR 356 API. | 已有1人翻译此段我来翻译 |
Step 5: Create and Start a WebSocket ServerWe need a server to deploy our WebSocket @ServerEndpoint. The server is created using tyrus server API as shown below. The server will be running on port 8025. The WordgameServerEndpoint will be accessible at ws://localhost:8025/websockets/game. 01 | package com.shekhar.wordgame.server; |
03 | import java.io.BufferedReader; |
04 | import java.io.InputStreamReader; |
06 | import org.glassfish.tyrus.server.Server; |
08 | public class WebSocketServer { |
10 | public static void main(String[] args) { |
14 | public static void runServer() { |
15 | Server server = new Server( "localhost" , 8025 , "/websockets" , WordgameServerEndpoint. class ); |
19 | BufferedReader reader = new BufferedReader( new InputStreamReader(System.in)); |
20 | System.out.print( "Please press a key to stop the server." ); |
22 | } catch (Exception e) { |
23 | throw new RuntimeException(e); |
If you are using Eclipse, then you can start the server by running it as a Java application(ALT+SHIFT+X,J). You will see logs as shown below. 01 | Jul 26 , 2013 1 : 39 : 37 PM org.glassfish.tyrus.server.ServerContainerFactory create |
02 | INFO: Provider class loaded: org.glassfish.tyrus.container.grizzly.GrizzlyEngine |
03 | Jul 26 , 2013 1 : 39 : 38 PM org.glassfish.grizzly.http.server.NetworkListener start |
04 | INFO: Started listener bound to [ 0.0 . 0.0 : 8025 ] |
05 | Jul 26 , 2013 1 : 39 : 38 PM org.glassfish.grizzly.http.server.HttpServer start |
06 | INFO: [HttpServer] Started. |
07 | Jul 26 , 2013 1 : 39 : 38 PM org.glassfish.tyrus.server.Server start |
08 | INFO: WebSocket Registered apps: URLs all start with ws: //localhost:8025 |
09 | Jul 26 , 2013 1 : 39 : 38 PM org.glassfish.tyrus.server.Server start |
10 | INFO: WebSocket server started. |
11 | Please press a key to stop the server. |
| 已有1人翻译此段我来翻译 |
Step 6 : Start the WebSocket ClientNow that server is started and WebSocket @ServerEndpoint is deployed, we will start the client as a Java application. We will create an instance of ClientManager and connect to server endpoint as shown below. 02 | public class WordgameClientEndpoint { |
04 | private static CountDownLatch latch; |
06 | private Logger logger = Logger.getLogger( this .getClass().getName()); |
09 | public void onOpen(Session session) { |
14 | public String onMessage(String message, Session session) { |
19 | public void onClose(Session session, CloseReason closeReason) { |
20 | logger.info(String.format( "Session %s close because of %s" , session.getId(), closeReason)); |
24 | public static void main(String[] args) { |
25 | latch = new CountDownLatch( 1 ); |
27 | ClientManager client = ClientManager.createClient(); |
32 | } catch (DeploymentException | URISyntaxException | InterruptedException e) { |
33 | throw new RuntimeException(e); |
We used CountDownLatch to make sure that main thread does not exit after executing the code. The main thread waits till the time latch decrements the counter in onClose() method. Then program terminates. In the main() method we create instance of ClientManager which is then used to connect to @ServerEndpoint available at ws://localhost:8025/websockets/game. Run the Client as a Java application(ALT + SHIFT + X , J) and you will see logs as shown below. 1 | Jul 26 , 2013 1 : 40 : 26 PM com.shekhar.wordgame.client.WordgameClientEndpoint onOpen |
2 | INFO: Connected ... 95f58833-c168-4a5f-a580-085810b4dc5a |
3 | Jul 26 , 2013 1 : 40 : 26 PM com.shekhar.wordgame.client.WordgameClientEndpoint onMessage |
4 | INFO: Received ....start |
Send any message like "hello world" and that will be echoed back to you as shown below. 1 | INFO: Received ....start |
3 | Jul 26 , 2013 1 : 41 : 04 PM com.shekhar.wordgame.client.WordgameClientEndpoint onMessage |
4 | INFO: Received ....hello world |
Send the "quit" message, and WebSocket connection will be closed. 1 | INFO: Received ....hello world |
3 | Jul 26 , 2013 1 : 42 : 23 PM com.shekhar.wordgame.client.WordgameClientEndpoint onClose |
4 | INFO: Session 95f58833-c168-4a5f-a580-085810b4dc5a close because of CloseReason[ 1000 ,Game ended] |
| 已有1人翻译此段我来翻译 |
Step 7 : Add Game LogicNow we will add the game logic which will sent a scrambled word to client and on receiving an unscrambled word from client will check whether it is correct or not. Update the WordgameServerEndpoint code as shown below. 01 | package com.shekhar.wordgame.server; |
03 | import java.io.IOException; |
04 | import java.util.logging.Logger; |
06 | import javax.websocket.CloseReason; |
07 | import javax.websocket.OnClose; |
08 | import javax.websocket.OnMessage; |
09 | import javax.websocket.OnOpen; |
10 | import javax.websocket.Session; |
11 | import javax.websocket.CloseReason.CloseCodes; |
12 | import javax.websocket.server.ServerEndpoint; |
14 | @ServerEndpoint (value = "/game" ) |
15 | public class WordgameServerEndpoint { |
17 | private Logger logger = Logger.getLogger( this .getClass().getName()); |
20 | public void onOpen(Session session) { |
21 | logger.info( "Connected ... " + session.getId()); |
25 | public String onMessage(String unscrambledWord, Session session) { |
26 | switch (unscrambledWord) { |
28 | logger.info( "Starting the game by sending first word" ); |
29 | String scrambledWord = WordRepository.getInstance().getRandomWord().getScrambledWord(); |
30 | session.getUserProperties().put( "scrambledWord" , scrambledWord); |
33 | logger.info( "Quitting the game" ); |
35 | session.close( new CloseReason(CloseCodes.NORMAL_CLOSURE, "Game finished" )); |
36 | } catch (IOException e) { |
37 | throw new RuntimeException(e); |
40 | String scrambledWord = (String) session.getUserProperties().get( "scrambledWord" ); |
41 | return checkLastWordAndSendANewWord(scrambledWord, unscrambledWord, session); |
45 | public void onClose(Session session, CloseReason closeReason) { |
46 | logger.info(String.format( "Session %s closed because of %s" , session.getId(), closeReason)); |
49 | private String checkLastWordAndSendANewWord(String scrambledWord, String unscrambledWord, Session session) { |
50 | WordRepository repository = WordRepository.getInstance(); |
51 | Word word = repository.getWord(scrambledWord); |
53 | String nextScrambledWord = repository.getRandomWord().getScrambledWord(); |
55 | session.getUserProperties().put( "scrambledWord" , nextScrambledWord); |
57 | String correctUnscrambledWord = word.getUnscrambbledWord(); |
59 | if (word == null || !correctUnscrambledWord.equals(unscrambledWord)) { |
60 | return String.format( "You guessed it wrong. Correct answer %s. Try the next one .. %s" , |
61 | correctUnscrambledWord, nextScrambledWord); |
63 | return String.format( "You guessed it right. Try the next word ... %s" , nextScrambledWord); |
Restart the server and client and enjoy the game. ConclusionIn this blog post we looked at how the JSR 356 WebSocket API can help us build realtime full duplex Java applications. JSR 356 WebSocket API is very simple and the annotation based programming model makes it very easy to build WebSocket applications. In the next blog post, we will look at Undertow, a flexible performant web server written in java from JBoss. | 已有1人翻译此段我来翻译 |
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照
CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们