实际情况是: 公司需要开发一个接口给新产品使用,需求如下

1.有一款硬件设备,客户用usb接上电脑就可以,但是此设备功能比较单一,所以开发一个服务器程序,辅助此设备业务功能

2.解决方案,使用Socket调用此设备

3.增强此设备功能,增加Socket客户端连接到Socket服务端

4.Http请求,同步响应

测试注意:

1.nettyServer 在ubuntu下编码,使用Epoll

2.Http请求的测试最好运行再Linux 下进行,因为Windows 可能会因为并发高的时候占满端口限制,HttpClient或者RestTemplate 请求不了.

3.ProtoBuf 插件无论再Windows,还是linux同样适用,在linux 下,会自动下载 protoc-3.5.1-linux-x86_64.exe

简单的流程如下

解决方案:

1.使用Netty框架

2.使用ProtoBuf,配合Netty 对ProtoBuf解决半包问题

3.Future 实现伪同步响应

4.SpringBoot + jetty

pom.xml 添加ProtoBuf依赖以及插件

 <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<grpc.version>1.11.0</grpc.version>
<protobuf.version>3.5.1</protobuf.version>
</properties>
  <dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>${grpc.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
<scope>provided</scope>
</dependency> <dependency>
<groupId>com.googlecode.protobuf-java-format</groupId>
<artifactId>protobuf-java-format</artifactId>
<version>1.4</version>
</dependency>

插件

    <build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

屏蔽Tomcat 使用 Jetty

        <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

编写proto:再/src/main中创建文件夹 proto,创建一个Message.proto

文件内容

syntax = "proto3";
option java_package = "com.lzw.netty";
option java_outer_classname = "MessageProto";
message Message { int32 type = 1; sfixed64 id = 2; string msgBody = 3; enum Type {
ACTIVE = 0;
MESSAGE = 1;
} }

生成java 文件

文件目录,挪到自己需要的包下面

服务端代码

/**
* User: laizhenwei
* Date: 2018-03-26 Time: 21:46
* Description:
*/
public class EchoServer { //缓存ResponseFuture
public static Map<Long, ResponseFuture<MessageProto.Message>> responseFutureMap = new HashMap<>(); private final int port; public EchoServer(int port) {
this.port = port;
} public void start() throws InterruptedException { EventLoopGroup bossGroup = new EpollEventLoopGroup(1);
EventLoopGroup workerGroup = new EpollEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workerGroup).channel(EpollServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new MyServerChannelInitializer());
try {
ChannelFuture f = bootstrap.bind().sync();
//清理不可预知而失败的脏数据
f.channel().eventLoop().scheduleAtFixedRate(() -> {
long nowTime = System.currentTimeMillis();
responseFutureMap.entrySet().stream().filter(e -> (nowTime - e.getValue().getBeginTime()) > 60000).map(e -> e.getKey()).forEach(k->responseFutureMap.remove(k));
}, 300, 300, TimeUnit.SECONDS);
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully().sync();
workerGroup.shutdownGracefully().sync();
}
}
}

ContextHelper缓存ChannelHandlerContext

/**
* User: laizhenwei
* Date: 2018-03-26 Time: 21:46
* Description: 缓存客户端的ChannelHandlerContext
*/
public class ContextHelper { private final static Map<String, ChannelHandlerContext> clientMap = new ConcurrentHashMap<>(); public static Map<String, ChannelHandlerContext> getClientMap() {
return Collections.unmodifiableMap(clientMap);
} public static ChannelHandlerContext get(String id){
return clientMap.get(id);
} public static void add(String id, ChannelHandlerContext ctx) {
clientMap.put(id, ctx);
} public static void remove(String id) {
clientMap.remove(id);
}
}

MyServerHandler

/**
* User: laizhenwei
* Date: 2018-03-26 Time: 21:46
* Description:
*/
@Slf4j
@ChannelHandler.Sharable
public class MyServerHandler extends SimpleChannelInboundHandler<MessageProto.Message> { @Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageProto.Message msg) {
String message = msg.getMsgBody();
if ((MessageProto.Message.Type.ACTIVE_VALUE) == msg.getType()) {
Attribute<String> attribute = channelHandlerContext.channel().attr(AttributeKey.valueOf("userName"));
//连接上以后获取消息参数,设置到channelAttr
String userName = message.split(":")[1];
attribute.setIfAbsent(userName);
//缓存channelHandlerContext
ContextHelper.add(userName, channelHandlerContext);
} else if (MessageProto.Message.Type.MESSAGE_VALUE == msg.getType()) {
ResponseFuture<MessageProto.Message> resutl = EchoServer.responseFutureMap.get(msg.getId());
if (resutl == null)
log.warn("result is null ! msgId:" + msg.getId());
MessageProto.Message message1 = MessageProto.Message.newBuilder().setId(msg.getId()).setType(MessageProto.Message.Type.MESSAGE_VALUE).setMsgBody("接收成功!msg:" + message).build();
resutl.setResponse(message1);
}
// System.out.println("Client->Server:" + channelHandlerContext.channel().remoteAddress() + " send " + msg.getMsgBody());
} @Override
public void channelInactive(ChannelHandlerContext ctx){
Attribute<String> attribute = ctx.channel().attr(AttributeKey.valueOf("userName"));
ContextHelper.remove(attribute.get());
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
}
}

ChannelInitializer,添加 Netty 支持 ProtoBuf 的拆包处理,以及编码解码

/**
* User: laizhenwei
* Date: 2018-03-26 Time: 21:46
* Description:
*/
public class MyServerChannelInitializer extends ChannelInitializer<SocketChannel> { @Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new ProtobufVarint32FrameDecoder())
.addLast(new ProtobufDecoder(MessageProto.Message.getDefaultInstance()))
.addLast(new ProtobufVarint32LengthFieldPrepender())
.addLast(new ProtobufEncoder())
.addLast(new MyServerHandler());
} }

ResponseFuture

@NoArgsConstructor
public class ResponseFuture<T> implements Future<T> {
// 因为请求和响应是一一对应的,因此初始化CountDownLatch值为1。
private CountDownLatch latch = new CountDownLatch(1);
// 响应结果
private T response;
// Futrue的请求时间,用于计算Future是否超时
private long beginTime = System.currentTimeMillis(); @Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
} @Override
public boolean isCancelled() {
return false;
} @Override
public boolean isDone() {
if (response != null)
return true;
return false;
} // 获取响应结果,直到有结果才返回。
@Override
public T get() throws InterruptedException {
latch.await();
return this.response;
} // 获取响应结果,直到有结果或者超过指定时间就返回。
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException {
if (latch.await(timeout, unit))
return this.response;
return null;
} // 用于设置响应结果,并且做countDown操作,通知请求线程
public void setResponse(T response) {
this.response = response;
latch.countDown();
} public long getBeginTime() {
return beginTime;
}
}

ApplicationStartup SpringBoot 完全启动以后,运行Netty服务

/**
* User: laizhenwei
* Date: 2018-03-26 Time: 21:46
* Description:
*/
@Component
public class ApplicationStartup implements CommandLineRunner { @Override
public void run(String... args) throws Exception {
new EchoServer(5000).start();
}
}

客户端 EchoClient

/**
* User: laizhenwei
* Date: 2018-03-27 Time: 21:50
* Description:
*/
public class EchoClient { private final String host; private final int port; public EchoClient(String host,int port){
this.host = host;
this.port = port;
} public void start(String userName) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup(); Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host,port))
.handler(new ChannelInitializer<SocketChannel>(){
@Override
protected void initChannel(SocketChannel socketChannel){
socketChannel.attr(AttributeKey.valueOf("userName")).setIfAbsent(userName);
socketChannel.pipeline()
.addLast(new ProtobufVarint32FrameDecoder())
.addLast(new ProtobufDecoder(MessageProto.Message.getDefaultInstance()))
.addLast(new ProtobufVarint32LengthFieldPrepender())
.addLast(new ProtobufEncoder())
.addLast(new MyClientHandler());
}
}); try {
ChannelFuture f = b.connect().sync();
f.channel().closeFuture().sync();
}finally {
group.shutdownGracefully().sync();
}
} public static void main(String[] args){
threadRun("Athos");
threadRun("Nero");
threadRun("Dante");
threadRun("Vergil");
threadRun("lzw");
threadRun("Churchill");
threadRun("Peter");
threadRun("Bob");
} private static void threadRun(String userName){
new Thread(()-> {
try {
new EchoClient("192.168.1.8",5000).start(userName);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
} }

MyClientHandler

/**
* User: laizhenwei
* Date: 2018-04-09 Time: 11:20
* Description:
*/
@ChannelHandler.Sharable
public class MyClientHandler extends SimpleChannelInboundHandler<MessageProto.Message> { @Override
public void channelActive(ChannelHandlerContext ctx) {
Attribute<Object> attribute = ctx.channel().attr(AttributeKey.valueOf("userName"));
String m = "userName:" + attribute.get();
MessageProto.Message.Builder builder = MessageProto.Message.newBuilder();
builder.setType(MessageProto.Message.Type.ACTIVE_VALUE).setMsgBody(m);
ctx.writeAndFlush(builder.build());
} @Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageProto.Message msg) {
MessageProto.Message.Builder builder = MessageProto.Message.newBuilder();
//把接收到的消息写回到服务端
builder.setId(msg.getId()).setType(MessageProto.Message.Type.MESSAGE_VALUE).setMsgBody(msg.getMsgBody());
channelHandlerContext.channel().writeAndFlush(builder.build());
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
} }

JunitTest

    @Test
public void testRest() throws InterruptedException {
final Gson gson = new Gson();
AtomicLong atomicLong = new AtomicLong(0); ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(50);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(512);
executor.setThreadNamePrefix("Executor-");
executor.setAllowCoreThreadTimeOut(false);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
String[] userNames = {"Athos", "Nero", "Dante"
, "Vergil", "lzw", "Churchill"
, "Peter", "Bob"}; // String[] userNames = {"Athos"}; RestTemplate restTemplate = new RestTemplate();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON_UTF8));
httpHeaders.add("connection", "keep-alive");
// httpHeaders.setConnection("close");
List<CompletableFuture<Boolean>> futures = new ArrayList<>();
long begin = System.nanoTime();
Arrays.stream(userNames).forEach(userName -> new Thread(() -> {
for (int i = 0; i < 100000; i++) {
futures.add(CompletableFuture.supplyAsync(() -> {
long currentId = atomicLong.getAndIncrement();
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("userName", userName);
params.add("msg", "你好啊!" + currentId);
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(params, httpHeaders);
String response = restTemplate.postForObject("http://192.168.91.130:8010/process", httpEntity, String.class);
if (response != null) {
Map<String, Object> responseMap;
responseMap = gson.fromJson(response, HashMap.class);
return responseMap.get("msgBody").equals("接收成功!msg:你好啊!" + currentId);
}
return false;
}, executor));
}
}).start()); while(futures.size()!=(100000*userNames.length)){
TimeUnit.MILLISECONDS.sleep(500);
} List<Boolean> result = futures.stream().map(CompletableFuture::join).collect(Collectors.toList()); System.out.println((System.nanoTime() - begin) / 1000000); result.stream().filter(r -> !r).forEach(r -> System.out.println(r)); }

1.启动NettyServer

2.启动NettyClient

3.启动N个JunitTest windows 启动5个,Linux 启动5个

看看server输出,从请求到响应非常迅速

Client 多个线程也没有看到输出有false,证明伪同步响应成功

Http 调用netty 服务,服务调用客户端,伪同步响应.ProtoBuf 解决粘包,半包问题.的更多相关文章

  1. Netty 粘包/半包原理与拆包实战

    Java NIO 粘包 拆包 (实战) - 史上最全解读 - 疯狂创客圈 - 博客园 https://www.cnblogs.com/crazymakercircle/p/9941658.html 本 ...

  2. netty解决粘包半包问题

    前言:开发者用到TCP/IP交互时,偶尔会遇到粘包或者半包的数据,这种情况有时会对我们的程序造成严重的影响,netty框架为解决这种问题提供了若干框架 1. LineBasedFrameDecoder ...

  3. Netty - 粘包和半包(上)

    在网络传输中,粘包和半包应该是最常出现的问题,作为 Java 中最常使用的 NIO 网络框架 Netty,它又是如何解决的呢?今天就让我们来看看. 定义 TCP 传输中,客户端发送数据,实际是把数据写 ...

  4. 客户端(springmvc)调用netty构建的nio服务端,获得响应后返回页面(同步响应)

    后面考虑通过netty做一个真正意义的简约版RPC框架,今天先尝试通过正常调用逻辑调用netty构建的nio服务端并同步获得返回信息.为后面做铺垫 服务端实现 我们先完成服务端的逻辑,逻辑很简单,把客 ...

  5. 使用Spring Cloud Feign作为HTTP客户端调用远程HTTP服务

    在Spring Cloud Netflix栈中,各个微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端.我们可以使用JDK原生的URLConnection.Ap ...

  6. spring Cloud Feign作为HTTP客户端调用远程HTTP服务

    在Spring Cloud Netflix栈中,各个微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端.我们可以使用JDK原生的URLConnection.Ap ...

  7. Spring Cloud 入门教程(六): 用声明式REST客户端Feign调用远端HTTP服务

    首先简单解释一下什么是声明式实现? 要做一件事, 需要知道三个要素,where, what, how.即在哪里( where)用什么办法(how)做什么(what).什么时候做(when)我们纳入ho ...

  8. spring cloud 声明式rest客户端feign调用远程http服务

    在Spring Cloud Netflix栈中,各个微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端.Feign就是Spring Cloud提供的一种声明式R ...

  9. wcf 中客户端调用之死 感悟 wcf与原来的webservice2.0 的客户端调用区别(wcf调用完不关闭的话那就把web服务搞死了)

    说到wcf,本人也是刚刚使用所以不是很熟悉 在做项目的时候采用webservice+客户端程序架构 写了一个wcf中的webservice之后,又写了很多的客户端exe程序,有的是轮询调用这个webs ...

随机推荐

  1. RxJava2 源码解析(一)

    概述 最近事情太多了,现在公司内部的变动,自己岗位的变化,以及最近决定找工作.所以博客耽误了,准备面试中,打算看一看RxJava2的源码,遂有了这篇文章. 不会对RxJava2的源码逐字逐句的阅读,只 ...

  2. C++ stringstream 简化数据类型转换

    C++标准库中的<sstream>提供了比ANSI C的<stdio.h>更高级的一些功能,即单纯性.类型安全和可扩展性. 在C++中经常会使用到snprintf来格式化一些输 ...

  3. AndroidStudio下加入百度地图的使用(四)——路线规划

    上一章中我们已经完成了API常用的方法及常量属性的使用,这一章向大家介绍一下地图的重要功能-路线规划. (1) 定义API中的路线查询类: RoutePlanSearch mSearch = Rout ...

  4. 如何将Revit明细表导出为Excel文档

    Revit软件没有将明细表直接导出为Excel电子表格的功能,Revit只能将明细表导出为TXT格式,但是这种TXT文件用EXCEL处理软件打开然后另存为XLS格式即可,以Revit2013版自带的建 ...

  5. [JS]两个常用的取随机整数的函数

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/ ...

  6. 基于CentOS 搭建 Seafile 专属网盘

    系统要求:CentOS 7.2 64 位操作系统 安装 Seafile 安装依赖环境 使用 yum 安装 Python 及 MySQL: yum install python python-setup ...

  7. SNF快速开发平台3.0之-界面个性化配置+10种皮肤+7种菜单-Asp.net+MVC4.0+WebAPI+EasyUI+Knockout

    一.个性配置-首页:可以进行拖动保存配置,下次登录时就会按配置的进行加载 二.个人配置页面 7种菜单用户可自定义配置,和预览效果 10种皮肤自定义配置,和预览效果 皮肤和菜单可以随意组合-部分截图: ...

  8. 揭开Docker的神秘面纱

    Docker 相信在飞速发展的今天已经越来越火,它已成为如今各大企业都争相使用的技术.那么Docker 是什么呢?为什么这么多人开始使用Docker? 本节课我们将一起解开Docker的神秘面纱. 本 ...

  9. 译: 3. RabbitMQ Spring AMQP 之 Publish/Subscribe 发布和订阅

    在第一篇教程中,我们展示了如何使用start.spring.io来利用Spring Initializr创建一个具有RabbitMQ starter dependency的项目来创建spring-am ...

  10. Vivado开发工具熟悉之工具使用杂记

    这两天基本完成了实验室工程从ISE向vivado的移植,包括了两片FPGA的两个工程,这两个工程还算是比较大的工程,包括了内存,接口,embedded system,算法模块等,在这过程中也很好的熟悉 ...