It’s time to teach everyone how to write code, even though no one is watching…
This is the second part of the distributed Systems Hands-on series. Why is this series written? And what was canto I?
These projects are I spent a lot of day and night out of the masturbation, but the reading volume is not high, as expected, hardcore technology text we do not like to see.
But that’s okay, I’m going to stick with it, because it’s valuable and it’s something that really adds value to your resume.
Read carefully and follow me to achieve the partners, I believe you will have a harvest.
Github address: (welcome star)
Websocket is introduced
Defects of the HTTP protocol
- HTTP is a half-duplex protocol. Data can be transmitted in only one direction at a time
- Is stateless, so the client can only poll to find out the status of the server, which is inefficient and wasteful of resources
- HTTP messages are tedious and have low bandwidth utilization
The advantage of websocket
HTTP vs. Websocket
HTML 5 defines the WebSocket protocol, which became an international standard in 2011 and is now supported by most browsers. Its biggest advantage is that the server can actively push information to the client. It is based on TCP for two-way full duplex message transmission, compared with HTTP half duplex performance and efficiency has been greatly improved.
As can be seen from the following figure, HTTP connection needs to be established for each interaction between the client and the server. After the server responds, the connection is disconnected and the connection needs to be established again for the next request, which consumes very high performance. The WebSocket connection is always there after it is established.
Common application scenarios of WebSocket:
- Real-time Web applications. Display real-time data on the server on the client. Such as trading site price information.
- Game applications.
- Chat apps.
The life cycle of webSocket connections
The full life cycle of a Websocket connection can be represented as follows:
- In CONNECTING-, a connection is established
- OPEN- A connection has been established and data can be transferred
- CLOSING- Closes the connection handshake
- CLOSED – has been CLOSED
Handshake phase – Establishes a connection
To establish a Websocket connection between the client and server, the client sends an HTTP handshake request:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: example.com\ Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
“Upgrade: websocket” indicates that this is an HTTP request for a protocol Upgrade. The server parses these additional headers and returns a reply:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
The connection is made.
Data transfer stage
The basic unit of data transmission is a frame, and a message may consist of multiple frames. Frames are divided into data frames and control frames.
- Data frames are used to transfer application data between the client and server. There are three data types: text data, binary data, and the frame used to identify the connection with the previous frame.
- Control frame: used to transmit metadata information about the connection itself: disable frame, ping, pong “heartbeat”.
Format of data frame:
Opcode is used to identify the type of frame:
Use Netty to implement a Websocket chat room
We use Netty to implement a browser-based chat application based on Websocket.
The overall logic is as follows:
Websocket server can accept the connection of multiple clients and provide services.
netty server:
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.*; import io.netty.handler.stream.ChunkedWriteHandler; import lombok.extern.slf4j.Slf4j; /** * websocket server based netty * * @author summer * @version $Id: SimpleWebsocketServer.java, V 0.1 2022年01月26日 9:34 AM Summer Exp $*/ @slf4j public class SimpleWebsocketServer {/** * host */ public final static String host = "127.0.0.1"; /** * port number */ public final static Integer port = 8085; /** * Netty server start method */ public void start() {log.info("SimpleWebsocketServer start begin "); EventLoopGroup bossEventLoopGroup = new NioEventLoopGroup(1); EventLoopGroup workerEventLoopGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap() .group(bossEventLoopGroup, WorkerEventLoopGroup). Channel (NioServerSocketChannel. Class) / / open TCP nagle algorithm. ChildOption (ChannelOption TCP_NODELAY, True) // Enable the long connection. ChildOption (channeloption.so_keepalive, true) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel c) { c.pipeline().addLast(new HttpServerCodec()) .addLast(new HttpObjectAggregator(512 * 1024)) .addLast(new ChunkedWriteHandler()) .addLast(new SimpleWebsocketServerHandler()); }}); ChannelFuture channelFuture = serverBootstrap.bind(host, port).sync(); log.info("SimpleWebsocketServer start at port " + port); channelFuture.channel().closeFuture().sync(); } catch (Exception e) { log.error("SimpleWebsocketServer start exception,", e); } finally { log.info("SimpleWebsocketServer shutdown bossEventLoopGroup&workerEventLoopGroup gracefully"); bossEventLoopGroup.shutdownGracefully(); workerEventLoopGroup.shutdownGracefully(); }}}Copy the code
The actual HTTP request handler:
import com.alibaba.fastjson.JSON; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.*; import io.netty.handler.codec.http.websocketx.*; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; ** @author summer * @version $Id: SimpleWebsocketServerHandler.java, January 26, 2022 v 0.1 9:44 AM summer Exp $* / @ Slf4j public class SimpleWebsocketServerHandler extends SimpleChannelInboundHandler<Object> { /** *websocket shake handler */ private WebSocketServerHandshaker handshaker; @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) { try { log.info("SimpleWebsocketServerHandler receive msg=" + msg); if (msg instanceof FullHttpRequest) { handleHttpShakehandRequest(ctx, (FullHttpRequest)msg); } else if (msg instanceof WebSocketFrame) { handleWebsocketFrame(ctx, (WebSocketFrame)msg); } else { log.error("SimpleWebsocketServerHandler channelRead0,unkown msg"); } } catch (Exception e) { log.error("channelRead0 exception,", e); }} /** * handle the handshake request ** @param CTX * @param fullHttpRequest */ private void handleHttpShakehandRequest(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) { log.info("handleHttpShakehandRequest begin~"); // Check HTTP header validity if (! fullHttpRequest.getDecoderResult().isSuccess() || ! StringUtils.equals("websocket", fullHttpRequest.headers().get("Upgrade"))) { log.warn("handleHttpShakehandRequest fail,fullHttpRequest illegal,fullHttpRequest=" + fullHttpRequest.toString()); return; } / / construct a handshake response returned String webSocketURL = SimpleWebsocketServer. Host + ":" + SimpleWebsocketServer. Port + "/ websocket"; WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory(webSocketURL, null, false); Handshaker = factory.newhandshaker (fullHttpRequest); if (handshaker == null) { log.warn("handleHttpShakehandRequest fail,sendUnsupportedVersionResponse,fullHttpRequest=" + fullHttpRequest.toString()); / / does not support WebSocketServerHandshakerFactory. SendUnsupportedVersionResponse (CTX) channel ()); } else { log.info("handleHttpShakehandRequest success."); handshaker.handshake(ctx.channel(), fullHttpRequest); Private void handleWebsocketFrame(ChannelHandlerContext CTX, WebSocketFrame webSocketFrame) { if (webSocketFrame instanceof CloseWebSocketFrame) { log.info("handleWebsocketFrame close frame"); // Control frames - close handshaker.close(ctx.channel(), (CloseWebSocketFrame)webSocketFrame.retain()); return; } if (webSocketFrame instanceof PingWebSocketFrame) { log.info("handleWebsocketFrame ping frame"); -ping ctx.channel().write(new PongWebSocketFrame(webSocketframe.content ().retain())); return; } // Data frame, support text only if (! (webSocketFrame instanceof TextWebSocketFrame)) { log.error("handleWebsocketFrame,unsupprted data frame"); return; } TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame)webSocketFrame; String Request = textWebSocketFrame.text(); log.info("handleWebsocketFrame,receive data frame,text=" + request); ctx.channel().write(new TextWebSocketFrame(request + "_" + System.currentTimeMillis())); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { log.error("SimpleWebsocketServerHandler exception,", cause); ctx.close(); } @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); }}Copy the code
Start netty Server in springBoot main program:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SimplewebsocketserverApplication { public static void main(String[] args) { SimpleWebsocketServer simpleWebsocketServer = new SimpleWebsocketServer(); simpleWebsocketServer.start(); SpringApplication.run(SimplewebsocketserverApplication.class, args); }}Copy the code
To facilitate testing, we directly use JS to initiate the request and save the following file as websocket.html:
<! DOCTYPE HTML > < HTML > <head> <meta charset=" utF-8 "> <title>websocket </title> <style type="text/ CSS "> h3,h4{ text-align:center; </style> </head> <body> <h3> <span style="color:red"> console </span> </h3> <h4> Single message <li>url=/ API /ws/sendOne? Message = Single message content &id=none</li> Group message <li>url=/ API /ws/sendAll? </li> </h4> <script type="text/javascript"> var socket; If (typeof (WebSocket) == "undefined") {console.log(" Sorry: your browser does not support WebSocket"); } else {console.log(" Congratulations: your browser supports WebSocket"); // Set up a connection with the port. // Note that WS and WSS use different ports. WSS can not be used, the browser opened WebSocket error //ws corresponds to HTTP, WSS corresponds to HTTPS. socket = new WebSocket("ws://localhost:8085/ws/asset"); Onopen = function() {console.log("Socket open "); Socket. send(" Message sending test (From Client)"); }; Onmessage = function(MSG) {console.log(MSG); // get server push alert(msg.data) //console.log(msg.data); }; Onclose = function() {console.log(" socket closed "); }; Onerror = function() {alert(" socket error "); } // Close connection window.unload=function() {socket.close(); }; } </script> </body> </html>Copy the code
Open the websocket. HTML file with a websocket-enabled browser, and you can see the result from the server:
The server logs are displayed normally:
[nioEventLoopGroup 23:52:26. 559-3-1] INFO com. Summer. Simplewebsocketserver. SimpleWebsocketServerHandler - SimpleWebsocketServerHandler receive msg=HttpObjectAggregator$AggregatedFullHttpRequest(decodeResult: success, version: HTTP/1.1, Content: CompositeByteBuf(Ridx: 0, widx: 0, CAP: 0, Components =0)) GET /ws/asset Localhost :8085 Connection: Upgrade Pragma: no-cache cache-control: no-cache user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36 Upgrade: websocket Origin: null Sec-WebSocket-Version: 13 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh; Q = 0.9, en. Q= 0.8 sec-websocket-key: 2qmtoAbFKDi3nccZdLJrzQ== sec-websocket-extensions: permessage-deflate; client_max_window_bits content-length: 0 23:52:26. [559] nioEventLoopGroup - 3-1 the INFO com. Summer. Simplewebsocketserver. SimpleWebsocketServerHandler - [nioEventLoopGroup handleHttpShakehandRequest begin ~ 23:52:26. 563-3-1] INFO Com. Summer. Simplewebsocketserver. SimpleWebsocketServerHandler - handleHttpShakehandRequest success. 23:52:26. 574 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker - [id: 0x0f7a984c, L:/127.0.0.1:8085 -r :/127.0.0.1:50041] WebSocket Version V13 Server Handshake 23:52:26.576 [nioEventLoopgroup-3-1] DEBUG io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker - WebSocket version 13 server handshake key: 2qmtoAbFKDi3nccZdLJrzQ==, response: [nioEventLoopGroup XQR0ok8e59cI934NeuY5LCbiVXY = 23:52:26. 645-3-1] INFO com.summer.simplewebsocketserver.SimpleWebsocketServerHandler - SimpleWebsocketServerHandler receive msg=TextWebSocketFrame(data: PooledUnsafeDirectByteBuf(ridx: 0, widx: 31, cap: 31)) 23:52:26. [646] nioEventLoopGroup - 3-1 the INFO com. Summer. Simplewebsocketserver. SimpleWebsocketServerHandler - HandleWebsocketFrame, receive the data frame, the text message sending = test (From the Client) 23:52:26. 648] [nioEventLoopGroup - 3-1 the INFO com.summer.simplewebsocketserver.SimpleWebsocketServerHandler - handleWebsocketFrame,send response text Success :[This is the response result [message send test (From Client)]_1643471546646Copy the code
This article took me quite a long time to write out, look at these detailed code implementation, you should feel I want to teach you sincerity ~~~
If you feel useful, click “like” + “share” + “favorites”, one button three links to prevent lost wow ~