WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,客户端和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
应用场景十分广泛:
http协议是用在应用层的协议,他是基于tcp协议的,http协议建立连接也必须要有三次握手才能发送信息。 http连接分为短连接,长连接,短连接是每次请求都要三次握手才能发送自己的信息。即每一个request对应一个response。长连接是在一定的期限内保持连接。保持TCP连接不断开。客户端与服务器通信,必须要有客户端先发起, 然后服务器返回结果。客户端是主动的,服务器是被动的。 客户端要想实时获取服务端消息就得不断发送长连接到服务端.
WebSocket实现了多路复用,他是全双工通信。在webSocket协议下服务端和客户端可以同时发送信息。 建立了WebSocket连接之后, 服务端可以主动发送信息到客户端。而且信息当中不必在带有head的部分信息了与http的长链接通信来说,这种方式,不仅能降低服务器的压力。而且信息当中也减少了部分多余的信息。
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-webartifactId>
- dependency>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-thymeleafartifactId>
- dependency>
- <dependency>
- <groupId>org.projectlombokgroupId>
- <artifactId>lombokartifactId>
- dependency>
- server:
- port: 8080
- resources:
- static-locations:
- - classpath:/static/
- spring:
- thymeleaf:
- cache: false
- checktemplatelocation: true
- enabled: true
- encoding: UTF-8
- mode: HTML5
- prefix: classpath:/templates/
- suffix: .html
- <dependency>
- <groupId>io.nettygroupId>
- <artifactId>netty-allartifactId>
- dependency>
- netty:
- port: 8081
- path: /chat
- package com.lagou.config;
-
- import lombok.Data;
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.stereotype.Component;
-
- @Component
- @ConfigurationProperties("netty")
- @Data
- public class NettyConfigure {
- private int port; // netty监听的端口
- private String path; // websocket访问路径
- }
- package com.lagou.netty;
-
- import com.lagou.config.NettyConfigure;
- import io.netty.bootstrap.ServerBootstrap;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.nio.NioServerSocketChannel;
- import io.netty.handler.logging.LogLevel;
- import io.netty.handler.logging.LoggingHandler;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
-
- import javax.annotation.PreDestroy;
-
- /**
- * netty服务器
- */
- @Component
- public class NettyWebSocketServer implements Runnable{
- @Autowired
- NettyConfigure nettyConfigure;
- @Autowired
- WebSocketChannelInit webSocketChannelInit;
- private EventLoopGroup bossGroup = new NioEventLoopGroup(1);
- private EventLoopGroup workerGroup = new NioEventLoopGroup();
-
- /**
- * 资源关闭--在容器销毁时关闭
- */
- @PreDestroy
- public void close() {
- bossGroup.shutdownGracefully();
- workerGroup.shutdownGracefully();
- }
-
- @Override
- public 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();
- }
- }
- }
- package com.lagou.netty;
-
- import com.lagou.config.NettyConfigure;
- import io.netty.channel.Channel;
- import io.netty.channel.ChannelInitializer;
- import io.netty.channel.ChannelPipeline;
- import io.netty.handler.codec.http.HttpObjectAggregator;
- import io.netty.handler.codec.http.HttpServerCodec;
- import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
- import io.netty.handler.stream.ChunkedWriteHandler;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
-
- /**
- * 通道初始化对象
- */
- @Component
- public class WebSocketChannelInit extends ChannelInitializer {
- @Autowired
- NettyConfigure nettyConfigure;
- @Autowired
- WebSocketHandler webSocketHandler;
-
- @Override
- protected 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()));
-
- // 自定义处理handler
- pipeline.addLast(webSocketHandler);
- }
- }
- package com.lagou.netty;
-
- import io.netty.channel.Channel;
- import io.netty.channel.ChannelHandler;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.channel.SimpleChannelInboundHandler;
- import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
- import org.springframework.stereotype.Component;
-
- import java.util.ArrayList;
- import java.util.List;
-
- /**
- * 自定义处理类
- * TextWebSocketFrame : websocket数据是帧的形式处理
- */
- @Component
- @ChannelHandler.Sharable // 设置通道共享
- public class WebSocketHandler extends SimpleChannelInboundHandler
{ - public static List
channelList = new ArrayList<>(); -
- /**
- * 通道就绪事件
- *
- * @param ctx
- * @throws Exception
- */
- @Override
- public void channelActive(ChannelHandlerContext ctx) throws Exception {
- Channel channel = ctx.channel();
- // 当有新的客户端连接的时候,将通道放入集合
- channelList.add(channel);
- System.out.println("有新的连接");
- }
-
- /**
- * 读就绪事件
- *
- * @param ctx
- * @param textWebSocketFrame
- * @throws Exception
- */
- @Override
- protected 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
- */
- @Override
- public void channelInactive(ChannelHandlerContext ctx) throws Exception {
- Channel channel = ctx.channel();
- // 当已有客户端断开连接的时候,就一处对应的通道
- channelList.remove(channel);
- }
-
- /**
- * 异常处理事件
- *
- * @param ctx
- * @param cause
- * @throws Exception
- */
-
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- cause.printStackTrace();
- Channel channel = ctx.channel();
- // 移除集合
- channelList.remove(channel);
- }
- }
- package com.lagou;
-
- import com.lagou.netty.NettyWebSocketServer;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.CommandLineRunner;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
-
- @SpringBootApplication
- public class NettySpringbootApplication implements CommandLineRunner {
- @Autowired
- NettyWebSocketServer nettyWebSocketServer;
-
- public static void main(String[] args) {
- SpringApplication.run(NettySpringbootApplication.class, args);
- }
-
- @Override
- public void run(String... args) throws Exception {
- new Thread(nettyWebSocketServer).start();
- }
- }
- $(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(`
-
-
-
- ${str[0]}
- ${str[1]}
-
-
- `);
- // 置底
- 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(`
-
- ` + message + `
-
- `);
- $("#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
- });
- }
- });
上述只是列举了部分核心代码,完整代码示例见下方下载地址
链接:https://pan.baidu.com/s/1LBgd_Xlk-19YIPZnP4XuQg?pwd=sj45
提取码:sj45