前言
最近在学习使用websocket,故写篇博客记录使用方法。刚学到这,日后持续研究相关知识点及项目实际应用场景并对此博客持续更新,若有错误或改进之处还请看客指出更正。
知识点
1、websocket是一个基于TCP连接上进行全双工通讯的协议;服务端/客户端都可主动推送信息给另一端;
2、websocket的连接由客户端发起一个 HTTP 请求,服务器端解析后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。
3、客户端、服务端都有以下事件:
1)onOpen – 建立连接时触发;
2)onMessage – 收到消息时触发;
3)onClose – 连接关闭时触发;
4)onError – 抛出异常时触发。
4、客户端、服务端建立连接后会保持一定时间的连接,若长时间没有信息发送时,连接会断开关闭,因此应该添加心跳机制维护连接。
5、心跳机制由客户端每隔一段时间向服务器发送一个数据包,告诉服务器自己还活着,维持两者间的连接
案例说明
利用springboot环境搭建websocket的服务端与客户端环境,在客户端与服务端连接:
1、客户端循环发送5条数据给服务端,同事开启一个心跳包线程,检测信息发送时间间隔,超过5秒则由客户端发送心跳包给服务端保持两者的连接;
2、服务端接收信息后打印信息,并同时返回一条信息给客户端。
3、客户端程序连接由一个controller的方法中执行,访问方法则开启相应的连接。
服务端
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2、添加springboot的websocket支持
编写WebsocketConfiguration 这个类,开启websocket的支持
@Configuration
public class WebsocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
3、websocket服务端类
package com.gjx.server.webserver.server;
import org.springframework.web.bind.annotation.RestController;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Author gjx
* @email 13450753745@163.com
* @description websocket的服务端类
* @Date 2019/10/14
*/
@ServerEndpoint("/serverTest")
@RestController
public class WebSocketServer {
/**
*存放所有的在线客户端
* */
private static Map<String, Session> clients = new ConcurrentHashMap<>();
/**
* 客户端与服务端连接时触发执行事件
*/
@OnOpen
public void onOpen(Session session) throws InterruptedException, IOException {
System.out.println("有新的客户端连接进来了");
clients.put(session.getId(), session);
}
/**
* 向客户端发送字符串信息
*/
private static void sendMsg(Session session, String msg) throws IOException {
session.getBasicRemote().sendText(msg);
}
/**
*接收到消息后的处理方式,其中包含客户端的普通信息和心跳包信息,
* 简单区别处理
*/
@OnMessage
public void onMessage(Session session, String msg) throws Exception {
if (!msg.equals("keepalive")){
System.out.println("服务端收到消息:" + msg);
Thread.sleep(3000L);
sendMsg(session,msg);
}else{
System.out.println("心跳维护包:" + msg);
}
}
@OnClose
public void onClose(Session session) {
System.out.println("有客户端断开连接了,断开客户为:" + session.getId());
clients.remove(session.getId());
}
@OnError
public void onError(Throwable throwable) {
System.out.println("服务端出现错误");
throwable.printStackTrace();
}
}
客户端
1、导入依赖
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.3.8</version>
</dependency>
2、客户端的事件类Client
package com.gjx.werclient.client.webcllient;
import javax.websocket.*;
import java.io.IOException;
/**
* @Author gjx
* @email 13450753745@163.com
* @description
* @Date 2019/10/14
*/
@ClientEndpoint()
public class Client {
@OnOpen
public void onOpen(Session session) {
}
@OnMessage
public void onMessage(Session session,String message) throws Exception {
System.out.println("Client onMessage: " + message);
}
@OnClose
public void onClose() {}
private static void sendMsg(Session session,String msg) throws IOException {
session.getBasicRemote().sendText(msg);
}
}
3、Main类
package com.gjx.werclient.client.webcllient;
import javax.websocket.*;
import java.io.IOException;
import java.net.URI;
/**
* @Author gjx
* @email 13450753745@163.com
* @description
* @Date 2019/10/14
*/
public class Main {
/**
*服务器地址
*/
private static String uri = "ws://localhost:8088/serverTest";
private static Session session;
/**
*消息发送事件
*/
private static long date;
/**
*连接状态
*/
private boolean running=false;
private void start() {
WebSocketContainer container = null;
try {
container = ContainerProvider.getWebSocketContainer();
} catch (Exception ex) {
System.out.println("error" + ex);
}
try {
URI r = URI.create(uri);
session = container.connectToServer(Client.class, r);
} catch (DeploymentException | IOException e) {
e.printStackTrace();
}
}
public static void aciton() {
Main client = new Main();
client.start();
new Thread(client.new KeepAlive()).start();
String input = "";
try {
for (int i = 0; i < 5; i++) {
/**
*注意:此处对session做了同步处理,
* 因为下文中发送心跳包也是用的此session,
* 不用synchronized做同步处理会报
* Exception in thread "Thread-5" java.lang.IllegalStateException: The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for called method
* 错误
*/
synchronized (session){
Main.session.getBasicRemote().sendText("javaclient");
}
date = System.currentTimeMillis();
Thread.sleep(3000L);
}
} catch (Exception e) {
System.out.println("客户端出错");
e.printStackTrace();
}
}
/**
*内部类,用来客户端给服务单发送心跳包维持连接
*/
class KeepAlive implements Runnable{
@Override
public void run() {
while (true){
if (System.currentTimeMillis()-date>5000L){
try {
System.out.println("发送心跳包");
synchronized (session){
Main.session.getBasicRemote().sendText("keepalive");
}
date = System.currentTimeMillis();
} catch (IOException e) {
System.out.println("維持心跳包出錯");
e.printStackTrace();
}
}else {
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
}
}
}
}
public KeepAlive() {
}
}
}
4、编写访问的controller
package com.gjx.werclient.client.controller;
import com.gjx.werclient.client.webcllient.Main;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author gjx
* @email 13450753745@163.com
* @description
* @Date 2019/10/14
*/
@RestController
@RequestMapping("/socket")
public class SocketController {
//访问此方法建立websocket连接。
@RequestMapping("/action")
public void actionSocket(){
Main.aciton();
}
@RequestMapping("/test")
public void actionTest(){
System.out.println("測試聯通性");
}
}
结果
服务端的程序和客户端的程序开启后,访问http://localhost:8088/socket/action,客户端打印如下
客户端建立连接后循环发送了5条信息给服务端,服务端收到每条信息后返回一条信息给客户端并在客户端打印,与此同时程序开启了一个心跳包线程,检测客户端最后一次发送信息的时间,超过5秒则发送心跳包维持连接。
服务端打印如下: