neutron 网络总览  (gitbook

之前大师发个结构图.

understanding_neutron.pdf

自己走读了代码:

1.  get_extensions_path()

# 在/opt/stack/neutron/neutron/api/extensions.py 中, get_extensions_path()

# 为什么 paths 是个list,也就是说 extensions 应该可以放在多个位置。

# 是不是说,我可以在config entry中配置的plugin, 不用放在neutron的目录下面?

# Returns the extension paths from a config entry and the __path__
# of neutron.extensions
def get_extensions_path(service_plugins=None):
paths = collections.OrderedDict() # 首先, 加载 core extensions 的路径。
# Add Neutron core extensions
paths[neutron.extensions.__path__[0]] = 1 # 为什么要设置value为1, 其实没啥意义对吧? 可以是任何值? 只是确保 path的key不重合就可以吧。
if service_plugins:
# Add Neutron *-aas extensions # 什么是 Neutron *-aas 的 extensions
for plugin in service_plugins.values(): # 根据plugin找extensions的位置。
neutron_mod = provider_configuration.NeutronModule(
plugin.__module__.split('.')[0])
try:
paths[neutron_mod.module().extensions.__path__[0]] = 1
except AttributeError:
# Occurs normally if module has no extensions sub-module
pass
# 而且我可以在 config中配置 extentions的路径。 这个代码不需要检查, path存不存在吗?
# Add external/other plugins extensions
if cfg.CONF.api_extensions_path:
for path in cfg.CONF.api_extensions_path.split(":"):
paths[path] = 1 LOG.debug("get_extension_paths = %s", paths) # Re-build the extension string
path = ':'.join(paths)
return path

看了一下, service_plugins 是可以配置的。
就是说可以放到不用在 neutron的目录下面。

第三方可以任意写

自己的plugin,
放到自己的目录, 是这样的吗?

Neutron-fwaas

Neutron-lbaas

等等都是service plugin,都不在neutron目录下面,都是单独的repo

$ grep plugin  etc/neutron.conf.sample 

# The core plugin Neutron will use (string
value)
#core_plugin = <None>
# The service plugins Neutron will use
(list value)
#service_plugins =

$ grep Plugin setup.cfg

    ml2 = neutron.plugins.ml2.plugin:Ml2Plugin
    dummy =
neutron.tests.unit.dummy_plugin:DummyServicePlugin
    router =
neutron.services.l3_router.l3_router_plugin:L3RouterPlugin
    metering =
neutron.services.metering.metering_plugin:MeteringPlugin
    qos =
neutron.services.qos.qos_plugin:QoSPlugin
    tag =
neutron.services.tag.tag_plugin:TagPlugin
    flavors =
neutron.services.flavors.flavors_plugin:FlavorsPlugin
    auto_allocate =
neutron.services.auto_allocate.plugin:Plugin
    segments =
neutron.services.segments.plugin:Plugin
    network_ip_availability =
neutron.services.network_ip_availability.plugin:NetworkIPAvailabilityPlugin
    revisions =
neutron.services.revisions.revision_plugin:RevisionPlugin
    timestamp =
neutron.services.timestamp.timestamp_plugin:TimeStampPlugin
    trunk =
neutron.services.trunk.plugin:TrunkPlugin
    neutron_tests =
neutron.tests.tempest.plugin:NeutronTempestPlugin

2. 怎么写一个extension的例子

$ pydoc neutron.api.extensions.ExtensionManager
neutron.api.extensions.ExtensionManager = class
ExtensionManager(__builtin__.object)
 |  Load extensions from the configured extension
path.
 | 
 |  See tests/unit/extensions/foxinsocks.py for an
 |  example extension implementation.
 | 
doc string 告诉我们,

怎么写一个extension的例子。

neutron的文档写的还是不错的。

$ cat neutron/tests/unit/extensions/foxinsocks.py

3. plugin 加载的过程。

NeutronManager 再 获取 plugin和service_plugin 的时候, 实际上是个 Singleton

而且, NeutronManager
在获取 plugin和service_plugin
的时候,返回的是弱引用。

这个文档也写的很好:
$ pydoc neutron.manager.NeutronManager
neutron.manager.NeutronManager = class
NeutronManager(__builtin__.object)
 | 
Neutron's Manager class.
 | 
 |  Neutron's Manager class is responsible for
parsing a config file and
 |  instantiating the correct plugin that concretely
implements
 |  neutron_plugin_base class.
 |  The caller should make sure that NeutronManager
is a singleton.
 | 
 |  Methods defined here:

_load_service_plugins的neutron分析。

1. 首先获得core_plugin
def _load_services_from_core_plugin():
该函数 只获取了
core_plugin
的service
self.plugin
<neutron.plugins.ml2.plugin.Ml2Plugin object at
0x7f77afd95690>
self.plugin 支持的扩展有:

getattr(self.plugin, "supported_extension_aliases", [])
['provider', 'external-net', 'binding', 'quotas', 'security-group', 'agent', 'dhcp_agent_scheduler', 'multi-provider', 'allowed-address-pairs', 'extra_dhcp_opt', 'subnet_allocation', 'net-mtu', 'address-scope', 'availability_zone', 'network_availability_zone', 'default-subnetpools', 'subnet-service-types', 'port-security']

而constants.EXT_TO_SERVICE_MAPPING

{'lbaasv2': 'LOADBALANCERV2', 'dummy': 'DUMMY', 'qos': 'QOS', 'fwaas': 'FIREWALL', 'router': 'L3_ROUTER_NAT', 'vpnaas': 'VPN', 'metering': 'METERING', 'lbaas': 'LOADBALANCER'}

# Maps
extension alias to service type
that                                                                  

# can be implemented by the core plugin.

EXT_TO_SERVICE_MAPPING = {
'dummy': DUMMY,
'lbaas': LOADBALANCER,
'lbaasv2': LOADBALANCERV2,
'fwaas': FIREWALL,
'vpnaas': VPN,
'metering': METERING,
'router': L3_ROUTER_NAT,
'qos': QOS,
}

没有一个在 self.plugin 支持的扩展中,所以并没有加载。

2.  获取 service_plugins,
并将之实例化。 每一种plugin
有type定义。
_load_service_plugins ,
还将系统默认的plugins 加载进来了。
不仅仅加载了conf中的。

$ grep service_plugins  /etc/neutron/neutron.conf
service_plugins =
neutron.services.l3_router.l3_router_plugin.L3RouterPlugin

cfg.CONF.service_plugins

['neutron.services.l3_router.l3_router_plugin.L3RouterPlugin']

系统还支持一些默认的plugins

    self._get_default_service_plugins()
['flavors', 'auto_allocate', 'timestamp', 'network_ip_availability', 'tag', 'revisions'] # Maps default service plugins entry points to their extension aliases
DEFAULT_SERVICE_PLUGINS = {
'auto_allocate': 'auto-allocated-topology',
'tag': 'tag',
'timestamp': 'timestamp',
'network_ip_availability': 'network-ip-availability',
'flavors': 'flavors',
'revisions': 'revisions',
}

实例化的过程, 可以是alias的名字(entry_point 定义), 也可以是类名conf中定义(其实conf中可以用类名, 也可以是alias)。
utils.load_class_by_alias_or_classname

实例化的过程中,如果有agent,
会更新到 core_plugin中。
self.plugin.agent_notifiers.update(plugin_inst.agent_notifiers)

4. 走读PluginAwareExtensionManager

这个thread 主要是extension相关代码初始化的走读。

这个是APIRouter 类中很重要的一个环节。 Router 是最靠近wsgi的。
因为, Plugin的目的就是提供服务, 提供的服务最终都是经过 wsgi 的形式提供。

PluginAwareExtensionManager 实例化的过程,实际上是传入了前面我们分析的 service_plugins
所在的extensions路径 和 service_plugins 本身。
获得了所有的 Extensions(父类做的事情), 并且做了check。

获得了所有的 Extensions的过程,其实就是遍历 extensions路径下的文件是不是 有效的python文件。
是的话,就加载, 存在 extensions 中, key就是alias。

PluginAwareExtensionManager 很重要的一个功能,就是怎么扩展 resources, 这些resources 就是wsgi暴露出去的。

# 都段代码没有读懂啊 , 见下文。

def extend_resources(self, version, attr_map):
$ pydoc neutron.api.extensions.ExtensionManager.extend_resources
neutron.api.extensions.ExtensionManager.extend_resources = extend_resources(self, version, attr_map) unbound neutron.api.extensions.ExtensionManager method
Extend resources with additional resources or attributes. :param attr_map: the existing mapping from resource name to
attrs definition. After this function, we will extend the attr_map if an extension
wants to extend this map. def extend_resources(self, version, attr_map):
"""Extend resources with additional resources or attributes. :param attr_map: the existing mapping from resource name to
attrs definition. After this function, we will extend the attr_map if an extension
wants to extend this map.
"""
processed_exts = {}
exts_to_process = self.extensions.copy()
check_optionals = True
# Iterate until there are unprocessed extensions or if no progress
# is made in a whole iteration
while exts_to_process: # 不能用len, 因为需要处理的extension, 有依赖关系。
processed_ext_count = len(processed_exts)
for ext_name, ext in list(exts_to_process.items()):
# Process extension only if all required extensions
# have been processed already
required_exts_set = set(ext.get_required_extensions())
if required_exts_set - set(processed_exts): # 有需要提前 处理的 extension, 那么就不先处理。
continue
optional_exts_set = set(ext.get_optional_extensions())
if check_optionals and optional_exts_set - set(processed_exts): # 有需要提前 处理的 extension, 那么就不先处理。
continue
extended_attrs = ext.get_extended_resources(version)
for res, resource_attrs in six.iteritems(extended_attrs):
attr_map.setdefault(res, {}).update(resource_attrs) # 用extension的RESOURCE_ATTRIBUTE_MAP 更新 attr_map
processed_exts[ext_name] = ext # 没处理一个,增加一个。
del exts_to_process[ext_name] # 同时需要处理的,减去一个。
if len(processed_exts) == processed_ext_count: # 这段代码没有读懂啊
# if we hit here, it means there are unsatisfied # 感觉是我们可以容忍 optional 的依赖出问题
# dependencies. try again without optionals since optionals
# are only necessary to set order if they are present.
if check_optionals:
check_optionals = False
continue
# Exit loop as no progress was made
break
if exts_to_process:  # 感觉是我们不能容忍 require 的依赖出问题
unloadable_extensions = set(exts_to_process.keys())
LOG.error(_LE("Unable to process extensions (%s) because "
"the configured plugins do not satisfy "
"their requirements. Some features will not "
"work as expected."),
', '.join(unloadable_extensions))
self._check_faulty_extensions(unloadable_extensions)
# Extending extensions' attributes map. # 最后讲 attr_map 反更新 extension的RESOURCE_ATTRIBUTE_MAP,这个逻辑有点绕。
for ext in processed_exts.values():
ext.update_attributes_map(attr_map)

我们可以打开一个extensions的例子,或者看 neutron 提供的example
$ cat neutron/tests/unit/extensions/foxinsocks.py

1.

每个extension, 有如下属性:  这实际上就是指明了依赖关系。
get_required_extensions
get_optional_extensions   交叉的resource

    def get_required_extensions(self):
"""Returns a list of extensions to be processed before this one."""
return [] def get_optional_extensions(self):
"""Returns a list of extensions to be processed before this one. Unlike get_required_extensions. This will not fail the loading of
the extension if one of these extensions is not present. This is
useful for an extension that extends multiple resources across
other extensions that should still work for the remaining extensions
when one is missing.
"""
return []

2. 每个extension, 有get_extended_resources函数:
neutron的代码,描述的非常清楚。
这个函数,就是用来指明, 这个改extension 
怎么去扩展core plugin的。
扩展完成后的效果,跟我们直接改core plugin的效果一样。 访问wsgi的话, 就会多一些属性了。

当然, 也可以产生自己的wsgi的resource,
这些resource的属性,会被集成到RESOURCE_ATTRIBUTE_MAP

RESOURCE_ATTRIBUTE_MAP 是这个neutron wsgi暴露出去的所有的资源。
在我的机器上,最后暴露出来的resource有:

print attr_map.keys()
['flavors', 'subnets', 'availability_zones', 'rbac_policies', 'floatingips', 'routers', 'segments', 'service_providers', 'quotas', 'address_scopes', 'networks', 'service_profiles', 'trunks', 'agents', 'security_group_rules', 'policies', 'auto_allocated_topologies', 'subnetpools', 'network_ip_availabilities', 'security_groups', 'ports']
    def get_extended_resources(self, version):
"""Retrieve extended resources or attributes for core resources. Extended attributes are implemented by a core plugin similarly
to the attributes defined in the core, and can appear in
request and response messages. Their names are scoped with the
extension's prefix. The core API version is passed to this
function, which must return a
map[<resource_name>][<attribute_name>][<attribute_property>]
specifying the extended resource attribute properties required
by that API version. Extension can add resources and their attr definitions too.
The returned map can be integrated into RESOURCE_ATTRIBUTE_MAP.
"""
return {}

3. update_attributes_map

每个extend_resources 过程中, 每个 extension 最终会调用这个函数 来更新 attributes

    def update_attributes_map(self, extended_attributes,
extension_attrs_map=None):
"""Update attributes map for this extension. This is default method for extending an extension's attributes map.
An extension can use this method and supplying its own resource
attribute map in extension_attrs_map argument to extend all its
attributes that needs to be extended. If an extension does not implement update_attributes_map, the method
does nothing and just return.
"""
if not extension_attrs_map:
return for resource, attrs in six.iteritems(extension_attrs_map):
extended_attrs = extended_attributes.get(resource)
if extended_attrs:
attrs.update(extended_attrs)

5. Router 代码(luyao 分析)

首先我们知道一个shell下的命令到具体函数的执行分为三个阶段

1.nova list命令转化为HTTP请求

2.HTTP请求到WSGI Application

3.WSGI Application到具体的执行函数

先从第三阶段直接介绍,第二阶段在下面会有所提及,第一阶段不做介绍

************

第三阶段 WSGI Application到具体的执行函数

************

这是APIRouter

/opt/stack/neutron/neutron/api/v2/router.py

Class APIRouter(base_wsgi.Router):完成路由规则建立

......

def __init__(self, **local_config):

......

# 主要是为所有的resource生成一个mapper,这个mapper包含了所有resource对应的路径

mapper.connect('index', '/', controller=Index(RESOURCES))

for resource in RESOURCES:

_map_resource(RESOURCES[resource], resource,

attributes.RESOURCE_ATTRIBUTE_MAP.get(

RESOURCES[resource], dict()))

resource_registry.register_resource_by_name(resource)

# 这里的SUB_RESOURCES类似NOVA中的扩展资源

for resource in SUB_RESOURCES:

_map_resource(SUB_RESOURCES[resource]['collection_name'], resource,

attributes.RESOURCE_ATTRIBUTE_MAP.get(

SUB_RESOURCES[resource]['collection_name'],

dict()),

SUB_RESOURCES[resource]['parent'])

......

# 这一句调用父类的__init__方法,接下来看父类Router中做了那些操作

super(APIRouter, self).__init__(mapper)

......

这是Router

/usr/local/lib/python2.7/dist-packages/oslo_service/wsgi.py

base_wsgi.Router:mapper和dispatch()关联起来,是为了找到真正的我们所需要的WSGI app

class Router(object):

"""WSGI middleware that maps incoming requests to WSGI apps."""

def __init__(self, mapper):

self.map = mapper

self._router = routes.middleware.RoutesMiddleware(self._dispatch, self.map)

# 这里的__call__函数进行了wsgify的封装,在这个Router被call的时候,或者是它的子类APIRouterV21在被call的时候,

# 需要看webob.dec.wsgify里面是怎么进行封装的才能知道具体的执行过程

# webob.dec.wsgify定义了当函数被call的时候,会把“外衣”脱掉,继续call外衣里面的函数,一直到最核心的app,

# 这个app是直接接受参数(environ, start_response)的wsgi app

# 这里提到的“外衣”是从HTTP请求到WSGI app的时候一层层穿上的,其实是一系列的filter,为了做验证等一系列的操作

@webob.dec.wsgify(RequestClass=Request)

def __call__(self, req):

return self._router

@staticmethod

@webob.dec.wsgify(RequestClass=Request)

def _dispatch(req):

match = req.environ['wsgiorg.routing_args'][1]

if not match:

return webob.exc.HTTPNotFound()

app = match['controller']

return app

**************

HTTP请求到WSGI Application

**************

这是pipeline对应的filter

这部分是HTTP到WSGI application的过程,主要是根据paste配置文件做相应的操作

这是在Router之前的部分

/opt/stack/neutron/etc/api-paste.ini

[composite:neutronapi_v2_0]

use = call:neutron.auth:pipeline_factory

noauth = cors request_id catch_errors extensions neutronapiapp_v2_0

keystone = cors request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0 #这就是一系列filter

如果大家觉得有需要我可以再介绍HTTP到WSGI Application的详细过程

6.  SUB_RESOURCES

好了, 回到 APIRouter, 继续走读。 具体生成理由的代码, 直接看python的routes库, docs介绍的很详细。
我们剩下的需要把 SUB_RESOURCES 怎么生成的看看, 目前SUB_RESOURCES 默认值是空的。

 def __init__(self, **local_config):
mapper = routes_mapper.Mapper() # 这个是用来做路由用的。
plugin = manager.NeutronManager.get_plugin()
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
  # 这个需要重点分析,怎么扩展resources, 这些resources就是最终通过wsgi可访问到的资源。 我们之前已经分析过了啊。
ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP) col_kwargs = dict(collection_actions=COLLECTION_ACTIONS,    # 这里的 collection 就是collection
member_actions=MEMBER_ACTIONS)   # 这里的 member 就是具体的resource def _map_resource(collection, resource, params, parent=None):
allow_bulk = cfg.CONF.allow_bulk
allow_pagination = cfg.CONF.allow_pagination
allow_sorting = cfg.CONF.allow_sorting
controller = base.create_resource( # controller 是动态生成的。
collection, resource, plugin, params, allow_bulk=allow_bulk,
parent=parent, allow_pagination=allow_pagination,
allow_sorting=allow_sorting)
path_prefix = None
if parent:
path_prefix = "/%s/{%s_id}/%s" % (parent['collection_name'],
parent['member_name'],
collection)
mapper_kwargs = dict(controller=controller,
requirements=REQUIREMENTS,
path_prefix=path_prefix,
**col_kwargs)
return mapper.collection(collection, resource,   # 生成路由
**mapper_kwargs) mapper.connect('index', '/', controller=Index(RESOURCES)) # 生成URL的根, 这个可以做个实验。
# TOKEN=`openstack token issue -f value -c id`
# curl -s -H "X-Auth-Token:$TOKEN" http://10.238.155.177:9696/v2.0/ | python -m json.tool
# 这两条命令, 会call 到 root
for resource in RESOURCES:    # 生成URL的第一级resource目录, 这个可以做个实验。
# TOKEN=`openstack token issue -f value -c id`
# curl -s -H "X-Auth-Token:$TOKEN" http://10.238.155.177:9696/v2.0/networks | python -m json.tool
# 这个 请求 会 call 到 neutron.api.v2.base.Controller.index
# 最终会call 到 ml2的 plugin的get_networks 方法。
_map_resource(RESOURCES[resource], resource,
attributes.RESOURCE_ATTRIBUTE_MAP.get(
RESOURCES[resource], dict()))
resource_registry.register_resource_by_name(resource) for resource in SUB_RESOURCES:    # 生成URL的子resource目录, 这个可以做个实验。
_map_resource(SUB_RESOURCES[resource]['collection_name'], resource,
attributes.RESOURCE_ATTRIBUTE_MAP.get(
SUB_RESOURCES[resource]['collection_name'],
dict()),
SUB_RESOURCES[resource]['parent']) # openstack的homebrew的app框架, 好像目前只能生成两级的resource。  # Certain policy checks require that the extensions are loaded
# and the RESOURCE_ATTRIBUTE_MAP populated before they can be
# properly initialized. This can only be claimed with certainty
# once this point in the code has been reached. In the event
# that the policies have been initialized before this point,
# calling reset will cause the next policy check to
# re-initialize with all of the required data in place.
policy.reset()
super(APIRouter, self).__init__(mapper)

8.  实验阶段: inline

只做两个实验, 获得根, 获得networks.

其他的extension扩展的实验, 由志愿者来补充。

好了,我们终于跨越了社会主义初级阶段了。 
可以进行高级阶段了。 激动吗, 高级阶段 就是干活啦。

不过万里长征刚刚起步, neutron还是很复杂的, 尤其需要很多网络背景知识和linux的内核知识。

9. 最近发现很多人做个neuron的分析

openstack學習之neutron_linuxbridge_agent分析  作者zhengleiguo还有其他相关的分析

用ml2 plugin配置OpenStack Neutron flat 网络   做过很多nuetron的分析

Neutron分析(5)—— neutron-l3-agent中的iptables 相关分析

nova boot代码流程分析(四):nova与neutron的l2 agent(neutron-linuxbridge-agent)交互

nova boot代码流程分析(三):nova与neutron的plugin交互 , SRIOV的创建流程很清楚了。《nova boot代码流程分析(一):Claim机制》文章继续分析后面的代码,即后面的代码涉及nova与neutron的交互。

zookeeper入门 各个系列

Python 弱引用 学习 (org doc

例子: Write Neutron ML2 Mechanism Driver

理解 neutron的更多相关文章

  1. 理解 Neutron Server 分层模型 - 每天5分钟玩转 OpenStack(69)

    本节开始讨论 Neutron 的各个服务组件,首先学习 Neutron Server . 上图是 Neutron Server 的分层结构,至上而下依次为: Core API对外提供管理 networ ...

  2. OpenStack实践系列⑦深入理解neutron和虚拟机

    OpenStack实践系列⑦深入理解neutron和虚拟机 五.深入理解Neutron 5.1 虚拟机网卡和网桥 [root@node1 ~]# ifconfig brq65c11cc3-8e: fl ...

  3. 深入理解 Neutron -- OpenStack 网络实现(4):网络名字空间

    问题导读1.如何查看网络名字空间?2.网络名字空间开头的名字有什么规律?3.dhcp服务是如何实现的?4.router的实现是通过iptables进行的是否正确?5.SNAT和DNAT规则有什么作用? ...

  4. 深入理解 Neutron -- OpenStack 网络实现(3):VXLAN 模式

    问题导读1.VXLAN 模式下,网络的架构跟 GRE 模式类似,他们的不同点在什么地方?2.网络节点的作用是什么?3.tap-xxx.qr-xxx是指什么? 接上篇:深入理解 Neutron -- O ...

  5. 深入理解 Neutron -- OpenStack 网络实现(2):VLAN 模式

    问题导读 1.br-int.br-ethx的作用是什么?2.安全组策略是如何实现的?3.VLAN 模式与GRE模式有哪些不同点?流量上有哪些不同?4.L3 agent实现了什么功能? 接上篇深入理解 ...

  6. 深入理解 Neutron -- OpenStack 网络实现(1):GRE 模式

    问题导读1.什么是VETH.qvb.qvo?2.qbr的存在的作用是什么?3.router服务的作用是什么? 如果不具有Linux网络基础,比如防火墙如何过滤ip.端口或则对openstack ovs ...

  7. 理解 Neutron FWaaS - 每天5分钟玩转 OpenStack(117)

    前面我们学习了安全组,今天学习另一个与安全相关的服务 -- FWaaS.理解概念 Firewall as a Service(FWaaS)是 Neutron 的一个高级服务.用户可以用它来创建和管理防 ...

  8. 理解 neutron(15):Neutron linux-bridge-agent 创建 linux bridge 的简要过程

    学习 Neutron 系列文章: (1)Neutron 所实现的虚拟化网络 (2)Neutron OpenvSwitch + VLAN 虚拟网络 (3)Neutron OpenvSwitch + GR ...

  9. 干货分享:Neutron的PPT,帮助你理解Neutron的各种细节

    深入解析Neutron http://files.cnblogs.com/popsuper1982/Neutron.pptx 经典的三节点部署 架构 怎么理解? 更加深入:Tap Interface ...

  10. 理解 neutron(15):Neutron Linux Bridge + VLAN/VXLAN 虚拟网络

    学习 Neutron 系列文章: (1)Neutron 所实现的虚拟化网络 (2)Neutron OpenvSwitch + VLAN 虚拟网络 (3)Neutron OpenvSwitch + GR ...

随机推荐

  1. Xampp单独升级某个软件

    XAMPP是一个集合的PHP+Apache+MySQL的工具包. 现在PHP升级到了7.0.6,而XAMPP目前在7.0.5,所以我需要将其升级到7.0.6. 首先将php.ini备份好,然后是php ...

  2. 第一章入门篇CSS样式的分类、盒模型

    1.CSS样式的分类 CSS样式分为一项4种: 1.内联样式表,直接写在元素style属性里面的样式,如 <p style="color:red;">内联样式</ ...

  3. gedit 没有preference项,使preference回归,并用命令行设置行号,解决centos7下中文乱码,text wrapping等问题

    1. 最简单的,使preference选项回来: gsettings set org.gnome.settings-daemon.plugins.xsettings overrides '@a{sv} ...

  4. vim自动格式化

    ,gg 跳转到第一行 ,shift+v 转到可视模式 ,shift+g 全选 ,按下神奇的 = 你会惊奇的发现代码自动缩进了,呵呵,当然也可能是悲剧了.

  5. matlab 字符串处理函数

    https://www.cnblogs.com/emanlee/archive/2012/09/13/2683912.html % 字符串处理 a='  a';b='b  b';c='cccc';m= ...

  6. VC6.0 error LNK2001: unresolved external symbol __imp__ntohl@4

    --------------------Configuration: oxToint1 - Win32 Debug-------------------- Linking... main.obj : ...

  7. SqlServer表和EXCEL数据互相复制方法

    一.SqlServer表数据复制到excel 1.新建查询,用sql语句把表数据读出来 2.然后,选择数据,右键,复制(也可以点击连同标题复制),复制到记事本中(不然会乱码) 3.然后再把记事本的内容 ...

  8. MYSQLi数据访问修改数据

    <link href="../bootstrap.min.css" rel="stylesheet" type="text/css" ...

  9. hive中安装hive_utils模块

    1. 因为在linux部署的python 3.6 在安装模块的时候遇到了许多问题,所以使用linux中的python3.6环境 2. 首先使用pip安装 hive_utils 模块sudo pip i ...

  10. Unity shader学习之切线空间下计算凹凸映射

    切线空间,即使用顶点的切线作为x轴,法线作为z轴,法线与切线的叉积作为y轴. 使用切线空间存储法线,使得法线纹理可以复用,很好. 在切线空间中计算光照,比在世界空间中计算光照少了很多计算量.在切线空间 ...