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