WebSocket是一种新的协议,本质上和HTTP一样(握手连接等)。但它并不是在HTTP之上模拟推送,而是直接在TCP之上定义了帧Frame,实现客户端与服务器间的全双工通讯。
https://en.wikipedia.org/wiki/WebSocket
关于浏览器与服务器间的实时通讯,比较常见的方案是Polling轮询(Ajax)、Long Polling轮询(Comet)。也可以自己写Socket长连接,自定义协议,自己实现封包,拆包以及解决tcp粘包等问题。这些方案都比较麻烦。
Polling
|
Comet
/ \
/ \
SSE WebSocket
Polling、SSE、WebSocket的通讯流程:
WebSocket当初是作为HTML5的一部分,后来经过多次修订后独立出来 成为IETF的RFC 6455。目前大部分浏览器都支持WebSocket。 http://caniuse.com/#feat=websockets
(1)WebSocket组成:
WebSocket Protocol: IETF https://tools.ietf.org/html/rfc6455
WebSocket API: W3C https://www.w3.org/TR/2011/WD-websockets-20110929/
(2)WebSocket的subprotocol:
STOMP (Simple/Streaming Text Oriented Messaging Protocol) http://stomp.github.io/
WAMP (Web Application Messaging Protocol) http://wamp-proto.org/
XMPP (Extensible Messaging and Presence Protocol) https://xmpp.org/
AMQP (Advanced Message Queuing Protocol) https://www.amqp.org/
MQTT (Message Queue Telemetry Transport) http://mqtt.org/
(3)WebSocket特点:
从HTTP到WebScoket协议通过“Upgrade”建立连接
运行端口80/443,所以Proxy和Firewall是友好的 ws:// wss://
HTTP兼容的握手,基于Cookie的认证
并非纯正的TCP Socket,可以传输:UTF-8字符、二进制Frame
大大减少网络流量
(4)服务器端实现:
Java: JavaEE7实现了WebSocket协议(JSR 356)、Jetty
node.js: https://github.com/websockets/ws
https://github.com/socketio/socket.io
PHP: https://github.com/ratchetphp/Ratchet/
Ruby: https://github.com/igrigorik/em-websocket
等
uWebSockets https://github.com/uWebSockets/uWebSockets
Undertow http://undertow.io/index.html
(5)WebSocket API:
①JavaScript API
// Create a socket instance
var socket = new WebSocket('ws://localhost:8080');
// Open the socket
socket.onopen = function(event) {
// Send an initial message
socket.send('I am the client and I\'m listening!');
// Listen for messages
socket.onmessage = function(event) {
console.log('Client received a message',event);
};
// Listen for socket closes
socket.onclose = function(event) {
console.log('Client notified socket has closed',event);
};
// To close the socket....
//socket.close()
};
客户端兼容性测试:
if( window.WebSocket ){
// supported
}else{
// not supported
}
②Events:
ws.onopen = function(e) { };
ws.onmessage = function(e) { };
ws.onerror = function(e) { };
ws.onclose = function(e) { };
ws.addEventListener('message', onMessageHandler);
ws.addEventListener('open', onOpenHandler);
ws.addEventListener('close', onCloseHandler);
***on<eventname> 或 addEventListener() 都可以。
③Methods:
ws.send(data);
// Sending String
ws.send('your message');
// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
ws.send(binary.buffer);
// Sending file as Blob (ws.binaryType = 'arraybuffer';)
var file = document.querySelector('input[type="file"]').files[0];
ws.send(file);
ws.close();
ws.close(1000, "Goodbye, World!"); // pass a code and a reason
④Attributes:
ws.readyState
ws.bufferedAmount
ws.protocol
(6)node.js的简单测试
server.js
var WebSocketServer = require('ws').Server,
wss = new WebSocketServer({port: 8181});
wss.on('connection', function(ws) {
console.log('client connected');
ws.on('message', function(message) {
console.log(message);
});
});
client.html
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Echo Demo</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script>
var ws = new WebSocket("ws://localhost:8181"); // wss:// (if using TLS)
ws.onopen = function(e) {
console.log('Connection to server opened');
};
ws.onerror = function (error) {
console.log('WebSocket Error ' + error);
};
ws.onmessage = function (e) {
console.log('Server: ' + e.data);
};
function sendMessage() {
ws.send($('#message').val());
}
</script>
</head>
<body>
<div class="vertical-center">
<div class="container">
<p> </p>
<form role="form" id="chat_form" onsubmit="sendMessage(); return false;">
<div class="form-group">
<input class="form-control" type="text" name="message" id="message" placeholder="Type text to echo in here" value="" autofocus/>
</div>
<button type="button" id="send" class="btn btn-primary" onclick="sendMessage();">Send!</button>
</form>
</div>
</div>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
</body>
</html>
HTTP Headers
Handshake Request
Handshake Response
101 Switching Protocols
upgraded HTTP request
(7)Java测试 - Jetty WebSocket Example
@WebServlet(urlPatterns="/test")
public class WebSocketServletImpl extends WebSocketServlet {
@Override
public void configure(WebSocketServletFactory factory) {
factory.register(WebSocketSample.class);
}
}
@WebSocket
public class WebSocketSample {
@OnWebSocketConnect
public void onConnect(Session session) {
System.out.println(session.getRemoteAddress().getHostString() + " connected!");
}
@OnWebSocketMessage
public void onText(String message) {
System.out.println("Message received:" + message);
if (session.isOpen()) {
session.getRemote().sendString(message + " is received.");
}
}
@OnWebSocketClose
public void onClose(int statusCode, String reason) {
WSystem.out.println(session.getRemoteAddress().getHostString() + " closed!");
}
}
(8)Java测试 - JavaEE7 WebSocket Example
@ServerEndpoint("/ws/sample")
public class SampleEndpoint {
@OnOpen
public void onOpen(Session session) {
System.out.println("New connection with client: {0}" + session.getId());
}
@OnMessage
public String onMessage(String message, Session session) {
System.out.println("New message from Client [{0}]: {1}" + new Object[] {session.getId(), message});
return "Server received [" + message + "]";
}
@OnClose
public void onClose(Session session) {
System.out.println("Close connection for client: {0}" + session.getId());
}
@OnError
public void onError(Throwable exception, Session session) {
System.out.println("Error for client: {0}" + session.getId());
}
}
Java WebSocket API : JSR 356
(9)Java测试 - Spring WebSocket Example
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat").withSockJS();
}
}
@MessageMapping("/chat")
@SendTo("/topic/messages")
public OutputMessage send(Message message) throws Exception {
String time = new SimpleDateFormat("HH:mm").format(new Date());
return new OutputMessage(message.getFrom(), message.getText(), time);
}
(10)Java测试 - Spring Boot WebSocket Example
pom.xml
<!-- use websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
@Component
public class EchoHandler extends TextWebSocketHandler {
private Map<String, WebSocketSession> sessionPool = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
this.sessionPool.put(session.getId(), session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
this.sessionPool.remove(session.getId());
}
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
for (Entry<String, WebSocketSession> entry : this.sessionPool.entrySet()) {
entry.getValue().sendMessage(message);
}
}
}
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private EchoHandler echoHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(echoHandler, "/echo");
}
}
(11)第三方API
Sockjs https://github.com/sockjs
STOMP.js https://github.com/jmesnil/stomp-websocket
服务器端:
@Configuration
@EnableWebSocketMessageBroker
public class AppWebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/calcApp");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/add").withSockJS();
}
}
@Controller
public class WebSocketController {
@MessageMapping("/add" )
@SendTo("/topic/showResult")
public Result addNum(CalcInput input) throws Exception {
Thread.sleep(2000);
Result result = new Result(input.getNum1()+"+"+input.getNum2()+"="+(input.getNum1()+input.getNum2()));
return result;
}
}
客户端:
var socket = new SockJS('/Spring4WebSocket/add');
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/showResult', function(calResult){
console.log(calResult.body);
});
});
(12)问题
LoadBalancer
Web Filtering
参考:
https://hpbn.co/websocket/
http://www.ibm.com/developerworks/cn/java/j-lo-WebSocket/
https://www.webcodegeeks.com/html5/html5-websocket-example/
http://qiita.com/yuba/items/00fc1892b296fb7b8de9
http://www.slideshare.net/ffdead/the-html5-websocket-api