exosip2使用原理

  • eXosip是Osip2协议栈的封装和调用。它实现了作为单个sip终端的大部分功能,如register、call、subscription等。
  • eXosip使用UDP socket套接字实现底层sip协议的接收/发送。并且封装了sip消息的解释器。
  • eXosip使用定时轮循的方式调用Osip2的transaction处理函数,这部分是协议栈运转的核心。透过添加/读取transaction消息管道的方式,驱动transaction的状态机,使得来自远端的sip信令能汇报给调用程序,来自调用程序的反馈能通过sip信令回传给远端。
  • eXosip增加了对各个类型transaction的超时处理,确保所有资源都能循环使用,不会被耗用殆尽。
  • eXosip使用jevent消息管道来向上通知调用程序底层发生的事件,调用程序只要读取该消息管道,就能获得感兴趣的事件,进行相关的处理。
  • eXosip里比较重要的应用有j_calls、j_subscribes、j_notifies、j_reg、j_pub、osip_negotiation和authinfos。J_calls对应呼叫链表,记录所有当前活动的呼叫。J_reg对应注册链表,记录所有当前活动的注册信息。Osip_negotiation记录本地的能力集,用于能力交换。Authinfos记录需要的认证信息。

模块组成

底层连接管理

与网络连接有关。实现了连接的建立、数据的接收以及发送等相关的接口

  • eXtl_udp.c:为使用UDP连接的实现
  • extl_tcp.c:为使用TCP连接的实现
  • eXtl_dtls.c、eXtl_tls.c:为使用安全socket连接的实现

内部功能模块实现

Jauth.c、jcall.c、jdialog.c、jevents.c、jnotify.c、jpublish.c、jreg.c、jrequest.c、jresponse.c、jsubscribe.c 等文件实现了内部对一些模块的管理,这些模块正如其文件名所表示的,jauth.c主要是认证,jcall.c 则是通话等等。

上层API 封装实现

eXcall_api.c、eXinsubscription_api.c、eXmessage_api.c…这几个以api 为后缀的文件,实现各个子模块的管理。应用程序可以调用这里提供的接口,方便的构造或者发送sip 消息。

其他

  • inet_ntop.c 实现ip 地址的点分十进制与十六进制表示之间的转换。
  • jcallback.c 实现一堆回调函数,这些回调函数就是用来注册到osip 库的。我们使用exosip 库,就是避免直接使用osip 库,因为一些工作exosip 已经帮我们做了,所以这样一来,可以简化上层的实现。
  • udp.c:主要用来对通过UDP 连接接收到的消息进行分类处理。
  • eXutils.c:实现一些杂项的函数。有ip 地址到字符串之间的转换,域名的解析等一些 辅助的功能函数。
  • exconf.c 文件实现了exosip 的初始化相关的接口,包括后台任务的实现。实际上是 “configuration api”的实现。
  • eXosip.c文件实现了与exconf.c 文件相似的功能。比如管道的使用,exosip 上事务的创建和查找,register 和subscribe 的更新,认证信息的处理等。

关键数据结构

eXtl_protocol

eXtl_protocol 是为实现网络通信专门定义的一个数据结构,包括了变量和方法两部分。其中:

  • 变量包括了建立网络连接过程中使用的ip 地址、端口等;
  • 方法部分封装了网络socket编程常用的系统调用接口。
struct eXtl_protocol {int enabled;int proto_port;char proto_name[10];char proto_ifs[20];int proto_num;int proto_family;int proto_secure;int proto_reliable;int (*tl_init) (void);int (*tl_free) (void);int (*tl_open) (void);int (*tl_set_fdset) (fd_set * osip_fdset, fd_set * osip_wrset, int *fd_max);int (*tl_read_message) (fd_set * osip_fdset, fd_set * osip_wrset);int (*tl_send_message) (osip_transaction_t * tr, osip_message_t * sip,char *host, int port, int out_socket);int (*tl_keepalive) (void);int (*tl_set_socket) (int socket);int (*tl_masquerade_contact) (const char *ip, int port);int (*tl_get_masquerade_contact) (char *ip, int ip_size, char *port,int port_size);
};struct eXtl_protocol eXtl_udp = {1,5060,"UDP","0.0.0.0",IPPROTO_UDP,AF_INET,0,0,&udp_tl_init,&udp_tl_free,&udp_tl_open,&udp_tl_set_fdset,&udp_tl_read_message,&udp_tl_send_message,&udp_tl_keepalive,&udp_tl_set_socket,&udp_tl_masquerade_contact,&udp_tl_get_masquerade_contact
};struct eXtl_protocol eXtl_tcp = {1,5060,"TCP","0.0.0.0",IPPROTO_TCP,AF_INET,0,0,&tcp_tl_init,&tcp_tl_free,&tcp_tl_open,&tcp_tl_set_fdset,&tcp_tl_read_message,&tcp_tl_send_message,&tcp_tl_keepalive,&tcp_tl_set_socket,&tcp_tl_masquerade_contact,&tcp_tl_get_masquerade_contact
};struct eXtl_protocol eXtl_dtls = {1,5061,"DTLS-UDP","0.0.0.0",IPPROTO_UDP,AF_INET,0,0,&dtls_tl_init,&dtls_tl_free,&dtls_tl_open,&dtls_tl_set_fdset,&dtls_tl_read_message,&dtls_tl_send_message,&dtls_tl_keepalive,&dtls_tl_set_socket,&dtls_tl_masquerade_contact,&dtls_tl_get_masquerade_contact
};struct eXtl_protocol eXtl_tls = {1,5061,"TLS","0.0.0.0",IPPROTO_TCP,AF_INET,0,0,&tls_tl_init,&tls_tl_free,&tls_tl_open,&tls_tl_set_fdset,&tls_tl_read_message,&tls_tl_send_message,&tls_tl_keepalive,&tls_tl_set_socket,&tls_tl_masquerade_contact,&tls_tl_get_masquerade_contact
};

代码中定义了四个该数据结构体的全局变量:eXtl_udp、eXtl_tcp、eXtl_tls 以及eXtl_dtls。分别针对使用UDP、TCP 以及安全加密连接进行了实现。

eXosip_call_t

eXosip_call_t定义了call 相关的信息,包括call 的id,call 的dialogs,call 上incoming的事务和outgoing 的事务。另外,还包括了前向和后向指针,所以,所有的call 可以通过该结构体串接起来。

typedef struct eXosip_call_t eXosip_call_t;struct eXosip_call_t {int c_id;eXosip_dialog_t *c_dialogs;osip_transaction_t *c_inc_tr;osip_transaction_t *c_out_tr;osip_transaction_t *c_cancel_tr;int c_retry; /* avoid too many unsuccessful retry */void *external_reference;time_t expire_time;eXosip_call_t *next;eXosip_call_t *parent;
};

eXosip_dialog_t

exosip_dialog_t 包含了dialog 相关的信息。

eXosip_reg_t

用来管理Register模块

eXosip_subscribe_t

用来管理subscribe模块

eXosip_pub_t

用来管理publish模块

eXosip_notify_t

用来管理notify模块

jinfo_t

这个结构体关联了dialog、call、subscribe以及notify几个结构体

eXosip_event_t

与 event 有关的结构体。这个结构体主要用来在应用层和exosip 之间通信。Exosip 在处理事务的过程中,如果需要将结果反馈给上层应用,则会生成如上结构类型的事件,并将其放到exosip 的事件队列中。应用层会不断循环从事件队列中读取事件,然后进行应用层的处理。

 /*** Structure for event description* @struct eXosip_event*/struct eXosip_event{eXosip_event_type_t type;               /**< type of the event */char textinfo[256];                     /**< text description of event */void *external_reference;               /**< external reference (for calls) */osip_message_t *request;       /**< request within current transaction */osip_message_t *response;      /**< last response within current transaction */osip_message_t *ack;           /**< ack within current transaction */int tid; /**< unique id for transactions (to be used for answers) */int did; /**< unique id for SIP dialogs */int rid; /**< unique id for registration */int cid; /**< unique id for SIP calls (but multiple dialogs!) */int sid; /**< unique id for outgoing subscriptions */int nid; /**< unique id for incoming subscriptions */int ss_status;  /**< current Subscription-State for subscription */int ss_reason;  /**< current Reason status for subscription */};

eXosip_t


exosip_t 是exosip 中最重要的结构体之一。从图可以看出,这个结构体比较大,其中包含了exosip 中用到的各个子模块的结构。比如call、reg、pub 等等。代码中定义了一个该结构类型的全局变量,通过该全局变量,就可以对exosip 前的状态进行掌控(许多相关的信息要么包含在该结构上,要么可以通过该结构找到)。

  • extl 是eXtl_protocol 类型的指针,保存了网络接口类。
  • j_osip 保存了osip 初始化时返回的osip 结构体。
  • j_transactions 一般是等待释放的事务。在事务经过osip处理完后,不再需要时,exosip会将其放在j_transactions上,等待释放。
    struct eXosip_t {struct eXtl_protocol *eXtl;char transport[10];char *user_agent;eXosip_call_t *j_calls;    /* my calls        */
#ifndef MINISIZEeXosip_subscribe_t *j_subscribes;    /* my friends      */eXosip_notify_t *j_notifies;    /* my susbscribers */
#endifosip_list_t j_transactions;eXosip_reg_t *j_reg;    /* my registrations */
#ifndef MINISIZEeXosip_pub_t *j_pub;    /* my publications  */
#endif#ifdef OSIP_MTvoid *j_cond;void *j_mutexlock;
#endifosip_t *j_osip;int j_stop_ua;
#ifdef OSIP_MTvoid *j_thread;jpipe_t *j_socketctl;jpipe_t *j_socketctl_event;
#endifosip_fifo_t *j_events;jauthinfo_t *authinfos;int keep_alive;int keep_alive_options;int learn_port;
#ifndef MINISIZEint http_port;char http_proxy[256];char http_outbound_proxy[256];int dontsend_101;
#endifint use_rport;int dns_capabilities;int dscp;char ipv4_for_gateway[256];char ipv6_for_gateway[256];
#ifndef MINISIZEchar event_package[256];
#endifstruct eXosip_dns_cache dns_entries[MAX_EXOSIP_DNS_ENTRY];struct eXosip_account_info account_entries[MAX_EXOSIP_ACCOUNT_INFO];struct eXosip_http_auth http_auths[MAX_EXOSIP_HTTP_AUTH];CbSipCallback cbsipCallback;};

exosip的初始化

Exosip 的初始化有两部分组成,这主要是从使用exosip 的角度看。

(1)对exosip 全局结构体变量的配置。这步通过调用接口eXosip_init 完成。主要完成工作如下:

  • 初始化条件变量和互斥信号量。
  • 调用osip_init 初始化osip 库,并将生成osip 结构体给exosip,同时也让osip 的application_contexgt 指针指向exosip,也就是二者相互指向。
  • 调用eXosip_set_callbacks 设置osip 的回调函数,所以回调函数都是exosip 自己实现。
  • 调用jpipe 创建通信用的pipe。对于windows 平台,是通过socket 接口模拟实现的。
  • 初始化其上的事务和事件队列。这不同于osip 的事务和事件队列。
  • 调用extl 指向的结构体的init 函数指针,初始化网络接口。

初始化完后,全局exosip结构在内存中的状态基本如下图:

其中的橘红色标识了初始化过程中涉及到的部分。

(2)在socket 接口上进行监听
这步通过调用eXosip_listen_addr 接口完成。主要完成工作如下:

  • 将eXosip 全局变量的eXtl 指针指向eXtl_udp 全局变量

  • 根据参数,配置extl_protocol 和exosip 上有关ip 端口地址等信息。另外,调用extl_udp的tl_open 函数指针,完成在本机指定的端口上监听连接的工作。需要注意的是,虽然是监听,但是使用的UDP 来建立连接的,所以消息的recv 和发送在同一个socket 上完成。在osip中设置的out_socket 并不会起作用。

  • 调用osip_thread_create 创建exosip 后台任务,用于驱动osip 的状态机。(在osip 中,在发送sip 消息部分,提到将9 个函数放到一个线程中执行,exosip 就是这样做的)

    这部分工作完成后,内存状态如下图所示:

    上图中浅绿色部分显示了这部分工作所作的配置。J_thread保存了任务的句柄。

示例代码

#include "stdio.h"
#include <eXosip2/eXosip.h>
#include <iostream>
#include <rpc/types.h>using namespace std;int main(void)
{int listenport = 8888;//库处理结果int result = OSIP_SUCCESS;TRACE_INITIALIZE (6, stdout);eXosip_t *ctx = eXosip_malloc();result = eXosip_init(ctx);if (OSIP_SUCCESS != result){printf("Can't initialize eXosip!\n");return 1;}printf("eXosip_init successfully!\n");//监听result = eXosip_listen_addr(ctx, IPPROTO_UDP,NULL,listenport,AF_INET,0);if (OSIP_SUCCESS != result){printf("eXosip_listen_addr failure.\n");return 1;}printf("eXosip_listen_addr successfully.\n");// 结束eXosip_quit(ctx);ctx = nullptr;return 0;
}

这样,在初始化完成后,我们基本上完成了对内存中所用数据结构的配置,同时启动了一个后台任务负责osip状态机的驱动。用户就可以发送和接收SIP事件了

数据收发整体框架

接收过程

在初始化过程中我们创建了一个后台任务,现在可以看看这个后台任务都做了哪些操作。任务的执行函数为_eXosip_thread,在该接口中,循环不断的调用eXosip_execute。在每一次的eXosip_execute执行中,完成如下的工作:

  • 首先计算出底层osip离当前时间最近的超时时间。也就是查看底层所有的超时事件,找出其中的最小值,然后将其与当前时间做差,结果就是最小的超时间隔了。这步是通过调用接口osip_timers_gettimeout完成的。主要检查osip全局结构体上的ict、ist、nict、nist以及ixt上所有事务的事件的超时时间。 如果ict事务队列上没有事件,则说明没有有效的数据交互存在,返回值为默认的一年,实际上就是让后面的接收接口死等。如果有事务队列上的事件的超时时间小于当前值,则说明已经超时了,需要马上处理,此时将超时时间清为零,并返回。
  • 调用eXosip_read_message接口从底层接收消息并处理。如果返回-2,则任务退出。
  • 执行osip的状态机。具体为执行osip_timers_ict(ist|nict|nist)_execute和osip_ict(ist|nict|nist)_execute这几个函数。最后还检查释放已经终结的call、registrations以及publications。
  • 如果keep_alive设置了,则调用_eXosip_keep_alive检查发送keep_alive消息。

这样,当远端的终端代理发送sip消息过来时,会被之前创建的监听端口捕获(sip协议默认的端口为5060)。在调用eXosip_read_message接口时会将其接收上来。

接收上来的数据存放在buffer中交给接口_eXosip_handle_incoming_message来处理。在其中首先调用osip_parse进行消息的解析,这是osip的核心功能之一。数据解析后,会生成一个osip_event类型的事件。接着调用osip_message_fix_last_via_header将接收到该消息的ip地址和端口根据需要设置到数据头的via域中。这在消息返回时有可能发挥作用。为了能够让消息正确的被处理,调用osip_find_transaction_and_add_event接口将其添加到osip的事务队列上。处理在这之后发生了分叉,如果osip接纳了该事件,接口直接返回,因为这说明该事件在osip上已经有匹配的事务了,或者说该事件是某一个事务过程的一部分。这样在后面执行状态机的接口时,该事件会被正确的处理。如果osip没有拿走该事件,则说明针对该事件还没有事务与之对应。此时,我们首先检查其类型,如果是request,则说明很可能是一个新的事件到来(这将触发服务端的状态机的建立),调用eXosip_process_newrequest接口进行处理。如果是response,则调用接口eXosip_process_response_out_of_transaction处理。

在eXosip_process_newrequest接口中,如果是合法的事件,则会为其创建一个新的事务。也就是说这是新事务的第一个事件。经过一大堆的处理后,该事件可能就被osip消化了,或者被exosip消化了。如果需要上报给应用,由应用拿来对一些信息进行存储或者进行图形显示之类,则会将该事件添加到exosip的事件队列上。如下图所示:


应用程序在exosip初始化完之后需要调用如下类似的代码,不断从事件队列上读取事件,并进行处理。

//开启循环消息,实际应用中可以开启多线程同时接收信号eXosip_event_t* osipEventPtr = NULL;while (true){// Wait the osip event.osipEventPtr = ::eXosip_event_wait(0, 200);eXosip_lock();//一般处理401/407采用库默认处理eXosip_default_action(osipEventPtr);eXosip_unlock();// If get nothing osip event,then continue the loop.if (NULL == osipEventPtr){continue;}// 事件处理switch (osipEventPtr->type){//需要继续验证REGISTER是什么类型case EXOSIP_REGISTRATION_NEW:{//注册事件处理}break;case EXOSIP_MESSAGE_NEW:{//消息事件处理}break;case XXXXX:{//事件处理}break;case XXXXX:{//事件处理}break;default:cout << "未处理消息 : " << osipEventPtr->type<<endl;break;}eXosip_event_free(osipEventPtr);osipEventPtr = NULL;

每当发送一个SPI消息,都会收到一个事件。每个事件包含受影响事务的原始请求,以及触发有效事件的最后应答,
你可以从这些信息获取所有的头部,并存储起来,以方便后续的操作或者显示。
例如:当收到一个呼叫传输REFER请求时,你可以检索“refer-To”头部:

发送过程

要发送数据时,需要根据消息类型,调用exosip对应模块的api接口函数来完成。如果要发送的sip消息不属于当前已有的任何事务,则类似接收过程,调用osip的相关接口创建一个新的事务,同时根据消息生成一个事件,加到事务的事件队列上。最后,唤醒exosip后台进程,使其驱动osip状态机,执行刚添加的事件,从而完成数据的状态处理和发送。当然,也有一些消息并不通过osip状态机,而是由exosip直接调用回调函数cb_snd_message完成发送。

小结

使用eXosip时,第一步工作是初始化eXosip和libosip库(解析器和状态机)。在使用libeXosip2前必须必须先做这一步。

   #include <eXosip2/eXosip.h>int i;i = eXosip_init(); // 初始化eXosip和osip协议栈if (i != 0)return -1;i = eXosip_listen_addr (IPPROTO_UDP, NULL, 5060, AF_INET, 0); // 打开信号socketif (i != 0) // 传输层初始化失败{eXosip_quit();return -1;}

然后可以发送消息,等待并处理eXosip事件。

     eXosip_event_t *je = NULL;while (1){    je = eXosip_event_wait(0,50); // 侦听是否有消息到来eXosip_lock();eXosip_default_action(je);eXosip_unlock ();if (je == NULL) // 没有接收到消息continue;switch (je->type){case EXOSIP_MESSAGE_NEW:break;case EXOSIP_CALL_INVITE:break;case EXOSIP_CALL_ACK:break;case EXOSIP_CALL_CLOSED:   break;case ...:break; default:break;      }eXosip_event_free(je);}

每当发送一个SIP消息,都会收到一个事件。每个事件包含受影响事务的原始请求,以及触发有效事件的最后应答,

你可以从这些信息获取所有的头部,并存储起来,以方便后续的操作或者显示。

例如:当收到一个呼叫传输REFER请求时,你可以检索“refer-To”头部:

     osip_header_t *referto_head = NULL;i  = osip_message_header_get_byname(evt->sip, "refer-To", 0, &referto_head);if (referto_head == NULL || referto_head->value == NULL)

eXosip_event包含呼叫标识符、注册信息、传入订阅、呼出订阅等。这些标识符将会被API用于控制呼叫、注册、传入或呼出订阅,这些API将会建立默认的带SIP头的信息头(From, To, Call-ID, CSeq, Route, Record-Route,Max-Forward…),并为你发送信息。

关系

exosip与上层应用以及osip之间的流程关系

应用需要接收数据时流程如下:

上图为接收过程的示意图。Exosip后台任务不断从网络另一端读取sip消息,交给osip的parser模块解析,并将其转换为events,添加到事务队列上。同时,exosip后台任务在不断的驱动osip的状态机,这样,事务队列上的事件就会被处理。如果需要响应对端,状态机会根据回调函数的设置,直接完成数据的发送。同样,如果要将当前处理反馈给应用,则将其发送到事件队列上(这里是exosip的事件队列),并通过e-ctl管道通知应用进行处理。

应用需要发送数据时,流程如下图所示:

此时,应用调用exosip提供的辅助函数(上图中虚线示意此关系),构造osip事件,将其添加到osip的事务队列上。同时,应用通过s-ctl管道通知exosip后台任务执行状态机。在exosip执行状态机的过程中,sip消息会被发送到网络另一端的终端。

数据结构体之间的关系

在exosip中,有一个比较大的数据结构图是exosip_t,这个在前面的数据结构说明中就介绍了。这个结构体中包含了一些子模块的数据结构,比如call,register等等。程序中,定义了一个exosip_t类型的全局变量,这样,通过该全局变量,就可以获取各个子模块的信息。

在许多的开源软件中,经常会看到这种设计方法。

上图中,像call、register、subscribe等等,都有指向自身类型的前后指针,因此,实际运行中,多个实例都是串接在一起,形成一个双向链。

其他资源

  • libeXosip2 Documentation

参考

  • 音视频通讯之SIP协议
  • Linux下osip2+eXosip2的编译及开发
  • 线程中使用LIBEXOSIP库接收设备注册
  • GB/T28181平台C++实现学习笔记2:libosip2与libexosip2编译
  • exosip2协议栈原理分析以与总结
  • SIP协议栈eXosip2分析

国标28181:libexosip2协议栈原理相关推荐

  1. 国标28181:接收设备注册

    工具 自从国标28181推出以来,国家安防行业一直在主推这个标准协议.刚开始确实有不少阻力,比如很多厂家还采用私有协议或是ONVIF协议作为主要的对接协议.这样很大的阻碍了安防行业的互联互通,虽然GB ...

  2. 视频监控安防平台-国标28181平台(支持国标28181转RTSP/RTMP/HLS/WEBRTC直播)

    视频监控安防平台-国标28181平台(支持国标28181转RTSP/RTMP/HLS/WEBRTC直播) 发现很久都未更新博客了,最近把小平台的功能做了完善,在原来的功能基础上添加了功能,支持国标28 ...

  3. wvp+zlmediakit实现国标28181对讲

    wvp+zlmediakit实现国标28181对讲 一.前言 ZLMediaKit WVP-GB28181 语音对讲源码地址 首先感谢wvp作者和zlmediakit作者提供这么棒的开源项目,我这个例 ...

  4. 视频监控安防平台(企业级)-国标28181平台

    很久没有更新博客了,最近在完善平台的改造,下面我把平台信息发布出来,可以对接国标28181平台.国标28181摄像机.NVR,平台接入量目前项目上使用最多是接入50W路摄像机,欢迎大家使用! 客户端登 ...

  5. 国标28181之服务端下发云台PTZ命令浅谈

    文章目录 一.看协议文档 二.具体代码实现 一.看协议文档 网上资料挺少的,求人不如求己,打铁还需自身硬,啃文档是最直接的学习方式.国标28181对ptz信令这块,有确切的描述. 其实文档里已经说的很 ...

  6. 国标28181:IPC信号检索设备目录查询

    待IPC客户端注册了服务端之后,服务端就应该查询设备 设备目录查询 设备目录查询是国标平台对国标设备接入的目录查询,目的是查询该设备带有的监控点和报警设备信息以及语音设备信息. 使用场景: 比如平台国 ...

  7. java使用国标方式取流,一种基于JAIN-SIP的国标28181平台分布式集群实现系统的制作方法...

    本发明涉及国标设备接入相关技术领域,尤其是指一种基于jain-sip的国标28181平台分布式集群实现系统. 背景技术: 在传统安防行业,采用较多的是用c++编写的产品,该类产品存在一些缺陷:该类产品 ...

  8. GB35114检测GB28181检测GB1400检测国标35114检测 国标28181检测 国标35114检测

    GB35114 A级和C级.GB28181.GB1400.4已经检测完成,国标35114检测A级C级 国标28181检测 国标35114检测检测完成. 需要拿证书的可以代理检测.目前已经合作多家企业完 ...

  9. 国标28181:什么是SIP协议

    前言 对讲设备作为一种专业无线通信工具,能进行一对一,多对多的群组即时通信,在应急调度和突发事故处理中是其他通信工具所不能替代的,在城市治理.公安.运输等行业有广泛的应用. 对讲机按照通信方式分模拟对 ...

  10. 一文搞懂TCP/IP 协议栈原理

    转载自:https://www.toutiao.com/a6708509605044945421/?app=news_article_lite&is_hit_share_recommend=0 ...

最新文章

  1. 【五】搜索推荐技术在电商导购领域的应用——截图小王子
  2. 使用C语言进行面向对象的开发--GObject入门[5]
  3. Android之popWindows底部弹出挡住了华为虚拟键盘问题
  4. 编程不仅是写代码!?
  5. 基于高斯分布和OneClassSVM的异常点检测
  6. asp.net 通过context.RewritePath和ashx开发接口
  7. 怎么提升企业数据分析能力
  8. 蓝桥杯_算法训练_审美课
  9. Rust的各种花式汇编操作
  10. 利用OD去软件弹窗广告教程-[WinRAR_3.9]为例
  11. Linux系统下搭建DNS服务器——原理总结
  12. 人生的镜像-菌群人生,从出生到死亡的菌群演替
  13. Error C4668 : ‘USE_RTTI‘ is not defined as a preprocessor macro, replacing with ‘0‘ for ‘#if/#elif‘
  14. Android 编译命令 make j8 21 | tee build.log 解释
  15. php 登录 登出,个人博客—用户登陆登出
  16. 海老师的技术博客: OCA 考试 准备笔记 前言
  17. 星载高分宽幅方位多通道合成孔径雷达SAR卫星调研
  18. 【技术干货】跨境茶话会9月期丨微服务的挑战
  19. 免费开源的建站程序大全,不会编程也可以自助搭建网站了哦
  20. 前端:深拷贝的多种方法(超全详解)

热门文章

  1. 遥感植被指数128个
  2. python脚本的编写_python脚本编写与执行
  3. static 在C/C++中的用法总结
  4. python如何查看opencv当前版本
  5. 机器视觉笔记:RANSAC算法以及思想
  6. BIM标准化系列写作思路
  7. 小米案例分析PPT模板
  8. SDN 技术介绍整理 学习网站 - (持续更新)
  9. 失控的京东高管文化:频繁离职背后原因是什么?
  10. 大佬们用代码写的故事,代码你打算写到几岁?