Netty(三):IdleStateHandler源码解析
IdleStateHandler是Netty为我们提供的检测连接有效性的处理器,一共有读空闲,写空闲,读/写空闲三种监测机制。
将其添加到我们的ChannelPipline中,便可以用来检测空闲。
先通过一段代码来学习下IdleStateHandler的用法:
ConnectStateHandler:(负责监测通道的各种状态并处理空闲事件IdleStateEvent)
package com.insaneXs.netty.idlestate; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent; public class ConnectStateHandler extends ChannelInboundHandlerAdapter { public ConnectStateHandler() {
super();
} @Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("Channel Register");
super.channelRegistered(ctx);
} @Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("Channel Unregister");
super.channelUnregistered(ctx);
} @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Channel Active");
super.channelActive(ctx);
} @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Channel Inactive");
super.channelInactive(ctx);
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Channel Read");
super.channelRead(ctx, msg);
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("Channel Read Complete");
super.channelReadComplete(ctx);
} @Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent){
if(((IdleStateEvent)evt).state().equals(IdleState.READER_IDLE)){
System.out.println("Read Idle");
ctx.close();
}
}else{
super.userEventTriggered(ctx, evt);
}
} @Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
super.channelWritabilityChanged(ctx);
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
服务器代码:
package com.insaneXs.netty.idlestate; import io.netty.bootstrap.ServerBootstrap;
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.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler; public class NettyServer { public static void main(String[] args){
EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup work = new NioEventLoopGroup(4); try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss,work)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new IdleStateHandler(30, 0, 0));
pipeline.addLast(new ConnectStateHandler());
}
}); bootstrap.bind(8322).sync().channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试客户端代码:
package com.insaneXs.netty.common; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel; public class CommonNettyClient { public static void main(String[] args){
EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.remoteAddress("localhost", 8322)
.handler(new ChannelInboundHandlerAdapter()); bootstrap.connect();
}
}
测试结果:
从上面的输出中我们可以看到Channel的状态变化:
1.连接建立时会从register -> active,
2.当读空闲时,我们产生了一个空闲事件,当ConnectStateHandler捕获这个事件后,会主动断开连接。
3.断开时则是从inactive -> unregister。
接下来我们学习下IdleStateHandler源码:
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.timeout; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.Channel.Unsafe;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOutboundBuffer;
import io.netty.channel.ChannelPromise; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; /
*
* @see ReadTimeoutHandler
* @see WriteTimeoutHandler
*/
public class IdleStateHandler extends ChannelDuplexHandler {
private static final long MIN_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(1); // Not create a new ChannelFutureListener per write operation to reduce GC pressure.
private final ChannelFutureListener writeListener = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
lastWriteTime = ticksInNanos();
firstWriterIdleEvent = firstAllIdleEvent = true;
}
}; //是否观察出站情况;默认false
private final boolean observeOutput; /*******三种超时情况管理*********/
//读超时时间
private final long readerIdleTimeNanos;
//写超时时间
private final long writerIdleTimeNanos;
//读或写超时时间
private final long allIdleTimeNanos; //读空闲定时任务,验证是否读超时
private ScheduledFuture<?> readerIdleTimeout;
//最后一次读时间
private long lastReadTime;
//是否第一次读超时
private boolean firstReaderIdleEvent = true; //写空闲定时任务,验证是否写超时
private ScheduledFuture<?> writerIdleTimeout;
//最后一次写超时
private long lastWriteTime;
//是否第一次写超时
private boolean firstWriterIdleEvent = true; //读或写超时定时任务
private ScheduledFuture<?> allIdleTimeout;
//是否读或写超时
private boolean firstAllIdleEvent = true; //处理器状态: 0-无状态, 1-初始化, 2-销毁
private byte state; // 0 - none, 1 - initialized, 2 - destroyed
//读状态标志
private boolean reading; /****用于观察输出情况*****/
private long lastChangeCheckTimeStamp;
private int lastMessageHashCode;
private long lastPendingWriteBytes; //设置超时时间;默认单位为秒
public IdleStateHandler(
int readerIdleTimeSeconds,
int writerIdleTimeSeconds,
int allIdleTimeSeconds) { this(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds,
TimeUnit.SECONDS);
} //设置三种情况超时时间与时间单位
public IdleStateHandler(
long readerIdleTime, long writerIdleTime, long allIdleTime,
TimeUnit unit) {
this(false, readerIdleTime, writerIdleTime, allIdleTime, unit);
} //设置是否观察输出情况,三种情况超时时间以及时间单位
public IdleStateHandler(boolean observeOutput,
long readerIdleTime, long writerIdleTime, long allIdleTime,
TimeUnit unit) {
if (unit == null) {
throw new NullPointerException("unit");
} this.observeOutput = observeOutput; if (readerIdleTime <= 0) {
readerIdleTimeNanos = 0;
} else {
readerIdleTimeNanos = Math.max(unit.toNanos(readerIdleTime), MIN_TIMEOUT_NANOS);
}
if (writerIdleTime <= 0) {
writerIdleTimeNanos = 0;
} else {
writerIdleTimeNanos = Math.max(unit.toNanos(writerIdleTime), MIN_TIMEOUT_NANOS);
}
if (allIdleTime <= 0) {
allIdleTimeNanos = 0;
} else {
allIdleTimeNanos = Math.max(unit.toNanos(allIdleTime), MIN_TIMEOUT_NANOS);
}
} /************将时间转换成毫秒级***************/
public long getReaderIdleTimeInMillis() {
return TimeUnit.NANOSECONDS.toMillis(readerIdleTimeNanos);
} public long getWriterIdleTimeInMillis() {
return TimeUnit.NANOSECONDS.toMillis(writerIdleTimeNanos);
} public long getAllIdleTimeInMillis() {
return TimeUnit.NANOSECONDS.toMillis(allIdleTimeNanos);
} //当处理器被添加时,视情况决定是否初始化
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
//判断channel是否已经激活和注册,避免
initialize(ctx);
} else {
// channelActive() event has not been fired yet. this.channelActive() will be invoked
// and initialization will occur there.
}
} //移除时,调用destroy销毁
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
destroy();
} //当通道被注册时,视情况决定是否初始化
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
// Initialize early if channel is active already.
if (ctx.channel().isActive()) {
initialize(ctx);
}
super.channelRegistered(ctx);
} //当通道激活时,初始化
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// This method will be invoked only if this handler was added
// before channelActive() event is fired. If a user adds this handler
// after the channelActive() event, initialize() will be called by beforeAdd().
initialize(ctx);
super.channelActive(ctx);
} //通道不活跃时,调用destroy销毁
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
destroy();
super.channelInactive(ctx);
} //读事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//开启了读空闲监测 或 读写空闲检测
if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
//修改为正在读,并重置首次读写事件的标志位为true
reading = true;
firstReaderIdleEvent = firstAllIdleEvent = true;
}
ctx.fireChannelRead(msg);
} //读完成
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//如果开启了 读/读写标志 且 正在读
if ((readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) && reading) {
//修改最后一次读取时间,重置正在读标识
lastReadTime = ticksInNanos();
reading = false;
}
ctx.fireChannelReadComplete();
} @Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
//如果开启了 写/读写标志
if (writerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
ctx.write(msg, promise.unvoid()).addListener(writeListener);
} else {
ctx.write(msg, promise);
}
} //初始化
private void initialize(ChannelHandlerContext ctx) {
//判断状态避免重复初始化
switch (state) {
case 1:
case 2:
return;
} state = 1;
//观察输出情况
initOutputChanged(ctx); //初始化最后一次读写时间
lastReadTime = lastWriteTime = ticksInNanos(); //根据超时时间,判断是否开启超时监测
if (readerIdleTimeNanos > 0) {
readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),
readerIdleTimeNanos, TimeUnit.NANOSECONDS);
}
if (writerIdleTimeNanos > 0) {
writerIdleTimeout = schedule(ctx, new WriterIdleTimeoutTask(ctx),
writerIdleTimeNanos, TimeUnit.NANOSECONDS);
}
if (allIdleTimeNanos > 0) {
allIdleTimeout = schedule(ctx, new AllIdleTimeoutTask(ctx),
allIdleTimeNanos, TimeUnit.NANOSECONDS);
}
} /**
* This method is visible for testing!
*/
long ticksInNanos() {
return System.nanoTime();
} /**
* This method is visible for testing!
*/
ScheduledFuture<?> schedule(ChannelHandlerContext ctx, Runnable task, long delay, TimeUnit unit) {
return ctx.executor().schedule(task, delay, unit);
} //销毁
private void destroy() {
//更改状态 取消定时器
state = 2; if (readerIdleTimeout != null) {
readerIdleTimeout.cancel(false);
readerIdleTimeout = null;
}
if (writerIdleTimeout != null) {
writerIdleTimeout.cancel(false);
writerIdleTimeout = null;
}
if (allIdleTimeout != null) {
allIdleTimeout.cancel(false);
allIdleTimeout = null;
}
} /**
* Is called when an {@link IdleStateEvent} should be fired. This implementation calls
* {@link ChannelHandlerContext#fireUserEventTriggered(Object)}.
*/
protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
ctx.fireUserEventTriggered(evt);
} /**
* Returns a {@link IdleStateEvent}.
*/
protected IdleStateEvent newIdleStateEvent(IdleState state, boolean first) {
switch (state) {
case ALL_IDLE:
return first ? IdleStateEvent.FIRST_ALL_IDLE_STATE_EVENT : IdleStateEvent.ALL_IDLE_STATE_EVENT;
case READER_IDLE:
return first ? IdleStateEvent.FIRST_READER_IDLE_STATE_EVENT : IdleStateEvent.READER_IDLE_STATE_EVENT;
case WRITER_IDLE:
return first ? IdleStateEvent.FIRST_WRITER_IDLE_STATE_EVENT : IdleStateEvent.WRITER_IDLE_STATE_EVENT;
default:
throw new IllegalArgumentException("Unhandled: state=" + state + ", first=" + first);
}
} /**
* @see #hasOutputChanged(ChannelHandlerContext, boolean)
*/
private void initOutputChanged(ChannelHandlerContext ctx) {
if (observeOutput) {
Channel channel = ctx.channel();
Unsafe unsafe = channel.unsafe();
ChannelOutboundBuffer buf = unsafe.outboundBuffer();
//初始化消息内容的HashCode和byte
if (buf != null) {
lastMessageHashCode = System.identityHashCode(buf.current());
lastPendingWriteBytes = buf.totalPendingWriteBytes();
}
}
} //判断输出是否有变化
private boolean hasOutputChanged(ChannelHandlerContext ctx, boolean first) {
if (observeOutput) { // We can take this shortcut if the ChannelPromises that got passed into write()
// appear to complete. It indicates "change" on message level and we simply assume
// that there's change happening on byte level. If the user doesn't observe channel
// writability events then they'll eventually OOME and there's clearly a different
// problem and idleness is least of their concerns.
if (lastChangeCheckTimeStamp != lastWriteTime) {
lastChangeCheckTimeStamp = lastWriteTime; // But this applies only if it's the non-first call.
if (!first) {
return true;
}
} Channel channel = ctx.channel();
Unsafe unsafe = channel.unsafe();
ChannelOutboundBuffer buf = unsafe.outboundBuffer(); if (buf != null) {
int messageHashCode = System.identityHashCode(buf.current());
long pendingWriteBytes = buf.totalPendingWriteBytes(); if (messageHashCode != lastMessageHashCode || pendingWriteBytes != lastPendingWriteBytes) {
lastMessageHashCode = messageHashCode;
lastPendingWriteBytes = pendingWriteBytes; if (!first) {
return true;
}
}
}
} return false;
} //监测任务的父类
private abstract static class AbstractIdleTask implements Runnable { private final ChannelHandlerContext ctx; AbstractIdleTask(ChannelHandlerContext ctx) {
this.ctx = ctx;
} @Override
public void run() {
if (!ctx.channel().isOpen()) {
return;
} run(ctx);
} protected abstract void run(ChannelHandlerContext ctx);
} //读监测定时任务
private final class ReaderIdleTimeoutTask extends AbstractIdleTask { ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
super(ctx);
} @Override
protected void run(ChannelHandlerContext ctx) {
long nextDelay = readerIdleTimeNanos;
if (!reading) {//不在读的过程中
//计算是否超时
nextDelay -= ticksInNanos() - lastReadTime;
} if (nextDelay <= 0) {//已经超时
//下次空闲监测
readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS); boolean first = firstReaderIdleEvent;
firstReaderIdleEvent = false; try {
//出发读超时事件
IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
channelIdle(ctx, event);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
// Read occurred before the timeout - set a new timeout with shorter delay.
readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
}
}
} //写监测定时任务
private final class WriterIdleTimeoutTask extends AbstractIdleTask { WriterIdleTimeoutTask(ChannelHandlerContext ctx) {
super(ctx);
} @Override
protected void run(ChannelHandlerContext ctx) { long lastWriteTime = IdleStateHandler.this.lastWriteTime;
long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime);
if (nextDelay <= 0) {//写超时
//下一次超时检查时间
writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS); boolean first = firstWriterIdleEvent;
firstWriterIdleEvent = false; try {
//输入内容是否发生变化:观察输出模式下且输出内容发生变化则不认为写超时
if (hasOutputChanged(ctx, first)) {
return;
}
//传递写超时事件
IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
channelIdle(ctx, event);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
// Write occurred before the timeout - set a new timeout with shorter delay.
writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
}
}
} //读写监测定时任务
private final class AllIdleTimeoutTask extends AbstractIdleTask { AllIdleTimeoutTask(ChannelHandlerContext ctx) {
super(ctx);
} @Override
protected void run(ChannelHandlerContext ctx) { long nextDelay = allIdleTimeNanos;
if (!reading) {
nextDelay -= ticksInNanos() - Math.max(lastReadTime, lastWriteTime);
}
if (nextDelay <= 0) {
// Both reader and writer are idle - set a new timeout and
// notify the callback.
allIdleTimeout = schedule(ctx, this, allIdleTimeNanos, TimeUnit.NANOSECONDS); boolean first = firstAllIdleEvent;
firstAllIdleEvent = false; try {
if (hasOutputChanged(ctx, first)) {
return;
} IdleStateEvent event = newIdleStateEvent(IdleState.ALL_IDLE, first);
channelIdle(ctx, event);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
// Either read or write occurred before the timeout - set a new
// timeout with shorter delay.
allIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
}
}
}
}
代码的关键部分都已经给出了注释。
这里主要关注几个问题,带着这些问题去思考代码设计的目的:
1.代码中firstXXXIdleEvent的标志位的作用是什么?
2.ObserveOutput标志位的作用是什么?
3.reading的标志的作用是什么?
4.lastReadTime和lastWriteTime会在什么时候被重置?
首先来回答第一个问题,为什么会有firstXXXIdleEvent的标识。
这是因为一次读写事件中,可能会产生多次的空闲超时事件。比如当我设置写空闲时间为5秒,而在某些情况下,我整个写过程需要30秒。那么这一次写过程就会产生多个写空闲事件。firstXXXIdleEvent标志位就是用来表明此次空闲事件是否为第一次空闲事件。
知道了第一个问题后,我们在看ObserveOutput标志的作用。ObserveOutput标志用来解决上述的慢输出的问题。如果设置为true,那么即使写过程中发生了写空闲事件,但是只要hasOutputChanged方法判断此时仍然在向外写(写缓存发生变化),那么就不会为此次超时产生写超时事件。
那么reading标志的作用是什么?reading的标志是在read方法开始时,被设置为true,在readComplete方法中又被设置为false。也就是reading标志表示正在发生读的过程。
最后的问题,lastReadTime和lastWriteTime在什么时候被重置?lastReadTime和lastWriteTime会在相应的定时器中被用来和空闲时间作比较,以此来检测在这段空闲时间中,是否发生过完整的读或写过程。因此,它们在handler初始化时被初始化值。lastReadTime会在readComplete时更新值。而lastWriteTime则是在WriterListener中设置(写过程完成后的回掉中)。
Netty(三):IdleStateHandler源码解析的更多相关文章
- Netty系列之源码解析(一)
本文首发于微信公众号[猿灯塔],转载引用请说明出处 接下来的时间灯塔君持续更新Netty系列一共九篇 当前:Netty 源码解析(一)开始 Netty 源码解析(二): Netty 的 Channel ...
- Android Handler机制(三)----Looper源码解析
一.Looper Looper对象,顾名思义,直译过来就是循环的意思,从MessageQueue中不断取出message. Class used to run a message loop for a ...
- ThreadPoolExecutor系列三——ThreadPoolExecutor 源码解析
ThreadPoolExecutor 源码解析 本文系作者原创,转载请注明出处:http://www.cnblogs.com/further-further-further/p/7681826.htm ...
- Netty 核心组件 EventLoop 源码解析
前言 在前文 Netty 启动过程源码分析 (本文超长慎读)(基于4.1.23) 中,我们分析了整个服务器端的启动过程.在那篇文章中,我们重点关注了启动过程,而在启动过程中对核心组件并没有进行详细介绍 ...
- 三.jQuery源码解析之jQuery的框架图
这张图片是对jQuery源码截图,一点一点拼出来的. 现在根据这张图片来对jQuery框架做一些说明. 一.16~9404行可以发现,最外层是一个自调用函数.当jQuery初始化时,这个自调用函数包含 ...
- 多线程与高并发(三)—— 源码解析 AQS 原理
一.前言 AQS 是一个同步框架,关于同步在操作系统(一)-- 进程同步 中对进程同步做了些概念性的介绍,我们了解到进程(线程同理,本文基于 JVM 讲解,故下文只称线程)同步的工具有很多:Mutex ...
- Spring Boot系列(三):Spring Boot整合Mybatis源码解析
一.Mybatis回顾 1.MyBatis介绍 Mybatis是一个半ORM框架,它使用简单的 XML 或注解用于配置和原始映射,将接口和Java的POJOs(普通的Java 对象)映射成数据库中的记 ...
- .Net Core 中间件之主机地址过滤(HostFiltering)源码解析
一.介绍 主机地址过滤中间件相当于一个白名单,标记哪些主机地址能访问接口. 二.使用 新建WebAPI项目,修改Startup中的代码段如下所示.下面表示允许主机名为“localhost”的主机访问( ...
- .Net Core缓存组件(Redis)源码解析
上一篇文章已经介绍了MemoryCache,MemoryCache存储的数据类型是Object,也说了Redis支持五中数据类型的存储,但是微软的Redis缓存组件只实现了Hash类型的存储.在分析源 ...
随机推荐
- DALI开关
DALI开关简介 一:符合协议IEC 62386 二:DALI总线取电,低压操作,安全可靠 三:可以开,关,调光,调色温 四:DALI总线取电,低压操作,安全可靠 五:可按压开,关,旋转调光,调光器耐 ...
- Spring(二):初始值赋值
依赖注入 1.构造器注入 见spring简介最后一大点. 2.Set方式注入 依赖注入:Set注入 依赖:bean对象的创建依赖于容器. 注入:bean对象的所有属性,由容器注入. bean ,里面属 ...
- Java第二十八天,Lambda表达式
一.函数式编程 1.什么是函数式编程 函数式编程是种编程方式,是一种编程的思维,它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用.它的地位等同于面向对象 ...
- Ubuntu 安装配置Dosbox
1.安装dosbox sudo apt-get install dosbox 方法一: 2.挂载虚拟空间到dosbox的c盘 在linux终端输入dosbox,进入dosbox后输入 mount c ...
- 通过GSM模块发送经纬度求救信息。
本博客作为实验笔记,仅供学习交流.(转载请注明出处) 本实验通过GSM模块:SIM900a,实现向特定手机发送sos求救信号,并且利用GPS模块:微科VK2828U7G5LF,将经纬度信息同时发送到手 ...
- GO中的channel使用小结
go关键字可以用来开启一个goroutine(协程))进行任务处理,而多个任务之间如果需要通信,就需要用到channel了. func testSimple(){ intChan := make(ch ...
- win10+ubuntu双系统修复ubuntu启动引导
因为windows是不能引导linux的,而每次win10升级或恢复都会将linux的启动引导覆盖掉,导致无法进入linux, 所以一直就禁止了win10更新.这几天win10出了点小毛病,所以就狠下 ...
- xxx 表 is marked as crashed and last (automatic?) repair 解决办法
如上图出现 xxx 表 is marked xxxx 的问题 运维那说是因为数据库非正常停掉 时 刚好有数据正在写入 数据库 导致的问题,这个没多大影响,需要 执行命令修复数据库,至于命令是什么? ...
- Cilium架构 (Cilium 2)
Cilium架构 译自:http://docs.cilium.io/en/stable/architecture/ 本文档描述了Cilium的架构.它通过记录BPF数据路径(datapath)的钩子来 ...
- 基于linux或windows平台上的c/s简单通信
linux: tcpclient.cpp #include<iostream> #include<unistd.h> #include<sys/types.h> # ...