cowboy源码分析

 2013-01-21 by 谢鸿锋  

原创文章,转载请注明:转载自Erlang云中漫步 

目录

=================================

一、概述

二、ranch源码分析

三、cowboy源码分析

   1、Request调度规则

   2、http协议实现分析

   3、http协议之chunked编码

   4、http协议之long_polling

   5、http协议之websocket

   6、http协议之rest-api

=================================

 

cowboy 越来越让人舒服了,改版之后的cowboy分为两大application,将TCP拆分出来,成了ranch application,cowboy成了基于TCP(ranch)的一个cowboy_protocol(http实现)。不仅如此,cowboy还给出了rest-api、websocket、chunked、long-polling的支持,相当之完美!

 

一、概述

cowboy是一个小型、快速,模块化,采用Erlang开发的HTTP服务器。

ranch 是一个socket acceptor pool,TCP协议类型。

 

cowboy的特点:

1.代码少。
2.速度快。
3.模块化程度高,transport和protocol都可轻易替换。
4.采用二进制语法实现http服务,更快更小。
5.极易嵌入其它应用。
6.有dispatcher,可以嵌入FastCGI PHP 或者是 Ruby.
7.没有进程字典,代码干净。

 

总体来讲cowboy的特点在于分层架构及模块化设计,即把网络层的套接字管理和应用层协议实现,以及对消息的处理,这三层几乎完全解藕。

 

cowboy application详细介绍见:https://github.com/extend/cowboy/

{application, cowboy, [

      {id, "Cowboy"},

      {description, "Small, fast, modular HTTP server."},

      {sub_description, "Cowboy is also a socket acceptor pool, "

           "able to accept connections for any kind of TCP protocol."},

      {vsn, "0.7.0"},

      {applications, [

           kernel,

           stdlib,

           ranch,

           crypto

      ]},

      {mod, {cowboy_app, []}},

 

ranch application详细介绍见:https://github.com/extend/ranch/

{application, ranch, [

      {id, "Ranch"},

      {description, "Socket acceptor pool for TCP protocols."},

      {sub_description, "Reusable library for building networked applications."},

      {vsn, "0.6.0"},

      {mod, {ranch_app, []}},

  

二、ranch源码分析

 

1、看下效果

 

a) 启动ranch应用 application:start(ranch).

 

b)启动例子tcp_echo应用 application:start(tcp_echo).

 

c)客户端连接测试

 

 

d)处理客户端请求

 

e)客户端断开

 

 

下面对关键代码执行轨迹进行分析

 

2、启动ranch应用

> application:start(ranch).

 

代码执行轨迹关注点见下面几张图红线框住部分

 

  

 

 启动了监督进程ranch_sup,以one_for_one方式监督启动ranch_server工作进程

 

 

此时进程监督树如下:

 

关注数据:etsranch_server,此时为空,

               ranch_server进程状态数据state,此时为空,

下面跟踪客户端连接进来时,这两个数据的变化情况。

 

3、启动例子tcp_echo应用

> application:start(tcp_echo).

 代码执行轨迹关注点见下面几张图红线框住部分

 

 

 It will have a pool of 1 acceptors, use a TCP transport and forward connections to the “echo_protocol” handler.

 

 动态生成ranch_sup的子进程{ranch_listener_sup, Ref},类型为supervisor。Ref值为tcp_echo。

结束此进程可以调用ranch:stop_listener(tcp_echo)。ranch_sup在application:start(ranch)时已经产生。

 

各参数说明见其注释:

Start a listener for the given transport and protocol.

A listener is effectively a pool of NbAcceptors acceptors. Acceptors accept connections on the given Transport and forward connections to the given Protocol handler. Both transport and protocol modules can be given options through the TransOpts and the ProtoOpts arguments. Available options are documented in the listen transport function and in the protocol module of your choice.

All acceptor and connection processes are supervised by the listener.

It is recommended to set a large enough number of acceptors to improve performance. The exact number depends of course on your hardware, on the protocol used and on the number of expected simultaneous connections.

The Transport option max_connections allows you to define the maximum number of simultaneous connections for this listener. It defaults to 1024. See ranch_listener for more details on limiting the number of connections.

Ref can be used to stop the listener later on.

This function will return {error, badarg}` if and only if the transport module given doesnt appear to be correct.

 

 

 

 此时进程状态数据及ets表数据如下:

 

ranch_server进程monitor进程<0.41.0>、<0.44.0>。<0.41.0>为listener,<0.44.0>为acceptor。<0.42.0>为connections 管理者,客户端连接进程由其以simple_one_for_one方式监控。此时客户端连接数为0。

 

ets表及进程状态数据生成跟踪代码轨迹:

 

 

至此,application(ranch)、application(tcp_echo)启动完成,

进程监督树产生/监督类型及关键代码轨迹分析完毕。

进程状态数据及ets表数据生成及关键代码轨迹也已分析完毕。

 

下面跟踪有客户端连接时的情况。

 

 4、客户端连接

 

 

 

接收到客户端连接,从下面几个方面进行分析

A、 生成ConnsSup子进程,controlling_process(CSocket, ConnPid)绑定接收的客户端socket。

 

上图<0.42.0>为ConnsSup,<0.78.0>为ConnPid

 

B、 ConnPid生成,代码轨迹如下

 

 

C、 更新表ranch_server客户端连接数

59行ranch_listener:add_connection(ListenerPid,ConnPid)更新客户端连接数

 

 

D、 max_connections最大连接数处理:超过最大连接数将不再进行accept,轮询检测连接数,当小于最大连接数时,才开始accept接受客户端的连接。

 

 5、处理客户端请求

 

客户端发的数据,服务器端收到后,原样响应传回给客户端。

 

6、客户端断开。

 

 

到这里,ranch的源码分析就完成了。

 

不足之处:acceptor接收客户端连接,用了一个临时的进程来中转,中转完毕此进程即销毁。

 

上图<0.79.0>即为临时进程,其功能完全可以合并到<0.44.0>中来进行。何必“创建->中转->销毁”多此一举呢?hotwheels的处理就是如此,干脆利落,堪称优雅。hotwheels源码分析见博主另外一篇原创文章: http://www.cnblogs.com/poti/archive/2012/11/06/hotwheels.html。

 

 三、cowboy源码分析

 

cowboy application实现了http协议,给出了rest-api、websocket、chunked、long-polling的支持,相当完美。

 

1、Request调度规则

 

见cowboy_dispatcher:match(Dispatch, Host, Path)

 

-spec match(Dispatch::dispatch_rules(), Host::binary() | tokens(), Path::binary())

    -> {ok, module(), any(), bindings(),

      HostInfo::undefined | tokens(), PathInfo::undefined | tokens()}

    | {error, notfound, host} | {error, notfound, path}

    | {error, badrequest, path}.

 

-type tokens() :: [binary()].

-type match_rule() :: '_' | <<_:8>> | [binary() | '_' | '...' | atom()].

-type dispatch_path() :: [{match_rule(), module(), any()}].

-type dispatch_rule() :: {Host::match_rule(), Path::dispatch_path()}.

-type dispatch_rules() :: [dispatch_rule()].

  

示例说明:

match_test_() ->

    Dispatch = [

       {[<<"www">>, '_', <<"ninenines">>, <<"eu">>], [

           {[<<"users">>, '_', <<"mails">>], match_any_subdomain_users, []}

       ]},

       {[<<"ninenines">>, <<"eu">>], [

           {[<<"users">>, id, <<"friends">>], match_extend_users_friends, []},

           {'_', match_extend, []}

       ]},

       {[<<"ninenines">>, var], [

           {[<<"threads">>, var], match_duplicate_vars,

              [we, {expect, two}, var, here]}

       ]},

       {[<<"erlang">>, ext], [

           {'_', match_erlang_ext, []}

       ]},

       {'_', [

           {[<<"users">>, id, <<"friends">>], match_users_friends, []},

           {'_', match_any, []}

       ]}

    ],

    %% {Host, Path, Result}

    Tests = [

       {<<"any">>, <<"/">>, {ok, match_any, [], []}},

       {<<"www.any.ninenines.eu">>, <<"/users/42/mails">>,

           {ok, match_any_subdomain_users, [], []}},

       {<<"www.ninenines.eu">>, <<"/users/42/mails">>,

           {ok, match_any, [], []}},

       {<<"www.ninenines.eu">>, <<"/">>,

           {ok, match_any, [], []}},

       {<<"www.any.ninenines.eu">>, <<"/not_users/42/mails">>,

           {error, notfound, path}},

       {<<"ninenines.eu">>, <<"/">>,

           {ok, match_extend, [], []}},

       {<<"ninenines.eu">>, <<"/users/42/friends">>,

           {ok, match_extend_users_friends, [], [{id, <<"42">>}]}},

       {<<"erlang.fr">>, '_',

           {ok, match_erlang_ext, [], [{ext, <<"fr">>}]}},

       {<<"any">>, <<"/users/444/friends">>,

           {ok, match_users_friends, [], [{id, <<"444">>}]}},

       {<<"ninenines.fr">>, <<"/threads/987">>,

           {ok, match_duplicate_vars, [we, {expect, two}, var, here],

              [{var, <<"fr">>}, {var, <<"987">>}]}}

    ],

    [{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() ->

       {ok, Handler, Opts, Binds, undefined, undefined}

           = match(Dispatch, H, P)

    end} || {H, P, {ok, Handler, Opts, Binds}} <- Tests].

 

match_info_test_() ->

    Dispatch = [

       {[<<"www">>, <<"ninenines">>, <<"eu">>], [

           {[<<"pathinfo">>, <<"is">>, <<"next">>, '...'], match_path, []}

       ]},

       {['...', <<"ninenines">>, <<"eu">>], [

           {'_', match_any, []}

       ]}

    ],

    Tests = [

       {<<"ninenines.eu">>, <<"/">>,

           {ok, match_any, [], [], [], undefined}},

       {<<"bugs.ninenines.eu">>, <<"/">>,

           {ok, match_any, [], [], [<<"bugs">>], undefined}},

       {<<"cowboy.bugs.ninenines.eu">>, <<"/">>,

           {ok, match_any, [], [], [<<"cowboy">>, <<"bugs">>], undefined}},

       {<<"www.ninenines.eu">>, <<"/pathinfo/is/next">>,

           {ok, match_path, [], [], undefined, []}},

       {<<"www.ninenines.eu">>, <<"/pathinfo/is/next/path_info">>,

           {ok, match_path, [], [], undefined, [<<"path_info">>]}},

       {<<"www.ninenines.eu">>, <<"/pathinfo/is/next/foo/bar">>,

           {ok, match_path, [], [], undefined, [<<"foo">>, <<"bar">>]}}

    ],

    [{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() ->

       R = match(Dispatch, H, P)

    end} || {H, P, R} <- Tests].

 

2、http协议分析

 

http协议说明见:http://www.cnblogs.com/poti/articles/2851330.html

 

从http_SUITE: echo_body/1例子开始分析http协议解析。

 

命令行代码执行:

1> Config = [{priv_dir,"D:/eclipse/workspace/cowboy/test/priv"}].

2> http_SUITE:init_per_suite(Config).

3> Client = http_SUITE:init_per_group(http, Config).

4> http_SUITE:echo_body(Client).

{ok,<<"aaa">>,

    {client,request,[],#Port<0.1848>,ranch_tcp,5000,<<>>,

            keepalive,

            {1,1},

            undefined}}

 

调度规则:

Dispatch =

    [

        {[<<"localhost">>], [

            {[<<"echo">>, <<"body">>], http_handler_echo_body, []},

        ]}

    ]

 

测试代码:

 

请求处理模块

 

 以上代码完成了下面功能:

A、客户端http请求

 

B、服务器http响应

 

 

cowboy_protocol.erl代码分析

 parse_method:解析method。 例子中是POST

parse_uri_path:解析path。   /echo/body

parse_version:解析version。  HTTP/1.1

parse_uri_query:解析query,url中$?分割部分。 例子中为空。

parse_uri_fragment:解析fragment,url中$#分割部分。 例子中为空。

parse_header:解析header。

Headers = [{<<"connection">>,<<"close">>},

             {<<"user-agent">>,<<"Cow">>},

             {<<"host">>,<<"localhost:33080">>},

             {<<"content-length">>,<<"3">>}]

parse_host:解析host。 { <<"localhost">>, 33080 }

request:开始处理request。

 

 

 cowboy_router.erl作用:根据Reqeust从Dispatch中找到handler模块及handle_opts。

 

 

cowboy_handler.erl作用:执行handler模块,带上参数handler_opts。

 

接下来,看Handler:init/3、Handler:handle/2,这里Handler为http_handler_echo_body.erl。

 

init/3的结果HandlerState这里为undefined会传给handle/2参数State。

到这里,客户端http请求处理完毕,

 

服务器http响应完成后将根据request中connection字段的值进行相应处理。

如果connection=close则Transport:close(Socket)断开连接;

如果connection=keep-alive则保持连接,处理下一个请求;如果超时未收到请求,则断开连接。

 

3、http协议chunked编码

 

chunked编码是HTTP/1.1 RFC里定义的一种编码方式,

协议说明见:http://www.cnblogs.com/poti/articles/2822159.html

 

从http_SUITE: chunked_response/1例子开始进行分析

 

客户端http请求:

服务器http响应:

 

 

服务器http响应,分下面几个数据包进行:

a)cowboy_req:chunked_reply(200, Req) -> chunked协议http头部发送

 

关键代码轨迹如下:

 

b)cowboy_req:chunk("chunked_handler\r\n", Req2) -> 第1个chunk数据包发送。

发送chunk数据包chunked_handler\r\n

 

关键代码轨迹如下:

 

c)cowboy_req:chunk("works fine!", Req2) -> 第2个chunk数据包发送。

发送chunk数据包works fine!。分析同b),不赘述。

 

d)cowboy_req:ensure_response(#http_req{socket=Socket, transport=Transport,resp_state=chunks}, _) -> 最后一个chunk数据包发送。

发送last-chunk数据包<<"0\r\n\r\n">>

  

4、http协议long_polling

 

long-polling的服务,其客户端是不做轮询的,客户端在发起一次请求后立即挂起,一直到服务器端有更新的时候,服务器才会主动推送信息到客户端。 在服务器端有更新并推送信息过来之前这个周期内,客户端不会有新的多余的请求发生,服务器端对此客户端也啥都不用干,只保留最基本的连接信息,一旦服务器有更新将推送给客户端,客户端将相应的做出处理,处理完后再重新发起下一轮请求。

 

从http_SUITE: check_status/1例子开始进行分析

 

 

/long_polling 处理模块为http_handler_long_polling

 

 定时器动作5次后给客户端响应状态码102。下面分析下服务器代码处理过程:

 

 erlang:send_after/3、erlang:start_timer/3说明见:http://www.cnblogs.com/poti/articles/2823209.html

 

cowboy_handler:handler_init/4执行后返回结果为

 

 

 

 

 erlang:hibernate/3用途:使当前进程进入休眠状态,当有消息发送给进程时,激活进程调用Module:Function(Args)。

 这里Module为cowboy_protocol,Function为resume。

 这个例子中激活进程的消息从何而来?两个地方:

 

激活进程后执行方法:cowboy_protocol:resume/6

 

这里Module为cowboy_handler,Function为handler_loop。接下来的代码执行轨迹如下:

 

当前进程再次进入休眠状态,这里Module为cowboy_handler,Function为handler_loop。

激活进程后执行方法:cowboy_handler: handler_loop

直到计数器为0

 

 以上就是cowboy中long-polling的处理过程。

 

小结一下此例子中cowboy的long-polling处理过程:

a)  处理模块init方法启动一个定时器,同时返回结果: {loop, Req, state(), timeout(), hibernate}

b)  当前进程进入休眠状态

c)  定时器发送消息,激活休眠的进程,回调处理模块的方法info/3

d)  如果服务器响应客户端的条件符合,则服务器给客户端响应结果,到这里,客户端的长连接就处理完毕了

e)  否则,info/3中重新启动一个定时器,goto 到b)继续执行。

 

cowboy中实现long-polling的三种方式:

 

init/3 -> {loop, Req, state(), hibernate}

当前进程不创建定时器,有休眠状态。不限时的休眠方式长轮询。

 

init/3 -> {loop, Req, state(), timeout()}

当前进程创建定时器,无休眠状态。限时的无休眠方式长轮询。

 

init/3 -> {loop, Req, state(), timeout(), hibernate}

当前进程创建定时器,有休眠状态。限时且有休眠方式的长轮询。

 

hibernate的作用:进程进入休眠状态,消耗服务器资源最小化,直到有消息到达时被激活。

 

5、http协议websocket

 

5.1、websocket草案00协议 见:http://www.cnblogs.com/poti/articles/2828392.html

 

cowboy中websocket版本00的实现过程如下:

a)  握手协议,建立websocket连接通道

客户端发送消息:

 

请求中的“Sec-WebSocket-Key1”,“Sec-WebSocket-Key2”和最后的“8字节的Key3”都是随机的,

服务器端会用这些数据来构造出一个16字节的应答。

其中:8字节的Key3为请求的内容,其它的都是http请求头。

判断当前请求是否WEBSOCKET,主要是通过请求头中的Connection是不是等于Upgrade以及Upgrade是否等于WebSocket。

 

服务器响应消息:

 

把请求的第一个Key中的数字除以第一个Key的空白字符的数量,而第二个Key也是如此,然后把这两个结果与请求最后的8字节字符串连接起来,然后进行MD5构造产生16字节的加密数据。

 

b)  消息传送

客户端和服务端发送非握手文本消息,消息以0x00开头,0xFF结尾。

 

c)  连接断开

客户端发送<<0xFF, 0x00>>,服务器回复<<0xFF, 0x00>>。

 

下面对关键代码进行分析

 

a 握手协议

a.1 客户端发送:

 

 

 a.2 服务器处理:

处理模块websocket_handler.erl:init/3返回结果

{upgrade, protocol, cowboy_websocket}.

 

 

 http请求头部验证:下图红线框住部分必须要有。

 

 

服务器给客户端发送:

 

 

status(101) -> <<"101 Switching Protocols">>;切换协议。

 

a.3 客户端收到后,如下处理:

 

101状态检查。http响应头部验证:下图红线框住部分必须要有。

 

 接着,客户端往服务器发送一个随机的8个字节的字符串给服务器。

 

 

a.4 服务器收到此数据,作为key3,与前面的

Sec-Websocket-Key1: Y\" 4 1Lj!957b8@0H756!i

Sec-Websocket-Key2: 1711 M;4\\74  80<6

一起生成Challenge(16个字节的加密KEY),加密KEY算法:

Sec_WebSocket-Key1的产生方式:
(1)提取客户端请求的Sec_WebSocket-Key1中的数字字符组成字符串k1
(2)转换字符串k1为8个字节的长整型intKey1
(3)统计客户端请求的Sec_WebSocket-Key1中的空格数k1Spaces
(4)intKey1/k1Spaces取整k1FinalNum
(5)将k1FinalNum转换成字节数组再反转最终形成4个字节的Sec_WebSocket-Key1

Sec_WebSocket-Key2的产生方式:
(1)提取客户端请求的Sec_WebSocket-Key2中的数字字符组成字符串k2
(2)转换字符串k2为8个字节的长整型intKey2
(3)统计客户端请求的Sec_WebSocket-Key2中的空格数k2Spaces
(4)intKey2/k2Spaces取整k2FinalNum
(5)将k2FinalNum转换成字节数组再反转最终形成4个字节的Sec_WebSocket-Key2

Sec_WebSocket-Key3的产生方式:
客户端握手请求的最后8个字节

将Sec_WebSocket-Key1、Sec_WebSocket-Key2、Sec_WebSocket-Key3合并成一个16字节数组
再进行MD5加密形成最终的16个字节的加密KEY

 

 

服务器生成Challenge后,发送给客户端,

a.5 客户端收到此消息后,握手协议到此就算完成。

 

b 握手协议完成后,进行消息的发送接收:

客户端和服务端发送非握手文本消息,消息以0x00开头,0xFF结尾。

服务器端:

 

客户端:

 

 

c 断开连接

客户端发送<<0xFF, 0x00>>,服务器回复<<0xFF, 0x00>>。

客户端:

 

服务器:

 

 

 

5.2、websocket草案10协议 见:http://www.cnblogs.com/poti/articles/2828378.html

 

cowboy中websocket版本7、8、13的实现过程:

a)  握手协议,建立websocket连接通道

客户端发送消息:

 

1)Sec-WebSocket-Key后面的长度为24的字符串是客户端随机生成的,我们暂时叫他cli_key,服务器必须用它经过一定的运算规则生成服务器端的key,暂时叫做ser_key,然后把ser_key发回去,ser_key后面会介绍;

2)把http头中Upgrade的值由"WebSocket"修改为了"websocket";

3)把http头中的"Origin"修改为了"Sec-WebSocket-Origin";

4)增加了http头"Sec-WebSocket-Accept",用来返回原来草案00服务器返回给客户端的握手验证,原来是以body的形式返回,现在是放到了http头中。

 

服务器响应消息:

 

服务器端制作秘钥ser_key:

1)服务器端将cli_key(长度24)截取出来dGhlIHNhbXBsZSBub25jZQ==

用它和自定义的一个字符串(长度36):258EAFA5-E914-47DA-95CA-C5AB0DC85B11 连接起来,像这样:dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11

2)然后把这一长串经过SHA-1算法加密,得到长度为20字节的二进制数据,再将这些数据经过Base64编码,最终得到服务端的密钥,也就是ser_key:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

3)然后将ser_key发送给客户端

 

至此,算是握手成功了!

 

b)  消息传送

 

消息格式:

 

各字段详细说明见:http://www.cnblogs.com/poti/articles/2828378.html

cowboy中将按这个规则对数据进行编码及解码。下面对Opcode做个说明:

Opcode:4位操作码,定义有效负载数据,以下是定义的操作码:

      *  %x0 表示连续消息片断
      *  %x1 表示文本消息片断
      *  %x2 表未二进制消息片断
      *  %x3-7 为将来的非控制消息片断保留的操作码
      *  %x8 表示连接关闭
      *  %x9 表示心跳检查的ping
      *  %xA 表示心跳检查的pong
      *  %xB-F 为将来的控制消息片断的保留操作码

 

c)  心跳消息:

ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping

{ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong

 

d)  连接断开

ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close

{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),

 

 

 6、HTTP协议之rest-api

      未完待续

转载于:https://www.cnblogs.com/poti/archive/2013/01/21/2870302.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/433954.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux解压tz zip,TZ 文件扩展名: 它是什么以及如何打开它?

TZ 疑难解答常见的 TZ 打开问题Smith Micro StuffIt Deluxe 已删除尝试打开 TZ 文件时&#xff0c;您收到错误 “无法打开 TZ 文件类型”。 发生这种情况时&#xff0c;通常是由于 %%os%% 中缺少 Smith Micro StuffIt Deluxe。 操作系统不知道如何处理你的 TZ 文件&#xff0c;…

无法定位程序输入点 except_软件测试中的功能测试点(三)

testkuaibao|软件测试自学公众号26.输入法半角全角检查再输入信息中&#xff0c;输入一个或连串空格&#xff0c;查看系统如何处理&#xff0c;如对于要求输入符点型数据的项中&#xff0c;输入全角的小数点(“。”或“.”&#xff0c;如4.5)&#xff1b;输入全角的空格等。 27…

ASP.NET站点跨子域名单点登陆(SSO)的实现

http://blog.csdn.net/jason_dct/article/details/8502075 ASP.NET站点跨子域名单点登陆&#xff08;SSO&#xff09;的实现 在MSDN的文档“配置跨应用程序的 Forms 身份验证&#xff08;http://msdn2.microsoft.com/zh-CN/library/eb0zx8fc.aspx&#xff09;” 中&#xff0c;…

linux实验三makefile,实验平台上Makefile详细的解释

作者&#xff1a;甘老师,华清远见嵌入式学院讲师。# CORTEX-A8 PERI DRIVER CODE# VERSION 2.0# ATHUOR www.linuxidc.com# MODIFY DATE#2013.03.28 Makefile/***(下面的解释将用这个的形式进行标注)写好的源文件,要编译成二进制文件.需要指定工具链的,这里指定我们的工具链是…

基础C#总结

由于在学习c#这段视频是为了辅助设计模式的学习,这部分的内容也和VB的内容很大程度上是一样的.虽然在开始的 时候,有些困难.在接触了一些例子和实验后.理解起来变得顺畅了很多.下面是对c#基础内容的总结.很多内容都在VB中有 过接触,所以都是些基础知识.捋一捋,将这些时间脑子的…

e站app改内置hosts_米家踢脚线电暖器E评测:符合现代家居审美 全屋取暖“小钢炮”...

【科技犬】对于没有集中供暖的长江中下游地区居民而言&#xff0c;电暖器是不折不扣的"保命神器"。而在深秋的北方&#xff0c;昼夜温差较大&#xff0c;这种时候使用灵活、易于搬运的电暖器也成为更加明智的选择。在北方每年的冬季&#xff0c;室内温度就直接关系着…

锐炬显卡可以linux吗,Intel Broadwell桌面CPU性能测试:Iris Pro 6200核显无敌了

Intel已经在Computex 2015发布了Broadwell-H桌面版处理器&#xff0c;分别有Core i7-5775C和Core i5-5765C两款&#xff0c;另有3款BGA封装的嵌入式型号。它们的特色是内置Intel史上最强Iris Pro 6200核显、以及较大的超频空间。这款处理器现在已经解禁&#xff0c;发布了评测&…

经纬度转化为xy坐标系_Arcgis添加经纬度矢量点

今天帮舍友作图&#xff0c;才体会到九边说的。学技术使人清醒&#xff0c;清醒的认识自己能力有限。少去网上怨天怨地&#xff0c;踏踏实实去提升实力。这是她给我的原始数据&#xff0c;一堆经纬度点。首先我们先进行数据去重&#xff0c;当然这一步可以根据自己数据考虑是否…

物联网课程学习目标_学习攻略|软件工程统计方法amp;amp;物联网

软件工程统计方法&&物联网任课老师&#xff1a;余松森&#xff0c;葛红课程特点及困难本课程的主要内容涉及统计机器学习方法&#xff0c;以及如何采用Python进行应用实现。同学们在学习中主要遇到以下问题&#xff1a;1、在课程内容方面&#xff0c;课本上的关于pytho…

tdk怎么设置_你真的做好网站的标题、描述、关键词(TDK)设置了吗?

SEO其实是个苦活累活&#xff0c;大部分的工作都是在每日不断的坚持与重复。当然也是个细致活&#xff0c;很多的工作都是对一些细节问题的处理。可能平时你没留意到的地方&#xff0c;就是你的网站数据没能上来的原因。比如说SEO的基础设置&#xff1a;TDK。说到TDK(标题、描述…

C# 监控字段_监控交换机选择:千兆/百兆/核心/PoE/光纤交换机选型指南

我们就交换机选型时的四个主要方面讲一下。01选择千兆还是百兆&#xff1f;视频监控系统的网络中&#xff0c;需要传输大量、持续的视频数据&#xff0c;这就要求交换机具有稳定转发数据的能力。交换机接入的摄像头数量越多&#xff0c;流经该交换机的数据量就会越大。我们可以…

JAVA学习笔记——JAVA基础语法之精华

一、标识符 概念&#xff1a;JAVA里面我们可以给他取名字的&#xff08;变量、类、方法等等&#xff09;就是标识符&#xff1a; 注意&#xff1a;1、标识符只能包含字母、数字、下划线还有美元符号$ 2、只能以字母、下划线和美元符号开头 二、变量 概念&#xff1a;JAVA中储存…

c语言 行程长度编码,C语言编程题,求大佬帮助,关于数组的。

满意答案6kidf3xhs2017.11.07采纳率&#xff1a;41% 等级&#xff1a;8已帮助&#xff1a;62人2 个关键&#xff1a;2位数字的随机数&#xff1a; a[i] 10 rand() % 90;10位或个位 含5 的 并高于平均值的 数&#xff1a;if (a[i]>ave && ( a[i]%50 || (a[i]/10)…

wpf学习笔记二 深入学习 xaml

1、XAML 主要用于绘制UI界面&#xff0c;最大的优点是能使UI与运行逻辑分离开来&#xff0c;使得整个程序回到逻辑处理上来。 每一个标签对应.NET Framework类库的一个控件类。通过设置标签的Attribute&#xff0c;不仅可以对标签所对应的控件 对象Property进行赋值&#xf…

cortex a7 a53_小号“A7”终于亮相,配4米9车长 大溜背!堪称15万内最强颜值!

原标题&#xff1a;小号“A7”终于亮相&#xff0c;配4米9车长 大溜背&#xff01;堪称15万内最强颜值&#xff01;今天来推荐一款b级轿车&#xff0c;大家都知道现在国内热度最高的就上suv车型了&#xff0c;但是销量最高的车型依旧还是轿车车型&#xff0c;因为轿车车型的粉丝…

C语言数据结构迷宫实验报告,数据结构c语言课程设计报告之迷宫

数据结构c语言课程设计报告之迷宫 C语言与数据结构课程设计报告学 号 ** 姓 名 ** 课程设计题目 迷 宫 求 解 2012 年 5 月目 录1 需求分析 1.1 功能与数据需求 1.1.1 题目要求的功能 1.1.2 扩展功能 1.2 界面需求 1.3 开发环境与运行需求 2 概要设计 2.1主要数据结构2.2程序总…

unicode字符大全可复制_说说Excel不可见字符的那些事

今天小伙伴问了个问题看上去啥也没有&#xff0c;为什么黏贴到记事本上前面那么多空白呢&#xff1f;典型的不可见字符惹出来的麻烦&#xff0c;这个往往是公司软件导出数据造成的我们今天就来细说说不可见字符的那些事拿上面的例子说明大部分不可见字符&#xff0c;这一步就能…

删除后别人的微信号变成wxid_微信偷偷更新:终于能改微信号,每年改一次

跟微信打交道多年&#xff0c;机哥可以说是六号线最熟知微信套路的人。比如&#xff0c;微信最喜欢在临近周末的时候&#xff0c;来一波悄悄更新。。难不倒我&#xff01;微信新动态&#xff0c;几乎每次都被机哥妙手抓住。掐指一算&#xff0c;今天周五。安卓版微信 7.0.15 更…

android设置多个按钮,android代码中设置两个按钮之间位置

package com.example.helloworld01;//包名import java.util.ArrayList;import java.util.List;import android.os.Bundle;import android.app.Activity;import android.graphics.Color;import android.widget.ArrayAdapter;import android.widget.Button;import android.widget…

cad管线交叉怎么画_高效设计!多种方式进行管线连接、伸缩

节点连接就是需要把一些节点连接起来&#xff0c;需要通过拖拽把他连接起来&#xff0c;类似CAD的延伸。管立得中的节点连接是可以创建他们的连接关系的&#xff0c;会进行管道的联动。下面是使用管线连接功能进行连接节点&#xff0c;以及管道伸缩的的操作方式。一、管线连接1…