在update_engine-整体结构(二)中分析到了Action,那么我们接着继续分析.

首先来看一下BuildUpdateActons(...)这个方法。

src/system/update_engine/update_attempter_android.cc

  void UpdateAttempterAndroid::BuildUpdateActions(const string& url) {
CHECK(!processor_->IsRunning());
processor_->set_delegate(this); // Actions:
shared_ptr<InstallPlanAction> install_plan_action(
new InstallPlanAction(install_plan_)); HttpFetcher* download_fetcher = nullptr;
if (FileFetcher::SupportedUrl(url)) {
DLOG(INFO) << "Using FileFetcher for file URL.";
download_fetcher = new FileFetcher();
} else {
#ifdef _UE_SIDELOAD
LOG(FATAL) << "Unsupported sideload URI: " << url;
#else
LibcurlHttpFetcher* libcurl_fetcher =
new LibcurlHttpFetcher(&proxy_resolver_, hardware_);
libcurl_fetcher->set_server_to_check(ServerToCheck::kDownload);
download_fetcher = libcurl_fetcher;
#endif // _UE_SIDELOAD
}
shared_ptr<DownloadAction> download_action(
new DownloadAction(prefs_,
boot_control_,
hardware_,
nullptr, // system_state, not used.
download_fetcher)); // passes ownership
shared_ptr<FilesystemVerifierAction> filesystem_verifier_action(
new FilesystemVerifierAction()); shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
new PostinstallRunnerAction(boot_control_, hardware_)); download_action->set_delegate(this);
download_action->set_base_offset(base_offset_);
download_action_ = download_action;
postinstall_runner_action->set_delegate(this); actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
actions_.push_back(shared_ptr<AbstractAction>(download_action));
actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action));
actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action)); // Bond them together. We have to use the leaf-types when calling
// BondActions().
BondActions(install_plan_action.get(), download_action.get());
BondActions(download_action.get(), filesystem_verifier_action.get());
BondActions(filesystem_verifier_action.get(),
postinstall_runner_action.get()); // Enqueue the actions.
for (const shared_ptr<AbstractAction>& action : actions_)
processor_->EnqueueAction(action.get());
}

我们会发现processor_,InstallPlanAction,DownloadAction,FilesystemVerifierAction,PostinstallRunnerAction。首先分析processor_,它是在UpdateAttempterAndroid的构造方法中被赋值

  UpdateAttempterAndroid::UpdateAttempterAndroid(
DaemonStateInterface* daemon_state,
PrefsInterface* prefs,
BootControlInterface* boot_control,
HardwareInterface* hardware)
: daemon_state_(daemon_state),
prefs_(prefs),
boot_control_(boot_control),
hardware_(hardware),
processor_(new ActionProcessor()) {
network_selector_ = network::CreateNetworkSelector();
}

ActionProcessor的数据结构为:

   // An ActionProcessor keeps a queue of Actions and processes them in order.

   namespace chromeos_update_engine {

   class AbstractAction;
class ActionProcessorDelegate; class ActionProcessor {
public:
ActionProcessor() = default; virtual ~ActionProcessor(); // Starts processing the first Action in the queue. If there's a delegate,
// when all processing is complete, ProcessingDone() will be called on the
// delegate.
virtual void StartProcessing(); // Aborts processing. If an Action is running, it will have
// TerminateProcessing() called on it. The Action that was running and all the
// remaining actions will be lost and must be re-enqueued if this Processor is
// to use it.
void StopProcessing(); // Suspend the processing. If an Action is running, it will have the
// SuspendProcessing() called on it, and it should suspend operations until
// ResumeProcessing() is called on this class to continue. While suspended,
// no new actions will be started. Calling SuspendProcessing while the
// processing is suspended or not running this method performs no action.
void SuspendProcessing(); // Resume the suspended processing. If the ActionProcessor is not suspended
// or not running in the first place this method performs no action.
void ResumeProcessing(); // Returns true iff the processing was started but not yet completed nor
// stopped.
bool IsRunning() const { return current_action_ != nullptr || suspended_; } // Adds another Action to the end of the queue.
virtual void EnqueueAction(AbstractAction* action); // Sets/gets the current delegate. Set to null to remove a delegate.
ActionProcessorDelegate* delegate() const { return delegate_; }
void set_delegate(ActionProcessorDelegate *delegate) {
delegate_ = delegate;
} // Returns a pointer to the current Action that's processing.
AbstractAction* current_action() const {
return current_action_;
} // Called by an action to notify processor that it's done. Caller passes self.
void ActionComplete(AbstractAction* actionptr, ErrorCode code); private:
// Continue processing actions (if any) after the last action terminated with
// the passed error code. If there are no more actions to process, the
// processing will terminate.
void StartNextActionOrFinish(ErrorCode code); // Actions that have not yet begun processing, in the order in which
// they'll be processed.
std::deque<AbstractAction*> actions_; // A pointer to the currently processing Action, if any.
AbstractAction* current_action_{nullptr}; // The ErrorCode reported by an action that was suspended but finished while
// being suspended. This error code is stored here to be reported back to the
// delegate once the processor is resumed.
ErrorCode suspended_error_code_{ErrorCode::kSuccess}; // Whether the action processor is or should be suspended.
bool suspended_{false}; // A pointer to the delegate, or null if none.
ActionProcessorDelegate* delegate_{nullptr}; DISALLOW_COPY_AND_ASSIGN(ActionProcessor);
}; // A delegate object can be used to be notified of events that happen
// in an ActionProcessor. An instance of this class can be passed to an
// ActionProcessor to register itself.
class ActionProcessorDelegate {
public:
virtual ~ActionProcessorDelegate() = default; // Called when all processing in an ActionProcessor has completed. A pointer
// to the ActionProcessor is passed. |code| is set to the exit code of the
// last completed action.
virtual void ProcessingDone(const ActionProcessor* processor,
ErrorCode code) {} // Called when processing has stopped. Does not mean that all Actions have
// completed. If/when all Actions complete, ProcessingDone() will be called.
virtual void ProcessingStopped(const ActionProcessor* processor) {} // Called whenever an action has finished processing, either successfully
// or otherwise.
virtual void ActionCompleted(ActionProcessor* processor,
AbstractAction* action,
ErrorCode code) {}
}; }

从中可以看到ActionProcessor其实就是用来管理Action的,它的方法都比较简单,根据注释我们大体就能够明白每个方法的意思,在遇到的时候某一个方法再具体分析。接下来再看Action它所存在的继承关系如下

Aciton继承关系

FilesystemVerifierAction,PostinstallRunnerAction,DownloadAction都继承了InstallPlanAction,根据继承关系可以看出他们都会有PerformAction,ActionCompleted等方法。PerformAction()是在Action开始执行前进行调用,而ActionCompleted是在执行完成后进行调用。先来看看InstallPlanAction中的内容

 src/system/update_engine/payload_consumer/install_plan.h

  class InstallPlanAction : public Action<InstallPlanAction> {
public:
InstallPlanAction() {}
explicit InstallPlanAction(const InstallPlan& install_plan):
install_plan_(install_plan) {} void PerformAction() override {
if (HasOutputPipe()) {
SetOutputObject(install_plan_);
}
processor_->ActionComplete(this, ErrorCode::kSuccess);
} InstallPlan* install_plan() { return &install_plan_; } static std::string StaticType() { return "InstallPlanAction"; }
std::string Type() const override { return StaticType(); } typedef ActionTraits<InstallPlanAction>::InputObjectType InputObjectType;
typedef ActionTraits<InstallPlanAction>::OutputObjectType OutputObjectType; private:
InstallPlan install_plan_; DISALLOW_COPY_AND_ASSIGN(InstallPlanAction);
};

可以看到InstallAction比较简单,仅仅是将install_plan_设置为了输出对象,传递给了下一个Action,这是Action之间的一个通信方式,这个方式可以称之为pipe方式,下面来分析一下这种通信方式。先来看在Action这个类里面提到的ActionPipe

src/system/update_engine/common/action_pipe.h

  namespace chromeos_update_engine {

  // Used by Actions an InputObjectType or OutputObjectType to specify that
// for that type, no object is taken/given.
class NoneType {}; template<typename T>
class Action; template<typename ObjectType>
class ActionPipe {
public:
virtual ~ActionPipe() {} // This should be called by an Action on its input pipe.
// Returns a reference to the stored object.
const ObjectType& contents() const { return contents_; } //获取管道中的内容 // This should be called by an Action on its output pipe.
// Stores a copy of the passed object in this pipe.
void set_contents(const ObjectType& contents) { contents_ = contents; } //设置管道中的内容 // Bonds two Actions together with a new ActionPipe. The ActionPipe is
// jointly owned by the two Actions and will be automatically destroyed
// when the last Action is destroyed.
template<typename FromAction, typename ToAction>
static void Bond(FromAction* from, ToAction* to) { //将两个Action连接通过pipe连接在一起
std::shared_ptr<ActionPipe<ObjectType>> pipe(new ActionPipe<ObjectType>);
from->set_out_pipe(pipe); to->set_in_pipe(pipe); // If you get an error on this line, then
// it most likely means that the From object's OutputObjectType is
// different from the To object's InputObjectType.
} private:
ObjectType contents_; // The ctor is private. This is because this class should construct itself
// via the static Bond() method.
ActionPipe() {}
DISALLOW_COPY_AND_ASSIGN(ActionPipe);
}; // Utility function
template<typename FromAction, typename ToAction>
void BondActions(FromAction* from, ToAction* to) {
static_assert(
std::is_same<typename FromAction::OutputObjectType,
typename ToAction::InputObjectType>::value,
"FromAction::OutputObjectType doesn't match ToAction::InputObjectType");
ActionPipe<typename FromAction::OutputObjectType>::Bond(from, to);
} }

可以看到ActionPipe主要就是将两个Action连接在一起。为什么说就会连接在一起呢?再来看Action中相关的方法

src/system/update_engine/common/action.h

  template<typename SubClass>
class Action : public AbstractAction {
public:
~Action() override {} // Attaches an input pipe to this Action. This is optional; an Action
// doesn't need to have an input pipe. The input pipe must be of the type
// of object that this class expects.
// This is generally called by ActionPipe::Bond()
void set_in_pipe( //设置输入管道
// this type is a fancy way of saying: a shared_ptr to an
// ActionPipe<InputObjectType>.
const std::shared_ptr<ActionPipe<
typename ActionTraits<SubClass>::InputObjectType>>& in_pipe) {
in_pipe_ = in_pipe;
} // Attaches an output pipe to this Action. This is optional; an Action
// doesn't need to have an output pipe. The output pipe must be of the type
// of object that this class expects.
// This is generally called by ActionPipe::Bond()
void set_out_pipe( //设置输出管道
// this type is a fancy way of saying: a shared_ptr to an
// ActionPipe<OutputObjectType>.
const std::shared_ptr<ActionPipe<
typename ActionTraits<SubClass>::OutputObjectType>>& out_pipe) {
out_pipe_ = out_pipe;
} // Returns true iff there is an associated input pipe. If there's an input
// pipe, there's an input object, but it may have been constructed with the
// default ctor if the previous action didn't call SetOutputObject().
bool HasInputObject() const { return in_pipe_.get(); } //是否有输入管道 // returns a const reference to the object in the input pipe.
const typename ActionTraits<SubClass>::InputObjectType& GetInputObject() //获取输入的内容
const {
CHECK(HasInputObject());
return in_pipe_->contents();
} // Returns true iff there's an output pipe.
bool HasOutputPipe() const { //是否有输出管道
return out_pipe_.get();
} // Copies the object passed into the output pipe. It will be accessible to
// the next Action via that action's input pipe (which is the same as this
// Action's output pipe).
void SetOutputObject( //设置输出的内容
const typename ActionTraits<SubClass>::OutputObjectType& out_obj) {
CHECK(HasOutputPipe());
out_pipe_->set_contents(out_obj);
} // Returns a reference to the object sitting in the output pipe.
const typename ActionTraits<SubClass>::OutputObjectType& GetOutputObject() { //获取输出的内容
CHECK(HasOutputPipe());
return out_pipe_->contents();
} protected:
// We use a shared_ptr to the pipe. shared_ptr objects destroy what they
// point to when the last such shared_ptr object dies. We consider the
// Actions on either end of a pipe to "own" the pipe. When the last Action
// of the two dies, the ActionPipe will die, too.
std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::InputObjectType>>
in_pipe_;
std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::OutputObjectType>>
out_pipe_;
};

从这里我们就能够看出每个Action其实有两个ActionPipe,一个是输入ActionPipe,一个是输出ActionPipe,输入ActionPipe和前一个Action的输出ActionPipe其实是一个ActionPipe,输出Actionpipe和下一个Action的输出ActionPipe是一个ActionPipe.

ActionTraits在这个类里仅仅是为InstallPlan这个类型定义了一个新的类型

src/system/update_engine/payload_consumer/install_plan.h

 template<>
class ActionTraits<InstallPlanAction> {
public:
// Takes the install plan as input
typedef InstallPlan InputObjectType;
// Passes the install plan as output
typedef InstallPlan OutputObjectType;
};

到这里Action机制也分析的差不多了,我们可以回到BuildUpdateActions中继续进行分析了。

  void UpdateAttempterAndroid::BuildUpdateActions(const string& url) {
CHECK(!processor_->IsRunning());
processor_->set_delegate(this); // Actions:
shared_ptr<InstallPlanAction> install_plan_action(
new InstallPlanAction(install_plan_)); HttpFetcher* download_fetcher = nullptr;
if (FileFetcher::SupportedUrl(url)) {
DLOG(INFO) << "Using FileFetcher for file URL.";
download_fetcher = new FileFetcher();
} else {
#ifdef _UE_SIDELOAD
LOG(FATAL) << "Unsupported sideload URI: " << url;
#else
LibcurlHttpFetcher* libcurl_fetcher =
new LibcurlHttpFetcher(&proxy_resolver_, hardware_);
libcurl_fetcher->set_server_to_check(ServerToCheck::kDownload);
download_fetcher = libcurl_fetcher;
#endif // _UE_SIDELOAD
}
shared_ptr<DownloadAction> download_action(
new DownloadAction(prefs_,
boot_control_,
hardware_,
nullptr, // system_state, not used.
download_fetcher)); // passes ownership
shared_ptr<FilesystemVerifierAction> filesystem_verifier_action(
new FilesystemVerifierAction()); shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
new PostinstallRunnerAction(boot_control_, hardware_)); download_action->set_delegate(this);
download_action->set_base_offset(base_offset_);
download_action_ = download_action;
postinstall_runner_action->set_delegate(this); actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
actions_.push_back(shared_ptr<AbstractAction>(download_action));
actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action));
actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action)); // Bond them together. We have to use the leaf-types when calling
// BondActions().
BondActions(install_plan_action.get(), download_action.get());
BondActions(download_action.get(), filesystem_verifier_action.get());
BondActions(filesystem_verifier_action.get(),
postinstall_runner_action.get()); // Enqueue the actions.
for (const shared_ptr<AbstractAction>& action : actions_)
processor_->EnqueueAction(action.get());
}

在这个方法里主要做了:

1.为processor_设置delegate,其实也就是注册了回调方法,UpdateAttempterAndroid实现了ActionProcessDelegate中的方法.

2. 创建了InstallPlanAction

3.创建了download_fetcher,我们这里假定用的是本地的文件既使用file:///协议,所以download_fetcher即为FileFetcher,从这一部分的代码可以看HtppFetcher,FileFetcher,LibcurlHttpFetcher之间具有继承或实现的关系。

4.创建DownloadAction,注意在创建的时候传入了download_fetcher为FileFetcher类型

5.创建FilesystemVerifierAction,PostinstallRunnerAction.从这里可以看出升级流程的精华应该就是这三个Action了

6.为download_action设置delegate,设置开始下载的offfset等,因为代码中设置delegate的操作比较多,如果不注意很有可能记混乱了。

7.为postinstall_runner_action设置delegate

8.将Action加入到Action的集合中

9.使用BondActions方法为Action之间建立管道。

10.将action遍历放入到processor_的队列中,并且设置action的管理者为processor_。

在分析完这个方法所干的事情之后,再分析一下HtppFetcher,FileFetcher,LibcurlHttpFetcher这三者之间的关系

 HtppFetcher,FileFetcher,LibcurlHttpFetcher这三者之间的关系

现在继续分析ApplyPayload中的最后一个方法UpdateBootFlags()

  void UpdateAttempterAndroid::UpdateBootFlags() {
if (updated_boot_flags_) {
LOG(INFO) << "Already updated boot flags. Skipping.";
CompleteUpdateBootFlags(true);
return;
}
// This is purely best effort.
LOG(INFO) << "Marking booted slot as good.";
if (!boot_control_->MarkBootSuccessfulAsync(
Bind(&UpdateAttempterAndroid::CompleteUpdateBootFlags,
base::Unretained(this)))) {
LOG(ERROR) << "Failed to mark current boot as successful.";
CompleteUpdateBootFlags(false);
}
}

首先检查当前运行的slot是否已经被标记为successful状态,如果是则调用CompleteUpdateBootFlags方法,否则的就调用MarkBootSuccessfulAsync将当前的slot标记为successful。标记完成后调用CompleteUpdateBootFlags方法

 void UpdateAttempterAndroid::CompleteUpdateBootFlags(bool successful) {
updated_boot_flags_ = true;
ScheduleProcessingStart();
}

从这里看出即使标记失败了仍然调用 ScheduleProcessingStart(),这个方法主要就是开始执行Action

 void UpdateAttempterAndroid::ScheduleProcessingStart() {
LOG(INFO) << "Scheduling an action processor start.";
brillo::MessageLoop::current()->PostTask(
FROM_HERE,
Bind([](ActionProcessor* processor) { processor->StartProcessing(); },
base::Unretained(processor_.get())));
}

在来看看StartProcessing()方法的实现,首先是获取对列中的第一个action,打印action的类型,之后将action移出队列,并且调用PerformAction。

src/system/update_engine/common/action_processor.cc

 void ActionProcessor::StartProcessing() {
CHECK(!IsRunning());
if (!actions_.empty()) {
current_action_ = actions_.front();
LOG(INFO) << "ActionProcessor: starting " << current_action_->Type();
actions_.pop_front();
current_action_->PerformAction();
}
}

分析到了这里就对整体的update_engine有了一定的了解,接下来只需要对各个Action逐个击破就好了。在之前已经看过了InstallPlanAction,它的内容很简单,仅仅是在输出管道中设置了install_plan_,接下来就调用了processor_->ActionComplete(this, ErrorCode::kSuccess),看一下ActionComplete的内容,它是如何让下一个action开始执行的。

  void ActionProcessor::ActionComplete(AbstractAction* actionptr,
ErrorCode code) {
CHECK_EQ(actionptr, current_action_);
if (delegate_)
delegate_->ActionCompleted(this, actionptr, code);
string old_type = current_action_->Type();
current_action_->ActionCompleted(code);
current_action_->SetProcessor(nullptr);
current_action_ = nullptr;
LOG(INFO) << "ActionProcessor: finished "
<< (actions_.empty() ? "last action " : "") << old_type
<< (suspended_ ? " while suspended" : "")
<< " with code " << utils::ErrorCodeToString(code);
if (!actions_.empty() && code != ErrorCode::kSuccess) {
LOG(INFO) << "ActionProcessor: Aborting processing due to failure.";
actions_.clear();
}
if (suspended_) {
// If an action finished while suspended we don't start the next action (or
// terminate the processing) until the processor is resumed. This condition
// will be flagged by a nullptr current_action_ while suspended_ is true.
suspended_error_code_ = code;
return;
}
StartNextActionOrFinish(code);
}

其实这个方法中也就进行了善后和开始下一个Action的工作。包括:

1.判断是否注册了回调方法。这里的delegate_的类型为UpdateAttempterAndroid。如果注册了就回调ActionCompleted方法,在UpdateAttempterAndroid中它的内容为

  void UpdateAttempterAndroid::ActionCompleted(ActionProcessor* processor,
AbstractAction* action,
ErrorCode code) {
// Reset download progress regardless of whether or not the download
// action succeeded.
const string type = action->Type();
if (type == DownloadAction::StaticType()) {
download_progress_ = ;
}
if (code != ErrorCode::kSuccess) {
// If an action failed, the ActionProcessor will cancel the whole thing.
return;
}
if (type == DownloadAction::StaticType()) {
SetStatusAndNotify(UpdateStatus::FINALIZING);
}
}

可以看到这个方法主要就是为重置下载的进度。

2.调用当前的Action的ActionCompleted,将Processor和当前Action置空等。

3.如果执行到某人Action的时候出了错,则停止执行其他的Action

4. processor如果被挂起,则暂停执行下一个Action

5.执行下一个Action或者是否完成了所有的Action,StartNextActionOrFinish(code),该方法比较简单,就不进行分析了。

到这里整体的Action的执行流程也就通了,下一篇开始会分析其他三个Action

.

update_engine-整体结构(三)的更多相关文章

  1. Nginx基本功能及其原理,配置原理

    Nginx基本功能及其原理,配置原理 一.正向代理.反向代理 二.Nginx配置文件的整体结构 三.Nginx配置SSL及HTTP跳转到HTTPS 四.nginx 配置管理 [nginx.conf 基 ...

  2. 阅读笔记:ImageNet Classification with Deep Convolutional Neural Networks

    概要: 本文中的Alexnet神经网络在LSVRC-2010图像分类比赛中得到了第一名和第五名,将120万高分辨率的图像分到1000不同的类别中,分类结果比以往的神经网络的分类都要好.为了训练更快,使 ...

  3. 车牌识别LPR(三)-- LPR系统整体结构

    第三篇:系统的整体架构 LPR系统大体上可由图像采集系统,图像处理系统,数据库管理系统三个子系统组成.它综合了通讯.信息.控制.传感.计算机等各种先进技术,构成一个智能电子系统. 图像采集系统:图像采 ...

  4. 浅谈Excel开发:三 Excel 对象模型

    前一篇文章介绍了Excel中的菜单系统,在创建完菜单和工具栏之后,就要着手进行功能的开发了.不论您采用何种方式来开发Excel应用程序,了解Excel对象模型尤其重要,这些对象是您与Excel进行交互 ...

  5. lucene学习笔记:三,Lucene的索引文件格式

    Lucene的索引里面存了些什么,如何存放的,也即Lucene的索引文件格式,是读懂Lucene源代码的一把钥匙. 当我们真正进入到Lucene源代码之中的时候,我们会发现: Lucene的索引过程, ...

  6. 《InsideUE4》UObject(三)类型系统设定和结构

    垃圾分类,从我做起! 引言 上篇我们谈到了为何设计一个Object系统要从类型系统开始做起,并探讨了C#的实现,以及C++中各种方案的对比,最后得到的结论是UE采用UHT的方式搜集并生成反射所需代码. ...

  7. Lucene.Net 2.3.1开发介绍 —— 二、分词(三)

    原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(三) 1.3 分词器结构 1.3.1 分词器整体结构 从1.2节的分析,终于做到了管中窥豹,现在在Lucene.Net项目中添加一个类关 ...

  8. 【Beta】 第三次Daily Scrum Meeting

    一.本次会议为第三次meeting会议 二.时间:10:00AM-10:20AM 地点:禹州楼 三.会议站立式照片 四.今日任务安排 成员 昨日任务 今日任务 林晓芳 查询app提醒功能模块和用户登录 ...

  9. SSM框架开发web项目系列(三) MyBatis之resultMap及关联映射

    前言 在上篇MyBatis基础篇中我们独立使用MyBatis构建了一个简单的数据库访问程序,可以实现单表的基本增删改查等操作,通过该实例我们可以初步了解MyBatis操作数据库需要的一些组成部分(配置 ...

  10. 操作系统内核Hack:(三)引导程序制作

    操作系统内核Hack:(三)引导程序制作 关于本文涉及到的完整源码请参考MiniOS的v1_bootloader分支. 1.制作方法 现在我们已经了解了关于BootLoader的一切知识,让我们开始动 ...

随机推荐

  1. python的标识符

    1.在pyhon中,标识符由字母.数字.下划线组成 2.在python中,所有标识符可以有字幕,下划线开头,但不能以数字开头 3.python的标识符是区分大小写的 4.以下划线开头的标识符是有特殊意 ...

  2. nc/netcat命令

    nc/netcat命令 语法 nc/netcat(选项)(参数) 选项 -g<网关>:设置路由器跃程通信网关,最多设置8个: -G<指向器数目>:设置来源路由指向器,其数值为4 ...

  3. webservice和dubbo区别

    webservice  不需要搭建注册中心,是一个服务框架,主要内容有soap协议,uuid注册中心,wsdl文件. dubbo   需要搭建注册中心,可以是zookeeper,redis.它是一个分 ...

  4. 无线局域网(WLAN)

    无线局域网根据结构可以分为两大类:有固定基础设施的无线局域网和无固定基础设施的局域网. 有固定基础设施是指网络中已经预先存在了一批固定的数据处理和转发设备,这些固定设备可以通过有线方式连接其他网络或 ...

  5. linux安装虚拟机ping不通网关,ping不通外网问题解决(nat模式)

    1.将网关字段改成自己想要的字段

  6. html 使表格随着内容自动适应宽度

    所谓难而不会,会儿不难.这个问题让我纠结了很长时间,一句css解决了,仅仅靠一个属性 td { white-space: nowrap; } from:http://blog.csdn.net/liu ...

  7. node,npm,vue的全局升级

    pc环境:windows 10, OS:win32, Arch:x64 1.升级node.js到最新 ⑴.别人成功的方法: . 第一步 npm -g install n //此处可以加上 --forc ...

  8. 微信小程序笑话小程序实践开发学习

    首先做出笑话展示页面 1.修改app.json文件,在"pages"中添加一条 "pages/joke/joke",然后ctrl+s就可以自动创建joke文件夹 ...

  9. L2-007 家庭房产 (25 分)

    L2-007 家庭房产 (25 分)   给定每个人的家庭成员和其自己名下的房产,请你统计出每个家庭的人口数.人均房产面积及房产套数. 输入格式: 输入第一行给出一个正整数N(≤),随后N行,每行按下 ...

  10. 怎么在Mac中的Safari查看网页源码

    一般情况下,Safari中右键是没有查看网页源文件这个选项的: 但是通过设置是可以看到的~ 1.首先找到电脑左上角的Safari然后选择偏好设置: 2.接着选择 “高级”页签,勾选最下面的 “在菜单栏 ...