1、WebSocket简介
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,客户端和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
应用场景十分广泛:
社交订阅协同编辑/编程股票基金报价体育实况更新多媒体聊天在线教育
2、WebSocket和HTTP的区别
http协议是用在应用层的协议,他是基于tcp协议的,http协议建立连接也必须要有三次握手才能发送信息。 http连接分为短连接,长连接,短连接是每次请求都要三次握手才能发送自己的信息。即每一个request对应一个response。长连接是在一定的期限内保持连接。保持TCP连接不断开。客户端与服务器通信,必须要有客户端先发起, 然后服务器返回结果。客户端是主动的,服务器是被动的。客户端要想实时获取服务端消息就得不断发送长连接到服务端.
WebSocket实现了多路复用,他是全双工通信。在webSocket协议下服务端和客户端可以同时发送信息。 建立了WebSocket连接之后, 服务端可以主动发送信息到客户端。而且信息当中不必在带有head的部分信息了与http的长链接通信来说,这种方式,不仅能降低服务器的压力。而且信息当中也减少了部分多余的信息。
3、导入基础环境
1. 将资料中Netty-Springboot工程导入到idea
2. 相关依赖
<!--整合web模块--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--整合模板引擎 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>
3. 静态资源
4. yam配置
server:port: 8080resources:static-locations:- classpath:/static/spring:thymeleaf:cache: falsechecktemplatelocation: trueenabled: trueencoding: UTF-8mode: HTML5prefix: classpath:/templates/suffix: .html
4、服务端开发
1. 添加Netty依赖
<!--引入netty依赖 --><dependency><groupId>ty</groupId><artifactId>netty-all</artifactId></dependency>
2. Netty相关配置
netty:port: 8081path: /chat
3. Netty配置类
package com.lagou.config;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.ponent;@Component@ConfigurationProperties("netty")@Datapublic class NettyConfigure {private int port; // netty监听的端口private String path; // websocket访问路径}
4. NettyWebSocketServer开发
package ty;import com.tyConfigure;import ty.bootstrap.ServerBootstrap;import ty.channel.ChannelFuture;import ty.channel.EventLoopGroup;import ty.channel.nio.NioEventLoopGroup;import ty.channel.socket.nio.NioServerSocketChannel;import ty.handler.logging.LogLevel;import ty.handler.logging.LoggingHandler;import org.springframework.beans.factory.annotation.Autowired;import org.ponent;import javax.annotation.PreDestroy;/*** netty服务器*/@Componentpublic class NettyWebSocketServer implements Runnable{@AutowiredNettyConfigure nettyConfigure;@AutowiredWebSocketChannelInit webSocketChannelInit;private EventLoopGroup bossGroup = new NioEventLoopGroup(1);private EventLoopGroup workerGroup = new NioEventLoopGroup();/*** 资源关闭--在容器销毁时关闭*/@PreDestroypublic void close() {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}@Overridepublic void run() {try {// 创建服务端启动助手ServerBootstrap serverBootstrap = new ServerBootstrap();// 设置线程组serverBootstrap.group(bossGroup, workerGroup);// 设置参数serverBootstrap.channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.DEBUG)).childHandler(webSocketChannelInit);// 启动ChannelFuture channelFuture = serverBootstrap.bind(nettyConfigure.getPort()).sync();System.out.println("--netty服务端启动成功--");channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {e.printStackTrace();bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}}
5. 通道初始化对象
package ty;import com.tyConfigure;import ty.channel.Channel;import ty.channel.ChannelInitializer;import ty.channel.ChannelPipeline;import ty.handler.codec.http.HttpObjectAggregator;import ty.handler.codec.http.HttpServerCodec;import ty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;import ty.handler.stream.ChunkedWriteHandler;import org.springframework.beans.factory.annotation.Autowired;import org.ponent;/*** 通道初始化对象*/@Componentpublic class WebSocketChannelInit extends ChannelInitializer {@AutowiredNettyConfigure nettyConfigure;@AutowiredWebSocketHandler webSocketHandler;@Overrideprotected void initChannel(Channel channel) throws Exception {ChannelPipeline pipeline = channel.pipeline();// 对Http协议的支持pipeline.addLast(new HttpServerCodec());// 对大数据流的支持pipeline.addLast(new ChunkedWriteHandler());// post请求三部分 request line / request header/ message body// HttpObjectAggregator将多个信息转化成单一的request或者response对象pipeline.addLast(new HttpObjectAggregator(8000));// 将Http协议升级为ws协议,websocket的支持pipeline.addLast(new WebSocketServerProtocolHandler(nettyConfigure.getPath()));// 自定义处理handlerpipeline.addLast(webSocketHandler);}}
6. 处理对象
package ty;import ty.channel.Channel;import ty.channel.ChannelHandler;import ty.channel.ChannelHandlerContext;import ty.channel.SimpleChannelInboundHandler;import ty.handler.codec.http.websocketx.TextWebSocketFrame;import org.ponent;import java.util.ArrayList;import java.util.List;/*** 自定义处理类* TextWebSocketFrame : websocket数据是帧的形式处理*/@Component@ChannelHandler.Sharable // 设置通道共享public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {public static List<Channel> channelList = new ArrayList<>();/*** 通道就绪事件** @param ctx* @throws Exception*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {Channel channel = ctx.channel();// 当有新的客户端连接的时候,将通道放入集合channelList.add(channel);System.out.println("有新的连接");}/*** 读就绪事件** @param ctx* @param textWebSocketFrame* @throws Exception*/@Overrideprotected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame textWebSocketFrame) throws Exception {String msg = textWebSocketFrame.text();System.out.println("msg:" + msg);// 当前发送消息的通道,当前发送客户端的连接Channel channel = ctx.channel();for (Channel channel1 : channelList) {// 排除自身通道if (channel != channel1) {channel1.writeAndFlush(new TextWebSocketFrame(msg));}}}/*** 通道未就绪--channel 下线** @param ctx* @throws Exception*/@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {Channel channel = ctx.channel();// 当已有客户端断开连接的时候,就一处对应的通道channelList.remove(channel);}/*** 异常处理事件** @param ctx* @param cause* @throws Exception*/@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();Channel channel = ctx.channel();// 移除集合channelList.remove(channel);}}
7. 启动类
package com.lagou;import tyWebSocketServer;import org.springframework.beans.factory.annotation.Autowired;import org.mandLineRunner;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class NettySpringbootApplication implements CommandLineRunner {@AutowiredNettyWebSocketServer nettyWebSocketServer;public static void main(String[] args) {SpringApplication.run(NettySpringbootApplication.class, args);}@Overridepublic void run(String... args) throws Exception {new Thread(nettyWebSocketServer).start();}}
8. 前端js开发
$(function () {//这里需要注意的是,prompt有两个参数,前面是提示的话,后面是当对话框出来后,在对话框里的默认值var username = "";while (true) {//弹出一个输入框,输入一段文字,可以提交username = prompt("请输入您的名字", ""); //将输入的内容赋给变量 name ,if (username.trim() === "")//如果返回的有内容{alert("名称不能输入空")} else {$("#username").text(username);break;}}var ws = new WebSocket("ws://localhost:8081/chat");ws.onopen = function() {console.log("连接成功")}ws.onmessage = function (evt) {showMessage(evt.data);}ws.onclose = function () {console.log("连接关闭")}ws.onerror = function () {console.log("连接异常")}function showMessage(message) {var str = message.split(":");$("#msg_list").append(`<li class="active"}><div class="main"><img class="avatar" width="30" height="30" src="/img/user.png"><div><div class="user_name">${str[0]}</div><div class="text">${str[1]}</div></div> </div></li>`);// 置底setBottom();}$('#my_test').bind({focus: function (event) {event.stopPropagation()$('#my_test').val('');$('.arrow_box').hide()},keydown: function (event) {event.stopPropagation()if (event.keyCode === 13) {if ($('#my_test').val().trim() === '') {this.blur()$('.arrow_box').show()setTimeout(() => {this.focus()}, 1000)} else {$('.arrow_box').hide()//发送消息sendMsg();this.blur()setTimeout(() => {this.focus()})}}}});$('#send').on('click', function (event) {event.stopPropagation()if ($('#my_test').val().trim() === '') {$('.arrow_box').show()} else {sendMsg();}})function sendMsg() {var message = $("#my_test").val();$("#msg_list").append(`<li class="active"}><div class="main self"><div class="text">` + message + `</div></div></li>`);$("#my_test").val('');// 发送消息message = username + ":" + message;ws.send(message);// 置底setBottom();}// 置底function setBottom() {// 发送消息后滚动到底部const container = $('.m-message')const scroll = $('#msg_list')container.animate({scrollTop: scroll[0].scrollHeight - container[0].clientHeight + container.scrollTop() + 100});}});
5、注意
上述只是列举了部分核心代码,完整代码示例见下方下载地址
链接:/s/1LBgd_Xlk-19YIPZnP4XuQg?pwd=sj45
提取码:sj45