国标28181:libexosip2协议栈原理
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协议栈原理相关推荐
- 国标28181:接收设备注册
工具 自从国标28181推出以来,国家安防行业一直在主推这个标准协议.刚开始确实有不少阻力,比如很多厂家还采用私有协议或是ONVIF协议作为主要的对接协议.这样很大的阻碍了安防行业的互联互通,虽然GB ...
- 视频监控安防平台-国标28181平台(支持国标28181转RTSP/RTMP/HLS/WEBRTC直播)
视频监控安防平台-国标28181平台(支持国标28181转RTSP/RTMP/HLS/WEBRTC直播) 发现很久都未更新博客了,最近把小平台的功能做了完善,在原来的功能基础上添加了功能,支持国标28 ...
- wvp+zlmediakit实现国标28181对讲
wvp+zlmediakit实现国标28181对讲 一.前言 ZLMediaKit WVP-GB28181 语音对讲源码地址 首先感谢wvp作者和zlmediakit作者提供这么棒的开源项目,我这个例 ...
- 视频监控安防平台(企业级)-国标28181平台
很久没有更新博客了,最近在完善平台的改造,下面我把平台信息发布出来,可以对接国标28181平台.国标28181摄像机.NVR,平台接入量目前项目上使用最多是接入50W路摄像机,欢迎大家使用! 客户端登 ...
- 国标28181之服务端下发云台PTZ命令浅谈
文章目录 一.看协议文档 二.具体代码实现 一.看协议文档 网上资料挺少的,求人不如求己,打铁还需自身硬,啃文档是最直接的学习方式.国标28181对ptz信令这块,有确切的描述. 其实文档里已经说的很 ...
- 国标28181:IPC信号检索设备目录查询
待IPC客户端注册了服务端之后,服务端就应该查询设备 设备目录查询 设备目录查询是国标平台对国标设备接入的目录查询,目的是查询该设备带有的监控点和报警设备信息以及语音设备信息. 使用场景: 比如平台国 ...
- java使用国标方式取流,一种基于JAIN-SIP的国标28181平台分布式集群实现系统的制作方法...
本发明涉及国标设备接入相关技术领域,尤其是指一种基于jain-sip的国标28181平台分布式集群实现系统. 背景技术: 在传统安防行业,采用较多的是用c++编写的产品,该类产品存在一些缺陷:该类产品 ...
- GB35114检测GB28181检测GB1400检测国标35114检测 国标28181检测 国标35114检测
GB35114 A级和C级.GB28181.GB1400.4已经检测完成,国标35114检测A级C级 国标28181检测 国标35114检测检测完成. 需要拿证书的可以代理检测.目前已经合作多家企业完 ...
- 国标28181:什么是SIP协议
前言 对讲设备作为一种专业无线通信工具,能进行一对一,多对多的群组即时通信,在应急调度和突发事故处理中是其他通信工具所不能替代的,在城市治理.公安.运输等行业有广泛的应用. 对讲机按照通信方式分模拟对 ...
- 一文搞懂TCP/IP 协议栈原理
转载自:https://www.toutiao.com/a6708509605044945421/?app=news_article_lite&is_hit_share_recommend=0 ...
最新文章
- 【五】搜索推荐技术在电商导购领域的应用——截图小王子
- 使用C语言进行面向对象的开发--GObject入门[5]
- Android之popWindows底部弹出挡住了华为虚拟键盘问题
- 编程不仅是写代码!?
- 基于高斯分布和OneClassSVM的异常点检测
- asp.net 通过context.RewritePath和ashx开发接口
- 怎么提升企业数据分析能力
- 蓝桥杯_算法训练_审美课
- Rust的各种花式汇编操作
- 利用OD去软件弹窗广告教程-[WinRAR_3.9]为例
- Linux系统下搭建DNS服务器——原理总结
- 人生的镜像-菌群人生,从出生到死亡的菌群演替
- Error C4668 : ‘USE_RTTI‘ is not defined as a preprocessor macro, replacing with ‘0‘ for ‘#if/#elif‘
- Android 编译命令 make j8 21 | tee build.log 解释
- php 登录 登出,个人博客—用户登陆登出
- 海老师的技术博客: OCA 考试 准备笔记 前言
- 星载高分宽幅方位多通道合成孔径雷达SAR卫星调研
- 【技术干货】跨境茶话会9月期丨微服务的挑战
- 免费开源的建站程序大全,不会编程也可以自助搭建网站了哦
- 前端:深拷贝的多种方法(超全详解)