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.

WebSocket browser support

已有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 WebSockets

  1. WebSockets are more efficient and performant than other workarounds like polling. They require less bandwidth and reduce latency.

  2. WebSockets simplify real-time application architectures.

  3. WebSockets do not require headers to send messages between peers. This considerably lowers the required bandwidth.

WebSocket Use Cases

Some 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 Java

As 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 WebSockets

JSR 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 Implementation

Tyrus 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 WebSockets

Now 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 Project

We 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 Dependencies

As 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.

01<build>
02    <plugins>
03        <plugin>
04            <groupId>org.apache.maven.plugins</groupId>
05            <artifactId>maven-compiler-plugin</artifactId>
06            <version>3.1</version>
07            <configuration>
08                <compilerVersion>1.7</compilerVersion>
09                <source>1.7</source>
10                <target>1.7</target>
11            </configuration>
12        </plugin>
13    </plugins>
14</build>

Next, add the dependency for JSR 356 API. The current version of javax.websocket-api is 1.0.

1<dependency>
2    <groupId>javax.websocket</groupId>
3    <artifactId>javax.websocket-api</artifactId>
4    <version>1.0</version>
5</dependency>

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.

01<dependency>
02    <groupId>org.glassfish.tyrus</groupId>
03    <artifactId>tyrus-server</artifactId>
04    <version>1.1</version>
05</dependency>
06<dependency>
07    <groupId>org.glassfish.tyrus</groupId>
08    <artifactId>tyrus-client</artifactId>
09    <version>1.1</version>
10</dependency>

Finally we will add the tyrus-container-grizzly dependency to our pom.xml. This will provide a standalone container to deploy WebSocket applications.

1<dependency>
2    <groupId>org.glassfish.tyrus</groupId>
3    <artifactId>tyrus-container-grizzly</artifactId>
4    <version>1.1</version>
5</dependency>

You can view the full pom.xml here.

已有1人翻译此段

我来翻译

Step 3 : Write the First JSR 356 WebSocket Server Endpoint

Now 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.

01package com.shekhar.wordgame.server;
02  
03import java.io.IOException;
04import java.util.logging.Logger;
05  
06import javax.websocket.CloseReason;
07import javax.websocket.OnClose;
08import javax.websocket.OnMessage;
09import javax.websocket.OnOpen;
10import javax.websocket.Session;
11import javax.websocket.CloseReason.CloseCodes;
12import javax.websocket.server.ServerEndpoint;
13  
14@ServerEndpoint(value = "/game")
15public class WordgameServerEndpoint {
16  
17    private Logger logger = Logger.getLogger(this.getClass().getName());
18  
19    @OnOpen
20    public void onOpen(Session session) {
21        logger.info("Connected ... " + session.getId());
22    }
23  
24    @OnMessage
25    public String onMessage(String message, Session session) {
26        switch (message) {
27        case "quit":
28            try {
29                session.close(new CloseReason(CloseCodes.NORMAL_CLOSURE, "Game ended"));
30            catch (IOException e) {
31                throw new RuntimeException(e);
32            }
33            break;
34        }
35        return message;
36    }
37  
38    @OnClose
39    public void onClose(Session session, CloseReason closeReason) {
40        logger.info(String.format("Session %s closed because of %s", session.getId(), closeReason));
41    }
42}

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 Endpoint

The @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.

01package com.shekhar.wordgame.client;
02  
03import java.io.BufferedReader;
04import java.io.IOException;
05import java.io.InputStreamReader;
06import java.net.URI;
07import java.net.URISyntaxException;
08import java.util.concurrent.CountDownLatch;
09import java.util.logging.Logger;
10  
11import javax.websocket.ClientEndpoint;
12import javax.websocket.CloseReason;
13import javax.websocket.DeploymentException;
14import javax.websocket.OnClose;
15import javax.websocket.OnMessage;
16import javax.websocket.OnOpen;
17import javax.websocket.Session;
18  
19import org.glassfish.tyrus.client.ClientManager;
20  
21@ClientEndpoint
22public class WordgameClientEndpoint {
23  
24    private Logger logger = Logger.getLogger(this.getClass().getName());
25  
26    @OnOpen
27    public void onOpen(Session session) {
28        logger.info("Connected ... " + session.getId());
29        try {
30            session.getBasicRemote().sendText("start");
31        catch (IOException e) {
32            throw new RuntimeException(e);
33        }
34    }
35  
36    @OnMessage
37    public String onMessage(String message, Session session) {
38        BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));
39        try {
40            logger.info("Received ...." + message);
41            String userInput = bufferRead.readLine();
42            return userInput;
43        catch (IOException e) {
44            throw new RuntimeException(e);
45        }
46    }
47  
48    @OnClose
49    public void onClose(Session session, CloseReason closeReason) {
50        logger.info(String.format("Session %s close because of %s", session.getId(), closeReason));
51    }
52  
53  
54}

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 Server

We 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.

01package com.shekhar.wordgame.server;
02  
03import java.io.BufferedReader;
04import java.io.InputStreamReader;
05  
06import org.glassfish.tyrus.server.Server;
07  
08public class WebSocketServer {
09  
10    public static void main(String[] args) {
11        runServer();
12    }
13  
14    public static void runServer() {
15        Server server = new Server("localhost"8025"/websockets", WordgameServerEndpoint.class);
16  
17        try {
18            server.start();
19            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
20            System.out.print("Please press a key to stop the server.");
21            reader.readLine();
22        catch (Exception e) {
23            throw new RuntimeException(e);
24        finally {
25            server.stop();
26        }
27    }
28}

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.

01Jul 262013 1:39:37 PM org.glassfish.tyrus.server.ServerContainerFactory create
02INFO: Provider class loaded: org.glassfish.tyrus.container.grizzly.GrizzlyEngine
03Jul 262013 1:39:38 PM org.glassfish.grizzly.http.server.NetworkListener start
04INFO: Started listener bound to [0.0.0.0:8025]
05Jul 262013 1:39:38 PM org.glassfish.grizzly.http.server.HttpServer start
06INFO: [HttpServer] Started.
07Jul 262013 1:39:38 PM org.glassfish.tyrus.server.Server start
08INFO: WebSocket Registered apps: URLs all start with ws://localhost:8025
09Jul 262013 1:39:38 PM org.glassfish.tyrus.server.Server start
10INFO: WebSocket server started.
11Please press a key to stop the server.

已有1人翻译此段

我来翻译

Step 6 : Start the WebSocket Client

Now 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.

01@ClientEndpoint
02public class WordgameClientEndpoint {
03  
04    private static CountDownLatch latch;
05  
06    private Logger logger = Logger.getLogger(this.getClass().getName());
07  
08    @OnOpen
09    public void onOpen(Session session) {
10        // same as above
11    }
12  
13    @OnMessage
14    public String onMessage(String message, Session session) {
15        // same as above
16    }
17  
18    @OnClose
19    public void onClose(Session session, CloseReason closeReason) {
20        logger.info(String.format("Session %s close because of %s", session.getId(), closeReason));
21        latch.countDown();
22    }
23  
24    public static void main(String[] args) {
25        latch = new CountDownLatch(1);
26  
27        ClientManager client = ClientManager.createClient();
28        try {
29            client.connectToServer(WordgameClientEndpoint.classnewURI("ws://localhost:8025/websockets/game"));
30            latch.await();
31  
32        catch (DeploymentException | URISyntaxException | InterruptedException e) {
33            throw new RuntimeException(e);
34        }
35    }
36}

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.

1Jul 262013 1:40:26 PM com.shekhar.wordgame.client.WordgameClientEndpoint onOpen
2INFO: Connected ... 95f58833-c168-4a5f-a580-085810b4dc5a
3Jul 262013 1:40:26 PM com.shekhar.wordgame.client.WordgameClientEndpoint onMessage
4INFO: Received ....start

Send any message like "hello world" and that will be echoed back to you as shown below.

1INFO: Received ....start
2hello world
3Jul 262013 1:41:04 PM com.shekhar.wordgame.client.WordgameClientEndpoint onMessage
4INFO: Received ....hello world

Send the "quit" message, and WebSocket connection will be closed.

1INFO: Received ....hello world
2quit
3Jul 262013 1:42:23 PM com.shekhar.wordgame.client.WordgameClientEndpoint onClose
4INFO: Session 95f58833-c168-4a5f-a580-085810b4dc5a close because of CloseReason[1000,Game ended]

已有1人翻译此段

我来翻译

Step 7 : Add Game Logic

Now 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.

01package com.shekhar.wordgame.server;
02  
03import java.io.IOException;
04import java.util.logging.Logger;
05  
06import javax.websocket.CloseReason;
07import javax.websocket.OnClose;
08import javax.websocket.OnMessage;
09import javax.websocket.OnOpen;
10import javax.websocket.Session;
11import javax.websocket.CloseReason.CloseCodes;
12import javax.websocket.server.ServerEndpoint;
13  
14@ServerEndpoint(value = "/game")
15public class WordgameServerEndpoint {
16  
17    private Logger logger = Logger.getLogger(this.getClass().getName());
18  
19    @OnOpen
20    public void onOpen(Session session) {
21        logger.info("Connected ... " + session.getId());
22    }
23  
24    @OnMessage
25    public String onMessage(String unscrambledWord, Session session) {
26        switch (unscrambledWord) {
27        case "start":
28            logger.info("Starting the game by sending first word");
29            String scrambledWord = WordRepository.getInstance().getRandomWord().getScrambledWord();
30            session.getUserProperties().put("scrambledWord", scrambledWord);
31            return scrambledWord;
32        case "quit":
33            logger.info("Quitting the game");
34            try {
35                session.close(new CloseReason(CloseCodes.NORMAL_CLOSURE, "Game finished"));
36            catch (IOException e) {
37                throw new RuntimeException(e);
38            }
39        }
40        String scrambledWord = (String) session.getUserProperties().get("scrambledWord");
41        return checkLastWordAndSendANewWord(scrambledWord, unscrambledWord, session);
42    }
43  
44    @OnClose
45    public void onClose(Session session, CloseReason closeReason) {
46        logger.info(String.format("Session %s closed because of %s", session.getId(), closeReason));
47    }
48  
49    privateString checkLastWordAndSendANewWord(String scrambledWord, String unscrambledWord, Session session) {
50        WordRepository repository = WordRepository.getInstance();
51        Word word = repository.getWord(scrambledWord);
52  
53        String nextScrambledWord = repository.getRandomWord().getScrambledWord();
54  
55        session.getUserProperties().put("scrambledWord", nextScrambledWord);
56  
57        String correctUnscrambledWord = word.getUnscrambbledWord();
58  
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);
62        }
63        return String.format("You guessed it right. Try the next word ...  %s", nextScrambledWord);
64    }
65}

Restart the server and client and enjoy the game.

Conclusion

In 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 协议,如果我们的工作有侵犯到您的权益,请及时联系我们