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的优点:
- 连接端口在80(ws)和433(wss),所以不会被防火墙阻塞。
- 使用HTTP握手,可以自然地集成到网络浏览器和HTTP服务器上。
- 使用ping和pong保持WebSocket一直处于活跃状态。
- 当消息启动和它的内容到达时,服务器和客户端都可以知道。
- WebSocket在关闭连接时会发送特殊的关闭消息。
- 可以支持跨区域连接。
二、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,用于连接远程终端和初始化握手。
- 标注了@ClientEndpoint的任意类型的POJO
- 标注了@ClientEndpoint的任意类型的POJO的Class<?>
- Endpoint类的实例或者一个Class<? extends EndPoint>。
当握手完成是,connectToServer方法将返回一个Session。
其中WebSocket的Endpoint有3个方法,onOpen、onClose和onError,它们将在这些时间发生时进行调用。
而@ClientEndpoint类标注了@onOpen、@onClose和@onError的方法。
- @OnOpen方法可以有:一个可选的Session参数,一个可选的EndpointConfig参数。
- @OnClose方法可以有:一个可选的Session参数,一个可选的CloseReason参数。
- @OnError方法可以有:一个可选的Session参数,一个可选的Throwable参数。
- @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高级编程(四)的更多相关文章
- Java Web高级编程(二)
使用会话维持状态 一.会话 为了实现关联同一个用户端的多个请求和这些请求之间数据的共享,需要用到会话,会话用于维持请求和请求之间的状态.从服务器的角度,当用户的Web浏览器打开第一个链接到服务器的套接 ...
- Java Web高级编程(一)
Servlet 一.创建Servlet类 在Java EE中,Servlet用来接收和响应终端用户的请求.Servlet是所有Web应用程序的核心类,是唯一既可以直接处理和响应用户请求,也可以将处理工 ...
- Java Web高级编程(三)
使用过滤器改进应用程序 一.过滤器的目的 过滤器是可以拦截访问资源的请求.资源的响应或者同时拦截两者的应用组件.过滤器可以检测和修改请求和响应,同时也可以拒绝.重定向或转发请求.javax.servl ...
- java web高级编程 笔记1
chapter1:了解web应用程序 web应用程序主要组件: Servlet 过滤器 监听器 JSP chapter2:各类web容器介绍 略 chapter3:Servlet介绍 Servlet是 ...
- java web学习总结(四) -------------------HTTP协议
一.什么是HTTP协议 HTTP是hypertext transfer protocol(超文本传输协议)的简写,它是TCP/IP协议的一个应用层协议,用于定义WEB浏览器与WEB服务器之间交换数据的 ...
- C++面向对象高级编程(四)基础篇
技术在于交流.沟通,转载请注明出处并保持作品的完整性. 一.Static 二.模板类和模板函数 三.namespace 一.Static 静态成员是“类级别”的,也就是它和类的地位等同,而普通成员是“ ...
- java web开发入门四(spring)基于intellig idea
spring 1.spring简介 Spring框架,可以解决对象创建以及对象之间依赖关系的一种框架. 且可以和其他框架一起使用:Spring与Struts, Spring与hibernate (起 ...
- java web 学习十四(JSP原理)
一.什么是JSP? JSP全称是Java Server Pages,它和servle技术一样,都是SUN公司定义的一种用于开发动态web资源的技术. JSP这门技术的最大的特点在于,写jsp就像在写h ...
- Java web切面编程
在我们的 web开发中 我们在 对公用的 一些方法 我们需要抽取出来 这样达到 代码的冗余 今天 我利用项目上用的AOP的 实际 应用做了一个整理 首先 xml配置 扫描 <?xm ...
随机推荐
- CF Round#436 div2
额,这次的题目其实挺智障的.所以通过这次比赛,我也发现了自己是一个智障.... 不说太多,说多是泪... A. Fair Game 题意:给你一个数组,看你能否把它均分为两个所有元素均相同的子数组. ...
- window.open()被拦截问题
最近做项目的时候遇到一个需求,在商品详情页面中点击购买按钮,之后再新标签页中打开生成的订单页面,所以想用window.open()来实现.但是测试的时候发现打开的链接被浏览器拦截. 之后,开始在网上查 ...
- arguments.callee的临时指向特性
function r(){ alert('BBB'); } var a = { f: function(){ alert('AAA'); arguments.callee = r; } }; 弹出的都 ...
- 王立平--WebView的缓存机制
WebView的缓存能够分为页面缓存和数据缓存. 1. 页面缓存是指载入一个网页时的html.JS.CSS等页面或者资源数据. 这些缓存资源是因为浏览器的行为而产生.开发人员仅仅能通过配置HTTP ...
- LDA主题模型学习笔记5:C源代码理解
1.说明 本文对LDA原始论文的作者所提供的C代码中LDA的主要逻辑部分做凝视,原代码可在这里下载到:https://github.com/Blei-Lab/lda-c 这份代码实现论文<Lat ...
- Android从无知到有知——NO.1
如期而至的软件设计大赛吹响了重生的号角.正如同我们的指导老师所说,这个暑假会影响你近几年的发展,也可能会决定你以后所走的道路. 是的.我身边就有非常好的样例,有些师哥师姐们常常跟我们说.软件大赛不仅使 ...
- java注解细节
现在很多框架都使用注解了,典型的像Spring里面就可以看到大量的注解,比如@Service,@Controller这一类的都是注解.注解Annotation是java一项很重要的功能.下面就来整理一 ...
- Elasticsearch短语搜索——match_phrase
找出一个属性中的独立单词是没有问题的,但有时候想要精确匹配一系列单词或者短语 . 比如, 我们想执行这样一个查询,仅匹配同时包含 "rock" 和 "climbing&q ...
- 关于python的itertools模块
这是一个强大的模块 先来看一下它都有什么工具 无穷循环器 迭代器 参数 结果 ...
- java获取当前应用的运行信息(内存,线程,运行时间,状态等)
一:目的 写这一段程序的原因是需要监控部署的的应用是否正常运行,并且显示其运行状态.在进程莫名死掉后甚至可以自动启动该应用. 首先这段代码可以获取的信息如下 /** * 当前进程运行的主机名 */ p ...