Netty 私有协议栈 代码实现
私有协议介绍
通信协议从广义上区分,可以分为共有协议和私有协议。由于私有协议的灵活性,它往往会在某个公司或者组织内部使用,按需定制,也因为如此,升级起来会非常方便,灵活性好。
Netty私有协议栈功能设计
Netty协议栈用于内部各模块之间的通信,它基于TCP/IP协议栈,是一个类似HTTP协议的应用层协议栈,相比与传统的标准协议栈,它更加轻巧、灵活和实用
1.功能描述
1)基于Netty的NIO通信框架,提供高性能的异步通信能力。
2)提供消息的编解码框架,可以实现POJO的序列化和反序列化。
3)提供基于IP地址的白名单接入认证机制。
4)链路的有效性校验机制
5)链路的断连重连机制。
2.通信模型
3.消息定义
Netty消息定义表(NettyMessage)
名称 |
类型 |
长度 |
描述 |
Header |
Header |
变长 |
消息头 |
Body |
Object |
变长 |
对于请求消息,它是方法的参数 对于响应消息,它是返回值 |
Netty协议消息头定义(Header)
名称 |
类型 |
长度 |
描述 |
crcCode |
整型 int |
32 |
Netty消息的校验码,它由三部分组成 1)0xABEF:固定值,表明该消息是Netty私有协议,2个字节 2)主版本号:1~255,一个字节 3)此版本号:1~255,一个字节 crcCode=0xABEF+主版本号+次版本号 |
length |
整型 int |
32 |
对于请求消息,它是方法的参数 对于响应消息,它是返回值 |
sessionID |
长整型long |
64 |
集群节点全局唯一,由回话ID生成器生成 |
type |
Byte |
8 |
0:业务请求消息 1:业务响应消息 2:业务ONE WAY消息 3:握手请求消息 4:握手应答消息 5:心跳请求消息 6:心跳应答消息 |
priority |
Byte |
8 |
消息优先级 |
attachment |
Map<String,Object> |
变长 |
扩展消息 |
代码实现
package com.cw.netty.high.protocol.bean;import java.util.HashMap; import java.util.Map;/*** 消息头header定义** @author chenwei* @create 2018-07-02 11:20**/public class Header {private int crcCode=0xabef0101;private int length;//消息长度private long sessionID;//会话IDprivate byte type;//消息类型private byte priority;//消息优先级private Map<String, Object> attachment = new HashMap<String, Object>();//附件public int getCrcCode() {return crcCode;}public void setCrcCode(int crcCode) {this.crcCode = crcCode;}public int getLength() {return length;}public void setLength(int length) {this.length = length;}public long getSessionID() {return sessionID;}public void setSessionID(long sessionID) {this.sessionID = sessionID;}public byte getType() {return type;}public void setType(byte type) {this.type = type;}public byte getPriority() {return priority;}public void setPriority(byte priority) {this.priority = priority;}public Map<String, Object> getAttachment() {return attachment;}public void setAttachment(Map<String, Object> attachment) {this.attachment = attachment;}@Overridepublic String toString() {return "Header{" +"crcCode=" + crcCode +", length=" + length +", sessionID=" + sessionID +", type=" + type +", priority=" + priority +", attachment=" + attachment +'}';} }
package com.cw.netty.high.protocol;import com.cw.netty.high.protocol.bean.Header;/*** netty消息类定义** @author chenwei* @create 2018-07-02 11:19**/public class NettyMessage {private Header header;//消息头private Object body;//消息体public Header getHeader() {return header;}public void setHeader(Header header) {this.header = header;}public Object getBody() {return body;}public void setBody(Object body) {this.body = body;}@Overridepublic String toString() {return "NettyMessage{" +"header=" + header +", body=" + body +'}';} }
package com.cw.netty.high.protocol;/*** @author chenwei* @create 2018-07-02 17:05**/public class MessageType {// public static final byte LOGIN_R2=0;//业务请求消息 // public static final byte LOGIN_1=1;//业务响应消息 // public static final byte LOGIN_2=2;//业务ONE WAY 消息public static final byte LOGIN_REQ=3;//握手请求消息public static final byte LOGIN_RESP=4;//握手应答消息public static final byte HEARTBEAT_REQ=5;//心跳请求消息public static final byte HEARTBEAT_RESP=6;//心跳应答消息 }
package com.cw.netty.high.protocol;import com.cw.netty.high.protocol.bean.MarshallingCodecFactory; import com.cw.netty.high.protocol.bean.NettyMarshallingEncoder; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageEncoder;import java.util.List; import java.util.Map;/*** 消息编码器** @author chenwei* @create 2018-07-02 11:32**/public class NettyMessageEncoder extends MessageToMessageEncoder<NettyMessage> {private NettyMarshallingEncoder marshallingEncoder;public NettyMessageEncoder(){marshallingEncoder= MarshallingCodecFactory.buildMarshallingEncoder();}@Overrideprotected void encode(ChannelHandlerContext channelHandlerContext, NettyMessage nettyMessage, List<Object> out) throws Exception{if (nettyMessage == null || nettyMessage.getHeader()==null) {throw new Exception("The encode message is null");}ByteBuf buffer = Unpooled.buffer();System.out.println("开始编码:"+nettyMessage);//按顺利编码后,根据定义的字段数据类型写入ByteBuf,解码时也要按顺序挨个取出 buffer.writeInt(nettyMessage.getHeader().getCrcCode());buffer.writeInt(nettyMessage.getHeader().getLength());buffer.writeLong(nettyMessage.getHeader().getSessionID());buffer.writeByte(nettyMessage.getHeader().getType());buffer.writeByte(nettyMessage.getHeader().getPriority());buffer.writeInt(nettyMessage.getHeader().getAttachment().size());String key=null;Object value=null;byte[] keyArray=null;//针对header中的附件编码for (Map.Entry<String,Object> param : nettyMessage.getHeader().getAttachment().entrySet()) {key=param.getKey();keyArray= key.getBytes("UTF-8");value= param.getValue();buffer.writeInt(keyArray.length);buffer.writeBytes(keyArray);marshallingEncoder.encode(channelHandlerContext,value,buffer);}if (nettyMessage.getBody() != null) {//使用MarshallingEncoder编码消息体 marshallingEncoder.encode(channelHandlerContext,nettyMessage.getBody(),buffer);}else {//没有消息体的话,就赋予0值buffer.writeInt(0);}//更新消息长度字段的值,至于为什么-8,是因为8是长度字段后的偏移量,LengthFieldBasedFrameDecoder的源码中//对长度字段和长度的偏移量之和做了判断,如果不-8,会导致LengthFieldBasedFrameDecoder解码返回null//这是 《Netty权威指南》中的写错的地方buffer.setInt(4, buffer.readableBytes()-8);//书中此处没有add,也即没有将ByteBuf加入到List中,也就没有消息进行编码了,所以导致运行了没有效果…… out.add(buffer);}}
package com.cw.netty.high.protocol;import com.cw.netty.high.protocol.bean.Header; import com.cw.netty.high.protocol.bean.MarshallingCodecFactory; import com.cw.netty.high.protocol.bean.NettyMarshallingDecoder; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.LengthFieldBasedFrameDecoder;import java.util.HashMap; import java.util.Map;/*** Netty消息解码类* @author chenwei* @create 2018-07-02 16:31** 继承LengthFieldBasedFrameDecoder是为了更好了使用它对tcp的粘包和半包处理,* 只需要给我表示消息长度的字段偏移量和消息长度自身所占的字节数,该解码器就能* 自动实现对半包的处理,调用父类LengthFieldBasedFrameDecoder的decode方法后,* 返回的就是整包消息或者为null,**/public class NettyMessageDecoder extends LengthFieldBasedFrameDecoder {NettyMarshallingDecoder marshallingDecoder;public NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {super(maxFrameLength, lengthFieldOffset, lengthFieldLength);marshallingDecoder = MarshallingCodecFactory.buildMarshallingDecoder();}@Overrideprotected Object decode(ChannelHandlerContext ctx, ByteBuf in1) throws Exception {//父类解码后的消息,后续就针对处理后的消息体进行解码,这也是//《Netty权威指南》的另一处书写错误,书中仍对原ByteBuf进行读取//由于父类decode后,读指针已经到达了消息头总长度处,此后再对原消息//进行读取后报处下标越界的异常ByteBuf frame=(ByteBuf) super.decode(ctx, in1);if (frame == null) {return null;}NettyMessage message =new NettyMessage();Header header=new Header();header.setCrcCode(frame.readInt());header.setLength(frame.readInt());header.setSessionID(frame.readLong());header.setType(frame.readByte());header.setPriority(frame.readByte());int size= frame.readInt();if (size > 0) {int keySize=0;byte[] keyArray=null;String key=null;Map<String, Object> attch = new HashMap<String, Object>();for (int i = 0; i <size ; i++) {keySize= frame.readInt();keyArray = new byte[keySize];frame.readBytes(keyArray);key = new String(keyArray, "UTF-8");attch.put(key, marshallingDecoder.decode(ctx,frame));}header.setAttachment(attch);}//readableBytes即为判断剩余可读取的字节数( this.writerIndex - this.readerIndex)//大于4说明有消息体(无消息体时readableBytes=4),故进行解码if (frame.readableBytes() > 4) {message.setBody(marshallingDecoder.decode(ctx, frame));}message.setHeader(header);return message;} }
package com.cw.netty.high.protocol;import com.cw.netty.high.protocol.bean.Header; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext;/*** @author chenwei* @create 2018-07-02 17:02**/public class LoginAuthReqHandler extends ChannelHandlerAdapter {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {//建立连接后,发送认证消息NettyMessage message=buildLoginReq();System.out.println("client 发送 认证消息:message="+message);ctx.writeAndFlush(message);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {NettyMessage message=(NettyMessage)msg;//若是握手应答消息,判断是否认证成功if (message.getHeader() != null && message.getHeader().getType() == MessageType.LOGIN_RESP) {byte loginResult= (byte)message.getBody();if (loginResult != 0) {//握手失败,关闭连接 ctx.close();}else {System.out.println("login is ok :"+message);ctx.fireChannelRead(msg);}}else{ctx.fireChannelRead(msg);}}private NettyMessage buildLoginReq(){NettyMessage message=new NettyMessage();Header header=new Header();header.setType((byte) MessageType.LOGIN_REQ);message.setHeader(header);return message;}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.fireExceptionCaught(cause);} }
package com.cw.netty.high.protocol;import com.cw.netty.high.protocol.bean.Header; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext;import java.net.InetSocketAddress; import java.util.Map; import java.util.concurrent.ConcurrentHashMap;/*** @author chenwei* @create 2018-07-02 17:19**/public class LoginAuthRespHandler extends ChannelHandlerAdapter {private Map<String, Boolean> nodeCheck = new ConcurrentHashMap<>();private String [] whiteList={"127.0.0.1","10.155.33.113"};@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {NettyMessage message=(NettyMessage)msg;//若为握手认证消息,则校验并返回响应,否则传递到下一个handlerif (message.getHeader() != null && message.getHeader().getType() == MessageType.LOGIN_REQ) {String nodeIndex= ctx.channel().remoteAddress().toString();NettyMessage loginResp=null;if (nodeCheck.containsKey(nodeIndex)) {//重复登陆,拒绝loginResp=buildResponse((byte)-1);System.out.println("重复登陆,拒绝 :ip="+nodeIndex);}else{boolean isOk=true;InetSocketAddress address=(InetSocketAddress) ctx.channel().remoteAddress();String ip= address.getAddress().getHostAddress();for (String wip:whiteList){if (wip.equals(ip)) {isOk=true;nodeCheck.put(ip, true);System.out.println("通过白名单检测 ip="+ip);break;}}loginResp=isOk?buildResponse((byte) 0):buildResponse((byte)-1);}ctx.writeAndFlush(loginResp);}else {ctx.fireChannelRead(msg);}}private NettyMessage buildResponse(byte result){NettyMessage message=new NettyMessage();Header header=new Header();header.setType(MessageType.LOGIN_RESP);message.setHeader(header);message.setBody(result);return message;}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {nodeCheck.remove(ctx.channel().remoteAddress().toString());ctx.close();ctx.fireExceptionCaught(cause);} }
package com.cw.netty.high.protocol;import com.cw.netty.high.protocol.bean.Header; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext;import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit;/*** 客户端心跳检测* @author chenwei* @create 2018-07-02 17:36**/public class HeartBeatReqHandler extends ChannelHandlerAdapter {private volatile ScheduledFuture heartBeaet;@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {NettyMessage message= (NettyMessage)msg;//认证成功后,定时发送心跳检测if (message.getHeader() != null && message.getHeader().getType() == MessageType.LOGIN_RESP) {heartBeaet=ctx.executor().scheduleAtFixedRate(new HeartBeatTask(ctx), 0, 5000, TimeUnit.MILLISECONDS);} else if (message.getHeader() != null && message.getHeader().getType() == MessageType.HEARTBEAT_RESP) {System.out.println("client receive server heart beat message :-->"+message);}else {ctx.fireChannelRead(msg);}}private class HeartBeatTask implements Runnable{ChannelHandlerContext ctx;public HeartBeatTask(ChannelHandlerContext ctx) {this.ctx=ctx;}@Overridepublic void run() {NettyMessage nettyMessage=buildHeatBeat();System.out.println("client send heart beat message to server :--->"+nettyMessage);ctx.writeAndFlush(nettyMessage);}}//心跳检测仅消息头就够了private NettyMessage buildHeatBeat(){NettyMessage message=new NettyMessage();Header header=new Header();header.setType(MessageType.HEARTBEAT_REQ);message.setHeader(header);return message;}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {if (heartBeaet != null) {heartBeaet.cancel(true);heartBeaet=null;}ctx.fireExceptionCaught(cause);} }
package com.cw.netty.high.protocol;import com.cw.netty.high.protocol.bean.Header; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext;/*** 服务器端心跳handler* @author chenwei* @create 2018-07-02 18:00**/public class HeartBeatRespHandler extends ChannelHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {NettyMessage message=(NettyMessage)msg;//收到心跳消息后,构造心跳应答消息返回if (message.getHeader() != null && message.getHeader().getType() == MessageType.HEARTBEAT_REQ) {System.out.println("receive client heart beat message :-->");NettyMessage heartBeat=buildHeatBeat();System.out.println("send heart beat response message to client:-->"+heartBeat);ctx.writeAndFlush(heartBeat);}else {ctx.fireChannelRead(msg);}}private NettyMessage buildHeatBeat(){NettyMessage message=new NettyMessage();Header header=new Header();header.setType(MessageType.HEARTBEAT_RESP);message.setHeader(header);return message;}}
package com.cw.netty.high.protocol.bean;import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.marshalling.MarshallingDecoder; import io.netty.handler.codec.marshalling.UnmarshallerProvider;/*** @author chenwei* @create 2018-07-02 16:12**/public class NettyMarshallingDecoder extends MarshallingDecoder {public NettyMarshallingDecoder(UnmarshallerProvider provider) {super(provider);}public NettyMarshallingDecoder(UnmarshallerProvider provider, int maxObjectSize) {super(provider, maxObjectSize);}public Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception{return super.decode(ctx,in);} }
package com.cw.netty.high.protocol.bean;import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.marshalling.MarshallerProvider; import io.netty.handler.codec.marshalling.MarshallingEncoder;/*** @author chenwei* @create 2018-07-02 16:15**/public class NettyMarshallingEncoder extends MarshallingEncoder {public NettyMarshallingEncoder(MarshallerProvider provider) {super(provider);}@Overridepublic void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {super.encode(ctx, msg, out);} }
package com.cw.netty.high.protocol.bean;import io.netty.handler.codec.marshalling.DefaultMarshallerProvider; import io.netty.handler.codec.marshalling.DefaultUnmarshallerProvider; import io.netty.handler.codec.marshalling.MarshallerProvider; import io.netty.handler.codec.marshalling.UnmarshallerProvider; import org.jboss.marshalling.MarshallerFactory; import org.jboss.marshalling.Marshalling; import org.jboss.marshalling.MarshallingConfiguration;/*** @author chenwei* @create 2018-07-02 14:15**/public class MarshallingCodecFactory {public static NettyMarshallingDecoder buildMarshallingDecoder(){final MarshallerFactory factory= Marshalling.getProvidedMarshallerFactory("serial");final MarshallingConfiguration configuration=new MarshallingConfiguration();configuration.setVersion(5);UnmarshallerProvider provider = new DefaultUnmarshallerProvider(factory, configuration);NettyMarshallingDecoder decoder = new NettyMarshallingDecoder(provider, 1024);return decoder;}public static NettyMarshallingEncoder buildMarshallingEncoder(){final MarshallerFactory factory= Marshalling.getProvidedMarshallerFactory("serial");final MarshallingConfiguration configuration=new MarshallingConfiguration();configuration.setVersion(5);MarshallerProvider provider = new DefaultMarshallerProvider(factory, configuration);NettyMarshallingEncoder encoder = new NettyMarshallingEncoder(provider);return encoder;}}
Netty客户端:
package com.cw.netty.high.protocol;import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.timeout.ReadTimeoutHandler;import java.net.InetSocketAddress; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit;/*** @author chenwei* @create 2018-07-02 18:15**/public class NettyClient {private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);public void connect(final int port, final String host)throws Exception{try {Bootstrap boot=new Bootstrap();EventLoopGroup group = new NioEventLoopGroup();boot.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY,true).handler(new ChannelInitializer<Channel>() {@Overrideprotected void initChannel(Channel channel) throws Exception {channel.pipeline().addLast(new NettyMessageDecoder(1024*1024,4,4));channel.pipeline().addLast("messageEncoder",new NettyMessageEncoder());//添加超时处理handler,规定时间内没有收到消息则关闭链路channel.pipeline().addLast("readTimeoutHandler", new ReadTimeoutHandler(50));channel.pipeline().addLast("loginAuthHandler", new LoginAuthReqHandler());channel.pipeline().addLast("heartbeatHandler", new HeartBeatReqHandler());}});ChannelFuture future = boot.connect(new InetSocketAddress(host, port),new InetSocketAddress("127.0.0.1",8889)).sync();System.out.println("client is start……");future.channel().closeFuture().sync();}finally {//释放完毕后,清空资源,再次发起重连操作executorService.execute(new Runnable() {@Overridepublic void run() {try {System.out.println("重连……");TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}try {connect(port,host);} catch (Exception e) {e.printStackTrace();}}});}}public static void main(String[] args) throws Exception {new NettyClient().connect(8000,"127.0.0.1");}}
服务端:
package com.cw.netty.high.protocol;import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; 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 io.netty.handler.timeout.ReadTimeoutHandler;/*** @author chenwei* @create 2018-07-02 18:36**/public class NettyServer {public void bind() throws Exception{EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workGroup = new NioEventLoopGroup();ServerBootstrap bootstrap=new ServerBootstrap();bootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100).handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {socketChannel.pipeline().addLast(new NettyMessageDecoder(1024*1024,4,4));socketChannel.pipeline().addLast(new NettyMessageEncoder());socketChannel.pipeline().addLast("readTimeoutHandler",new ReadTimeoutHandler(50));socketChannel.pipeline().addLast(new LoginAuthRespHandler());socketChannel.pipeline().addLast("HeartBeathandler",new HeartBeatRespHandler());}});bootstrap.bind("127.0.0.1",8000).sync();System.out.println("netty server start ok:"+("127.0.0.1"+8000));}public static void main(String[] args) throws Exception {new NettyServer().bind();}}
转载于:https://www.cnblogs.com/manmanrenshenglu/p/9264769.html
Netty 私有协议栈 代码实现相关推荐
- Netty私有协议栈 读书笔记
1.数据结构定义 1)netty消息:NettyMessage 1 package com.cherry.netty.demo.protocolstack.pojo; 2 3 import com.c ...
- 【程序员基础篇】开源中国私有库代码更新
开源中国私有库代码更新 环境 expect bash 步骤 在开源中国新建私有库 脚本执行代码库更新 在本地web项目目录下添加远程库 在本地web目录下/Appliactions/XAMPP/htd ...
- [置顶] Linux协议栈代码阅读笔记(一)
Linux协议栈代码阅读笔记(一) (基于linux-2.6.21.7) (一)用户态通过诸如下面的C库函数访问协议栈服务 int socket(int domain, int type, int p ...
- linux 协议栈 位置,[置顶] Linux协议栈代码阅读笔记(一)
Linux协议栈代码阅读笔记(一) (基于linux-2.6.21.7) (一)用户态通过诸如下面的C库函数访问协议栈服务 int socket(int domain, int type, int p ...
- clone远程代码 在不同电脑上git_用树莓派3搭建私有git代码仓库
0.出发点 由于项目开发的规模和复杂度越来越大,有时候需要同时多人在不同的嵌入式linux上开发代码,在没有用git管理代码和文档前,很难同步代码和文档的版本,宝贵的代码存储也没有安全感. githu ...
- golang配置export GOPRIVATE拉取私有库代码
golang配置export GOPRIVATE拉取私有库代码 可参考链接: http://t.zoukankan.com/jwentest-p-12520378.html
- git clone 添加代理_用树莓派3搭建私有git代码仓库
0.出发点 由于项目开发的规模和复杂度越来越大,有时候需要同时多人在不同的嵌入式linux上开发代码,在没有用git管理代码和文档前,很难同步代码和文档的版本,宝贵的代码存储也没有安全感. githu ...
- Git学习二、GitLab官网,安装自己的一个私有Gitlab代码管理库
Git学习一.TortoiseGit 安装后,文件夹前面不显示相关状态图标解决办法 Git学习二.GitLab官网,安装自己的一个私有Gitlab代码管理库 Git学习三.Git与SVN的差异,Git ...
- go mod拉取gitlab私有仓库代码问题最全总结
1问题:go mod拉取gitlab私有仓库代码时报错如下 1.1 go: git@gitlab.com:groupName/projectName.git: invalid version: git ...
最新文章
- Rootkit之SSDT hook(通过CR0)
- Oracle 查询结果去重保留一项
- php 筛选数组,php数组如何按照字段筛选
- 【ruoyi若依】layer 重置大小/resize
- 基于主观感兴趣区域的视频编码实践
- 全国计算机等级考试题库二级C操作题100套(第48套)
- python并发编程5-线程
- 关于TCP三次握手过程
- 人工智能与深度学习实战(4)——口罩佩戴识别(CNN)
- BrnShop开源网上商城第三讲:插件的工作机制
- Junit 4 的 @Before 和 @BeforeClass 对比 Junit 5 @BeforeEach 和 @BeforeAll
- mbedtls | 03 - 单向散列算法的配置与使用(MD5、SHA1、SHA256、SHA512)
- 杰理语音芯片ic玩具芯片ic的介绍_AD14NAD15N全系列开发
- 背景透明及引发的文字透明问题
- 根据指定日期获取该日期所在周的所有日期
- 自动化1121和1122班学生链接
- 将QTextEdit右键菜单设置为中文
- 535A: Tavas and Nafas
- 拿来就能用的五个前端表白特效(免费)
- kettle下载百度网盘地址含入门教程7(全库表迁移)
热门文章
- desc excel 公式_Excel小技巧之Power Pivot Generate函数、高级DAX函数与常用筛选器函数...
- java 连续运算_JS连续运算
- ai如何旋转画布_Ai绘制科技感晶格球体!
- 19-7-16学习笔记
- hadoop 基础环境搭建(一)
- 免费网络推广浅析对网站首页优化的注意细节把控!
- 计算机显卡敀主要性能参数,显卡性能看什么参数(新手必备的显卡性能知识全解)...
- gmapping matlab实现_gmapping学习
- 怎样制作一个 Python Egg
- DESTOON 404