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

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

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

  1. Java Web高级编程(二)

    使用会话维持状态 一.会话 为了实现关联同一个用户端的多个请求和这些请求之间数据的共享,需要用到会话,会话用于维持请求和请求之间的状态.从服务器的角度,当用户的Web浏览器打开第一个链接到服务器的套接 ...

  2. Java Web高级编程(一)

    Servlet 一.创建Servlet类 在Java EE中,Servlet用来接收和响应终端用户的请求.Servlet是所有Web应用程序的核心类,是唯一既可以直接处理和响应用户请求,也可以将处理工 ...

  3. Java Web高级编程(三)

    使用过滤器改进应用程序 一.过滤器的目的 过滤器是可以拦截访问资源的请求.资源的响应或者同时拦截两者的应用组件.过滤器可以检测和修改请求和响应,同时也可以拒绝.重定向或转发请求.javax.servl ...

  4. java web高级编程 笔记1

    chapter1:了解web应用程序 web应用程序主要组件: Servlet 过滤器 监听器 JSP chapter2:各类web容器介绍 略 chapter3:Servlet介绍 Servlet是 ...

  5. java web学习总结(四) -------------------HTTP协议

    一.什么是HTTP协议 HTTP是hypertext transfer protocol(超文本传输协议)的简写,它是TCP/IP协议的一个应用层协议,用于定义WEB浏览器与WEB服务器之间交换数据的 ...

  6. C++面向对象高级编程(四)基础篇

    技术在于交流.沟通,转载请注明出处并保持作品的完整性. 一.Static 二.模板类和模板函数 三.namespace 一.Static 静态成员是“类级别”的,也就是它和类的地位等同,而普通成员是“ ...

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

    spring 1.spring简介 Spring框架,可以解决对象创建以及对象之间依赖关系的一种框架. 且可以和其他框架一起使用:Spring与Struts,  Spring与hibernate (起 ...

  8. java web 学习十四(JSP原理)

    一.什么是JSP? JSP全称是Java Server Pages,它和servle技术一样,都是SUN公司定义的一种用于开发动态web资源的技术. JSP这门技术的最大的特点在于,写jsp就像在写h ...

  9. Java web切面编程

    在我们的 web开发中  我们在 对公用的 一些方法 我们需要抽取出来   这样达到 代码的冗余   今天 我利用项目上用的AOP的 实际 应用做了一个整理 首先  xml配置  扫描 <?xm ...

随机推荐

  1. jQuery的$.ajax方法响应数据类型有哪几种?本质上原生ajax响应数据格式有哪几种,分别对应哪个属性?

    jQuery的$.ajax方法响应数据类型有:xml.html.script.json.jsonp.text 本质上原生ajax响应数据格式只有2种:xml和text,分别对应xhr.response ...

  2. UWP 图片缩放

    给Image外面包裹一个ScrollViewer,你会回来感激我的. 哦,对了,PC上需要按住Ctrl键,滑动鼠标滑轮即可:手机上双指就可以缩放. <ScrollViewer ZoomMode= ...

  3. python通过webservice接口实现配置下发

    项目上要开发一个小工具,通过webservice接口实现配置下发,考虑到python的第三方库对soap的良好支持,果断决定用python来完成这一使命. Python的支持webservice的第三 ...

  4. 数据库介绍(MySQL安装 体系结构、基本管理)

    第1章 数据库介绍及mysql安装 1.1 数据库简介 数据库,简而言之可视为电子化的文件柜——存储电子文件的处所,用户可以对文件中的数据运行新增.截取.更新.删除等操作. 所谓“数据库”系以一定方式 ...

  5. 升级安装php7要考虑的几个方面

    php7从推出到现在,前两天官方已经发布到7.2版本,把自己升级和安装php7中遇到的问题和解决方法做一个笔记,内容主要包括:1) mysql2) php3) nginx4) memcache扩展5) ...

  6. JDBC中的Statement和PreparedStatement的差别

    以Oracle为例吧 Statement为一条Sql语句生成运行计划, 假设要运行两条sql语句 select colume from table where colume=1; select col ...

  7. 在Flex中推断是否在组件之外单击的技巧

    在Flex中推断是否在组件之外单击的技巧 昨天在做Flex开发的时候.遇到了这样一种操作,之前也遇到过.那时的办法不是非常好,今天又碰上了类似的问题,先看一张图吧! watermark/2/text/ ...

  8. .net core控制台应用程序初识

    .net core控制台应用程序与之前.net版本有所不同,编译之后不会生成.exe启动程序,而是生成.dll文件 所以在使用的时候需要敲入命令来启动控制台应用程序,命令是: start  dotne ...

  9. mybatis的逆向工程——命令行方式

    1.由于MyBatis属于一种半自动的ORM框架,所以主要的工作就是配置Mapping映射文件,但是由于手写映射文件很容易出错,可利用MyBatis生成器自动生成实体类.DAO接口和Mapping映射 ...

  10. SVN 安装与使用教程总结

    转载:http://www.cnblogs.com/armyfai/p/3985660.html SVN简介: 为什么要使用SVN? 程序员在编写程序的过程中,每个程序员都会生成很多不同的版本,这就需 ...