Java Web高级编程(四)

WebSocketjava

1、WebSocket的产生web

用户但愿Web页面能够进行交互,用于解决这个问题的技术是JavaScript,如今Web上有许多的可用的JavaScript框架,在使用极少的JavaScript的状况下就能够建立出丰富的单页面Web——Ajax技术(异步JavaScript和XML)。浏览器

在采用了Ajax以后,浏览器中的Web应用程序能够与服务器端的组件进行通讯,而不须要改变浏览器页面或者刷新。这个通讯过程不须要用户知道,而且它能够用于向服务器发送新数据或者从服务器得到新数据。服务器

可是,浏览器只能够从服务器专区新的数据,可是浏览器并不知道数据何时使用,只有服务器知道何时有新数据发送到浏览器,而浏览器并不知道。websocket

解决方法1,频繁轮询网络

频繁轮询服务器获取新数据,以一个固定的频率,一般是每秒一次,浏览器将发送Ajax请求到服务器查询新数据。若是浏览器有新的数据发送到服务器,数据将被添加到轮询请求中一同发送给浏览器(可是大量请求会被浪费)。session

解决方法2,长轮询app

服务器只有在发送数据时才会响应浏览器(若是浏览器在服务器响应以前有新数据要发送,浏览器就必需要建立一个新的并行请求,或者终止当前的请求;TCP和HTTP规定了链接超时的状况;HTTP存在着强制的链接限制)。框架

解决方法3,分块编码异步

服务器能够在不声明内容长度的状况下响应请求。在响应中,每一个块的开头一次是:一个用于表示块长度的数字、一系列表示块扩展的可选字符和一个CRLF(回车换行)序列。接着是块包含的数据和另外一个CRLF。浏览器将建立一个链接到“下游端点”的长生命链接,而且服务器将使用该链接以块的方式向浏览器发送更新。

解决方法4,Applet和Adobe Flash

建立链接到服务器的普通TCP套接字链接,当浏览器有了新的数据要发送到服务器,它将由浏览器插件暴露出的JavaScript DOM函数调用Java或Flash方法,而后该方法吧数据转发到服务器上。

解决方法5,WebSocket

WebSocket链接首先将使用非正常的HTTP请求以特定的模式访问一个URL,WebSocket是持久的全双工通讯协议。在握手完成以后,文本和二进制消息将能够同时在两个方向上进行发送,而不须要关闭和从新链接。

WebSocket的优势:

  1. 链接端口在80(ws)和433(wss),因此不会被防火墙阻塞。
  2. 使用HTTP握手,能够天然地集成到网络浏览器和HTTP服务器上。
  3. 使用ping和pong保持WebSocket一直处于活跃状态。
  4. 当消息启动和它的内容到达时,服务器和客户端均可以知道。
  5. WebSocket在关闭链接时会发送特殊的关闭消息。
  6. 能够支持跨区域链接。

2、WebSocket API

WebSocket并不仅是在浏览器和服务器的通讯,两个以任何框架编写、支持WebSocket的应用程序均可以建立WebSocket链接进行通讯。

WebSocket的Java API包含在javax.websocket中,并指定了一组类和接口包含全部的常见功能。

客户端API

客户端API基于ContainerProvider类和WebSocketContainer、RemoteEndpoint和Session接口构建。

WebSocketContainer提供了对全部WebSocket客户端特性的访问,而ContainerProvider类听了静态的getWebSocketContainer方法用来获取底层WebSocket客户端的实现。

WebSocketContainer提供了4个重载的connectToServer方法,它们都将接受一个URI,用于链接远程终端和初始化握手。

  1. 标注了@ClientEndpoint的任意类型的POJO
  2. 标注了@ClientEndpoint的任意类型的POJO的Class<?>
  3. Endpoint类的实例或者一个Class<? extends EndPoint>。

当握手完成是,connectToServer方法将返回一个Session。

其中WebSocket的Endpoint有3个方法,onOpen、onClose和onError,它们将在这些时间发生时进行调用。

而@ClientEndpoint类标注了@onOpen、@onClose和@onError的方法。

  1. @OnOpen方法能够有:一个可选的Session参数,一个可选的EndpointConfig参数。
  2. @OnClose方法能够有:一个可选的Session参数,一个可选的CloseReason参数。
  3. @OnError方法能够有:一个可选的Session参数,一个可选的Throwable参数。
  4. @OnMessage方法能够有:一个可选的Session参数,其它参数的组合。

这是一个WebSocket建立多人游戏的服务器终端代码:

public class TicTacToeServer
{
    private static Map<Long, Game> games = new Hashtable<>();
    private static ObjectMapper mapper = new ObjectMapper();

    @OnOpen
    public void onOpen(Session session, @PathParam("gameId") long gameId,
                       @PathParam("username") String username)
    {
        try
        {
            TicTacToeGame ticTacToeGame = TicTacToeGame.getActiveGame(gameId);
            if(ticTacToeGame != null)
            {
                session.close(new CloseReason(
                        CloseReason.CloseCodes.UNEXPECTED_CONDITION,
                        "This game has already started."
                ));
            }

            List<String> actions = session.getRequestParameterMap().get("action");
            if(actions != null && actions.size() == 1)
            {
                String action = actions.get(0);
                if("start".equalsIgnoreCase(action))
                {
                    Game game = new Game();
                    game.gameId = gameId;
                    game.player1 = session;
                    TicTacToeServer.games.put(gameId, game);
                }
                else if("join".equalsIgnoreCase(action))
                {
                    Game game = TicTacToeServer.games.get(gameId);
                    game.player2 = session;
                    game.ticTacToeGame = TicTacToeGame.startGame(gameId, username);
                    this.sendJsonMessage(game.player1, game,
                            new GameStartedMessage(game.ticTacToeGame));
                    this.sendJsonMessage(game.player2, game,
                            new GameStartedMessage(game.ticTacToeGame));
                }
            }
        }
        catch(IOException e)
        {
            e.printStackTrace();
            try
            {
                session.close(new CloseReason(
                        CloseReason.CloseCodes.UNEXPECTED_CONDITION, e.toString()
                ));
            }
            catch(IOException ignore) { }
        }
    }

    @OnMessage
    public void onMessage(Session session, String message,
                          @PathParam("gameId") long gameId)
    {
        Game game = TicTacToeServer.games.get(gameId);
        boolean isPlayer1 = session == game.player1;

        try
        {
            Move move = TicTacToeServer.mapper.readValue(message, Move.class);
            game.ticTacToeGame.move(
                    isPlayer1 ? TicTacToeGame.Player.PLAYER1 :
                            TicTacToeGame.Player.PLAYER2,
                    move.getRow(),
                    move.getColumn()
            );
            this.sendJsonMessage((isPlayer1 ? game.player2 : game.player1), game,
                    new OpponentMadeMoveMessage(move));
            if(game.ticTacToeGame.isOver())
            {
                if(game.ticTacToeGame.isDraw())
                {
                    this.sendJsonMessage(game.player1, game,
                            new GameIsDrawMessage());
                    this.sendJsonMessage(game.player2, game,
                            new GameIsDrawMessage());
                }
                else
                {
                    boolean wasPlayer1 = game.ticTacToeGame.getWinner() ==
                            TicTacToeGame.Player.PLAYER1;
                    this.sendJsonMessage(game.player1, game,
                            new GameOverMessage(wasPlayer1));
                    this.sendJsonMessage(game.player2, game,
                            new GameOverMessage(!wasPlayer1));
                }
                game.player1.close();
                game.player2.close();
            }
        }
        catch(IOException e)
        {
            this.handleException(e, game);
        }
    }

    @OnClose
    public void onClose(Session session, @PathParam("gameId") long gameId)
    {
        Game game = TicTacToeServer.games.get(gameId);
        if(game == null)
            return;
        boolean isPlayer1 = session == game.player1;
        if(game.ticTacToeGame == null)
        {
            TicTacToeGame.removeQueuedGame(game.gameId);
        }
        else if(!game.ticTacToeGame.isOver())
        {
            game.ticTacToeGame.forfeit(isPlayer1 ? TicTacToeGame.Player.PLAYER1 :
                    TicTacToeGame.Player.PLAYER2);
            Session opponent = (isPlayer1 ? game.player2 : game.player1);
            this.sendJsonMessage(opponent, game, new GameForfeitedMessage());
            try
            {
                opponent.close();
            }
            catch(IOException e)
            {
                e.printStackTrace();
            }
        }
    }

服务器API

服务器API依赖于完整的客户端API,它只添加了少数的类和接口,ServerContainer集成了WebSocketContainer,在Servlet环境中调用ServletContext.getAttribute("javax.websocket.server.ServerCOntainer")能够得到ServerContainer实例,在独立运行的应用程序中,须要按照特定的WebSocket实现的指令获取ServerContainer实例。

不过,其实可使用@ServerEndPoint标注服务器终端类便可,WebSocket实现能够扫描类的注解,并自动选择和注册服务器终端,容器在每次收到WebSocket链接时建立对应终端的实例,在链接关闭以后在销毁实例。

在使用@ServerEndPoint,至少须要制定必须的value特性目标是该终端能够作出像的应用程序相对应的URL:

@ServerEndpoint("/ticTacToe/{gameId}/{username}")

若是应用程序部署到的地址为:http://www.example.org/app,那么该服务器终端会响应地址:ws://www.example.org/app/ticTacToe/1/andre等,而后服务器终端中全部的@OnOpen、@OnClose、@OnError和@OnMessage方法均可以只用@PathParam(“{gameId}/{username}”)标注出一个可选的额外参数,而且其内容为改参数的值(1/andre)。

服务器终端中的时间处理方法将和客户端中的时间处理方法同样工做,区别只存在于握手阶段,以后并无服务器和客户端的差异。

相关文章
相关标签/搜索