当前位置:   article > 正文

Springboot2使用注解方式构建Netty的高性能Websocket服务器_@serverendpoint netty

@serverendpoint netty

前言

最近在做数字货币交易所相关的项目,考虑到性能的一些问题,中间也研究了多个Socket框架,包括T-io、Netty-Socket等,最终放弃SpringBoot默认集成的Socket采用更高效的Netty-Socket来实现。

Netty为什么并发高

Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,对比于BIO(Blocking I/O,阻塞IO),他的并发性能得到了很大提高。

Netty为什么传输快

Netty的传输快其实也是依赖了NIO的一个特性——零拷贝。我们知道,Java的内存有堆内存、栈内存和字符串常量池等等,其中堆内存是占用内存空间最大的一块,也是Java对象存放的地方,一般我们的数据如果需要从IO读取到堆内存,中间需要经过Socket缓冲区,也就是说一个数据会被拷贝两次才能到达他的的终点,如果数据量大,就会造成不必要的资源浪费。

Netty针对这种情况,使用了NIO中的另一大特性——零拷贝,当他需要接收数据的时候,他会在堆内存之外开辟一块内存,数据就直接从IO读到了那块内存中去,在netty里面通过ByteBuf可以直接对这些数据进行直接操作,从而加快了传输速度。

Netty和Tomcat有什么区别?

Netty和Tomcat最大的区别就在于通信协议,Tomcat是基于Http协议的,他的实质是一个基于http协议的web容器,但是Netty不一样,他能通过编程自定义各种协议,因为netty能够通过codec自己来编码/解码字节流,完成类似redis访问的功能,这就是netty和tomcat最大的不同。

官方文档

首先这个框架跟原生的websocket配置上有点不一样的,更多详情可以多多看官方的文档,当时我也是觉得很快,但是发现参数设置/端口配置等,还有有点差异的。
https://github.com/YeautyYE/netty-websocket-spring-boot-starter/blob/master/README_zh.md

Netty-websocket-spring-boot-starter

这是个开源的框架。通过它,我们可以像spring-boot-starter-websocket一样使用注解进行开发,只需关注需要的事件(如OnMessage)。并且底层是使用Netty,netty-websocket-spring-boot-starter其他配置和spring-boot-starter-websocket完全一样,当需要调参的时候只需要修改配置参数即可,无需过多的关心handler的设置。

对应的Maven配置:

  1. <!-- 注释掉默认的websocket starter
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-websocket</artifactId>
  5. </dependency>-->
  6. <!-- 启用Netty https://mvnrepository.com/artifact/org.yeauty/netty-websocket-spring-boot-starter -->
  7. <dependency>
  8. <groupId>org.yeauty</groupId>
  9. <artifactId>netty-websocket-spring-boot-starter</artifactId>
  10. <version>0.7.6</version>
  11. </dependency>

快速开始

  • 添加依赖:
    1. <dependency>
    2. <groupId>org.yeauty</groupId>
    3. <artifactId>netty-websocket-spring-boot-starter</artifactId>
    4. <version>0.7.6</version>
    5. </dependency>

     

  • new一个ServerEndpointExporter对象,交给Spring IOC容器,表示要开启WebSocket功能,样例如下:
    1. @Configuration
    2. public class WebSocketConfig {
    3. @Bean
    4. public ServerEndpointExporter serverEndpointExporter() {
    5. return new ServerEndpointExporter();
    6. }
    7. }

     

  • 在端点类上加上@ServerEndpoint@Component注解,并在相应的方法上加上@OnOpen@OnClose@OnError@OnMessage@OnBinaryOnEvent注解,样例如下:
    1. import io.netty.handler.codec.http.HttpHeaders;
    2. import io.netty.handler.timeout.IdleStateEvent;
    3. import org.springframework.stereotype.Component;
    4. import org.yeauty.annotation.*;
    5. import org.yeauty.pojo.ParameterMap;
    6. import org.yeauty.pojo.Session;
    7. import java.io.IOException;
    8. @ServerEndpoint(prefix = "netty-websocket")
    9. @Component
    10. public class MyWebSocket {
    11. @OnOpen
    12. public void onOpen(Session session, HttpHeaders headers, ParameterMap parameterMap) throws IOException {
    13. System.out.println("new connection");
    14. String paramValue = parameterMap.getParameter("paramKey");
    15. System.out.println(paramValue);
    16. }
    17. @OnClose
    18. public void onClose(Session session) throws IOException {
    19. System.out.println("one connection closed");
    20. }
    21. @OnError
    22. public void onError(Session session, Throwable throwable) {
    23. throwable.printStackTrace();
    24. }
    25. @OnMessage
    26. public void onMessage(Session session, String message) {
    27. System.out.println(message);
    28. session.sendText("Hello Netty!");
    29. }
    30. @OnBinary
    31. public void onBinary(Session session, byte[] bytes) {
    32. for (byte b : bytes) {
    33. System.out.println(b);
    34. }
    35. session.sendBinary(bytes);
    36. }
    37. @OnEvent
    38. public void onEvent(Session session, Object evt) {
    39. if (evt instanceof IdleStateEvent) {
    40. IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
    41. switch (idleStateEvent.state()) {
    42. case READER_IDLE:
    43. System.out.println("read idle");
    44. break;
    45. case WRITER_IDLE:
    46. System.out.println("write idle");
    47. break;
    48. case ALL_IDLE:
    49. System.out.println("all idle");
    50. break;
    51. default:
    52. break;
    53. }
    54. }
    55. }
    56. }

     

  • 对注解中的prefix进行设置后,即可在application.properties中配置:
    1. netty-websocket:
    2. host: 0.0.0.0
    3. path: /
    4. port: 2222

     

  • 打开WebSocket客户端,连接到ws://127.0.0.1:2222

注解说明

@ServerEndpoint

当ServerEndpointExporter类通过Spring配置进行声明并被使用,它将会去扫描带有@ServerEndpoint注解的类
被注解的类将被注册成为一个WebSocket端点
所有的配置项都在这个注解的属性中 ( 如:@ServerEndpoint("/ws") )

@OnOpen

当有新的WebSocket连接进入时,对该方法进行回调
注入参数的类型:Session、HttpHeaders、ParameterMap

@OnClose

当有WebSocket连接关闭时,对该方法进行回调
注入参数的类型:Session

@OnError

当有WebSocket抛出异常时,对该方法进行回调
注入参数的类型:Session、Throwable

@OnMessage

当接收到字符串消息时,对该方法进行回调
注入参数的类型:Session、String

@OnBinary

当接收到二进制消息时,对该方法进行回调
注入参数的类型:Session、byte[]

@OnEvent

当接收到Netty的事件时,对该方法进行回调
注入参数的类型:Session、Object

配置说明

属性默认值说明
path“/”WebSocket的path,也可以用value来设置
host“0.0.0.0”WebSocket的host,"0.0.0.0"即是所有本地地址
port80WebSocket绑定端口号。如果为0,则使用随机端口(端口获取可见 多端点服务)
bossLoopGroupThreads0bossEventLoopGroup的线程数
workerLoopGroupThreads0workerEventLoopGroup的线程数
useCompressionHandlerfalse是否添加WebSocketServerCompressionHandler到pipeline
prefix“”当不为空时,即是使用application.properties进行配置,详情在 通过application.properties进行配置
optionConnectTimeoutMillis30000与Netty的ChannelOption.CONNECT_TIMEOUT_MILLIS一致
optionSoBacklog128与Netty的ChannelOption.SO_BACKLOG一致
childOptionWriteSpinCount16与Netty的ChannelOption.WRITE_SPIN_COUNT一致
childOptionWriteBufferHighWaterMark64*1024与Netty的ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK一致,但实际上是使用ChannelOption.WRITE_BUFFER_WATER_MARK
childOptionWriteBufferLowWaterMark32*1024与Netty的ChannelOption.WRITE_BUFFER_LOW_WATER_MARK一致,但实际上是使用 ChannelOption.WRITE_BUFFER_WATER_MARK
childOptionSoRcvbuf-1(即未设置)与Netty的ChannelOption.SO_RCVBUF一致
childOptionSoSndbuf-1(即未设置)与Netty的ChannelOption.SO_SNDBUF一致
childOptionTcpNodelaytrue与Netty的ChannelOption.TCP_NODELAY一致
childOptionSoKeepalivefalse与Netty的ChannelOption.SO_KEEPALIVE一致
childOptionSoLinger-1与Netty的ChannelOption.SO_LINGER一致
childOptionAllowHalfClosurefalse与Netty的ChannelOption.ALLOW_HALF_CLOSURE一致
readerIdleTimeSeconds0IdleStateHandler中的readerIdleTimeSeconds一致,并且当它不为0时,将在pipeline中添加IdleStateHandler
writerIdleTimeSeconds0IdleStateHandler中的writerIdleTimeSeconds一致,并且当它不为0时,将在pipeline中添加IdleStateHandler
allIdleTimeSeconds0IdleStateHandler中的allIdleTimeSeconds一致,并且当它不为0时,将在pipeline中添加IdleStateHandler
maxFramePayloadLength65536最大允许帧载荷长度

 

多端点服务

  • 快速启动的基础上,在多个需要成为端点的类上使用@ServerEndpoint@Component注解即可
  • 可通过ServerEndpointExporter.getInetSocketAddressSet()获取所有端点的地址
  • 当地址不同时(即host不同或port不同),使用不同的ServerBootstrap实例
  • 当地址相同,路径(path)不同时,使用同一个ServerBootstrap实例
  • 当多个端点服务的port为0时,将使用同一个随机的端口号
  • 当多个端点的port和path相同时,host不能设为"0.0.0.0",因为"0.0.0.0"意味着绑定所有的host

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/170914
推荐阅读
相关标签
  

闽ICP备14008679号