Java Web高级编程(四)

WebSocket

一、WebSocket的产生

用户希望Web页面可以进行交互,用于解决这个问题的技术是JavaScript,现在Web上有许多的可用的JavaScript框架,在使用极少的JavaScript的情况下就可以创建出丰富的单页面Web——Ajax技术(异步JavaScript和XML)。

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

但是,浏览器只可以从服务器专区新的数据,但是浏览器并不知道数据什么时候使用,只有服务器知道什么时候有新数据发送到浏览器,而浏览器并不知道。

解决方法1,频繁轮询

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

解决方法2,长轮询

服务器只有在发送数据时才会响应浏览器(如果浏览器在服务器响应之前有新数据要发送,浏览器就必须要创建一个新的并行请求,或者终止当前的请求;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. 可以支持跨区域连接。

二、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)。

服务器终端中的时间处理方法将和客户端中的时间处理方法一样工作,区别只存在于握手阶段,之后并没有服务器和客户端的差别。

时间: 2024-10-11 07:38:54

Java Web高级编程(四)的相关文章

moon Spring -- java Web 高级编程

第一部分--全章概览 第12章--介绍 Spring Framework 12.1Spring Framework简介 12.1.1  反转控制和依赖注入 12.2     面向切面编程 12.1.3  数据访问和事务管理 12.4    应用程序消息 12.5    Web应用程序的模型——试图-控制器模式 12.2使用Spring framework的原因 12.2.1  逻辑代码分组 12.2.2  使用同一代码库的多个用户界面 12.3了解应用上下文 12.4启动Spring Frame

Java web基础总结四之—— Servlet基础

Java web基础总结四之-- Servlet基础 一.什么是Servlet? 通过名字就能看出来,Servlet 就是在服务器上运行的小程序.Servlet是sun公司(现在已经属于oracle了)实现的一门用于开发动态java web资源的技术.Sun公司在其API中提供了一个servlet接口,如果你想开发一个动态的java web资源,需要完成以下2个步骤:编写一个Java类,实现servlet接口.把开发好的Java类部署到web服务器中. Servlet接口已经有了两个默认的实现类

java web开发入门四(spring)基于intellig idea

spring 1.spring简介 Spring框架,可以解决对象创建以及对象之间依赖关系的一种框架. 且可以和其他框架一起使用:Spring与Struts,  Spring与hibernate (起到整合(粘合)作用的一个框架) Spring提供了一站式解决方案: 1) Spring Core  spring的核心功能: IOC容器, 解决对象创建及依赖关系 2) Spring Web  Spring对web模块的支持. -à 可以与struts整合,让struts的action创建交给spr

Java web切面编程

在我们的 web开发中  我们在 对公用的 一些方法 我们需要抽取出来   这样达到 代码的冗余   今天 我利用项目上用的AOP的 实际 应用做了一个整理 首先  xml配置  扫描 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://ww

C#高级编程四十八天----列表

C#中的List C#中deList怎么样?List<T>类是ArrayList类的泛型等效类,该类使用大小可按需动态增长的数组实现List<T>泛型接口. 泛型的好处:它为使用C#语言编写面向对象程序增加了极大的效力和灵活性,不会强行对值类型进行装箱和拆箱,或对引用类型进行向下强制类型转化,所以性能得到提高. 性能注意事项:再决定使用List<T>还是使用ArrayList类(两者具有类似的功能)时,记住IList<T>类在大多数情况下执行得更好并且是类型

【学习笔记】java面向对象高级编程1

内容包含: /************************封装继承以及使用继承所带来的问题剖析变量隐藏与方法重写 *************************/ 一. 封装 通过get和set进行封装 这里面有个小技巧:通过快捷键 Alt + Shift + 3 进行选择一些快速高效的方法,选择get和set进行快速创建,如下代码: package com.jfu.one; public class packAge { private int dog; private String c

C#高级编程四十六天----正则表达式

正则表达式 1.定义一个Regex类的实例 Regex regex=new Regex(""); 这里初始化参数就是一个正则表达式,"\d"表示配置数字 2.判断是否匹配 判断一个字符串,是否匹配一个正则表达式,在Regex对象中,可以使用Regex.IsMatch(string )方法. Regex regex = new Regex(@"\d"); bool b1=regex.IsMatch("abc"); //返回值为f

JSP_DAO方式实现数据库查询(MyEclipse10,Tomcat7.0,JDK1.7,)——Java Web练习(四)

1.项目结构: 2.创建数据库.表.插入记录 create database TestDao; use TestDao; create table student( stuid int, username varchar(20), password varchar(20) ); insert student(stuid,username,password) values ("10001","Eastmount","111111"); insert

家庭记账本小程序之删(java web基础版四)

实现删除消费账单 1.main_left.jsp中该部分,调用Servlet中delete方法 2.Servlet中delete方法,调用Dao层list方法,跳转到del.jsp页面 3.Dao层list方法 4.del.jsp,调用Servlet中的del方法 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>