Netty自娱自乐之协议栈设计
---恢复内容开始---
俺工作已经一年又6个月了,想想过的真快,每天写业务,写业务,写业务......。然后就是祈祷着,这次上线不要出现线上bug。继续这每天无聊的增删改查,学习学习一下自己感兴趣的事,就把自己当作小学生。然后学学习,打发打发时间,如果以后自己能用到呢?这又有谁说的清楚。
好了,最近在学习Netty,主要看了这2本书的一些内容,第一本就是《Netty实战》,第二本就是《Netty权威指南》。然后在看到Netty权威指南上有一章比较感兴趣,用了整整一章用来描写如何取自己定义一个协议。接着阅读完后,我就按照书本上的相关内容,去实现了一下。纠正了一下书本上的错误代码。工作都是在开发电商项目,基本上对底层传输这一块接触甚少。如果有机会想去一个游戏公司,这样看看能不能接触更多的网络传输相关内容。哎,不知道这样的去转有木有要,纠结。。。。。。。。。
好了,现在开始看书和事件的经历吧。
现在,我们设计一个传输协议如下
2字节:协议固定值 1字节:主版本号 |
消息长度 :消息头 和消息体 |
回话ID, 全局唯一 |
业务请求消息 |
优先级别 |
附件 |
code |
length |
sessionId |
type |
primary |
attachment |
上面的定义,是来着Netty的权威指南。这个是协议的头。然后接下来是一个协议体。而协议体在编码上就是一个Object.
| 协议头 | 协议体 |
customHeader |
bodyMessage |
根据上面的定义,直接写出协议定义model.直接上代码:
@Data
@ToString
public class NettyCustomHeader {
/**
* code 2字节:netty协议消息, 1字节:主版本号 1字节:副版本号 4
*/
private int code = 0xABCD0101; /**
* 消息长度 :消息头 和消息题 32
*/
private int length; /**
* 回话ID, 全局唯一 64
*/
private long sessionId; /**
* 业务请求消息 1:业务请求消息 2:业务响应消息 3:握手请求消息 4:握手应答消息 5:心跳请求消息 6:心跳应答消息
*/
private byte type; /**
* 优先级别
*/
private byte primary; /**
* 附件
*/
Map<String, Object> attachment; }
@Data
@ToString
public class NettyCustomMessage { /**
* 消息头
*/
private NettyCustomHeader customHeader; /**
* 消息体
*/
private Object bodyMessage; }
学过Netty的同学或者了解的同学知道,Netty是通过ChannelHandler来处理IO消息的。我编码的Netty版本是4。那么处理消息首先第一步就是解码,LengthFieldBasedFrameDecoder这个解码器是基于长度的解码器,并且能解决TCP/IP包的粘包和拆包问题。代码如下。
public class ByteBuf2NettyMessageDecoder extends LengthFieldBasedFrameDecoder {
// private NettyMarshallingDecoder marshallingDecoder = NettyMarshallingFactory.buildNettyMarshallingDecoder();
public ByteBuf2NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength);
}
public ByteBuf2NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
}
public ByteBuf2NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast);
}
public ByteBuf2NettyMessageDecoder(ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
super(byteOrder, maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
//调用父类decode ,得到整包消息
ByteBuf readBuf = (ByteBuf) super.decode(ctx, in);
if (readBuf == null) {
return null;
}
NettyCustomMessage customMessage = new NettyCustomMessage();
NettyCustomHeader customHeader = new NettyCustomHeader();
customHeader.setCode(readBuf.readInt());
customHeader.setLength(readBuf.readInt());
customHeader.setSessionId(readBuf.readLong());
customHeader.setType(readBuf.readByte());
customHeader.setPrimary(readBuf.readByte());
int attachmentSize = readBuf.readByte();
if (attachmentSize > 0) {
Map<String, Object> attachment = new HashMap<String, Object>();
for (int i = 0; i < attachmentSize; i++) {
int keySize = readBuf.readInt();
byte[] keyByte = new byte[keySize];
readBuf.readBytes(keyByte);
String key = new String(keyByte, CharsetUtil.UTF_8.name());
Object value = JavaByteFactory.decode(readBuf);
//Object value = marshallingDecoder.decode(ctx, readBuf);
attachment.put(key, value);
}
customHeader.setAttachment(attachment);
}
customMessage.setCustomHeader(customHeader);
if (readBuf.readableBytes() > 0) {
Object body = JavaByteFactory.decode(readBuf);
//Object body = marshallingDecoder.decode(ctx, readBuf);
customMessage.setBodyMessage(body);
}
return customMessage;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println(cause.getStackTrace());
cause.getStackTrace();
super.exceptionCaught(ctx, cause);
}
}
上面注释的原因,marshallingDecoder不支持java7,所以我自己写了一个编码/解码帮助类,就是前4个字节代表长度,后面是就是时间内容。从上面的代码我们知道,就是把ByteBuf转化为自己定义的协议对象。从上面的解码上,可能有点模糊,但是从下面的如何编码上,就可以知道为啥是这么解码的。
public class NettyMessage2ByteBufEncoder extends MessageToMessageEncoder<NettyCustomMessage> {
private NettyMarshallingEncoder nettyMarshallingEncoder;
public NettyMessage2ByteBufEncoder() {
// this.nettyMarshallingEncoder = NettyMarshallingFactory.buildNettyMarshallingEncoder();
}
protected void encode(ChannelHandlerContext ctx, NettyCustomMessage msg, List<Object> out) throws Exception {
if (msg == null || msg.getCustomHeader() == null) {
throw new Exception("the encode message is null");
}
ByteBuf sendBuf = Unpooled.buffer();
sendBuf.writeInt(msg.getCustomHeader().getCode());
sendBuf.writeInt(msg.getCustomHeader().getLength());
sendBuf.writeLong(msg.getCustomHeader().getSessionId());
sendBuf.writeByte(msg.getCustomHeader().getType());
sendBuf.writeByte(msg.getCustomHeader().getPrimary());
//attachment ,
if (msg.getCustomHeader().getAttachment() != null) {
sendBuf.writeByte(msg.getCustomHeader().getAttachment().size());
String key = null;
byte[] keyArray = null;
for (Map.Entry<String, Object> entryKey : msg.getCustomHeader().getAttachment().entrySet()) {
key = entryKey.getKey();
keyArray = key.getBytes(CharsetUtil.UTF_8.name());
sendBuf.writeInt(keyArray.length);
sendBuf.writeBytes(keyArray);
ByteBuf value = JavaByteFactory.encode(entryKey.getValue());
sendBuf.writeBytes(value);
// nettyMarshallingEncoder.encode(ctx, entryKey.getValue(), sendBuf);
}
} else {
sendBuf.writeByte(0);
}
if (msg.getBodyMessage() != null) {
ByteBuf value = JavaByteFactory.encode(msg.getBodyMessage());
sendBuf.writeBytes(value);
//nettyMarshallingEncoder.encode(ctx, msg.getBodyMessage(), sendBuf);
}
//在第5个字节开始的int 是长度,重新设置
sendBuf.setInt(4, sendBuf.readableBytes());
out.add(sendBuf);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println(cause.getStackTrace());
cause.getStackTrace();
super.exceptionCaught(ctx, cause);
}
}
从上面可以知道解码,就是把自定义协议对象 NettyCustomMessage 通过自己的规则放到ByteBuf上。代码比较简单,不解释。JavaByteFactory的代码如下:
public class JavaByteFactory {
public static Object decode(ByteBuf byteBuf) {
if (byteBuf == null || byteBuf.readableBytes() <= 0) {
return null;
}
int valueSize = byteBuf.readInt();
byte[] value = new byte[valueSize];
byteBuf.readBytes(value);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(value);
ObjectInputStream inputStream = null;
try {
inputStream = new ObjectInputStream(byteArrayInputStream);
return inputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
public static ByteBuf encode(Object object) {
if (object == null) {
return null;
}
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOutput);
objectOutputStream.writeObject(object);
byte[] bytes = byteOutput.toByteArray();
ByteBuf buffer = Unpooled.buffer(bytes.length + 4);
buffer.writeInt(bytes.length);
buffer.writeBytes(bytes);
return buffer;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
编码就是首选把Object 对象转换了byte []数组,然后写入4个字节为byte[]数组的长度,接着是数组的内容到ByteBuf对象上。相应的解码就是先获取4个字节,得到后面字节长度,接着读取指定长度即可。
接着心跳和权限检测都是在解码器之后进行业务的处理。直接上代码。
下面是权限认证的请求handler和响应handler.
public class AuthorityCertificationRequestHanlder extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(buildAuthorityCertificationMsg());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
NettyCustomMessage message = (NettyCustomMessage) msg;
if (message != null && message.getCustomHeader() != null && message.getCustomHeader().getType() == NettyMessageConstant.CUSTOMER_AUTH_CERTI_TYPE) {
byte authResult = (Byte) message.getBodyMessage();
if (authResult != (byte) 0) { //握手失败。关闭链接
ctx.close();
return;
}
System.out.println("authority certification is success .....");
ctx.fireChannelRead(msg);
} else {
ctx.fireChannelRead(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.getStackTrace();
ctx.channel().close();
System.out.println(cause.getStackTrace());
ctx.fireExceptionCaught(cause);
}
protected NettyCustomMessage buildAuthorityCertificationMsg() {
NettyCustomMessage message = new NettyCustomMessage();
NettyCustomHeader customHeader = new NettyCustomHeader();
customHeader.setType(NettyMessageConstant.CUSTOMER_AUTH_CERTI_TYPE);
message.setCustomHeader(customHeader);
return message;
}
}
public class AuthorityCertificationResponseHanlder extends ChannelInboundHandlerAdapter {
private Map<String, Boolean> authority = new ConcurrentHashMap<String, Boolean>();
private String[] ipList = new String[]{"127.0.0.1"};
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
NettyCustomMessage customMessage = (NettyCustomMessage) msg;
NettyCustomMessage response;
if (customMessage.getCustomHeader() != null && customMessage.getCustomHeader().getType() == NettyMessageConstant.CUSTOMER_AUTH_CERTI_TYPE) {
String remoteAddress = ctx.channel().remoteAddress().toString();
if (authority.containsKey(remoteAddress)) { //重复登陆
response = buildAuthorCertiResponseMessage((byte) -1);
} else {
InetSocketAddress inetSocketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
boolean isAuth = false;
for (String ip : ipList) {
if (ip.equals(inetSocketAddress.getAddress().getHostAddress())) {
isAuth = true;
break;
}
}
if (isAuth) {
response = buildAuthorCertiResponseMessage((byte) 0);
authority.put(remoteAddress, true);
} else {
response = buildAuthorCertiResponseMessage((byte) -1);
}
}
System.out.println("the client [" + remoteAddress + "] is connecting ,status:" + response);
ctx.writeAndFlush(response);
return;
}
ctx.fireChannelRead(msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println(cause.getStackTrace());
cause.getStackTrace();
String remoteAddress = ctx.channel().remoteAddress().toString();
authority.remove(remoteAddress);
ctx.channel().close();
ctx.fireExceptionCaught(cause);
}
private NettyCustomMessage buildAuthorCertiResponseMessage(byte body) {
NettyCustomMessage message = new NettyCustomMessage();
NettyCustomHeader customHeader = new NettyCustomHeader();
customHeader.setType(NettyMessageConstant.SERVER_AUTH_CERTI_TYPE);
message.setCustomHeader(customHeader);
message.setBodyMessage(body);
return message;
}
}
下面是心跳检测handler
public class HeartBeatCheckRequestHandler extends ChannelInboundHandlerAdapter {
private volatile ScheduledFuture<?> scheduledFuture;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
NettyCustomMessage customMessage = (NettyCustomMessage) msg;
if (customMessage.getCustomHeader() != null && customMessage.getCustomHeader().getType() == NettyMessageConstant.SERVER_AUTH_CERTI_TYPE) {
scheduledFuture = ctx.executor().scheduleAtFixedRate(new HeartBeatCheckTask(ctx), 0, 5000, TimeUnit.MILLISECONDS);
System.out.println("the client [ " + ctx.channel().localAddress().toString() + " ] send heart beat ...........");
} else if (customMessage.getCustomHeader() != null && customMessage.getCustomHeader().getType() == NettyMessageConstant.HEART_BEAT_CHECK_PONG_TYPE) {
System.out.println("the client [ " + ctx.channel().localAddress().toString() + " ] recieve heart beat .............");
} else {
ctx.fireChannelRead(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println(cause.getStackTrace());
cause.getStackTrace();
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
scheduledFuture = null;
}
ctx.fireExceptionCaught(cause);
}
class HeartBeatCheckTask implements Runnable {
private ChannelHandlerContext context;
public HeartBeatCheckTask(ChannelHandlerContext context) {
this.context = context;
}
@Override
public void run() {
NettyCustomMessage customMessage = new NettyCustomMessage();
NettyCustomHeader customHeader = new NettyCustomHeader();
customHeader.setType(NettyMessageConstant.HEART_BEAT_CHECK_PING_TYPE);
customMessage.setCustomHeader(customHeader);
context.writeAndFlush(customMessage);
System.out.println("the client [ " + context.channel().localAddress().toString() + " ] send heart beat to server ....");
}
}
}
public class HeartBeatCheckResponseHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
NettyCustomMessage customMessage = (NettyCustomMessage) msg;
if (customMessage.getCustomHeader() != null && customMessage.getCustomHeader().getType() == NettyMessageConstant.HEART_BEAT_CHECK_PING_TYPE) {
System.out.println("the server recieve the client [ " + ctx.channel().remoteAddress().toString() + " ] heart beat check package,");
NettyCustomMessage sendPongMessage = new NettyCustomMessage();
NettyCustomHeader customHeader = new NettyCustomHeader();
customHeader.setType(NettyMessageConstant.HEART_BEAT_CHECK_PONG_TYPE);
sendPongMessage.setCustomHeader(customHeader);
ctx.writeAndFlush(customMessage);
return;
}
ctx.fireChannelRead(msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println(cause.getStackTrace());
cause.getStackTrace();
super.exceptionCaught(ctx, cause);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("the client [ " + ctx.channel().remoteAddress().toString() + " ] is close ....,then close channel");
ctx.channel().close();
}
}
最后是我们的客户端和服务端代码,如下:
public class NettyProtocalClient {
private ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1);
private Bootstrap bootstrap;
private EventLoopGroup eventLoopGroup;
private String host;
private int port;
private int localPort;
public NettyProtocalClient(String host, int port) {
this(7777, host, port);
}
public NettyProtocalClient(int localPort, String host, int port) {
this.host = host;
this.port = port;
this.localPort = localPort;
}
public void connect() throws InterruptedException {
try {
bootstrap = new Bootstrap();
eventLoopGroup = new NioEventLoopGroup();
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<io.netty.channel.Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline()
.addLast("log", new LoggingHandler(LogLevel.INFO))
.addLast("decoder", new ByteBuf2NettyMessageDecoder(6 * 1024, 4, 4, -8, 0, true))
.addLast("encoder", new NettyMessage2ByteBufEncoder())
.addLast("timeout", new ReadTimeoutHandler(50))
.addLast("authority", new AuthorityCertificationRequestHanlder())
.addLast("hearbeat", new HeartBeatCheckRequestHandler());
}
});
ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port), new InetSocketAddress("127.0.0.1", localPort)).sync();
future.channel().closeFuture().sync();
} finally {
if (eventLoopGroup != null) {
eventLoopGroup.shutdownGracefully().sync();
}
executorService.execute(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(5);
connect();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
public class NettyProtocalServer {
private ServerBootstrap serverBootstrap;
private EventLoopGroup boss;
private EventLoopGroup worker;
private String host;
private int port;
public NettyProtocalServer(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws InterruptedException {
try {
serverBootstrap = new ServerBootstrap();
boss = new NioEventLoopGroup(1);
worker = new NioEventLoopGroup();
serverBootstrap.group(boss, worker)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline()
.addLast("log",new LoggingHandler(LogLevel.INFO))
.addLast("decoder", new ByteBuf2NettyMessageDecoder(6 * 1024, 4, 4, -8, 0, true))
.addLast("encoder", new NettyMessage2ByteBufEncoder())
.addLast("timeout", new ReadTimeoutHandler(50))
.addLast("authority", new AuthorityCertificationResponseHanlder())
.addLast("hearbeat", new HeartBeatCheckResponseHandler());
}
});
ChannelFuture future = serverBootstrap.bind(new InetSocketAddress(host, port)).sync();
future.channel().closeFuture().sync();
} finally {
if (boss != null) {
boss.shutdownGracefully();
}
if (worker != null) {
worker.shutdownGracefully();
}
}
}
}
最后看一看运行结果吧:
服务端显示内容:

客户端显示内容:

---恢复内容结束---
Netty自娱自乐之协议栈设计的更多相关文章
- Netty自娱自乐之类Dubbo RPC 框架设计构想 【上篇】
之前在前一篇的<Netty自娱自乐之协议栈设计>,菜鸟我已经自娱自乐了设计协议栈,gitHub地址为https://github.com/vOoT/ncustomer-protocal.先 ...
- [自娱自乐] 3、超声波测距模块DIY笔记(三)
前言 上一节我们已经研究了超声波接收模块并自己设计了一个超声波接收模块,在此基础上又尝试用单片机加反相器构成生成40KHz的超声波发射电路,可是发现采用这种设计的发射电路存在严重的发射功率太低问题,对 ...
- [自娱自乐] 4、超声波测距模块DIY笔记(四)——终结篇·基于C#上位机软件开发
前言 上一节我们已经基本上把超声波硬件的发射和接收模块全部做好了,接下来我们着手开发一个软硬结合的基于C#的平面定位软件! 目录 一.整体思路 二.效果提前展示 2-1.软件部分展示 2-2.硬件部分 ...
- [转]为何TCP/IP协议栈设计成沙漏型的
http://m.blog.csdn.net/blog/dog250/18959371 前几天有人回复我的一篇文章问,为何TCP/IP协议栈设计成沙漏型的.这个问题问得好!我先不谈为何它如此设计,我一 ...
- 用C++ 自娱自乐
最无聊的时光当属 考试前的复习时段了,在一些论坛上看到一些用字符组成的图像,觉得有点意思,于是,自己 用C++ 参考一些论坛的图像,写了下面这个东西,来表达此时的心情. #include<ios ...
- [置顶] 自娱自乐7之Linux UDC驱动2(自编udc驱动,现完成枚举过程,从驱动代码分析枚举过程)
花了半个月,才搞定驱动中的枚举部分,现在说linux的枚举,windows可能有差别. 代码我会贴在后面,现在只是实现枚举,你可能对代码不感兴趣,我就不分析代码了,你可以看看 在<自娱自乐1&g ...
- 利用Python编写Windows恶意代码!自娱自乐!勿用于非法用途!
本文主要展示的是通过使用python和PyInstaller来构建恶意软件的一些poc. 利用Python编写Windows恶意代码!自娱自乐!勿用于非法用途!众所周知的,恶意软件如果影响到了他人的生 ...
- [置顶] 自娱自乐1之Linux UDC驱动(形式模板)
首先,我不是做驱动的开发人员.所以只能用自娱自乐来表示我的行为. 我不知道udc和gadget驱动是不是冷门的驱动,资料真是不多.我之前买了一本书,上面说到这些,就教你如何调试已写好的驱动.这样也可以 ...
- Netty服务器连接池管理设计思路
应用场景: 在RPC框架中,使用Netty作为高性能的网络通信框架时,每一次服务调用,都需要与Netty服务端建立连接的话,很容易导致Netty服务器资源耗尽.所以,想到连接池技术,将与同一个Nett ...
随机推荐
- mysql 5.7 root密码重置(centos 7)
mysql5.7版本之后,与mariadb不同,在安装之后,在启动之时,会进行自动随机密码的设定,所以在systemctl start mysqld之后,会出现mysql -uroot -p无法登陆的 ...
- ES6学习目录
前面的话 ES6是JavaScript语言的下一代标准,已经在 2015 年 6 月正式发布.它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言 为什么要学 ...
- ASP.NET windows验证IIS配置
Windows验证时,需要配置IIS,把匿名验证设为disable,windows验证设为enable,window7 默认为匿名验证为enable,windows验证为disable. 否则会sys ...
- idea如何添加外部jar包
假设我们要将G:\ModuleAPI_Java_2.2.0.0 .jar导入工程中: 首先,在mvn命令行执行下面命令: mvn install:install-file -Dfile=G:\Modu ...
- HTML中表格
HTML表格 [表格table] 表格用table表示,表格中的每一行tr表示,一行中的每一列用td表示 th表示的是:表头.表头中的文字,默认为加粗居中.th要放在tr中,用于替换掉td. [tab ...
- python学习===复制list
"""将一个列表的数据复制到另一个列表中.""" """ 使用[:] """ a ...
- 《利用python进行数据分析》NumPy基础:数组和矢量计算 学习笔记
一.有关NumPy (一)官方解释 NumPy is the fundamental package for scientific computing with Python. It contains ...
- WebAPi接口安全之公钥私钥加密
WebAPi使用公钥私钥加密介绍和使用 随着各种设备的兴起,WebApi作为服务也越来越流行.而在无任何保护措施的情况下接口完全暴露在外面,将导致被恶意请求.最近项目的项目中由于提供给APP的接口未对 ...
- Linux/Unix监控其他用户和信号
--Linux/Unix监控其他用户和信号 ------------------------------------------------------2013/10/27 查看有哪些用户登录 w ...
- Python2和Python3的一些语法区别
Python2和Python3的一些语法区别 python 1.print 在版本2的使用方法是: print 'this is version 2 也可以是 print('this is versi ...