前言
HTTP 协议有一个缺陷:通信只能由客户端发起
HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。可以替代长轮询,用于对实时性要求比较高的场景:
使用
服务端
SpringBoot中使用WebSocket非常简单:
- pom.xml中加入引用
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
|
- 新建WebSocketConfig配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Configuration @EnableWebSocket public class WebSocketConfig {
/** * 如果直接使用springboot的内置容器,而不是使用独立的servlet容器,就要注入ServerEndpointExporter,外部容器则不需要。 */ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); }
}
|
- 定义接受和处理消息的处理类
注意@ServerEndpoint的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| @Component @ServerEndpoint("/websocket/{username}") public class ChatRoomServerEndpoint {
public static final Map<String, Session> ONLINE_USER_SESSIONS = new ConcurrentHashMap<>();
@OnOpen public void openSession(@PathParam("username") String username, Session session) { ONLINE_USER_SESSIONS.put(username, session); String message = "[" + username + "] 客户端信息!"; sendMessageAll("服务器连接成功!"); sendMessage(session, ""); System.out.println("连接成功" + message); }
@OnMessage public void onMessage(@PathParam("username") String username, String message) { System.out.println("服务器收到:" + "[" + username + "] : " + message); sendMessageAll("我已收到你的消息》》[" + username + "] : " + message); }
@OnClose public void onClose(@PathParam("username") String username, Session session) { //当前的Session 移除 ONLINE_USER_SESSIONS.remove(username); //并且通知其他人当前用户已经断开连接了 sendMessageAll("[" + username + "] 断开连接!"); try { session.close(); } catch (IOException e) { } }
@OnError public void onError(Session session, Throwable throwable) { try { session.close(); } catch (IOException e) { e.printStackTrace(); } }
// 单用户推送 public void sendMessage(Session session, String message) { if (session == null) { return; } final RemoteEndpoint.Basic basic = session.getBasicRemote(); if (basic == null) { return; } try { basic.sendText(message); } catch (IOException e) { System.out.println("sendMessage IOException " + e); } }
// 全用户推送 public void sendMessageAll(String message) { ONLINE_USER_SESSIONS.forEach((sessionId, session) -> sendMessage(session, message)); } }
|
以上就实现了websocket server的集成,后面就是使用不同的技术实现客户端了(html、android等)
客户端
当然可以编写java版的用于测试连接,这里我们使用Java-WebSocket:
- pom.xml文件中添加引用
1 2 3 4 5 6 7
| <!-- https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket --> <dependency> <groupId>org.java-websocket</groupId> <artifactId>Java-WebSocket</artifactId> <version>1.5.1</version> </dependency>
|
- 编写测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| @RunWith(Runner.class) class WebSocketClientTests {
private final Logger log = LoggerFactory.getLogger(this.getClass());
WebSocketClient webSocketClient; @Test void testClient() { try { webSocketClient = new WebSocketClient(new URI("ws://localhost:8080/websocket/test" + System.currentTimeMillis()),new Draft_6455()) { @Override public void onOpen(ServerHandshake handshakedata) { log.info("[websocket] 连接成功"); webSocketClient.send("你好,我是客户端1111"); }
@Override public void onMessage(String message) { log.info("[websocket] 收到消息={}",message);
}
@Override public void onClose(int code, String reason, boolean remote) { log.info("[websocket] 退出连接"); }
@Override public void onError(Exception ex) { log.info("[websocket] 连接错误={}",ex.getMessage()); } }; webSocketClient.connect();
while (!webSocketClient.isClosed()) {
} log.info("[主程序] 退出"); } catch (Exception e) { e.printStackTrace(); } }
}
|
- 运行SpringBoot服务端和测试类,即可看到效果
连接地址说明
ws://localhost:8080/websocket/xxx
客户端需使用websocket的协议ws,由于是用SpringBoot集成的websocket,故url地址与正常的http的类似,后面的websocket/test是取决于@ServerEndpoint的定义(源码里为@ServerEndpoint(“/websocket/{username}”),故客户端可以使用websocket/xxx),不同ServerEndpoint处理不同策略
- 如果SpringBoot的application.yml指定了
1 2 3 4
| server: servlet: context-path: /spring-websocket port: 8888
|
则此时客户端连接的url应改为:
1
| ws://localhost:8888/spring-websocket/websocket/xxx
|
todo