WebSocket是一项很酷的新技术,它允许浏览器与服务器之间进行实时双向通信,而几乎没有开销。 我在这里想要做的是,提供一个非常简洁但足够全面的概述,以介绍如何开始使用该技术。 因此,从以下几件事开始:
- 在浏览器和服务器之间打开了一个tcp套接字连接,并且各方可以向另一方发送消息(即,只要有可用,服务器就可以推送数据-无需轮询,长时间轮询,iframe等)。
- 并非所有浏览器都支持它-IE 10是第一个支持它的IE版本,Android仍然存在问题。 幸运的是,如果不支持WebSocket,则可以使用SockJS ,它可以回溯到其他推式仿真。
- 并非所有代理服务器都支持/允许它,因此可能需要再次进行回退
- 适用于游戏,交易应用程序,以及实际上任何需要服务器将数据推送到浏览器的事物
- Java具有标准的API(JSR-356) ,您可以在服务器上使用它来处理WebSocket连接。
- Spring在Java API之上提供了一个 API。 spring支持的好处是它具有对SockJS的服务器端支持,您可以轻松使用依赖注入。 Spring还为消息驱动的体系结构提供了STOMP支持 。 这两篇Spring文章都包含指向我推荐的GitHub示例项目的链接。
在继续一些示例代码之前,这里是套接字的生命周期,包括客户端和服务器(假设上述API之一):
- 浏览器发送带有特殊升级头的HTTP请求,其值是“ websocket”。
- 如果服务器“说” webocket,它将以状态101(交换协议)答复。 从现在开始,我们不再使用HTTP
- 当服务器接受tcp套接字连接时,将调用初始化方法,并在其中传递当前的websocket会话。 每个套接字都有一个唯一的会话ID。
- 每当浏览器向服务器发送消息时,就会在获取会话和消息有效负载的地方调用另一种方法。
- 基于某些有效负载参数,应用程序代码执行几种操作之一。 有效负载格式完全取决于开发人员。 但是,通常,它是一个JSON序列化的对象。
- 每当服务器需要发送消息时,它都需要获取会话对象,并使用它来发送消息。
- 当浏览器关闭连接时,会通知服务器,以便它可以清除与特定会话相关的任何资源。
当前,没有API或框架支持基于注释的路由。 Java API支持基于注释的终结点处理程序,但是它为每个连接URL提供一个类,并且通常您希望在单个连接上执行多个操作。 即,您连接到ws://yourserver.com/game/,然后要传递“ joinGame”,“ leaveGame”消息。 同样,服务器需要发回不止一种消息。 我的实现方式是通过一个枚举,包含所有可能的动作/事件类型,并使用switch构造确定要调用的内容。
因此,我决定为我的算法音乐作曲家制作一个简单的游戏 。 它使用的是Spring API。 这是我在我所工作的公司中所做的相关演示的幻灯片 。 下面是一些示例代码:
@Component
public class GameHandler extends WebSocketHandlerAdapter {private Map players = new ConcurrentHashMap<>();private Map playerGames = new ConcurrentHashMap<>();@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {Player player = new Player(session);players.put(session.getId(), player);}@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {leaveGame(session.getId());
}@Override
protected void handleTextMessage(WebSocketSession session, TextMessage textMessage) throws Exception {try {GameMessage message = getMessage(textMessage); //deserializes the JSON payloadswitch(message.getAction()) { case INITIALIZE: initialize(message, session); break;case JOIN: join(message.getGameId(), message.getPlayerName(), session); break;case LEAVE: leave(session.getId()); break;case START: startGame(message); break;case ANSWER: answer(message, session.getId()); break;}} catch (Exception ex) {logger.error("Exception occurred while handling message", ex);}
}
让我们看一个示例服务,服务器需要在其中向客户端发送消息。 让我们以一个玩家加入游戏为例,而所有其他玩家都需要收到新的通知。 系统中的核心类是Game,其中包含玩家列表,并且如您所见,Player包含对WebSocket会话的引用。 因此,当玩家加入游戏时,将调用以下Game方法:
public boolean playerJoined(Player player) {for (Player otherPlayer : players.values()) {otherPlayer.playerJoined(player);}players.put(player.getSession().getId(), player);return true;
}
然后player.playerJoined(..)通过基础连接发送一条消息,通知浏览器新玩家加入:
public void playerJoined(Player player) {GameEvent event = new GameEvent(GameEventType.PLAYER_JOINED);event.setPlayerId(player.getSession().getId()); event.setPlayerName(player.getName());try {session.sendMessage(new TextMessage(event.toJson()));} catch (IOException e) {new IllegalStateException(e);}}
从服务器向浏览器发送消息也可能由计划的作业触发。
关键是要保留所有已连接浏览器的列表,以便可以将信息发送回去。 该列表可以是一个静态字段,但是对于单例spring bean,则不需要。
现在,两个重要方面–安全性和身份验证。 这是Heroku的一篇不错的文章 ,同时讨论了两者。 如果有任何敏感内容,您应该首选wss(相对于TLS,它是websocket)。 您还应该在两端验证您的输入,并且不应该依赖Origin标头,因为攻击者可能很容易欺骗浏览器。
身份验证可以依赖于HTTP会话cookie,但是显然,有些人更喜欢实现自己的类似于cookie的工作流,以获取短暂的令牌,该令牌可用于执行经过身份验证的操作。
WebSocket使DDD变得自然。 您不再需要使用贫血对象-您的对象具有各自的状态,并且在该状态下执行操作。 与此相关的是,websocket应用程序更易于测试。
这是开发WebSocket应用程序时要记住的一般事项。 请注意,您不必在所有地方都使用WebSocket –我将其仅限于需要“推送”的功能。
总体而言,WebSocket是一项很好的有趣技术,有望淘汰所有hacky推送仿真。
翻译自: https://www.javacodegeeks.com/2013/12/websocket-and-java.html