深入2浅出boot2.0 第13章4 WebSocket

websocket应用

  • 基于TCP的一种新的 网络协议
  • 浏览器 与 服务器 全双工 full-duplex , 通信
    • 允许服务端 主动 发送 信息给客户端
  • 为了兼容那些没有实现 该协议的浏览器,还需要 通过 STOMP协议来 完成这写兼容

加入pom依赖

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>
  • security ,因为有时候对于 webSocket而言,需要 点对点的通信,需要用户登录

简易的WebSocket服务

自定义websocket服务端点 配置

  • ServerEndpointExporter,定义webSocket服务器的端点(供客户端请求)
@Configuration
public class WebSocketConfig {
    // 如果你使用的不是Spring Boot依赖的服务器,才需要自己创建
	@Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

定义WebSocket服务端站点

  • @ServerEndpoint 定义端点服务类

  • 定义WebSocket的打开,关闭,错误,发送消息

    @ServerEndpoint("/ws") //创建服务端点 地址为/ws
    @Service
    public class WebSocketServiceImpl {
        //每一个客户端打开,都会创建WebSocketServiceImpl对象, 下面是计数 将这个对象保存到 CopyOnWriteArraySet 中
        
        //关闭是 清楚这个对象 ,并且 计数 减一
        
        //消息发送, 通过轮询所有的客户端,都发送消息
        //只发送特定的用户,则需要得到用户信息,然后在发送
        
        // 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
        private static int onlineCount = 0;
        
        // concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServiceImpl对象。
        private static CopyOnWriteArraySet<WebSocketServiceImpl> 
                webSocketSet = new CopyOnWriteArraySet<>();
        
        // 与某个客户端的连接会话,需要通过它来给客户端发送数据
        private Session session;
        
        /**
         * 连接建立成功调用的方法。标注客户端打开websocket服务端点调用方法*/
        @OnOpen
        public void onOpen(Session session) {
            this.session = session;
            webSocketSet.add(this);     // 加入set中
            addOnlineCount();           // 在线数加1
            System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
            try {
                sendMessage("有新的连接加入了!!");
            } catch (IOException e) {
                System.out.println("IO异常");
            }
        }
    
        /**
         * 连接关闭调用的方法。标注客户端关闭websocket服务端点调用方法
         */
        @OnClose
        public void onClose() {
            webSocketSet.remove(this);  // 从set中删除
            subOnlineCount();           // 在线数减1
            System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
        }
    
        /**
         * 收到客户端消息后调用的方法
         * @param message 客户端发送过来的消息
         */
        @OnMessage
        public void onMessage(String message, Session session) {
            System.out.println("来自客户端的消息:" + message);
    
            // 群发消息
            for (WebSocketServiceImpl item : webSocketSet) {
                try {
                    /*
                    // 获取当前用户名称
                    String userName = item.getSession()
                            .getUserPrincipal().getName();
                    System.out.println(userName);
                    */
                    item.sendMessage(message); 
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 发生错误时调用  客户端 请求服务端 发生异常调用
         */
        @OnError
        public void onError(Session session, Throwable error) {
            System.out.println("发生错误");
            error.printStackTrace();
        }
    
    
        /**
         * 发送消息
         * @param message 客户端消息
         * @throws IOException
         */
        private void sendMessage(String message) throws IOException {
            this.session.getBasicRemote().sendText(message);
    }
        
    	// 返回在线数
        private static synchronized int getOnlineCount() {
            return onlineCount;
        }
    
    	// 当连接人数增加时
        private static synchronized void addOnlineCount() {
            WebSocketServiceImpl.onlineCount++;
        }
    
    	// 当连接人数减少时
        private static synchronized void subOnlineCount() {
            WebSocketServiceImpl.onlineCount--;
        }
    }
    
  • this.session.getBasicRemote().sendText(message); 发送消息

  • @ServerEndpoint("/ws") //创建服务端点 地址为/ws

开发websocket 页面

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>My WebSocket</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="./../js/websocket.js"></script>
</head>
<body>
    测试一下WebSocket站点吧
    <br />
    <input id="message" type="text" />
    <button οnclick="sendMessage()">发送消息</button>
    <button οnclick="closeWebSocket()">关闭WebSocket连接</button>
    <div id="context"></div>
</body>
</html>
var websocket = null;
// 判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
	// 创建WebSocket对象,连接服务器端点
	websocket = new WebSocket("ws://localhost:8080/ws");
} else {
	alert('Not support websocket')
}

// 连接发生错误的回调方法
websocket.onerror = function() {
	appendMessage("error");
};

// 连接成功建立的回调方法
websocket.onopen = function(event) {
	appendMessage("open");
}

// 接收到消息的回调方法
websocket.onmessage = function(event) {
	appendMessage(event.data);
}

// 连接关闭的回调方法
websocket.onclose = function() {
	appendMessage("close");
}

// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,
// 防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function() {
	websocket.close();
}

// 将消息显示在网页上
function appendMessage(message) {
	var context = $("#context").html() +"<br/>" + message;
	$("#context").html(context);
}

// 关闭连接
function closeWebSocket() {
	websocket.close();
}

// 发送消息
function sendMessage() {
	var message = $("#message").val();
	websocket.send(message);
}
  • new WebSocket(“ws://localhost:8080/ws”);

控制器

@Controller
@RequestMapping("/websocket")
public class WebSocketController {
    // 跳转websocket页面
    @GetMapping("/index")
    public String websocket() {
        return "websocket";
    }
}

使用STOMP

  • 旧的版本浏览器 不能支持 webSocket协议,可以引用 WebSocket协议的子协议 STOMP simple or Streaming Text Orientated Messageing Protocol
  • 配置文件要加入 @EnableWebSocket MessageBroker (就会启动websocket下的子协议 stomp)
  • 配置stomp 实现 WebSocket MessageBroker Configurer
    • 为了更加简单 还提供了抽象类 Abstract WebSocket MessageBroker Configurer

配置STOMP的服务端点 和 请求订阅前缀


@Configuration
@EnableWebSocketMessageBroker //启用STOMP协议
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    // 如果你使用的不是Spring Boot依赖的服务器,才需要自己创建
	@Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
	
	// 注册服务器端点
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 增加一个聊天服务端点
        registry.addEndpoint("/socket").withSockJS();//也可以支持sockJS
        // 增加一个用户服务端点
        registry.addEndpoint("/wsuser").withSockJS();
    }

    // 定义服务器端点请求和订阅前缀
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 客户端订阅路径前缀
        registry.enableSimpleBroker("/sub", "/queue");
        // 服务端点请求前缀
        registry.setApplicationDestinationPrefixes("/request");
    }
}
  • sockJS 是一个 第三方关于 支持 WebSocket请求的 JavaScript框架
  • boot会创建 SimpMessaging Template对象

STOMP下的 控制器

@Controller
@RequestMapping("/websocket")
public class WebSocketController {
    
    @Autowired // 注入Spring Boot自动配置消息模板对象
    private SimpMessagingTemplate simpMessagingTemplate;
    
    // 发送页面
    @GetMapping("/send")
    public String send() {
        return "send";
    }
    
    // 接收页面
    @GetMapping("/receive")
    public String receive() {
        return "receive";
    }
    
    // 对特定用户发送页面
    @GetMapping("/sendUser")
    public String sendUser() {
        return "send-user";
    }
    
    // 接收用户消息页面
    @GetMapping("/receiveUser")
    public String receiveUser() {
        return "receive-user";
    }
    
    
    
    
    
    // 定义消息请求路径
    @MessageMapping("/send")
    // 定义结果发送到特定路径
    @SendTo("/sub/chat")
    public String sendMsg(String value) {
         return value;
    }
    
    // 将消息发送给特定用户
    @MessageMapping("/sendUser")
    public void sendToUser(Principal principal, String body) {
        String srcUser = principal.getName();
        // 解析用户和消息
        String []args = body.split(",");
        String desUser = args[0];
        String message = "【" + srcUser + "】给你发来消息:" + args[1];
        // 发送到用户和监听地址
        simpMessagingTemplate.convertAndSendToUser(desUser, 
            "/queue/customer", message);    
    }
}
  • @MessageMapping("/send") 定义消息请求路径

    • 与 registry.setApplicationDestinationPrefixes("/request") 连用
  • @SendTo("/sub/chat") 在执行完 这个方法后,将返回结果发送到订阅的这个目的址中

    • 这样客户端就可以 得到消息
  • principal 获得当前用户的消息

  • simpMessagingTemplate.convertAndSendToUser(desUser, “/queue/customer”, message);

    • 发送给对应的目的地,并且限定特定的用户消息

配置 Security


@SpringBootApplication(scanBasePackages = "com.springboot.chapter13")
@EnableScheduling
public class Chapter13Application extends WebSecurityConfigurerAdapter {

	public static void main(String[] args) {
		SpringApplication.run(Chapter13Application.class, args);
	}

    // 定义3个可以登录的内存用户
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 密码加密器
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        // 加入三个内存用户,密码分别为加密后的"p1","p2"和"p3"
		// 可以通过 passwordEncoder.encode("p1")这样获得加密后的密码
        auth.inMemoryAuthentication().passwordEncoder(passwordEncoder)
            .withUser("user1")
            .password("$2a$10$7njFQKL2WV862XP6Hlyly.F0lkSHtOOQyQ/rlY7Ok26h.gGZD4IqG").roles("USER").and()
            .withUser("user2").password("$2a$10$Q2PwvWNpog5sZX583LuQfet.y1rfPMsqtrb7IjmvRn7Ew/wNUjVwS")
            .roles("ADMIN").and().withUser("user3")
            .password("$2a$10$GskYZT.34BdhmEdOlAS8Re7D73RprpGN0NjaiqS2Ud8XdcBcJck4u").roles("USER");
    }
}

jsp

send.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>My WebSocket</title>
<script type="text/javascript" 
    src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script type="text/javascript" 
    src="https://cdn.jsdelivr.net/sockjs/1/sockjs.min.js"></script> 
<!--
stomp.min.js的下载地址:
https://raw.githubusercontent.com/jmesnil/stomp-websocket/master/lib/stomp.min.js
该地址设定为文本,所以不能直接载入,需要自行先下载,再使用
-->
<script type="text/javascript" src="./../js/stomp.min.js"></script>
</head>
    
<script type="text/javascript">
	var stompClient = null;
     // 设置连接
	function setConnected(connected) {
		$("#connect").attr({"disabled": connected});
		$("#disconnect").attr({"disabled": !connected});
		
		if (connected) {
		    $("#conversationDiv").show();
		} else {
			$("#conversationDiv").hide();
		}
		$("#response").html("");
	} 
	
	// 开启socket连接 
	function connect() {
          // 定义请求服务器的端点
		var socket = new SockJS('/socket');
          // stomp客户端
		stompClient = Stomp.over(socket);
          // 连接服务器端点
		stompClient.connect({}, function(frame) {
               // 建立连接后的回调
			setConnected(true);
		});
	}
	// 断开socket连接
	function disconnect() {
		if (stompClient != null) {
			stompClient.disconnect();
		}
		setConnected(false);
		console.log("Disconnected");
	}
	// 向‘/request/send’服务端发送消息
	function sendMsg() {
		var value = $("#message").val();
          // 发送消息到"/request/send",其中/request是服务器定义的前缀,
// 而/send则是@MessageMapping所配置的路径
		stompClient.send("/request/send", {}, value);
	}
	connect();
</script>

<body>
	<div>
		<div>
			<button id="connect" οnclick="connect();">连接</button>
			<button id="disconnect" disabled="disabled" 
			οnclick="disconnect();">断开连接</button>
		</div>
		<div id="conversationDiv">
			<p>
				<label>发送的内容</label>
			</p>
			<p>
				<textarea id="message" rows="5"></textarea>
			</p>
			<button id="sendMsg" οnclick="sendMsg();">Send</button>
			<p id="response"></p>
		</div>
	</div>
</body>
</html>
  • 加入了socket.min.js 和 stomp.min.js
receive.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>My WebSocket</title>
<script type="text/javascript"
    src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script type="text/javascript"
    src="https://cdn.jsdelivr.net/sockjs/1/sockjs.min.js"></script>
<script type="text/javascript" src="./js/stomp.min.js"></script>
</head>
    
    
<script type="text/javascript">
    var noticeSocket = function() {
        // 连接服务器端点
        var s = new SockJS('/socket');
        // 客户端
        var stompClient = Stomp.over(s);
        stompClient.connect({}, function() {
            console.log('notice socket connected!');
            // 订阅消息地址
            stompClient.subscribe('/sub/chat', function(data) {
                $('#receive').html(data.body);
            });
        });
    };
    noticeSocket();
</script>
    
    
<body>
<h1><span id="receive">等待接收消息</span></h1>
</body>
</html>
说明
  		// 客户端订阅路径前缀
        registry.enableSimpleBroker("/sub");
        // 服务端点请求前缀
        registry.setApplicationDestinationPrefixes("/request");
        
        // 增加一个聊天服务端点
        registry.addEndpoint("/socket").withSockJS();

        
    // 定义消息请求路径
    @MessageMapping("/send")
    // 定义结果发送到特定路径
    @SendTo("/sub/chat")

发送消息:
首先是创建了:new SockJS('/socket'); 路径
发送消息的路径:stompClient.send("/request/send", {}, value);


接收消息时:
var s = new SockJS('/socket');
 stompClient.subscribe('/sub/chat', function(data) {
       $('#receive').html(data.body);
});
send-user.jsp


<script type="text/javascript">
    var stompClient = null;
    // 重置连接状态页面
    function setConnected(connected) {
        $("#connect").attr({"disabled": connected});
        $("#disconnect").attr({"disabled": !connected});
        
        if (connected) {
            $("#conversationDiv").show();
        } else {
            $("#conversationDiv").hide();
        }
        $("#response").html("");
    } 
    
    // 开启socket连接 
function connect() {
    // 连接/wsuser服务端点
        var socket = new SockJS('/wsuser');
        // stomp客户端
        stompClient = Stomp.over(socket);
        stompClient.connect({}, function(frame) {
            setConnected(true);
        });
    }
    // 断开socket连接
    function disconnect() {
        if (stompClient != null) {
            stompClient.disconnect();
        }
        setConnected(false);
        console.log("Disconnected");
    }
    // 向‘/request/sendUser’服务端发送消息
    function sendMsg() {
        var value = $("#message").val();
        var user = $("#user").val();
        // 用户和消息组成的字符串
        var text = user +"," + value;
        stompClient.send("/request/sendUser", {}, text);
    }
    connect();
</script>
<body>
    <div>
        <div>
            <button id="connect" οnclick="connect();">连接</button>
            <button id="disconnect" disabled="disabled" οnclick="disconnect();">断开连接</button>
        </div>
        <div id="conversationDiv">
            <p><label>发送给用户</label></p>
            <p><input type="text" id="user"/></p>
            <p><label>发送的内容</label></p>
            <p><textarea id="message" rows="5"></textarea></p>
            <button id="sendMsg" οnclick="sendMsg();">发送</button>
            <p id="response"></p>
        </div>
    </div>
</body>
</html>

receive-user.jsp

<script type="text/javascript">
	var noticeSocket = function() {
		var s = new SockJS('/wsuser');
		var stompClient = Stomp.over(s);
		stompClient.connect({}, function() {
			console.log('notice socket connected!');
			stompClient.subscribe('/user/queue/customer', function(data) {
				$('#receive').html(data.body);
			});
		});
	};
	noticeSocket();
</script>
<body>
<h1><span id="receive">等待接收消息</span></h1>
</body>
</html>

说明
var socket = new SockJS('/wsuser');
stompClient.connect({}, function(frame) {
            setConnected(true);
 });
stompClient.send("/request/sendUser", {}, text);


    @MessageMapping("/sendUser")
    public void sendToUser(Principal principal, String body) {
        // 发送到用户和监听地址
        simpMessagingTemplate.convertAndSendToUser(desUser, 
            "/queue/customer", message);//发送这个地址,供客户端连接
    }

		var s = new SockJS('/wsuser');
		var stompClient = Stomp.over(s);
		stompClient.connect({}, function() {
			console.log('notice socket connected!');
            
			stompClient.subscribe('/user/queue/customer', function(data) {
				$('#receive').html(data.body);
			});
            
		});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值