Netty实现WebSocket通信案例

Netty实现WebSocket通信


基于springboot2.x版本实现






menu

🌿maven依赖
🌿自定义handler
🌿自定义子处理器
🌿Server
🌿WebSocket启动配置类
🌿前端测试页





maven依赖

<dependency>
  <groupId>io.netty</groupId>
  <artifactId>netty-all</artifactId>
  <version>4.1.25.Final</version>
</dependency>





自定义handler

package com.credi.websocket;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.time.LocalDateTime;

/**
 * 自定义的handler , 本handler用于处理消息
 * TextWebSocketFrame:
 * @Author: He Xingchi
 * Created by ALIENWARE on 2019/12/21.
 */
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

//    用以记录和管理所有客户端的channel
    private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
//        获取客户端传输过来的消息
        String content = msg.text();
        System.out.println("接收到来自客户端的数据: " + content );

//        遍历用以记录和管理所有客户端的channel中的通道channel
        for (Channel channel : clients){ //将该客户端channel通道收到的消息刷出给每一个channel通道
            /**
             * writeAndFlush : 将消息刷出给该channel , 虽然接受的参数类型为Object , 但是参数不能是String , 而是TextWebSocketFrame
             */
            channel.writeAndFlush(new TextWebSocketFrame(LocalDateTime.now() + " [服务器接收到消息:] " + " 接收到消息, 消息为: " + content));
        }
//        用以记录和管理所有客户端的channel其实自带一个writeAndFlush函数 : 作用是向维护的所有通道刷出消息 , 作用是一样的
        //clients.writeAndFlush(new TextWebSocketFrame(LocalDateTime.now() + " [服务器接收到消息:] " + " 接收到消息, 消息为: " + content));
    }

    /**
     *  当客户端链接服务端之后(打开链接)
     *  获取客户端的channel , 并且放到ChannelGroup中进行管理
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//        处理客户端所对应的的channal的通道
        Channel channel = ctx.channel();//当客户端和服务端链接之后 , 就有了一个双向的通道channel
        clients.add(channel); // 将通道channel交给用以记录和管理所有客户端的channel进行维护和管理
    }

//    用户离开或关闭客户端触发的方法
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        //clients.remove(ctx.channel()); //当客户端关闭 , 移除链接客户端和服务端的双向通道的chuannal
//        但以上这句话是多余的 , 因为客户端关闭之后 , 用以记录和管理所有客户端的channel会自动把通道移除
        System.out.println(ctx.channel().id()); //打印通道的id(每一个通道channel都有一份唯一的id , 长id(asLongText)和短id(asShortText))
        System.out.println("客户端断开 , channle对应的长id为: " + ctx.channel().id().asLongText());
        System.out.println("客户端断开 , channle对应的短id为: " + ctx.channel().id().asShortText()); // 短id可能出现重复
    }

}







自定义子处理器

package com.credi.websocket;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
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.context.annotation.Configuration;

/**
 * 自定义子处理器
 * @Author: He Xingchi
 * Created by ALIENWARE on 2019/12/21.
 */
@Configuration
//@EnableWebSocket
public class WSServerInitialzer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

//        websocket 基于http协议 , 所以要有http编解码器
        pipeline.addLast(new HttpServerCodec());
//        对写大数据流的支持
        pipeline.addLast(new ChunkedWriteHandler());
//        聚合器 , 对httpMessage进行聚合 , 聚合成FullHttpRequest或FullHttpResponse
//        几乎在netty中的编程 , 都会使用此handler
        pipeline.addLast(new HttpObjectAggregator(1024*64));

//        =============================  以上是用于支持http协议  ==========================================>>>>>>>>>>

        /**
         *  websocket 服务器处理的协议 , 用于指定连接客服端访问的路由 ip:端口/ws
         *  本handler会帮你处理一些繁重的复杂的事
         *  会帮你处理websocket的握手动作: handshaking(close, ping , pong)  ping + pong = 心跳
         *  对于websocket来讲 , 都是以frames进行传输的 , 不同的数据类型对应的frames也不同
         */
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws")); //

//        自定义的handler
        pipeline.addLast(new ChatHandler());
    }
}





Server

package com.credi.websocket;

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 org.springframework.stereotype.Component;

/**
 * @Author: He Xingchi
 * Created by ALIENWARE on 2019/12/21.
 */
@Component
public class WSServer {

    private static class SingletionWSServer {
        static final WSServer instance = new WSServer();
    }

    public static WSServer getInstance(){
        return SingletionWSServer.instance;
    }

//    线程组
    EventLoopGroup mainGroup;
    EventLoopGroup subGroup;

    ServerBootstrap server;
    ChannelFuture future;

    public WSServer(){
//        线程组初始化
        mainGroup = new NioEventLoopGroup();
        subGroup = new NioEventLoopGroup();
//        server初始化
        server = new ServerBootstrap();
        server.group(mainGroup,subGroup) //维护的channel
                .channel(NioServerSocketChannel.class)    //channel的类型 : nio
                .childHandler(new WSServerInitialzer());  // 自定义子处理器
    }

//    启动服务器
    public void start(){
        this.future = server.bind(8088); //绑定端口
        System.err.println("netty websocket server 启动完毕...");
    }

//    public static void main(String[] args) throws Exception{
        线程组
//        EventLoopGroup mainGroup = new NioEventLoopGroup();
//        EventLoopGroup subGroup = new NioEventLoopGroup();
//        try {
         server
//            ServerBootstrap server = new ServerBootstrap();
//            server.group(mainGroup,subGroup) //维护的channel
//                    .channel(NioServerSocketChannel.class)    //channel的类型 : nio
//                    .childHandler(new WSServerInitialzer());  // 子处理器
//
//            ChannelFuture future = server.bind(8088).sync();//绑定端口 , 设置同步 . 返回一个异步的future
        future监听channel
//            future.channel().closeFuture().sync();
//        } finally {
            关闭线程
//            mainGroup.shutdownGracefully();
//            subGroup.shutdownGracefully();
//        }
//    }
}





WebSocket启动配置类

package com.credi;

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import com.credi.websocket.WSServer;

/**
 * netty websocket 启动配置类
 * @Author: He Xingchi
 * Created by ALIENWARE on 2019/12/21.
 */
@Component
public class NettyBoot implements ApplicationListener<ContextRefreshedEvent>{
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if(event.getApplicationContext().getParent() == null){
            try {
                WSServer instance = WSServer.getInstance();
                instance.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}





前端测试页

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title></title>
	</head>
	<body>
		<div>发送消息:</div>
		<input type="text" id="msgContent" />
		<input type="button" value="点我发送" onclick="CHAT.chat()" />
		
		<div>接受消息:</div>
		<div id="receiveMsg" style="background-color: gainsboro;"></div>
		
		<!-- js -->
		<script type="application/javascript">
			//定义一个CHAT对象
			window.CHAT = {
				//socket对象
				socket: null,
				//初始化对象
				init: function(){
					//浏览器版本兼容问题
					if(window.WebSocket){
						//创建WebSocket对象123123
						CHAT.socket = new WebSocket("ws://127.0.0.1:8088/ws") // ws://服务端的ip:绑定的端口/接口路由
						// 客户端和服务端进行连接时  
						CHAT.socket.onopen = function(){
							console.log("连接建立成功...")
						},
						// 客户端和服务端关闭时
						CHAT.socket.onclose = function(){
							console.log("连接关闭...");
						},
						// 客户端和服务端链接异常时
						CHAT.socket.onerror = function(){
							console.log("连接异常...");
						},
						// 服务端向客户端刷出消息时
						CHAT.socket.onmessage = function(e){
							console.log("服务器刷出的消息: " + e.data);
							var receiveMsg = document.getElementById("receiveMsg");
							var html = receiveMsg.innerHTML;
							receiveMsg.innerHTML = html + "<br/>" + e.data;
						}
						
					}else{
						alert("浏览器不支持WebSocket");
					}
				},
				chat: function(){
					var msgContent = document.getElementById("msgContent");
					// 发送消息方法
					CHAT.socket.send(msgContent.value);
					// 关闭连接方法
					//CHAT.socket.close();
				}
			};
			// 初始化websocket链接
			CHAT.init();
		</script>
	</body>
</html>

完整POM.XML文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <!--<packaging>war</packaging>-->

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.3.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.hp</groupId>
  <artifactId>Talk</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>Talk</name>
  <description>Demo project for Spring Boot</description>

  <properties>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <!-- java生成条形码 -->
    <dependency>
      <groupId>net.sf.barcode4j</groupId>
      <artifactId>barcode4j-light</artifactId>
      <version>2.0</version>
    </dependency>


    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>2.0.0</version>
    </dependency>


    <!-- thymeleaf的依赖 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>
    <!-- 分页插件 -->
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper-spring-boot-starter</artifactId>
      <version>1.2.5</version>
    </dependency>
    <!-- alibaba的druid数据库连接池 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.1.9</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>

    <!-- swagger -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger2</artifactId>
      <version>2.9.2</version>
    </dependency>

    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger-ui</artifactId>
      <version>2.9.2</version>
    </dependency>

    <!--commons-lang3:数组集合字符串非空校验-->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.8.1</version>
    </dependency>

    <!-- Lombok包 -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.0</version>
      <scope>provided</scope>
    </dependency>

    <!-- 使用 JSONObject net.sf.json.JSON -->
    <dependency>
      <groupId>net.sf.json-lib</groupId>
      <artifactId>json-lib</artifactId>
      <version>2.4</version>
      <classifier>jdk15</classifier>
    </dependency>

    <!-- 添加GSON依赖包 -->
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.8.5</version>
    </dependency>


    <dependency>
      <groupId>com.googlecode.json-simple</groupId>
      <artifactId>json-simple</artifactId>
      <version>1.1.1</version>
    </dependency>

    <!-- mybatis-generator-core 反向生成java代码-->
    <dependency>
      <groupId>org.mybatis.generator</groupId>
      <artifactId>mybatis-generator-core</artifactId>
      <version>1.3.5</version>
    </dependency>
    <!-- 二维码 -->
    <dependency>
      <groupId>com.google.zxing</groupId>
      <artifactId>javase</artifactId>
      <version>3.3.3</version>
    </dependency>
    <!-- 高性能分布式文件服务器 -->
    <dependency>
      <groupId>com.github.tobato</groupId>
      <artifactId>fastdfs-client</artifactId>
      <version>1.26.2</version>
    </dependency>
    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.1.25.Final</version>
    </dependency>


  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <!--自动化生成代码-->
      <plugin>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-maven-plugin</artifactId>
        <version>1.3.2</version>
        <configuration>
          <configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
          <verbose>true</verbose>
          <overwrite>true</overwrite>
        </configuration>
      </plugin>
      <!--base64找不到问题解决-->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>${project.build.sourceEncoding}</encoding>
          <!-- added by xiluhua 20160627
              com.sun.org.apache.xml.internal.security.utils.Base64;编译异常
              将jre/lib/rt.jar添加到maven的compiler里面  编译正常..。
          -->
          <compilerArguments>
            <verbose />
            <bootclasspath>${java.home}/lib/rt.jar</bootclasspath>
          </compilerArguments>

        </configuration>
      </plugin>
      <!-- 前端图标展示BUG , 使用该注解会让所有xml映射文件失效 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <configuration>
          <nonFilteredFileExtensions>
            <nonFilteredFileExtension>xlsx</nonFilteredFileExtension>
            <nonFilteredFileExtension>xls</nonFilteredFileExtension>
            <nonFilteredFileExtension>zip</nonFilteredFileExtension>
            <nonFilteredFileExtension>eot</nonFilteredFileExtension>
            <nonFilteredFileExtension>svg</nonFilteredFileExtension>
            <nonFilteredFileExtension>ttf</nonFilteredFileExtension>
            <nonFilteredFileExtension>woff</nonFilteredFileExtension>
            <nonFilteredFileExtension>woff2</nonFilteredFileExtension>
          </nonFilteredFileExtensions>
        </configuration>
      </plugin>
    </plugins>

  </build>

</project>

启动类
package com.credi;

import javafx.application.Application;
import javafx.stage.Stage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Author: He Xingchi
 * Created by Administrator on 2019/12/21.
 */
@SpringBootApplication
public class Starter extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        SpringApplication.run(Starter.class);
    }
}

application.yml
# 开发环境
 server:
  #端口号
  port: 8080

 # 配置DataSource
 spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
#    开发
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root
    password: 123456
    initialSize: 5
    maxActive: 100
    minIdle: 3
    maxWait: 50000
  main:
    allow-bean-definition-overriding: true

  thymeleaf:
      prefix: classpath:/templates/
      suffix: .html
      cache: false

 tomcat:
  uri-encoding: UTF-8
# 配置MyBatis
 mybatis:
  type-aliases-package: com.credi.pojo
  mapper-locations: classpath:mapper/*.xml

# 配置PageHelper
 pagehelper:
  helper-dialect: mysql
  reasonable: true
  support-methods-arguments: true
  params: count=countSql





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值