项目地址:https://gitee.com/q529075990qqcom/NB-IOT.git
我们需要一个创建mavne项目,这个项目是我已经写好的项目,项目结构图如下:

创建子模块,准备好依赖Netty4.1版本
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.72.Final</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.28</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.28</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>5.3.0</version>
</dependency>
</dependencies>
maven依赖
序列化的定义是:将一个对象编码成一个字节流(I/O);而与之相反的操作被称为反序列化。
package serializer;
/**
* @description:
* @author: quliang
* @create: 2022-10-20 15:16
**/
public interface Serializer {
/**
* 序列化
*
* @param obj
* @return
* @throws Exception
*/
byte[] serialize(Object obj) throws Exception;
/**
* 反序列化
*
* @param bytes
* @param clazz
* @param <T>
* @return
* @throws Exception
*/
<T> T deserialize(byte[] bytes, Class<T> clazz) throws Exception;
}
自定义序列化接口
package serializer;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy;
import org.objenesis.strategy.StdInstantiatorStrategy;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* @description:
* @author: quliang
* @create: 2022-10-20 15:18
**/
public class KryoSerializer implements Serializer {
private static final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> {
Kryo kryo = new Kryo();
kryo.setReferences(true);
kryo.setRegistrationRequired(false);
((DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy())
.setFallbackInstantiatorStrategy(new StdInstantiatorStrategy());
return kryo;
});
@Override
public byte[] serialize(Object obj) throws Exception {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
Output output = new Output(baos);
Kryo kryo = kryoThreadLocal.get();
kryo.writeObject(output, obj);
kryoThreadLocal.remove();
return output.toBytes();
} catch (IOException e) {
throw new Exception("序列化失败", e);
}
}
@Override
public <T> T deserialize(byte[] bytes, Class<T> clazz) throws Exception {
try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
Input input = new Input(bais);
Kryo kryo = kryoThreadLocal.get();
Object obj = kryo.readObject(input, clazz);
kryoThreadLocal.remove();
return clazz.cast(obj);
} catch (IOException e) {
throw new Exception("反序化失败");
}
}
}
Kryo实现序列化接口
我们需要解析两种协议,那我们就要提前定义好两种协议,分别是消息协议、登录协议
消息协议相关
package protocol.msg;
import lombok.Data;
import lombok.Getter;
/**
* @description: 消息协议: |magic|version|data|
* @author: quliang
* @create: 2022-12-10 20:46
**/
@Data
public class MsgProtocol {
@Getter
private byte magic=0;
@Getter
private byte version=1;
}
消息协议基类
package protocol.msg.request;
import lombok.Data;
import protocol.msg.MsgProtocol;
/**
* @description:
* @author: quliang
* @create: 2022-12-10 20:58
**/
@Data
public class MsgRequest extends MsgProtocol {
private String msg;
}
消息请求子类
package protocol.msg.response;
import lombok.Data;
import protocol.msg.MsgProtocol;
/**
* @description:
* @author: quliang
* @create: 2022-12-10 20:41
**/
@Data
public class MsgResponse extends MsgProtocol {
private int statCode;
}
消息响应子类
package encoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import protocol.msg.MsgProtocol;
import serializer.KryoSerializer;
/**
* @description:
* @author: quliang
* @create: 2022-12-10 20:53
**/
public class MsgEncoder extends MessageToByteEncoder<MsgProtocol> {
@Override
protected void encode(ChannelHandlerContext ctx, MsgProtocol msgProtocol, ByteBuf in) throws Exception {
in.writeByte(msgProtocol.getMagic());
// in.writeByte(msgProtocol.code());
in.writeByte(msgProtocol.getVersion());
byte[] data = new KryoSerializer().serialize(msgProtocol);
in.writeShort(data.length);
in.writeBytes(data);
}
}
消息协议编码
package decoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;
import protocol.msg.MsgProtocol;
import serializer.KryoSerializer;
import java.util.List;
/**
* @description:
* @author: quliang
* @create: 2022-12-10 20:52
**/
@Slf4j
public class MsgDecoder extends ByteToMessageDecoder {
private Class<MsgProtocol> msgClass;
public MsgDecoder(Class clazz) {
this.msgClass = clazz;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
try {
byte magic = in.readByte();
byte version = in.readByte();
short dataSize = in.readShort();
byte[] data = new byte[dataSize];
in.readBytes(data);
MsgProtocol baseProtocol = new KryoSerializer().deserialize(data, msgClass);
out.add(baseProtocol);
} catch (Exception e) {
//如果解码错误,将数据传递到下一个解码器中
log.error("msg decoder {}",e.getMessage());
// 重置读取字节索引,因为上边已经读了(readBytes),不加这个会导致数据为空
in.resetReaderIndex();
// 这里是复制流,复制一份,防止skipBytes跳过,导致传递的消息变成空;
ByteBuf buff = in.retainedDuplicate();
//原因是netty不允许有字节内容不读的情况发生,所以采用下边的方法解决。
in.skipBytes(in.readableBytes());
//继续传递到下一个解码器中
out.add(buff);
}
}
}
消息协议解码
登录协议相关
package protocol.system;
import lombok.Getter;
/**
* @description: 登录协议: |magic|version|code|data|
* @author: quliang
* @create: 2022-12-09 18:10
**/
public class LoginProtocol {
@Getter
private byte magic=0;
@Getter
private byte version=1;
@Getter
public byte code;
}
登录协议基类
package protocol.system.request;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import protocol.system.LoginProtocol;
/**
* @description:
* @author: quliang
* @create: 2022-12-06 18:17
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginRequest extends LoginProtocol {
private String userId;
private String userName;
}
登录请求子类
package protocol.system.response;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import protocol.system.LoginProtocol;
/**
* @description:
* @author: quliang
* @create: 2022-12-06 18:22
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginResponse extends LoginProtocol {
private String msg;
private String data;
}
登录响应子类
package encoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import protocol.system.LoginProtocol;
import serializer.KryoSerializer;
/**
* @description:
* @author: quliang
* @create: 2022-12-06 22:11
**/
public class LoginEncoder extends MessageToByteEncoder<LoginProtocol> {
@Override
protected void encode(ChannelHandlerContext ctx, LoginProtocol baseProtocol, ByteBuf in) throws Exception {
in.writeByte(baseProtocol.getMagic());
in.writeByte(baseProtocol.getCode());
in.writeByte(baseProtocol.getVersion());
byte[] data = new KryoSerializer().serialize(baseProtocol);
in.writeShort(data.length);
in.writeBytes(data);
}
}
登录协议编码
package decoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;
import protocol.system.LoginProtocol;
import serializer.KryoSerializer;
import java.util.List;
/**
* @description:
* @author: quliang
* @create: 2022-12-06 17:59
**/
@Slf4j
public class LoginDecoder extends ByteToMessageDecoder {
private Class<LoginProtocol> clazz;
public LoginDecoder(Class clazz) {
this.clazz = clazz;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
try {
byte magic = in.readByte();
byte code = in.readByte();
byte version = in.readByte();
short dataSize = in.readShort();
byte[] data = new byte[dataSize];
in.readBytes(data);
LoginProtocol baseProtocol = new KryoSerializer().deserialize(data, clazz);
out.add(baseProtocol);
} catch (Exception e) {
//如果解码错误,将数据传递到下一个解码器中
log.error("login decoder {}", e.getMessage());
// 重置读取字节索引,因为上边已经读了(readBytes),不加这个会导致数据为空
in.resetReaderIndex();
// 这里是复制流,复制一份,防止skipBytes跳过,导致传递的消息变成空;
ByteBuf buff = in.retainedDuplicate();
//原因是netty不允许有字节内容不读的情况发生,所以采用下边的方法解决。
in.skipBytes(in.readableBytes());
//继续传递到下一个解码器中
out.add(buff);
}
}
}
登录协议解码
这样公共模块就创建完成了
package com.ql;
import com.ql.handler.MsgHandler;
import decoder.LoginDecoder;
import decoder.MsgDecoder;
import com.ql.handler.LoginHandler;
import encoder.LoginEncoder;
import encoder.MsgEncoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import protocol.system.request.LoginRequest;
import protocol.msg.request.MsgRequest;
/**
* @author quliang
* @description 服务端
* @date 2022-12-06 17:39:14
*/
@Slf4j
public class IotServer {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap().group(
bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new LoggingHandler(LogLevel.INFO));
/**
* 心跳机制
*/
//pipeline.addLast(new IdleStateHandler(5, 10, 5, TimeUnit.SECONDS));
/**
* 消息、登录解码器
*/
pipeline.addLast(new LoginDecoder(LoginRequest.class));
pipeline.addLast(new MsgDecoder(MsgRequest.class));
/**
* 消息、登录处理器
*/
pipeline.addLast(new MsgHandler());
pipeline.addLast(new LoginHandler());
/**
* 消息、登录编码器
*/
pipeline.addLast(new MsgEncoder());
pipeline.addLast(new LoginEncoder());
}
})
.option(ChannelOption.SO_BACKLOG, 1024);
ChannelFuture cf = bootstrap.bind(8849).sync();
log.info("socket服务端启动成功 {}", cf.channel().localAddress().toString());
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
服务端代码package com.ql.handler;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import protocol.msg.request.MsgRequest;
import protocol.msg.response.MsgResponse;
/**
* @description: 消息处理器
* @author: quliang
* @create: 2022-12-10 20:57
**/
@Slf4j
@ChannelHandler.Sharable
public class MsgHandler extends SimpleChannelInboundHandler<MsgRequest> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("上线{}", ctx.channel().remoteAddress().toString());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, MsgRequest request) throws Exception {
log.info("服务端读取消息体数据为{}", request.toString());
MsgResponse response = new MsgResponse();
response.setStatCode(200);
ctx.channel().writeAndFlush(response);
}
}
服务端消息处理器
package com.ql.handler;
import io.netty.channel.*;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import protocol.system.request.LoginRequest;
import protocol.system.response.LoginResponse;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @description: 登录处理器
* @author: quliang
* @create: 2022-12-06 18:14
**/
@Slf4j
@ChannelHandler.Sharable
public class LoginHandler extends SimpleChannelInboundHandler<LoginRequest>{
private static AtomicInteger READER_COUNT = new AtomicInteger(0);
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("服务端:{} 通道开启!", ctx.channel().localAddress().toString());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
log.info("服务端: {} 通道关闭!", ctx.channel().localAddress().toString());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, LoginRequest loginRequest) throws Exception {
log.info("读取数据 {} ", loginRequest.toString());
LoginResponse response= new LoginResponse("success", null);
ctx.channel().writeAndFlush(response);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
log.info("...............数据接收-完毕...............");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
log.error("...............业务处理异常...............{}", cause);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
Channel channel = ctx.channel();
switch (event.state()) {
case READER_IDLE:
log.info("读空闲");
READER_COUNT.addAndGet(1);
break;
case WRITER_IDLE:
log.info("写空闲");
break;
default:
break;
}
ctx.disconnect();
if (READER_COUNT.get() > 3) {
log.info("close this channel {}", channel.remoteAddress().toString());
}
}
}
}
服务端登录处理器
服务端其实很多都是直接引用公共模块的,代码也并不复杂
package com.ql;
import com.ql.handler.ClientMsgHandler;
import decoder.MsgDecoder;
import encoder.MsgEncoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import protocol.msg.response.MsgResponse;
import java.net.InetSocketAddress;
/**
* @author quliang
* @description 客户端
* @date 2022-12-06 17:37:56
*/
@Slf4j
public class IotClientMsg {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup clientGroup = new NioEventLoopGroup();
try {
Bootstrap bs = new Bootstrap();
bs.group(clientGroup)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress("169.254.190.154", 8849))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LoggingHandler(LogLevel.INFO));
//消息解码器
pipeline.addLast(new MsgDecoder(MsgResponse.class));
//客户端消息处理器
pipeline.addLast(new ClientMsgHandler());
//消息编码器
pipeline.addLast(new MsgEncoder());
}
});
ChannelFuture cf = bs.connect().sync();
log.info("启动成功{}", cf.channel().localAddress().toString());
// Scanner scanner = new Scanner(System.in);
cf.channel().closeFuture().sync();
} finally {
clientGroup.shutdownGracefully().sync();
}
}
}
客户端代码package com.ql.handler;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import protocol.msg.request.MsgRequest;
import protocol.msg.response.MsgResponse;
/**
* @description:
* @author: quliang
* @create: 2022-12-10 20:40
**/
@Slf4j
@ChannelHandler.Sharable
public class ClientMsgHandler extends SimpleChannelInboundHandler<MsgResponse> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
MsgRequest request = new MsgRequest();
request.setMsg("hello");
ctx.channel().writeAndFlush(request);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, MsgResponse response) throws Exception {
int code = response.getStatCode();
log.info("消息处理器读取响应对象数据为{}", code);
}
}
客户端消息处理器
消息客户端代码也并不复杂
package com.ql;
import com.ql.handler.ClientLoginHandler;
import decoder.LoginDecoder;
import encoder.LoginEncoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import protocol.system.response.LoginResponse;
import java.net.InetSocketAddress;
/**
* @author quliang
* @description 客户端
* @date 2022-12-06 17:37:56
*/
@Slf4j
public class IotClientLogin {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup clientGroup = new NioEventLoopGroup();
try {
Bootstrap bs = new Bootstrap();
bs.group(clientGroup)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress("169.254.190.154", 8849))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LoggingHandler(LogLevel.INFO));
pipeline.addLast(new LoginDecoder(LoginResponse.class));
//pipeline.addLast(new MsgDecoder(MsgResponse.class));
//pipeline.addLast(new ClientMsgHandler());
pipeline.addLast(new ClientLoginHandler());
//pipeline.addLast(new MsgEncoder());
pipeline.addLast(new LoginEncoder());
}
});
ChannelFuture cf = bs.connect().sync();
log.info("启动成功{}", cf.channel().localAddress().toString());
// Scanner scanner = new Scanner(System.in);
cf.channel().closeFuture().sync();
} finally {
clientGroup.shutdownGracefully().sync();
}
}
}
客户端代码package com.ql.handler;
import io.netty.channel.*;
import lombok.extern.slf4j.Slf4j;
import protocol.system.request.LoginRequest;
import protocol.system.response.LoginResponse;
import java.util.Scanner;
/**
* @description:
* @author: quliang
* @create: 2022-12-06 22:16
**/
@Slf4j
@ChannelHandler.Sharable
public class ClientLoginHandler extends SimpleChannelInboundHandler<LoginResponse> {
private Scanner scanner = new Scanner(System.in);
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("客户端:{} 通道开启!", ctx.channel().localAddress().toString());
login(ctx);
}
/**
* 登录方法
* @param ctx
*/
private void login(ChannelHandlerContext ctx) {
LoginRequest request = new LoginRequest("123", "123");
ctx.channel().writeAndFlush(request);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("客户端: {} 读取数据 {}", ctx.channel().localAddress().toString(), msg.toString());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, LoginResponse response) throws Exception {
log.info("客户端: {} 读取数据 {}", ctx.channel().localAddress().toString(), response.toString());
String msg = response.getMsg();
log.info("========{}", msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
log.info("...............数据接收-完毕...............");
}
}
客户端登录处理器

我们是怎么通过这个项目来实现不同协议编解码?
其实也不难,我们仔细看MsgDecoder、LoginDecoder两个类其中一个类的代码,其中有个巧妙的操作就是使用try-catch,
只要解码器无法解码发生异常,就重置读取字节索引传递到下一个解码器中,直到传递到正确解码器中。不过为了兼容多种协议,
解码异常也会让服务端性能有所下降的,取舍之间必有得失。
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][
我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总
我有一个像这样的ruby类:require'logger'classTdefdo_somethinglog=Logger.new(STDERR)log.info("Hereisaninfomessage")endend测试脚本行如下:#!/usr/bin/envrubygem"minitest"require'minitest/autorun'require_relative't'classTestMailProcessorClasses当我运行这个测试时,out和err都是空字符串。我看到消息打印在stderr上(在终端上)。有没有办法让Logger和capture_io一起玩得
我正在构建一个小部件来显示奥运会的奖牌数。我有一个“国家”对象的集合,其中每个对象都有一个“名称”属性,以及奖牌计数的“金”、“银”、“铜”。列表应该排序:1.首先是奖牌总数2.如果奖牌相同,按类型分割(金>银>铜,即2金>1金+1银)3.如果奖牌和类型相同,则按字母顺序子排序我正在用ruby做这件事,但我想语言并不重要。我确实找到了一个解决方案,但如果感觉必须有更优雅的方法来实现它。这是我做的:使用加权奖牌总数创建一个虚拟属性。因此,如果他们有2个金牌和1个银牌,加权总数将为“3.020100”。1金1银1铜为“3.010101”由于我们希望将奖牌数排序为最高的,因此列表按降序排