HTML5 WebSocket
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
以下 API 用于创建 WebSocket 对象。
var Socket = new WebSocket(url, [protocol] );
Javax.websocket.server包含注解,类,接口用于创建和配置服务端点
Javax.websocket包则包含服务端点和客户端点公用的注解,类,接口,异常
创建一个编程式的端点,需要继承Endpoint类,重写它的方法。
创建一个注解式的端点,将自己的写的类以及类中的一些方法用前面提到的包中的注解装饰(@EndPoint,@OnOpen等等)。
@ServerEndpoint注解的类被注解的类将被注册成为一个WebSocket端点,所有的配置项都在这个注解的属性中 ( 如:@ServerEndpoint(“/ws”)
有打了@ServerEndpoint注解的,都会被ServerEndpointExporter发现并注册
备注:如果使用war包部署,就不必做此步骤,因为它将由容器自己提供和管理
@Configuration
@ConditionalOnWebApplication
public class WebSocketAutoConfig {
@Bean
public ServerEndpointExporter endpointExporter() {
return new ServerEndpointExporter();
}
@Bean
public MySpringConfigurator mySpringConfigurator() {
return new MySpringConfigurator();
}
}
@OnOpen
当有新的WebSocket连接进入时,对该方法进行回调
注入参数的类型:
1. Session
2. HttpHeaders
3.ParameterMap
@OnClose
当有WebSocket连接关闭时,对该方法进行回调 注入参数的类型
1.Session
@OnError
当有WebSocket抛出异常时,对该方法进行回调
注入参数的类型:
1.Session
2.Throwable
@OnMessage
当接收到字符串消息时,对该方法进行回调
注入参数的类型:
1.Session
2.String
@OnBinary
当接收到二进制消息时,对该方法进行回调
注入参数的类型:
1.Session
2.byte[]
@OnEvent
当接收到Netty的事件时,对该方法进行回调
注入参数的类型:
1.Session
2.Object
调用
webSocketClient.sendMessage(socketMessageReq);
/**
* 向客户端发送消息
* @param socketMessageReq
*/
@PostMapping(value = "send")
public void sendMessage(@RequestBody SocketMessageReq socketMessageReq){
log.info("向客户端发送消息,SocketController.sendMessage, socketMessageReq : {}",socketMessageReq);
if(socketMessageReq.getAppType() == null){
log.info("sendMessage appType is null");
return;
}
StringBuffer sessionKey = new StringBuffer().append(socketMessageReq.getAppType()).append(":");
if(socketMessageReq.getEnterpriseId() != null && !StringUtils.isEmpty(socketMessageReq.getUserCode())){
sessionKey.append(socketMessageReq.getEnterpriseId()).append(":").append(socketMessageReq.getUserCode());
}else if(socketMessageReq.getEnterpriseId() != null && StringUtils.isEmpty(socketMessageReq.getUserCode())){
sessionKey.append(socketMessageReq.getEnterpriseId());
}else if(socketMessageReq.getEnterpriseId() == null && !StringUtils.isEmpty(socketMessageReq.getUserCode())){
sessionKey.append(socketMessageReq.getUserCode());
}
Session session = NewlinkWebSocket.sessionMap.get(sessionKey.toString());
log.info("向客户端发送消息 key为:{},-- session为 : {}",sessionKey,session);
if(session != null && session.isOpen()) {
session.sendText(socketMessageReq.getMessage());
}
}
@ServerEndpoint(path = "/ws/{arg}" )
@Slf4j
public class NewlinkWebSocket {
public static Map<String, Session> sessionMap = new ConcurrentHashMap<>();
@BeforeHandshake
public void handshake(Session session, HttpHeaders headers, @RequestParam Integer enterpriseId, @RequestParam String userCode, @RequestParam Integer appType, @RequestParam MultiValueMap reqMap, @PathVariable String arg, @PathVariable Map pathMap) {
session.setSubprotocols("stomp");
if (appType == null || (enterpriseId == null && StringUtils.isEmpty(userCode))) {
session.sendText("{\n" +
"\n" +
" \"code\": \"320\",\n" +
" \"message\" : \"连接失败!\"\n" +
" \"data\" : \n" +
"}");
session.close();
}
StringBuffer sessionKey = new StringBuffer().append(appType).append(":");
if (enterpriseId != null && !StringUtils.isEmpty(userCode)) {
sessionKey.append(enterpriseId).append(":").append(userCode);
} else if (enterpriseId != null && StringUtils.isEmpty(userCode)) {
sessionKey.append(enterpriseId);
} else if (enterpriseId == null && !StringUtils.isEmpty(userCode)) {
sessionKey.append(userCode);
}
log.info("建立连接成功:key: {}", sessionKey);
if(sessionKey != null ) {
sessionMap.put(sessionKey.toString(), session);
session.setAttribute("key",sessionKey.toString());
}
}
@OnOpen
public void onOpen(Session session, HttpHeaders headers, @RequestParam Integer enterpriseId, @RequestParam String userCode, @RequestParam Integer appType, @RequestParam MultiValueMap reqMap, @PathVariable String arg, @PathVariable Map pathMap) {
if(appType == null){
session.sendText("failed");
session.close();
}
log.info("new connection,session size: {}", sessionMap.size());
}
@OnClose
public void onClose(Session session) throws IOException {
String key = session.getAttribute("key");
log.info(key);
if(!StringUtils.isEmpty(key)) {
sessionMap.remove(key);
}
log.info("one connection closed ,session size: {}", sessionMap.size());
}
@OnError
public void onError(Session session, Throwable throwable) {
throwable.printStackTrace();
}
@OnMessage
public void onMessage(Session session, String message) {
log.info("收到客户端消息:{}",message);
session.sendText("ok");
}
@OnBinary
public void onBinary(Session session, byte[] bytes) throws UnsupportedEncodingException {
for (byte b : bytes) {
System.out.println(b);
}
log.info(new String(bytes, "utf-8"));
session.sendBinary(bytes);
}
@OnEvent
public void onEvent(Session session, Object evt) {
if (evt instanceof IdleStateEvent) {
IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
switch (idleStateEvent.state()) {
case READER_IDLE:
System.out.println("read idle");
break;
case WRITER_IDLE:
System.out.println("write idle");
break;
case ALL_IDLE:
System.out.println("all idle");
break;
default:
break;
}
}
}
}