Netty自娱自乐之协议栈设计
---恢复内容开始---
俺工作已经一年又6个月了,想想过的真快,每天写业务,写业务,写业务......。然后就是祈祷着,这次上线不要出现线上bug。继续这每天无聊的增删改查,学习学习一下自己感兴趣的事,就把自己当作小学生。然后学学习,打发打发时间,如果以后自己能用到呢?这又有谁说的清楚。
好了,最近在学习Netty,主要看了这2本书的一些内容,第一本就是《Netty实战》,第二本就是《Netty权威指南》。然后在看到Netty权威指南上有一章比较感兴趣,用了整整一章用来描写如何取自己定义一个协议。接着阅读完后,我就按照书本上的相关内容,去实现了一下。纠正了一下书本上的错误代码。工作都是在开发电商项目,基本上对底层传输这一块接触甚少。如果有机会想去一个游戏公司,这样看看能不能接触更多的网络传输相关内容。哎,不知道这样的去转有木有要,纠结。。。。。。。。。
好了,现在开始看书和事件的经历吧。
现在,我们设计一个传输协议如下
2字节:协议固定值 1字节:主版本号 1字节:副版本号 |
消息长度 :消息头 和消息体4字节 |
回话ID, 全局唯一8字节 |
业务请求消息 1:业务请求消息 2:业务响应消息 3:握手请求消息 4:握手应答消息 5:心跳请求消息 6:心跳应答消息1字节 |
优先级别1字节 |
附件 |
code |
length |
sessionId |
type |
primary |
attachment |
上面的定义,是来着Netty的权威指南。这个是协议的头。然后接下来是一个协议体。而协议体在编码上就是一个Object.
协议头 | 协议体 |
customHeader |
bodyMessage |
根据上面的定义,直接写出协议定义model.直接上代码:
1 @Data 2 @ToString 3 public class NettyCustomHeader { 4 /** 5 * code 2字节:netty协议消息, 1字节:主版本号 1字节:副版本号 4 6 */ 7 private int code = 0xABCD0101; 8 9 /** 10 * 消息长度 :消息头 和消息题 32 11 */ 12 private int length; 13 14 /** 15 * 回话ID, 全局唯一 64 16 */ 17 private long sessionId; 18 19 /** 20 * 业务请求消息 1:业务请求消息 2:业务响应消息 3:握手请求消息 4:握手应答消息 5:心跳请求消息 6:心跳应答消息 21 */ 22 private byte type; 23 24 /** 25 * 优先级别 26 */ 27 private byte primary; 28 29 /** 30 * 附件 31 */ 32 Map<String, Object> attachment; 33 34 }
1 @Data 2 @ToString 3 public class NettyCustomMessage { 4 5 /** 6 * 消息头 7 */ 8 private NettyCustomHeader customHeader; 9 10 /** 11 * 消息体 12 */ 13 private Object bodyMessage; 14 15 16 }
学过Netty的同学或者了解的同学知道,Netty是通过ChannelHandler来处理IO消息的。我编码的Netty版本是4。那么处理消息首先第一步就是解码,LengthFieldBasedFrameDecoder这个解码器是基于长度的解码器,并且能解决TCP/IP包的粘包和拆包问题。代码如下。
1 public class ByteBuf2NettyMessageDecoder extends LengthFieldBasedFrameDecoder { 2 3 // private NettyMarshallingDecoder marshallingDecoder = NettyMarshallingFactory.buildNettyMarshallingDecoder(); 4 5 public ByteBuf2NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) { 6 super(maxFrameLength, lengthFieldOffset, lengthFieldLength); 7 } 8 9 public ByteBuf2NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) { 10 super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip); 11 } 12 13 public ByteBuf2NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) { 14 super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast); 15 } 16 17 public ByteBuf2NettyMessageDecoder(ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) { 18 super(byteOrder, maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast); 19 } 20 21 @Override 22 protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { 23 //调用父类decode ,得到整包消息 24 ByteBuf readBuf = (ByteBuf) super.decode(ctx, in); 25 if (readBuf == null) { 26 return null; 27 } 28 NettyCustomMessage customMessage = new NettyCustomMessage(); 29 NettyCustomHeader customHeader = new NettyCustomHeader(); 30 customHeader.setCode(readBuf.readInt()); 31 customHeader.setLength(readBuf.readInt()); 32 customHeader.setSessionId(readBuf.readLong()); 33 customHeader.setType(readBuf.readByte()); 34 customHeader.setPrimary(readBuf.readByte()); 35 36 int attachmentSize = readBuf.readByte(); 37 if (attachmentSize > 0) { 38 Map<String, Object> attachment = new HashMap<String, Object>(); 39 for (int i = 0; i < attachmentSize; i++) { 40 int keySize = readBuf.readInt(); 41 byte[] keyByte = new byte[keySize]; 42 readBuf.readBytes(keyByte); 43 String key = new String(keyByte, CharsetUtil.UTF_8.name()); 44 45 Object value = JavaByteFactory.decode(readBuf); 46 //Object value = marshallingDecoder.decode(ctx, readBuf); 47 attachment.put(key, value); 48 } 49 customHeader.setAttachment(attachment); 50 } 51 52 customMessage.setCustomHeader(customHeader); 53 if (readBuf.readableBytes() > 0) { 54 Object body = JavaByteFactory.decode(readBuf); 55 //Object body = marshallingDecoder.decode(ctx, readBuf); 56 customMessage.setBodyMessage(body); 57 } 58 59 return customMessage; 60 } 61 62 @Override 63 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 64 System.out.println(cause.getStackTrace()); 65 cause.getStackTrace(); 66 super.exceptionCaught(ctx, cause); 67 } 68 }
上面注释的原因,marshallingDecoder不支持java7,所以我自己写了一个编码/解码帮助类,就是前4个字节代表长度,后面是就是时间内容。从上面的代码我们知道,就是把ByteBuf转化为自己定义的协议对象。从上面的解码上,可能有点模糊,但是从下面的如何编码上,就可以知道为啥是这么解码的。
1 public class NettyMessage2ByteBufEncoder extends MessageToMessageEncoder<NettyCustomMessage> { 2 3 private NettyMarshallingEncoder nettyMarshallingEncoder; 4 5 public NettyMessage2ByteBufEncoder() { 6 // this.nettyMarshallingEncoder = NettyMarshallingFactory.buildNettyMarshallingEncoder(); 7 8 } 9 10 protected void encode(ChannelHandlerContext ctx, NettyCustomMessage msg, List<Object> out) throws Exception { 11 12 if (msg == null || msg.getCustomHeader() == null) { 13 throw new Exception("the encode message is null"); 14 } 15 16 ByteBuf sendBuf = Unpooled.buffer(); 17 sendBuf.writeInt(msg.getCustomHeader().getCode()); 18 sendBuf.writeInt(msg.getCustomHeader().getLength()); 19 sendBuf.writeLong(msg.getCustomHeader().getSessionId()); 20 sendBuf.writeByte(msg.getCustomHeader().getType()); 21 sendBuf.writeByte(msg.getCustomHeader().getPrimary()); 22 23 //attachment , 24 25 if (msg.getCustomHeader().getAttachment() != null) { 26 sendBuf.writeByte(msg.getCustomHeader().getAttachment().size()); 27 String key = null; 28 byte[] keyArray = null; 29 for (Map.Entry<String, Object> entryKey : msg.getCustomHeader().getAttachment().entrySet()) { 30 key = entryKey.getKey(); 31 keyArray = key.getBytes(CharsetUtil.UTF_8.name()); 32 sendBuf.writeInt(keyArray.length); 33 sendBuf.writeBytes(keyArray); 34 ByteBuf value = JavaByteFactory.encode(entryKey.getValue()); 35 sendBuf.writeBytes(value); 36 // nettyMarshallingEncoder.encode(ctx, entryKey.getValue(), sendBuf); 37 } 38 } else { 39 sendBuf.writeByte(0); 40 } 41 42 43 if (msg.getBodyMessage() != null) { 44 ByteBuf value = JavaByteFactory.encode(msg.getBodyMessage()); 45 sendBuf.writeBytes(value); 46 //nettyMarshallingEncoder.encode(ctx, msg.getBodyMessage(), sendBuf); 47 } 48 49 //在第5个字节开始的int 是长度,重新设置 50 sendBuf.setInt(4, sendBuf.readableBytes()); 51 52 out.add(sendBuf); 53 } 54 55 @Override 56 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 57 System.out.println(cause.getStackTrace()); 58 cause.getStackTrace(); 59 super.exceptionCaught(ctx, cause); 60 } 61 }
从上面可以知道解码,就是把自定义协议对象 NettyCustomMessage 通过自己的规则放到ByteBuf上。代码比较简单,不解释。JavaByteFactory的代码如下:
1 public class JavaByteFactory { 2 3 4 public static Object decode(ByteBuf byteBuf) { 5 if (byteBuf == null || byteBuf.readableBytes() <= 0) { 6 return null; 7 } 8 int valueSize = byteBuf.readInt(); 9 byte[] value = new byte[valueSize]; 10 byteBuf.readBytes(value); 11 12 ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(value); 13 ObjectInputStream inputStream = null; 14 try { 15 inputStream = new ObjectInputStream(byteArrayInputStream); 16 return inputStream.readObject(); 17 } catch (IOException e) { 18 e.printStackTrace(); 19 } catch (ClassNotFoundException e) { 20 e.printStackTrace(); 21 } 22 return null; 23 24 25 } 26 27 public static ByteBuf encode(Object object) { 28 if (object == null) { 29 return null; 30 } 31 ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(); 32 try { 33 ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOutput); 34 objectOutputStream.writeObject(object); 35 byte[] bytes = byteOutput.toByteArray(); 36 37 ByteBuf buffer = Unpooled.buffer(bytes.length + 4); 38 buffer.writeInt(bytes.length); 39 buffer.writeBytes(bytes); 40 return buffer; 41 42 } catch (IOException e) { 43 e.printStackTrace(); 44 } 45 return null; 46 }
编码就是首选把Object 对象转换了byte []数组,然后写入4个字节为byte[]数组的长度,接着是数组的内容到ByteBuf对象上。相应的解码就是先获取4个字节,得到后面字节长度,接着读取指定长度即可。
接着心跳和权限检测都是在解码器之后进行业务的处理。直接上代码。
下面是权限认证的请求handler和响应handler.
1 public class AuthorityCertificationRequestHanlder extends ChannelInboundHandlerAdapter { 2 3 @Override 4 public void channelActive(ChannelHandlerContext ctx) throws Exception { 5 ctx.writeAndFlush(buildAuthorityCertificationMsg()); 6 } 7 8 @Override 9 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 10 NettyCustomMessage message = (NettyCustomMessage) msg; 11 if (message != null && message.getCustomHeader() != null && message.getCustomHeader().getType() == NettyMessageConstant.CUSTOMER_AUTH_CERTI_TYPE) { 12 byte authResult = (Byte) message.getBodyMessage(); 13 if (authResult != (byte) 0) { //握手失败。关闭链接 14 ctx.close(); 15 return; 16 } 17 System.out.println("authority certification is success ....."); 18 ctx.fireChannelRead(msg); 19 } else { 20 ctx.fireChannelRead(msg); 21 } 22 23 } 24 25 @Override 26 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 27 cause.getStackTrace(); 28 ctx.channel().close(); 29 System.out.println(cause.getStackTrace()); 30 ctx.fireExceptionCaught(cause); 31 } 32 33 34 protected NettyCustomMessage buildAuthorityCertificationMsg() { 35 NettyCustomMessage message = new NettyCustomMessage(); 36 NettyCustomHeader customHeader = new NettyCustomHeader(); 37 customHeader.setType(NettyMessageConstant.CUSTOMER_AUTH_CERTI_TYPE); 38 message.setCustomHeader(customHeader); 39 return message; 40 } 41 42 }
1 public class AuthorityCertificationResponseHanlder extends ChannelInboundHandlerAdapter { 2 3 private Map<String, Boolean> authority = new ConcurrentHashMap<String, Boolean>(); 4 5 private String[] ipList = new String[]{"127.0.0.1"}; 6 7 @Override 8 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 9 10 NettyCustomMessage customMessage = (NettyCustomMessage) msg; 11 NettyCustomMessage response; 12 if (customMessage.getCustomHeader() != null && customMessage.getCustomHeader().getType() == NettyMessageConstant.CUSTOMER_AUTH_CERTI_TYPE) { 13 String remoteAddress = ctx.channel().remoteAddress().toString(); 14 if (authority.containsKey(remoteAddress)) { //重复登陆 15 response = buildAuthorCertiResponseMessage((byte) -1); 16 } else { 17 InetSocketAddress inetSocketAddress = (InetSocketAddress) ctx.channel().remoteAddress(); 18 boolean isAuth = false; 19 for (String ip : ipList) { 20 if (ip.equals(inetSocketAddress.getAddress().getHostAddress())) { 21 isAuth = true; 22 break; 23 } 24 } 25 if (isAuth) { 26 response = buildAuthorCertiResponseMessage((byte) 0); 27 authority.put(remoteAddress, true); 28 } else { 29 response = buildAuthorCertiResponseMessage((byte) -1); 30 } 31 } 32 System.out.println("the client [" + remoteAddress + "] is connecting ,status:" + response); 33 ctx.writeAndFlush(response); 34 return; 35 } 36 ctx.fireChannelRead(msg); 37 } 38 39 40 @Override 41 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 42 System.out.println(cause.getStackTrace()); 43 cause.getStackTrace(); 44 String remoteAddress = ctx.channel().remoteAddress().toString(); 45 authority.remove(remoteAddress); 46 ctx.channel().close(); 47 ctx.fireExceptionCaught(cause); 48 } 49 50 private NettyCustomMessage buildAuthorCertiResponseMessage(byte body) { 51 NettyCustomMessage message = new NettyCustomMessage(); 52 NettyCustomHeader customHeader = new NettyCustomHeader(); 53 customHeader.setType(NettyMessageConstant.SERVER_AUTH_CERTI_TYPE); 54 message.setCustomHeader(customHeader); 55 message.setBodyMessage(body); 56 return message; 57 } 58 59 }
下面是心跳检测handler
1 public class HeartBeatCheckRequestHandler extends ChannelInboundHandlerAdapter { 2 3 private volatile ScheduledFuture<?> scheduledFuture; 4 5 @Override 6 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 7 NettyCustomMessage customMessage = (NettyCustomMessage) msg; 8 if (customMessage.getCustomHeader() != null && customMessage.getCustomHeader().getType() == NettyMessageConstant.SERVER_AUTH_CERTI_TYPE) { 9 scheduledFuture = ctx.executor().scheduleAtFixedRate(new HeartBeatCheckTask(ctx), 0, 5000, TimeUnit.MILLISECONDS); 10 System.out.println("the client [ " + ctx.channel().localAddress().toString() + " ] send heart beat ..........."); 11 } else if (customMessage.getCustomHeader() != null && customMessage.getCustomHeader().getType() == NettyMessageConstant.HEART_BEAT_CHECK_PONG_TYPE) { 12 System.out.println("the client [ " + ctx.channel().localAddress().toString() + " ] recieve heart beat ............."); 13 } else { 14 ctx.fireChannelRead(msg); 15 } 16 17 } 18 19 @Override 20 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 21 System.out.println(cause.getStackTrace()); 22 cause.getStackTrace(); 23 if (scheduledFuture != null) { 24 scheduledFuture.cancel(true); 25 scheduledFuture = null; 26 } 27 ctx.fireExceptionCaught(cause); 28 } 29 30 class HeartBeatCheckTask implements Runnable { 31 32 private ChannelHandlerContext context; 33 34 public HeartBeatCheckTask(ChannelHandlerContext context) { 35 this.context = context; 36 } 37 38 @Override 39 public void run() { 40 NettyCustomMessage customMessage = new NettyCustomMessage(); 41 NettyCustomHeader customHeader = new NettyCustomHeader(); 42 customHeader.setType(NettyMessageConstant.HEART_BEAT_CHECK_PING_TYPE); 43 customMessage.setCustomHeader(customHeader); 44 context.writeAndFlush(customMessage); 45 System.out.println("the client [ " + context.channel().localAddress().toString() + " ] send heart beat to server ...."); 46 47 } 48 } 49 }
1 public class HeartBeatCheckResponseHandler extends ChannelInboundHandlerAdapter { 2 3 @Override 4 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 5 NettyCustomMessage customMessage = (NettyCustomMessage) msg; 6 if (customMessage.getCustomHeader() != null && customMessage.getCustomHeader().getType() == NettyMessageConstant.HEART_BEAT_CHECK_PING_TYPE) { 7 System.out.println("the server recieve the client [ " + ctx.channel().remoteAddress().toString() + " ] heart beat check package,"); 8 9 NettyCustomMessage sendPongMessage = new NettyCustomMessage(); 10 NettyCustomHeader customHeader = new NettyCustomHeader(); 11 customHeader.setType(NettyMessageConstant.HEART_BEAT_CHECK_PONG_TYPE); 12 sendPongMessage.setCustomHeader(customHeader); 13 ctx.writeAndFlush(customMessage); 14 return; 15 } 16 ctx.fireChannelRead(msg); 17 } 18 19 @Override 20 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 21 System.out.println(cause.getStackTrace()); 22 cause.getStackTrace(); 23 super.exceptionCaught(ctx, cause); 24 } 25 26 @Override 27 public void channelInactive(ChannelHandlerContext ctx) throws Exception { 28 System.out.println("the client [ " + ctx.channel().remoteAddress().toString() + " ] is close ....,then close channel"); 29 ctx.channel().close(); 30 } 31 32 33 }
最后是我们的客户端和服务端代码,如下:
1 public class NettyProtocalClient { 2 private ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1); 3 4 private Bootstrap bootstrap; 5 6 private EventLoopGroup eventLoopGroup; 7 8 private String host; 9 10 private int port; 11 12 private int localPort; 13 14 public NettyProtocalClient(String host, int port) { 15 this(7777, host, port); 16 } 17 18 public NettyProtocalClient(int localPort, String host, int port) { 19 this.host = host; 20 this.port = port; 21 this.localPort = localPort; 22 } 23 24 public void connect() throws InterruptedException { 25 try { 26 bootstrap = new Bootstrap(); 27 eventLoopGroup = new NioEventLoopGroup(); 28 bootstrap.group(eventLoopGroup) 29 .channel(NioSocketChannel.class) 30 .option(ChannelOption.TCP_NODELAY, true) 31 .handler(new ChannelInitializer<io.netty.channel.Channel>() { 32 @Override 33 protected void initChannel(Channel ch) throws Exception { 34 ch.pipeline() 35 .addLast("log", new LoggingHandler(LogLevel.INFO)) 36 .addLast("decoder", new ByteBuf2NettyMessageDecoder(6 * 1024, 4, 4, -8, 0, true)) 37 .addLast("encoder", new NettyMessage2ByteBufEncoder()) 38 .addLast("timeout", new ReadTimeoutHandler(50)) 39 .addLast("authority", new AuthorityCertificationRequestHanlder()) 40 .addLast("hearbeat", new HeartBeatCheckRequestHandler()); 41 42 43 } 44 }); 45 ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port), new InetSocketAddress("127.0.0.1", localPort)).sync(); 46 future.channel().closeFuture().sync(); 47 } finally { 48 if (eventLoopGroup != null) { 49 eventLoopGroup.shutdownGracefully().sync(); 50 } 51 executorService.execute(new Runnable() { 52 @Override 53 public void run() { 54 try { 55 TimeUnit.SECONDS.sleep(5); 56 connect(); 57 } catch (InterruptedException e) { 58 e.printStackTrace(); 59 } 60 } 61 }); 62 63 } 64 } 65 }
1 public class NettyProtocalServer { 2 private ServerBootstrap serverBootstrap; 3 4 private EventLoopGroup boss; 5 6 private EventLoopGroup worker; 7 8 private String host; 9 10 11 private int port; 12 13 public NettyProtocalServer(String host, int port) { 14 this.host = host; 15 this.port = port; 16 } 17 18 public void start() throws InterruptedException { 19 try { 20 serverBootstrap = new ServerBootstrap(); 21 boss = new NioEventLoopGroup(1); 22 worker = new NioEventLoopGroup(); 23 24 25 serverBootstrap.group(boss, worker) 26 .channel(NioServerSocketChannel.class) 27 .handler(new LoggingHandler(LogLevel.INFO)) 28 .option(ChannelOption.SO_BACKLOG, 1024) 29 .childHandler(new ChannelInitializer<Channel>() { 30 @Override 31 protected void initChannel(Channel ch) throws Exception { 32 ch.pipeline() 33 .addLast("log",new LoggingHandler(LogLevel.INFO)) 34 .addLast("decoder", new ByteBuf2NettyMessageDecoder(6 * 1024, 4, 4, -8, 0, true)) 35 .addLast("encoder", new NettyMessage2ByteBufEncoder()) 36 .addLast("timeout", new ReadTimeoutHandler(50)) 37 .addLast("authority", new AuthorityCertificationResponseHanlder()) 38 .addLast("hearbeat", new HeartBeatCheckResponseHandler()); 39 40 } 41 }); 42 ChannelFuture future = serverBootstrap.bind(new InetSocketAddress(host, port)).sync(); 43 future.channel().closeFuture().sync(); 44 } finally { 45 if (boss != null) { 46 boss.shutdownGracefully(); 47 } 48 if (worker != null) { 49 worker.shutdownGracefully(); 50 } 51 } 52 } 53 }
最后看一看运行结果吧:
服务端显示内容:
客户端显示内容:
---恢复内容结束---
转载于:https://www.cnblogs.com/liferecord/p/7506487.html
Netty自娱自乐之协议栈设计相关推荐
- 纯做技术是自娱自乐 抛开技术做技术才是出路
短短一生不过数十载,对于很多人而言,作IT.作技术只是生命中的某一段,并非所有.而无论是换工作还是换行业,只是一种形式而已,最终我们追求的是成功.是荣誉.是收获.于是在年轻的这几年里,作为技术人员理应 ...
- 自娱自乐 之 彩球摇奖
上周刷微博的时候,发现写段代码还可能赢得一本好书,所以就一边学习一边练习着做了一个彩球摇奖的页面,用了一些 html5 和 css3 的内容. 不支持 IE 的! 时间短暂,也不可能深入模拟重力环境, ...
- 专家看台:纯做技术是自娱自乐 抛开技术做技术才是出路
短短一生不过数十载,对于很多人而言,作IT.作技术只是生命中的某一段,并非所有.而无论是换工作还是换行业,只是一种形式而已,最终我们追求的是成功.是荣誉.是收获.于是在年轻的这几年里,作为技术人员理应 ...
- 简单的仿QQ聊天(自娱自乐聊天室)
设计思路: 首先搭建聊天界面,想要的效果如下 整体为linearlayout线性布局 : 1.顶部是聊天界面的名称 一个TextView 2.中间是ListView 用来显示聊天信息 3.底部是一个水 ...
- 游山玩水拈花惹草 --- SAP 顾问出差期间自娱自乐的妙招
游山玩水拈花惹草 --- SAP 顾问出差期间自娱自乐的妙招 最近五年,笔者一直从事各个企业的SAP项目的实施工作.由于工作性质和要求,需要常年在客户的现场提供咨询与技术支持服务,也就是说需要长期出差 ...
- kali ip查询_UDP/IP硬件协议栈设计(一):缘起
算算也该准备毕业了,正好毕业设计中需要使用UDP进行PC与FPGA之间的通信,而直接实现UDP硬件协议栈想想也并不复杂,"兵马未动.粮草先行",等完成UDP之后再开始毕设课题的研究 ...
- crc32校验算法_UDP/IP硬件协议栈设计(三):校验
前面已经完成了协议的解析分类,但是不能对分类好的数据帧再进行相关的校验计算,白白浪费时间,为降低延时,所以需要在分类的同时完成校验计算. 校验,就是通过增加冗余位来验证数据完整性的一个操作,对于谁是真 ...
- 情人节的自娱自乐——情书事件
今天一时兴起写的一封小情书. 信封是用Axure画出来的,然后配上粉嫩的背景图,最后一张信纸也是网上找的图片,没有自己去画. 只是几个简单的跳转拼成的情书,自娱自乐一波~
- 电信:自娱自乐的全员揽装,让人心寒!
之前做过几个电信的项目,也和他们中的高层接触过,发现有人写了这么一篇文章,一样的感受. 我是中国电信分公司的员工,在此发表的言论均有事实依据,以下内容绝无捏造夸大事实和诋毁的成份.我对这个企业有深厚的 ...
最新文章
- GIS+=地理信息+云计算技术——Spark集群部署
- idea验证失败_SVN提示https证书验证失败解决办法
- MySQL之某个表中有近千万数据且CRUD访问缓慢的优化处理
- 微信抢红包代码 python_python实现红包裂变算法
- 异步读取数据库中数据
- JCGridMenu
- modelsim仿真ROM IP数据输出为0的解决办法
- iBATIS使用$和#的一些理解
- Hadoop工作流--JobControl(五)
- 网页监控检测网页变化同步推送百度
- 使用Optuna进行超参数优化
- 阿里双十一数据库技术
- UICC 之 USIM 详解全系列
- RK3399实际解码能力测试
- 计算机音乐谱黑猫警长,求大神给乐谱音符@要黑猫警长 舒克贝塔和葫芦娃的
- 一道关于扔球/扔鸡蛋/摔手机的DP问题(蓝桥杯题目/面试题)
- 大数据测试之ETL测试
- 特网云虚拟主机在线解压压缩
- 阿里云服务器与传统自建服务器的对比
- 【Python】实用小脚本
热门文章
- nutch mysql solr_Nutch2.1+mysql+solr3.6.1+中文网站抓取
- win10 linux安卓模拟器,genymotion安卓模拟器在Window10中使用的问题
- 朋友圈自动回复评论_微信新版,朋友圈可以表情包回复了!网友:评论区斗起来.jpg...
- php excel下载打不开了,php下载excel无法打开的解决方法
- 三星电视出现服务器未响应,三星电视遥控器失灵怎么办
- html缓慢下拉,html - 列表下拉导航-适用于除野生动物园之外的所有浏览器。 间隔开并缓慢 - 堆栈内存溢出...
- mysql评论表结构设计_文章评论嵌套显示mysql表结构如何设计(形式如网易新闻评论)...
- oracle更换rac节点,Oracle-rac 更改VIP地址—2节点的
- leetcode343. 整数拆分
- leetcode217. 存在重复元素(vip题)超简单