# spring-boot-protocol
**Repository Path**: wangzihaogitee/spring-boot-protocol
## Basic Information
- **Project Name**: spring-boot-protocol
- **Description**: springboot功能扩充-netty动态协议,可以支持各种网络协议的动态切换(单端口支持多个网络协议).支持mmap,sendfile零拷贝,http请求批量聚合
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 10
- **Forks**: 4
- **Created**: 2021-10-30
- **Last Updated**: 2025-06-20
## Categories & Tags
**Categories**: Uncategorized
**Tags**: mqtt, Netty, Protocol, springservlet, container
## README
# Spring-boot-protocol (用Netty实现)
### 简介
- 支持在一个端口号上,添加多个TCP协议,支持加自定义TCP协议
- 内置实现有: Dubbo-proxy, HttpServlet, RPC, MQTT, Websocket, H2, MYSQL协议.
- 解决Netty在EventLoop线程里写繁忙后不返回数据的BUG.
- 解决Netty的Http遇到请求参数携带%号会报错的问题.
- 从19年开始,一直跑在作者公司某产线的线上环境运行.

### 注意
本项目不支持springboot3和jakarta-servlet,如果有需要可联系我,看下是否需要继续支持。
### 优势
- 1.针对spring项目# 可以替代tomcat或jetty. 导包后一个@EnableNettyEmbedded注解即用.
- 2.针对非spring项目# 本项目可以只依赖一个netty(举个使用servlet的例子)
StartupServer server = new StartupServer(80);
ServletContext servletContext = new ServletContext();
servletContext.setDocBase("D://static", "/webapp");
servletContext.addServlet("myServlet", new MyHttpServlet()).addMapping("/test");
server.addProtocol(new HttpServletProtocol(servletContext));
server.start();
- 3.支持# tcp dubbo代理, 解决内外网运维问题
[DubboProxy{/192.168.11.126:61184 => [pay-service//127.0.0.1:20881(UP), order-service//127.0.0.1:20881(UP)]}]
server:
port: 8080
netty:
dubbo:
enabled: true
routes:
- path-patterns: 'com.github.netty.javadubbo.example.**'
address: '127.0.0.1:8002'
- application-name: 'order-service'
address: '127.0.0.1:8002'
- application-name: 'pay-service'
address: '127.0.0.1:8003'
default-application: true
- 4.支持# http请求聚合, 然后用 select * from id in (httpRequestList).
示例代码:com.github.netty.http.example.HttpGroupByApiController.java
- 5.支持# h2c (注: 不建议用h2,h2c当rpc, 原因在文档最底部有说明)
- 6.支持# 异步零拷贝。sendFile, mmap.
示例代码:com.github.netty.http.example.HttpZeroCopyController.java
((NettyOutputStream)servletResponse.getOutputStream()).write(new File("c://123.txt"));
((NettyOutputStream)servletResponse.getOutputStream()).write(MappedByteBuffer);
com.github.netty.protocol.servlet.DefaultServlet#sendFile
- 7.性能# HttpServlet比tomcat的NIO2高出25%/TPS。
1. Netty的池化内存,减少了GC对CPU的消耗
2. Tomcat的NIO2, 注册OP_WRITE后,tomcat会阻塞用户线程等待, 并没有释放线程.
3. 与tomcat不同,支持两种IO模型,可供用户选择
- 8.性能# RPC协议略胜阿里巴巴的Dubbo(因为IO模型设计与dubbo不同,减少了线程切换)
- 9.特性# 单机单端口上同时提供多个TCP协议
- 10.特性# 支持自定义TCP协议. 如:定长传输,分隔符传输
- 11.特性# 支持Mysql协议代理. 如:记录mysql日志.
/spring-boot-protocol/netty-mysql/zihaoapi.cn_3306-127.0.0.1_57998-packet.log
{
"timestamp":"2021-01-04 22:10:19",
"sequenceId":0,
"connectionId":8720,
"handlerType":"backend",
"clientCharset":"utf8_general_ci",
"serverCharset":"latin1_swedish_ci",
"packet":"ServerHandshakePacket,5.6.39-log,[AUTO_COMMIT]"
},
{
"timestamp":"2021-01-04 22:10:19",
"sequenceId":1,
"connectionId":8720,
"handlerType":"frontend",
"clientCharset":"utf8_general_ci",
"serverCharset":"latin1_swedish_ci",
"packet":"ClientHandshakePacket,db1,root,{_runtime_version=12.0.2, _client_version=8.0.19, _client_license=GPL, _runtime_vendor=Oracle Corporation, _client_name=MySQL Connector/J}"
},
{
"timestamp":"2021-01-04 22:10:19",
"sequenceId":2,
"connectionId":8720,
"handlerType":"backend",
"clientCharset":"utf8_general_ci",
"serverCharset":"latin1_swedish_ci",
"packet":"ServerOkPacket,[AUTO_COMMIT]"
},
{
"timestamp":"2021-01-04 22:10:19",
"sequenceId":0,
"connectionId":8720,
"handlerType":"frontend",
"clientCharset":"utf8_general_ci",
"serverCharset":"latin1_swedish_ci",
"packet":"ClientQueryPacket,COM_QUERY,select * from order"
},
{
"timestamp":"2021-01-04 22:10:19",
"sequenceId":1,
"connectionId":8720,
"handlerType":"backend",
"clientCharset":"utf8_general_ci",
"serverCharset":"latin1_swedish_ci",
"packet":"ServerColumnCountPacket,6"
},
{
"timestamp":"2021-01-04 22:10:19",
"sequenceId":2,
"connectionId":8720,
"handlerType":"backend",
"clientCharset":"utf8_general_ci",
"serverCharset":"latin1_swedish_ci",
"packet":"ServerColumnDefinitionPacket,order_id"
},
github地址 : https://github.com/wangzihaogithub/spring-boot-protocol
### 使用方法 - 添加依赖
#### 如果需要集成spring就用这个 [](https://search.maven.org/search?q=g:com.github.wangzihaogithub%20AND%20a:spring-boot-protocol)
```xml
com.github.wangzihaogithub
spring-boot-protocol
2.3.30
```
#### 如果不需要集成spring就用这个 [](https://search.maven.org/search?q=g:com.github.wangzihaogithub%20AND%20a:netty-servlet)
```xml
com.github.wangzihaogithub
netty-servlet
2.3.30
```
#### 2.开启netty容器
@EnableNettyEmbedded//切换容器的注解
@SpringBootApplication
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
#### 3.启动, 已经成功替换tomcat, 切换至 NettyTcpServer!
2019-02-28 22:06:16.192 INFO 9096 --- [er-Boss-NIO-2-1] c.g.n.springboot.server.NettyTcpServer : NettyTcpServer@1 start (port = 10004, pid = 9096, protocol = [my-protocol, http, nrpc, mqtt], os = windows 8.1) ...
2019-02-28 22:06:16.193 INFO 9096 --- [ main] c.g.example.ProtocolApplication10004 : Started ProtocolApplication10004 in 2.508 seconds (JVM running for 3.247)
---
#### 示例代码 -> [https://github.com/wangzihaogithub/netty-example](https://github.com/wangzihaogithub/netty-example "https://github.com/wangzihaogithub/netty-example")
#### 示例代码! /src/test包下有使用示例代码 -> [https://github.com/wangzihaogithub/spring-boot-protocol/tree/master/src/test](https://github.com/wangzihaogithub/spring-boot-protocol/tree/master/src/test "https://github.com/wangzihaogithub/spring-boot-protocol/tree/master/src/test")
##### 示例1. Springboot里使用HTTP或websocket模块(使用springboot后,默认是开启http的)
1. 引入http依赖
org.springframework.boot
spring-boot-starter-web
${spring-boot.version}
2. 可选!如果需要websocket,可以引入这个包,否则可以不引入
org.springframework.boot
spring-boot-starter-websocket
${spring-boot.version}
3.编写启动类
// @EnableWebSocket // 如果引入了websocket,可以打这个注解开启
@EnableNettyEmbedded//切换容器的注解
@SpringBootApplication
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
3. 启动后,控制台已经看到http协议出现了,开启成功! 可以用浏览器打开或websocket服务了. protocol = [http, NRPC/218]
2022-04-10 09:58:04.652 INFO 2716 --- [er-Boss-NIO-3-1] c.g.n.springboot.server.NettyTcpServer : NettyTcpServer@1 start (version = 2.2.3, port = 8080, pid = 2716, protocol = [http, NRPC/218], os = windows 10) ...
2022-04-10 09:58:04.673 INFO 2716 --- [ main] c.github.netty.ExampleApplication : Started ExampleApplication in 2.235 seconds (JVM running for 3.807)
4. 编写http代码
@RestController
@RequestMapping
public class HttpController {
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 访问地址: http://localhost:8080/test/hello
* @param name name
* @return hi! 小明
*/
@RequestMapping("/hello")
public String hello(String name, @RequestParam Map query,
@RequestBody(required = false) Map body,
HttpServletRequest request, HttpServletResponse response) {
return "hi! " + name;
}
}
5.如果引入了websocket,可以编写websocket服务端代码
@Component
public class WebsocketController extends AbstractWebSocketHandler implements WebSocketConfigurer, HandshakeInterceptor {
public static final Map sessionMap = new ConcurrentHashMap<>();
private static final Logger log = LoggerFactory.getLogger(WebsocketController.class);
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
log.info("应用启动时注册 websocket Controller {}", getClass());
registry.addHandler(this, "/my-websocket")
.addInterceptors(this).setAllowedOrigins("*");
}
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception {
log.info("握手前登录身份验证");
attributes.put("request", request);
attributes.put("response", response);
attributes.put("wsHandler", wsHandler);
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
log.info("握手后记录日志");
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
log.info("建立链接保存会话");
sessionMap.put(session.getId(), (NativeWebSocketSession) session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
log.info("WebSocket关闭: " + status);
sessionMap.remove(session.getId());
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
log.info("接受来自客户端发送的文本信息: " + message.getPayload());
}
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
log.info("接受来自客户端发送的二进制信息: " + message.getPayload().toString());
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
log.info("WebSocket异常:异常信息: " + exception.toString(), exception);
}
}
6. springboot使用https或http2
server:
port: 443
http2:
enabled: true
ssl:
key-store: 'classpath:mydomain.com.jks'
key-store-password: 'classpath:jks-password.txt'
key-store-type: 'JKS'
或
httpServletProtocol.setSslFileJks(jksFile, password)
httpServletProtocol.setSslFileCrtPem(crtFile, pemFile);
##### 示例2. 纯java版,不引入springboot, 使用HTTP模块
1. 引入依赖
com.github.wangzihaogithub
spring-boot-protocol
2.3.30
2.编写代码
public class HttpBootstrap {
public static void main(String[] args) {
StartupServer server = new StartupServer(8080);
server.addProtocol(newHttpProtocol());
server.start();
}
private static HttpServletProtocol newHttpProtocol() {
ServletContext servletContext = new ServletContext();
servletContext.setDocBase("D://demo", "/webapp"); // 静态资源文件夹(非必填,默认用临时目录)
servletContext.addServlet("myHttpServlet", new com.github.netty.protocol.servlet.DefaultServlet())
.addMapping("/*");
return new HttpServletProtocol(servletContext);
}
}
2. 启动后,控制台已经看到http协议出现了,开启成功! 可以用浏览器打开或websocket服务了. protocol = [http]
10:10:26.026 [NettyX-Server-Boss-NIO-1-1] INFO com.github.netty.StartupServer - StartupServer@1 start (version = 2.2.3, port = 8080, pid = 6972, protocol = [http], os = windows 10) ...
##### 示例2. 纯java版,不引入springboot, 使用HTTP2 模块
开启h2c
server:
netty:
http-servlet:
enable-h2c: true
或 HttpServletProtocol#setEnableH2c(true)
开启h2
server:
port: 443
http2:
enabled: true
ssl:
key-store: 'classpath:mydomain.com.jks'
key-store-password: 'classpath:jks-password.txt'
key-store-type: 'JKS'
1. 说明: http2分为两个协议 http2加密(h2), http2明文(h2c)
h2版本的协议是建立在TLS层之上的HTTP/2协议,这个标志被用在TLS应用层协议协商(TLS-ALPN)域和任何其它的TLS之上的HTTP/2协议。
h2c版本是建立在明文的TCP之上的HTTP/2协议,这个标志被用在HTTP/1.1的升级协议头域和其它任何直接在TCP层之上的HTTP/2协议。
想快速测试h2c可以用com.github.netty.protocol.servlet.http2.NettyHttp2Client 调用 http://localhost
如果想带https, 需要开启SSL, HttpServletProtocol#setSslContext
public static void main(String[] args) throws Exception {
// h2c 调用测试
NettyHttp2Client http2Client = new NettyHttp2Client("http://localhost")
.logger(LogLevel.INFO).awaitConnect();
for (int i = 0; i < 1; i++) {
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
"/test", Unpooled.EMPTY_BUFFER);
http2Client.writeAndFlush(request).onSuccess(e -> {
System.out.println(e);
e.release();
});
}
List httpPromises = http2Client.flush().get();
httpPromises.forEach(NettyHttp2Client.H2Response::close);
Long closeTime = http2Client.close(true).get();
}
##### 示例2. 纯java版,不引入springboot, 使用nprc(rpc-message)模块
1. 引入依赖(需要大于2.2.7版本)
com.github.wangzihaogithub
spring-boot-protocol
2.3.30
2.编写代码
package com.github.netty.javanrpc.server;
// rpc server demo
public class RpcServerApplication {
public static void main(String[] args) {
StartupServer server = new StartupServer(80);
server.addProtocol(newHttpProtocol());
server.addProtocol(newRpcMessageProtocol());
server.start();
}
private static NRpcProtocol newRpcMessageProtocol() {
ApplicationX applicationX = new ApplicationX();
applicationX.scanner(true,"com.github.netty.javanrpc.server")
.inject();
return new NRpcProtocol(applicationX);
}
@ApplicationX.Component
@NRpcService(value = "/demo", version = "1.0.0")
public static class DemoService {
public Map hello(String name) {
Map result = new LinkedHashMap();
result.put("name", name);
result.put("timestamp", System.currentTimeMillis());
return result;
}
}
}
// rpc client demo
public class RpcClientApplication {
public static void main(String[] args){
RpcClient rpcClient = new RpcClient("localhost", 80);
DemoClient demoClient = rpcClient.newInstance(DemoClient.class);
DemoMessageClient demoMessageClient = rpcClient.newInstance(DemoMessageClient.class);
DemoAsyncClient demoAsyncClient = rpcClient.newInstance(DemoAsyncClient.class);
Map result = demoClient.hello("wang");
System.out.println("result = " + result);
demoAsyncClient.hello("wang").whenComplete((data, exception) -> {
System.out.println("data = " + data);
System.out.println("exception = " + exception);
});
// ...
}
@NRpcService(value = "/demo", version = "1.0.0", timeout = 2000)
public interface DemoClient {
Map hello(@NRpcParam("name") String name);
}
@NRpcService(value = "/demo", version = "1.0.0", timeout = 2000)
public interface DemoAsyncClient {
CompletableFuture