前言

本篇文章主要介绍的是SpringBoot整合Netty以及使用Protobuf进行数据传输的相关内容。Protobuf会简单的介绍下用法,至于Netty在之前的文章中已经简单的介绍过了,这里就不再过多细说了。

Protobuf

介绍

protocolbuffer(以下简称PB)是google 的一种数据交换的格式,它独立于语言,独立于平台。google 提供了多种语言的实现:java、c#、c++、go 和python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用 xml进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。

官方地址: https://github.com/google/protobuf

使用

这里的使用就只介绍Java相关的使用。

首先我们需要建立一个proto文件,在该文件定义我们需要传输的文件。

例如我们需要定义一个用户的信息,包含的字段主要有编号、名称、年龄。

那么该protobuf文件的格式如下:

:这里使用的是proto3,相关的注释我已写了,这里便不再过多讲述了。需要注意一点的是proto文件和生成的Java文件名称不能一致!

  1. syntax = "proto3";
  2. // 生成的包名
  3. option java_package="com.pancm.protobuf";
  4. //生成的java名
  5. option java_outer_classname = "UserInfo";
  6. message UserMsg {
  7. // ID
  8. int32 id = 1;
  9. // 姓名
  10. string name = 2;
  11. // 年龄
  12. int32 age = 3;
  13. // 状态
  14. int32 state = 4;
  15. }

创建好该文件之后,我们把该文件和protoc.exe(生成Java文件的软件)放到E盘目录下的protobuf文件夹下,然后再到该目录的dos界面下输入:protoc.exe --java_out=文件绝对路径名称

例如:

  1. protoc.exe --java_out=E:\protobuf User.proto

输入完之后,回车即可在同级目录看到已经生成好的Java文件,然后将该文件放到项目中该文件指定的路径下即可。

注:生成protobuf的文件软件和测试的protobuf文件我也整合到该项目中了,可以直接获取的。

Java文件生成好之后,我们再来看怎么使用。

这里我就直接贴代码了,并且将注释写在代码中,应该更容易理解些吧。。。

代码示例:

  1. // 按照定义的数据结构,创建一个对象
  2. UserInfo.UserMsg.Builder userInfo = UserInfo.UserMsg.newBuilder();
  3. userInfo.setId(1);
  4. userInfo.setName("xuwujing");
  5. userInfo.setAge(18);
  6. UserInfo.UserMsg userMsg = userInfo.build();
  7. // 将数据写到输出流
  8. ByteArrayOutputStream output = new ByteArrayOutputStream();
  9. userMsg.writeTo(output);
  10. // 将数据序列化后发送
  11. byte[] byteArray = output.toByteArray();
  12. // 接收到流并读取
  13. ByteArrayInputStream input = new ByteArrayInputStream(byteArray);
  14. // 反序列化
  15. UserInfo.UserMsg userInfo2 = UserInfo.UserMsg.parseFrom(input);
  16. System.out.println("id:" + userInfo2.getId());
  17. System.out.println("name:" + userInfo2.getName());
  18. System.out.println("age:" + userInfo2.getAge());

注:这里说明一点,因为protobuf是通过二进制进行传输,所以需要注意下相应的编码。还有使用protobuf也需要注意一下一次传输的最大字节长度。

输出结果:

  1. id:1
  2. name:xuwujing
  3. age:18

SpringBoot整合Netty

说明:如果想直接获取工程那么可以直接跳到底部,通过链接下载工程代码。

开发准备

环境要求

JDK::1.8

Netty::4.0或以上(不包括5)

Protobuf:3.0或以上

如果对Netty不熟的话,可以看看我之前写的一些文章。大神请无视

地址:https://blog.csdn.net/column/details/17640.html

首先还是Maven的相关依赖:

  1. <properties>
  2. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  3. <java.version>1.8</java.version>
  4. <netty.version>4.1.22.Final</netty.version>
  5. <protobuf.version>3.5.1</protobuf.version>
  6. <springboot>1.5.9.RELEASE</springboot>
  7. <fastjson>1.2.41</fastjson>
  8. <maven.compiler.source>1.8</maven.compiler.source>
  9. <maven.compiler.target>1.8</maven.compiler.target>
  10. </properties>
  11. <dependencies>
  12. <dependency>
  13. <groupId>org.springframework.boot</groupId>
  14. <artifactId>spring-boot-starter</artifactId>
  15. <version>${springboot}</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-test</artifactId>
  20. <version>${springboot}</version>
  21. <scope>test</scope>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-devtools</artifactId>
  26. <version>${springboot}</version>
  27. <optional>true</optional>
  28. </dependency>
  29. <dependency>
  30. <groupId>io.netty</groupId>
  31. <artifactId>netty-all</artifactId>
  32. <version>${netty.version}</version>
  33. </dependency>
  34. <dependency>
  35. <groupId>com.google.protobuf</groupId>
  36. <artifactId>protobuf-java</artifactId>
  37. <version>${protobuf.version}</version>
  38. </dependency>
  39. <dependency>
  40. <groupId>com.alibaba</groupId>
  41. <artifactId>fastjson</artifactId>
  42. <version>${fastjson}</version>
  43. </dependency>
  44. <dependency>
  45. <groupId>junit</groupId>
  46. <artifactId>junit</artifactId>
  47. <version>4.12</version>
  48. <scope>test</scope>
  49. </dependency>
  50. </dependencies>

添加了相应的maven依赖之后,配置文件这块暂时没有什么可以添加的,因为暂时就一个监听的端口而已。

代码编写

代码模块主要分为服务端和客户端。

主要实现的业务逻辑:

服务端启动成功之后,客户端也启动成功,这时服务端会发送一条protobuf格式的信息给客户端,然后客户端给予相应的应答。客户端与服务端连接成功之后,客户端每个一段时间会发送心跳指令给服务端,告诉服务端该客户端还存过中,如果客户端没有在指定的时间发送信息,服务端会关闭与该客户端的连接。当客户端无法连接到服务端之后,会每隔一段时间去尝试重连,只到重连成功!

服务端

首先是编写服务端的启动类,相应的注释在代码中写得很详细了,这里也不再过多讲述了。不过需要注意的是,在之前的我写的Netty文章中,是通过main方法直接启动服务端,因此是直接new一个对象的。而在和SpringBoot整合之后,我们需要将Netty交给springBoot去管理,所以这里就用了相应的注解。

代码如下:

  1. @Service("nettyServer")
  2. public class NettyServer {
  3. private static final int port = 9876; // 设置服务端端口
  4. private static EventLoopGroup boss = new NioEventLoopGroup(); // 通过nio方式来接收连接和处理连接
  5. private static EventLoopGroup work = new NioEventLoopGroup(); // 通过nio方式来接收连接和处理连接
  6. private static ServerBootstrap b = new ServerBootstrap();
  7. @Autowired
  8. private NettyServerFilter nettyServerFilter;
  9. public void run() {
  10. try {
  11. b.group(boss, work);
  12. b.channel(NioServerSocketChannel.class);
  13. b.childHandler(nettyServerFilter); // 设置过滤器
  14. // 服务器绑定端口监听
  15. ChannelFuture f = b.bind(port).sync();
  16. System.out.println("服务端启动成功,端口是:" + port);
  17. // 监听服务器关闭监听
  18. f.channel().closeFuture().sync();
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. } finally {
  22. // 关闭EventLoopGroup,释放掉所有资源包括创建的线程
  23. work.shutdownGracefully();
  24. boss.shutdownGracefully();
  25. }
  26. }
  27. }

服务端主类编写完毕之后,我们再来设置下相应的过滤条件。

这里需要继承Netty中ChannelInitializer类,然后重写initChannel该方法,进行添加相应的设置,如心跳超时设置,传输协议设置,以及相应的业务实现类。

代码如下:

  1. @Component
  2. public class NettyServerFilter extends ChannelInitializer<SocketChannel> {
  3. @Autowired
  4. private NettyServerHandler nettyServerHandler;
  5. @Override
  6. protected void initChannel(SocketChannel ch) throws Exception {
  7. ChannelPipeline ph = ch.pipeline();
  8. //入参说明: 读超时时间、写超时时间、所有类型的超时时间、时间格式
  9. ph.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));
  10. // 解码和编码,应和客户端一致
  11. //传输的协议 Protobuf
  12. ph.addLast(new ProtobufVarint32FrameDecoder());
  13. ph.addLast(new ProtobufDecoder(UserMsg.getDefaultInstance()));
  14. ph.addLast(new ProtobufVarint32LengthFieldPrepender());
  15. ph.addLast(new ProtobufEncoder());
  16. //业务逻辑实现类
  17. ph.addLast("nettyServerHandler", nettyServerHandler);
  18. }
  19. }

服务相关的设置的代码写完之后,我们再来编写主要的业务代码。

使用Netty编写业务层的代码,我们需要继承ChannelInboundHandlerAdapterSimpleChannelInboundHandler类,在这里顺便说下它们两的区别吧。

继承SimpleChannelInboundHandler类之后,会在接收到数据后会自动release掉数据占用的Bytebuffer资源。并且继承该类需要指定数据格式。

而继承ChannelInboundHandlerAdapter则不会自动释放,需要手动调用ReferenceCountUtil.release()等方法进行释放。继承该类不需要指定数据格式。

所以在这里,个人推荐服务端继承ChannelInboundHandlerAdapter,手动进行释放,防止数据未处理完就自动释放了。而且服务端可能有多个客户端进行连接,并且每一个客户端请求的数据格式都不一致,这时便可以进行相应的处理。

客户端根据情况可以继承SimpleChannelInboundHandler类。好处是直接指定好传输的数据格式,就不需要再进行格式的转换了。

代码如下:

  1. @Service("nettyServerHandler")
  2. public class NettyServerHandler extends ChannelInboundHandlerAdapter {
  3. /** 空闲次数 */
  4. private int idle_count = 1;
  5. /** 发送次数 */
  6. private int count = 1;
  7. /**
  8. * 建立连接时,发送一条消息
  9. */
  10. @Override
  11. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  12. System.out.println("连接的客户端地址:" + ctx.channel().remoteAddress());
  13. UserInfo.UserMsg userMsg = UserInfo.UserMsg.newBuilder().setId(1).setAge(18).setName("xuwujing").setState(0)
  14. .build();
  15. ctx.writeAndFlush(userMsg);
  16. super.channelActive(ctx);
  17. }
  18. /**
  19. * 超时处理 如果5秒没有接受客户端的心跳,就触发; 如果超过两次,则直接关闭;
  20. */
  21. @Override
  22. public void userEventTriggered(ChannelHandlerContext ctx, Object obj) throws Exception {
  23. if (obj instanceof IdleStateEvent) {
  24. IdleStateEvent event = (IdleStateEvent) obj;
  25. if (IdleState.READER_IDLE.equals(event.state())) { // 如果读通道处于空闲状态,说明没有接收到心跳命令
  26. System.out.println("已经5秒没有接收到客户端的信息了");
  27. if (idle_count > 1) {
  28. System.out.println("关闭这个不活跃的channel");
  29. ctx.channel().close();
  30. }
  31. idle_count++;
  32. }
  33. } else {
  34. super.userEventTriggered(ctx, obj);
  35. }
  36. }
  37. /**
  38. * 业务逻辑处理
  39. */
  40. @Override
  41. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  42. System.out.println("第" + count + "次" + ",服务端接受的消息:" + msg);
  43. try {
  44. // 如果是protobuf类型的数据
  45. if (msg instanceof UserMsg) {
  46. UserInfo.UserMsg userState = (UserInfo.UserMsg) msg;
  47. if (userState.getState() == 1) {
  48. System.out.println("客户端业务处理成功!");
  49. } else if(userState.getState() == 2){
  50. System.out.println("接受到客户端发送的心跳!");
  51. }else{
  52. System.out.println("未知命令!");
  53. }
  54. } else {
  55. System.out.println("未知数据!" + msg);
  56. return;
  57. }
  58. } catch (Exception e) {
  59. e.printStackTrace();
  60. } finally {
  61. ReferenceCountUtil.release(msg);
  62. }
  63. count++;
  64. }
  65. /**
  66. * 异常处理
  67. */
  68. @Override
  69. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  70. cause.printStackTrace();
  71. ctx.close();
  72. }
  73. }

还有个服务端的启动类,之前是通过main方法直接启动, 不过这里改成了通过springBoot进行启动,差别不大。

代码如下:

  1. @SpringBootApplication
  2. public class NettyServerApp {
  3. public static void main(String[] args) {
  4. // 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
  5. ApplicationContext context = SpringApplication.run(NettyServerApp.class, args);
  6. NettyServer nettyServer = context.getBean(NettyServer.class);
  7. nettyServer.run();
  8. }
  9. }

到这里服务端相应的代码就编写完毕了。

客户端

客户端这边的代码和服务端的很多地方都类似,我就不再过多细说了,主要将一些不同的代码拿出来简单的讲述下。

首先是客户端的主类,基本和服务端的差不多,也就是多了监听的端口和一个监听器(用来监听是否和服务端断开连接,用于重连)。

主要实现的代码逻辑如下:

  1. public void doConnect(Bootstrap bootstrap, EventLoopGroup eventLoopGroup) {
  2. ChannelFuture f = null;
  3. try {
  4. if (bootstrap != null) {
  5. bootstrap.group(eventLoopGroup);
  6. bootstrap.channel(NioSocketChannel.class);
  7. bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
  8. bootstrap.handler(nettyClientFilter);
  9. bootstrap.remoteAddress(host, port);
  10. f = bootstrap.connect().addListener((ChannelFuture futureListener) -> {
  11. final EventLoop eventLoop = futureListener.channel().eventLoop();
  12. if (!futureListener.isSuccess()) {
  13. System.out.println("与服务端断开连接!在10s之后准备尝试重连!");
  14. eventLoop.schedule(() -> doConnect(new Bootstrap(), eventLoop), 10, TimeUnit.SECONDS);
  15. }
  16. });
  17. if(initFalg){
  18. System.out.println("Netty客户端启动成功!");
  19. initFalg=false;
  20. }
  21. // 阻塞
  22. f.channel().closeFuture().sync();
  23. }
  24. } catch (Exception e) {
  25. System.out.println("客户端连接失败!"+e.getMessage());
  26. }
  27. }

注:监听器这块的实现用的是JDK1.8的写法。

客户端过滤其这块基本和服务端一直。不过需要注意的是,传输协议、编码和解码应该一致,还有心跳的读写时间应该小于服务端所设置的时间。

改动的代码如下:

  1. ChannelPipeline ph = ch.pipeline();
  2. /*
  3. * 解码和编码,应和服务端一致
  4. * */
  5. //入参说明: 读超时时间、写超时时间、所有类型的超时时间、时间格式
  6. ph.addLast(new IdleStateHandler(0, 4, 0, TimeUnit.SECONDS));

客户端的业务代码逻辑。

主要实现的几点逻辑是心跳按时发送以及解析服务发送的protobuf格式的数据。

这里比服务端多个个注解, 该注解Sharable主要是为了多个handler可以被多个channel安全地共享,也就是保证线程安全。

废话就不多说了,代码如下:

  1. @Service("nettyClientHandler")
  2. @ChannelHandler.Sharable
  3. public class NettyClientHandler extends ChannelInboundHandlerAdapter {
  4. @Autowired
  5. private NettyClient nettyClient;
  6. /** 循环次数 */
  7. private int fcount = 1;
  8. /**
  9. * 建立连接时
  10. */
  11. @Override
  12. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  13. System.out.println("建立连接时:" + new Date());
  14. ctx.fireChannelActive();
  15. }
  16. /**
  17. * 关闭连接时
  18. */
  19. @Override
  20. public void channelInactive(ChannelHandlerContext ctx) throws Exception {
  21. System.out.println("关闭连接时:" + new Date());
  22. final EventLoop eventLoop = ctx.channel().eventLoop();
  23. nettyClient.doConnect(new Bootstrap(), eventLoop);
  24. super.channelInactive(ctx);
  25. }
  26. /**
  27. * 心跳请求处理 每4秒发送一次心跳请求;
  28. *
  29. */
  30. @Override
  31. public void userEventTriggered(ChannelHandlerContext ctx, Object obj) throws Exception {
  32. System.out.println("循环请求的时间:" + new Date() + ",次数" + fcount);
  33. if (obj instanceof IdleStateEvent) {
  34. IdleStateEvent event = (IdleStateEvent) obj;
  35. if (IdleState.WRITER_IDLE.equals(event.state())) { // 如果写通道处于空闲状态,就发送心跳命令
  36. UserMsg.Builder userState = UserMsg.newBuilder().setState(2);
  37. ctx.channel().writeAndFlush(userState);
  38. fcount++;
  39. }
  40. }
  41. }
  42. /**
  43. * 业务逻辑处理
  44. */
  45. @Override
  46. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  47. // 如果不是protobuf类型的数据
  48. if (!(msg instanceof UserMsg)) {
  49. System.out.println("未知数据!" + msg);
  50. return;
  51. }
  52. try {
  53. // 得到protobuf的数据
  54. UserInfo.UserMsg userMsg = (UserInfo.UserMsg) msg;
  55. // 进行相应的业务处理。。。
  56. // 这里就从简了,只是打印而已
  57. System.out.println(
  58. "客户端接受到的用户信息。编号:" + userMsg.getId() + ",姓名:" + userMsg.getName() + ",年龄:" + userMsg.getAge());
  59. // 这里返回一个已经接受到数据的状态
  60. UserMsg.Builder userState = UserMsg.newBuilder().setState(1);
  61. ctx.writeAndFlush(userState);
  62. System.out.println("成功发送给服务端!");
  63. } catch (Exception e) {
  64. e.printStackTrace();
  65. } finally {
  66. ReferenceCountUtil.release(msg);
  67. }
  68. }
  69. }

那么到这里客户端的代码也编写完毕了。

功能测试

首先启动服务端,然后再启动客户端。

我们来看看结果是否如上述所说。

服务端输出结果:

  1. 服务端启动成功,端口是:9876
  2. 连接的客户端地址:/127.0.0.1:53319
  3. 1次,服务端接受的消息:state: 1
  4. 客户端业务处理成功!
  5. 2次,服务端接受的消息:state: 2
  6. 接受到客户端发送的心跳!
  7. 3次,服务端接受的消息:state: 2
  8. 接受到客户端发送的心跳!
  9. 4次,服务端接受的消息:state: 2
  10. 接受到客户端发送的心跳!

客户端输入结果:

  1. Netty客户端启动成功!
  2. 建立连接时:Mon Jul 16 23:31:58 CST 2018
  3. 客户端接受到的用户信息。编号:1,姓名:xuwujing,年龄:18
  4. 成功发送给服务端!
  5. 循环请求的时间:Mon Jul 16 23:32:02 CST 2018,次数1
  6. 循环请求的时间:Mon Jul 16 23:32:06 CST 2018,次数2
  7. 循环请求的时间:Mon Jul 16 23:32:10 CST 2018,次数3
  8. 循环请求的时间:Mon Jul 16 23:32:14 CST 2018,次数4

通过打印信息可以看出如上述所说。

接下来我们再来看看客户端是否能够实现重连。

先启动客户端,再启动服务端。

客户端输入结果:

  1. Netty客户端启动成功!
  2. 与服务端断开连接!在10s之后准备尝试重连!
  3. 客户端连接失败!AbstractChannel$CloseFuture@1fbaa3ac(incomplete)
  4. 建立连接时:Mon Jul 16 23:41:33 CST 2018
  5. 客户端接受到的用户信息。编号:1,姓名:xuwujing,年龄:18
  6. 成功发送给服务端!
  7. 循环请求的时间:Mon Jul 16 23:41:38 CST 2018,次数1
  8. 循环请求的时间:Mon Jul 16 23:41:42 CST 2018,次数2
  9. 循环请求的时间:Mon Jul 16 23:41:46 CST 2018,次数3

服务端输出结果:

  1. 服务端启动成功,端口是:9876
  2. 连接的客户端地址:/127.0.0.1:53492
  3. 1次,服务端接受的消息:state: 1
  4. 客户端业务处理成功!
  5. 2次,服务端接受的消息:state: 2
  6. 接受到客户端发送的心跳!
  7. 3次,服务端接受的消息:state: 2
  8. 接受到客户端发送的心跳!
  9. 4次,服务端接受的消息:state: 2

结果也如上述所说!

其它

关于SpringBoot整合Netty使用Protobuf进行数据传输到这里就结束了。

SpringBoot整合Netty使用Protobuf进行数据传输的项目工程地址:

https://github.com/xuwujing/springBoot-study/tree/master/springboot-netty-protobuf

对了,也有不使用springBoot整合的Netty项目工程地址:

https://github.com/xuwujing/Netty-study/tree/master/Netty-protobuf

原创不易,如果感觉不错,希望给个推荐!您的支持是我写作的最大动力!

版权声明:

作者:虚无境

博客园出处:http://www.cnblogs.com/xuwujing

CSDN出处:http://blog.csdn.net/qazwsxpcm    

个人博客出处:http://www.panchengming.com

SpringBoot整合Netty并使用Protobuf进行数据传输(附工程)的更多相关文章

  1. springboot整合netty(二)

    目录 前言 正文 代码 1. 新建一个springboot项目,在pom文件中添加netty依赖: 2.新建netty服务 3.netty调用所需的服务类 4 springboot启动类 5.测试 我 ...

  2. springboot整合netty的多种方式

    netty作为一个高性能的io框架,是非好用的一个技术框架, Netty 是一个基于NIO的客户.服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户. ...

  3. springboot整合netty,多种启动netty的方式,展现bean得多种启动方法

    首先讲解下,spring中初始化加载问题: 很多时候,我们自己写的线程池,还有bean对象,还有其他的服务类,都可以通过,相关注解进行交给spring去管理,那么我们如何让nettyserver初始化 ...

  4. SpringBoot整合Netty

    总体来说只需要三个步骤,下面详细介绍 第一步 创建SpringBoot工程snetty 第二步 创建子模块也就是ModuleClient,Server,均为SpringBoot工程 第三步 将服务器端 ...

  5. 第6章 使用springboot整合netty搭建后台

    我们不会去使用自增长的id,在现阶段的互联网开发过程中,自增长的id是已经不适用了.在未来随着系统版本的迭代,用户数量的递增,肯定会做分库分表,去做一些相应的切分.在这个时候我们就需要有一个唯一的id ...

  6. SpringBoot整合Netty实现socket通讯简单demo

    依赖 <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifa ...

  7. SpringBoot 整合 spring security oauth2 jwt完整示例 附源码

    废话不说直接进入主题(假设您已对spring security.oauth2.jwt技术的了解,不懂的自行搜索了解) 依赖版本 springboot 2.1.5.RELEASE spring-secu ...

  8. SpringBoot整合Redis使用Restful风格实现CRUD功能

    前言 本篇文章主要介绍的是SpringBoot整合Redis,使用Restful风格实现的CRUD功能. Redis 介绍 Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-valu ...

  9. SpringBoot整合Swagger和Actuator

    前言 本篇文章主要介绍的是SpringBoot整合Swagger(API文档生成框架)和SpringBoot整合Actuator(项目监控)使用教程. SpringBoot整合Swagger 说明:如 ...

随机推荐

  1. java集合框架之ArrayList

    参考http://how2j.cn/k/collection/collection-arraylist/363.html 使用数组的局限性 一个长度是10的数据:Hero[] heroArr=new ...

  2. reader-write.go

    {         return n, err     }     r.bucket.Wait(int64(n))     return n, err } type writer struct {   ...

  3. 【莫比乌斯反演】BZOJ2154 Crash的数字表格

    Description 求sigma lcm(x,y),x<=n,y<=m.n,m<=1e7. Solution lcm没有什么直接做的好方法,用lcm=x*y/gcd转成gcd来做 ...

  4. modbus学习笔记——帧

    几个需要先搞懂的概念 1.modbus的数据类型 modbus定义了四种数据类型,这四种数据类型分别叫"离散量输入""线圈""输入寄存器"& ...

  5. js面试题1

    1.介绍js的基本数据类型 Undefined.Null.Boolean.Number.String 2.js有哪些内置对象? 数据封装类对象:Object.Array.Boolean.Number ...

  6. Android之微信朋友圈UI实现--ExpandableListView+GridView

    PS:我们都知道微信,更是知道朋友圈,很多人在朋友圈里卖起了化妆品,打入广告等为自己做一下推广,里面会附带一写好看的图片,上面有标题,有描述,整体布局每场的美观,那么这是怎么实现的呢,有些人可能会单个 ...

  7. python接口自动化(二十五)--unittest断言——下(详解)

    简介 本篇还是回归到我们最初始的话题,想必大家都忘记了,没关系看这里:传送门  没错最初的话题就是登录,由于博客园的登录机制改变了,本篇以我找到的开源免费的登录API为案例,结合 unittest 框 ...

  8. 手把手学会MySql主从配置

    001 开启二进制日志. vi /data/mysql/port-3306/my.cnf log-bin=mysql-bin 002 在master上为从主机新建一个专门用于同步的账户,并授权REPL ...

  9. 深入理解OkHttp源码(一)——提交请求

    本篇文章主要介绍OkHttp执行同步和异步请求的大体流程.主要流程如下图: 主要分析到getResponseWidthInterceptorChain方法,该方法为具体的根据请求获取响应部分,留着后面 ...

  10. WebGL three.js学习笔记 纹理贴图模拟太阳系运转

    纹理贴图的应用以及实现一个太阳系的自转公转 点击查看demo演示 demo地址:https://nsytsqdtn.github.io/demo/solar/solar three.js中的纹理 纹理 ...