nua 模块包含UA库的实现代码,它关心SIP UA的基本功能。它的功能包括呼叫话务管理、消息,及事件检索。

概述

NUA API为高层应用程序提供了一个透明的,完全控制的SIP协议引擎。NUA在现有的事务语义之(nta模块的实现)上提供呼叫语义。通过NUA可以搭建不同的SIP UA,比如说终端、网关,或MCU。

nua 引擎为应用程序员隐藏了许多底层信令和媒体管理细节。它可以使用不同的媒体接口,甚至可以是远程的,完全透明的方式处理媒体。

UA库内的协议引擎和应用程序可以运行在不同的线程中。协议引擎与应用层线程通过事件(events)机制通信,通过回调函数传递给应用层。回调函数由应用层的上下文相关线程调用,通过一个su_root_t 对象体现。

NUA用户的Sofia 概念

引言

Sofia 软件套件是基于目前的基础观点和概念建立的,这些概念在Sofia软件的所有层级中通用。里面的许多内容都由Sofia工具库(su)实现,并对多数主流OS提供了统一的接口封装。

这面章节介绍这些概念,NUA用户必须理解它们才能让Sofia程序正常工作。其它工具(SU库提供的,或Sofia 软件套件的其它库提供的)可能对应用程序开发者也是有帮助的,但是使用时务必小心,因为它们有可能改变Sofia软件套件的行为,从而导致NUA库工作异常。更多细节可以参考[su]模块的相关文档。

事件循环Event loop 与根对象 root object

NUA以事件反应器模式(也称为分发及通知模式)驱动事件系统(请参考[Using Design Patterns to Develop Reusable Object-oriented Communication Software, D.C. Schmidt, CACM October '95, 38(10): 65-74]一书)。Sofia以任务作为编程模型的基本执行单元。根据编程模型,程序可以请求事件循环在特定事件触发时调用回调函数。具体事件包括I/O激活,定时器或其它任务传递的异步消息。

root 对象是应用软件中描述一个任务的句柄。透视事件的另一种方式是:root对象描述任务的主事件循环。通过root对象,任务代码可以访问它的上下文信息(magic)和线程同步,比如说等待对象、定时器,消息。

使用NUA服务的应用必须创建一个root对象,并设置处理NUA事件的回调函数。调用 su_root_create() 创建root对象,调用nua_create() 函数注册回调函数。root对象的数据类型是su_root_t。

Magic

magic 是一种描述上下文指针的术语,应用程序可以把它绑定到Sofia栈的各种对象上(比如说root对象和操作句柄)。当主事件循环调用注册的回调函数时,上下文指针会回传给应用层代码。Sofia栈保留回调函数调用之间的上下文信息。应用层可以利用上下文信息存储处理事件所需要的任何信息。

内存处理

给定的任务需要分配许多小块内存时,使用home-based内存管理机制会很便利。内存通过home对象分配,它维护所有内存块的引用信息。当home对象释放时,所有相关内存一并释放。这个机制简化了应用逻辑,因为应用层代码不需要跟踪内存块的分配记录,也不需要独立释放每一个内存块。

使用NUA服务的应用程序可以使用SU库提供的内存管理服务,但这不是强制要求的。

Tags

Tagging 是Sofia软件所使用的一种机制,它用于向函数打包参数。它可以传递非固定类型的可变长度的参数。对应用层程序员来说,tagging是一个可见的宏,可用它封装传递的参数。评估时,tagging宏创建一个包含tag的结构(记录参数的类型),和一个值(指向不透明数据)。通过检查tag,Sofia软件的各层可以检查它们是否能够处理参数,或者简单地把它传递给下一层处理。

有一些具有特殊含义的tag:

  • TAG_NULL() (与TAG_END()同义) ,tag列表的结束
  • TAG_SKIP() 空tag 项
  • TAG_NEXT() 指向另一个tag列表的tag项,结束当前tag列表
  • TAG_ANY() 接受任何tag的的筛选tag
  • TAG_IF()  tag 项的条件包含

如果NUA函数的参数表末尾是以下这样的,那么可以通过tag列表调用:

tag_type_t   tag,
tag_value_t  value,
...);

参数表的最后一个tag值必须为TAG_NULL() (或 TAG_END(), 它等价于 TAG_NULL()).

每个tag都有两个版本:
NUTAG_<tagname>
它带有一个值参数;
NUTAG_<tagname>_REF

它带有一个引用参数。后者使用tl_gets()函数从tag列表中检查tag值。

对于SIP头域来说,还有另一种tag的版本:
SIPTAG_<tagname>_STR
这个版本以一个C语言字符串作为参数。不带_STR 后缀的相应tag将解析的值结构作为参数。

下面是一个调用实例:

nua_unregister(op->op_handle,TAG_IF(use_registrar, NUTAG_REGISTRAR(registrar)),SIPTAG_CONTACT_STR("*"),SIPTAG_EXPIRES_STR("0"),TAG_NULL());

使用NUA服务的应用程序必须使用tag参数为函数传参。nua_invite() 表现了从tag构建SIP消息的过程。

调试与日志

Sofia 栈的模块包含可配置的调试及日志功能,它们的服务接口在<sofia-sip/su_log.h>里定义。调试及日志细节(比如说日志等级,输出文件名)可以通过环境变量配置,或者通过配置文件指定,还有代码层的指定。

定义的环境变量及含义:

  • SOFIA_DEBUG 默认的调试等级 (0..9)
  • NUA_DEBUG NUA 调试等级 (0..9)
  • NTA_DEBUG 事务引擎调试等级 (0..9)
  • TPORT_DEBUG 传输层事件调试等级 (0..9)
  • TPORT_LOG 如果设置,在传输层打印所有解析出来的SIP消息
  • TPORT_DUMP 指定一个文件名,用于转储传输层未解析的消息

预定义的调试输出等级:

  • 0 致命性错误,崩溃级的
  • 1 严重错误,至少是子系统层的问题
  • 2 非严重的错误信息
  • 3 告警,进展信息
  • 5 信令协议动作 (收到包, ...)
  • 7 媒体协议动作 (收到包, ...)
  • 9 进入/退出函数,非常细致的进展信息

使用NUA服务的应用程序可以使用Sofia栈提供的调试和日志服务,但不强求。

NUA Concepts

NUA 栈对象

栈对象描述SIP栈和媒体引擎的实例。它包含指向栈root对象的引用,UA指定的设置,还有SIP事务引擎的引用。

NUA栈对象调用nua_create() 创建,调用nua_destroy()销毁。nua 引擎可以用nua_shutdown() 函数优雅地释放会话。

NUA栈对象的类型为nua_t。

NUA 操作句柄

操作句柄描述一个抽象的SIP call/session。它包含SIP dialog的信息和媒体session,呼叫的状态机,高层SDPoffer-answer协议,注册,订阅,发布和简单的SIP事务。操作句柄还可能包含用于NUA构建SIP消息的tag列表,比如说From和To头域。

应用程序用NUA显式创建操作句柄来发送消息(nua_handle()函数),或者栈处理收入呼时隐式创建(INVITE 或MESSAGE)。句柄通过NUA调用销毁(nua_handle_destroy())。

指示及响应事件与操作句柄关联。

NUA操作句柄的类型是nua_handle_t。

栈线程及消息传递概念

栈线程与应用程序分离,它提供实时的协议栈操作,因此应用程序线程可以自己堵塞处理,比如重绘UI。

栈线程与应用线程之间的通信是异步的。很多NUA API函数都会发消息给栈线程处理,类似的,某些事件发生后,栈线程也会向应用线程发消息。当应用程序调用su_root_run() 或 su_root_step()函数时,向应用线程发的消息作为它的回调进行传递。

SIP 消息和头域处理

SIP消息通过类型安全的SIPTAG_ tag处理。每个SIPtag有三种版本:

  • SIPTAG_<tagname>() 以解析后的值作为参数。
  • SIPTAG_<tagname>_STR() 以未解析的字符串为参数
  • SIPTAG_<tagname>_REF() 以引用为参数,与tl_gets() 函数一同使用,以从tag列表中检索tag值。
  • SIPTAG_<tagname>__STR_REF() 以引用为参数,与tl_gets() 函数一同使用,以从tag列表中检索字符串tag值。

For example a header named "Example" would have tags names SIPTAG_EXAMPLE(), SIPTAG_EXAMPLE_STR(), and SIPTAG_EXAMPLE_REF().

比如说,有个头域名为"Example",它将拥有这几个tag:SIPTAG_EXAMPLE(), SIPTAG_EXAMPLE_STR(), 和SIPTAG_EXAMPLE_REF()。

当在NUA呼叫中使用tag时,会将相应的头域添加到消息中。如果头域只能在消息中出现一次,并且消息中已经存在一个相应的头域,那么tag提供的值取代现有的头域值。传递tag值NULL对头域没有影响。传递tag值 (void *)-1将相应的头域从消息中移除。

例如:

  • 发送一条带有一个Event:头域和两个 Accept:头域的SUBSCRIBE 消息:
        nua_subscribe(nh,SIPTAG_EVENT_STR("presence"),SIPTAG_ACCEPT(accept1),SIPTAG_ACCEPT(accept2),TAG_END());
  • 处理nua_r_subscribe事件时提取tag值:
           sip_accept_t *ac = NULL;sip_event_t  *o  = NULL;tl_gets(tl,SIPTAG_EVENT_REF(o),   /* _REF takes a reference! */SIPTAG_ACCEPT_REF(ac),TAG_END());

SIP/NUA 教程

本节使用消息序列图描述NUA/Sofia栈的基本使用场景。

外呼

入呼

基本外发操作

基本消息接收操作

带鉴权的外发操作

简单应用实例

接下来,将以代码形式给出一个实例,展示如何利用NUA服务构建应用。实例代码并不完整,但能够体现NUA使用的所有相关细节。

在sourceforge上有一个可用的实例程序 sofisip_cli.c ,可以作为更完整的示例研究。

数据结构定义

使用NUA提供服务的程序通常定义一些用于存储上下文信息(比如说"magic")的数据域。指向这些上下文信息数据域的指针通过类型定义传递给NUA。

/* type for application context data */
typedef struct application application;
#define NUA_MAGIC_T   application/* type for operation context data */
typedef union oper_ctx_u oper_ctx_t;
#define NUA_HMAGIC_T  oper_ctx_t

信息域内容本身可以定义为C结构体或联合体:

/* example of application context information structure */
typedef struct application
{su_home_t       home[1];  /* memory home */su_root_t      *root;     /* root object */nua_t          *nua;      /* NUA stack object *//* other data as needed ... */
} application;/* Example of operation handle context information structure */
typedef union operation
{nua_handle_t    *handle;  /* operation handle /struct{nua_handle_t  *handle;  /* operation handle /...                     /* call-related information */} call;struct{nua_handle_t  *handle;  /* operation handle /...                     /* subscription-related information */} subscription;/* other data as needed ... */} operation;

NUA栈对象和句柄对应用层程序员来说是不透明的。同样,应用层的上下文对NUA栈模块也是完全不透明的。NUA函数传入一个指针,然后这个指针在回调函数的参数中回传给应用层。在这种场景下,应用层上下文信息结构还可以存储root对象和home内存基址。此外,应用层上下文信息中还包含NUA栈对象信息。

初始化与清除

以下代码展示应用层初始化系统,进入主循环处理消息,消息 处理结束,系统清除的过程。

如果应用程序不仅响应收到的SIP消息,还必须有向NUA发送消息的方法。这可以通过单独的线程处理,调用NUA函数来发消息,或建立socket连接向其它应用发命令(参考su_wait_create()和su_root_register())。

/* Application context structure */
application appl[1] = {{{{(sizeof appl)}}}};/* initialize system utilities */
su_init();/* initialize memory handling */
su_home_init(appl->home);/* initialize root object */
appl->root = su_root_create(appl);if (appl->root != NULL) {
/* create NUA stack */
appl->nua = nua_create(appl->root,
app_callback,
appl,
/* tags as necessary ...*/
TAG_NULL());if (appl->nua != NULL) {
/* set necessary parameters */
nua_set_params(appl->nua,
/* tags as necessary ... */
TAG_NULL());/* enter main loop for processing of messages */
su_root_run(appl->root);/* destroy NUA stack */
nua_destroy(appl->nua);
}/* deinit root object */
su_root_destroy(appl->root);
appl->root = NULL;
}/* deinitialize memory handling */
su_home_deinit(appl->home);/* deinitialize system utilities */
su_deinit();

事件处理

在应用初始化时,通过nua_create()函数向NUA栈注册事件的相关回调函数,NUA栈捕获事件时,就会调用回调函数处理事件。回调函数的内容是一组最简单的 switch/case语句,把捕获的事件分发给具体的处理函数。

void app_callback(nua_event_t   event,int           status,char const   *phrase,nua_t        *nua,nua_magic_t  *magic,nua_handle_t *nh,nua_hmagic_t *hmagic,sip_t const  *sip,tagi_t        tags[])
{switch (event) {case nua_i_invite:app_i_invite(status, phrase, nua, magic, nh, hmagic, sip, tags);break;case nua_r_invite:app_r_invite(status, phrase, nua, magic, nh, hmagic, sip, tags);break;/* and so on ... */default:/* unknown event -> print out error message */if (status > 100) {printf("unknown event %d: %03d %s\n",event,status,phrase);}else {printf("unknown event %d\n", event);}tl_print(stdout, "", tags);break;}
} /* app_callback */

发起呼叫

以下三个函数展示基本的SIP呼叫是怎样发起的。

place_a_call()函数创建一个句柄并调用SIP INVITE方法:

operation *place_a_call(char const *name, url_t const *url)
{operation *op;sip_to_t *to;/* create operation context information */op = su_zalloc(appl->home, (sizeof *op));if (!op)return NULL;/* Destination address */to = sip_to_create(NULL, url);if (!to)return NULL;to->a_display = name;/* create operation handle */op->handle = nua_handle(appl->nua, op, SIPTAG_TO(to), TAG_END());if (op->handle == NULL) {printf("cannot create operation handle\n");return NULL;}nua_invite(op->handle,/* other tags as needed ... */TAG_END());} /* place_a_call */

app_r_invite() 函数在收到INVITE消息的应答时,由协议栈的回调函数调用。这里假设没有激活automatic acknowledge,因此必须显式地发送ACK消息。

void app_r_invite(int           status,char const   *phrase,nua_t        *nua,nua_magic_t  *magic,nua_handle_t *nh,nua_hmagic_t *hmagic,sip_t const  *sip,tagi_t        tags[])
{if (status == 200) {nua_ack(nh, TAG_END());}else {printf("response to INVITE: %03d %s\n", status, phrase);}
} /* app_r_invite */

在呼叫状态变化时,发出nua_i_state event事件,回调函数调用app_i_state()函数:

void app_i_state(int           status,char const   *phrase,nua_t        *nua,nua_magic_t  *magic,nua_handle_t *nh,nua_hmagic_t *hmagic,sip_t const  *sip,tagi_t        tags[])
{nua_callstate_t state = nua_callstate_init;tl_gets(tags,NUTAG_CALLSTATE_REF(state),NUTAG__REF(state),state = (nua_callstate_t)t->t_value;printf("call %s\n", nua_callstate_name(state));} /* app_i_state */

接收来电

收到INVITE消息时,回调函数调用app_i_invite()函数。这里假设没有开启自动应答,因此必须显示发送应答消息:

void app_i_invite(int           status,char const   *phrase,nua_t        *nua,nua_magic_t  *magic,nua_handle_t *nh,nua_hmagic_t *hmagic,sip_t const  *sip,tagi_t        tags[])
{printf("incoming call\n");nua_respond(nh, 200, "OK", SOA_USER_SDP(magic->sdp), TAG_END());} /* app_i_invite */

呼叫成功建立,媒体激活时,回调函数调用app_i_state() 函数:

void app_i_active(int           status,char const   *phrase,nua_t        *nua,nua_magic_t  *magic,nua_handle_t *nh,nua_hmagic_t *hmagic,sip_t const  *sip,tagi_t        tags[])
{printf("call active\n");} /* app_i_active */

拆线

以下三个函数展示一通SIP呼叫是如何终止的。

erminate_call() 函数发出SIP BYE消息。

void terminate_call(void)
{nua_bye(op->handle, TAG_END());} /* terminate call */

在收到BYE消息的应答消息时,回调函数调用app_r_bye()函数。它销毁呼叫句柄并释放存储操作上下文信息的相关内存。

void app_r_bye(int           status,char const   *phrase,nua_t        *nua,nua_magic_t  *magic,nua_handle_t *nh,nua_hmagic_t *hmagic,sip_t const  *sip,tagi_t        tags[])
{if (status < 200)return;printf("call released\n");/* release operation handle */nua_handle_destroy(hmagic->handle);op->handle = NULL;/* release operation context information */su_free(appl->home, hmagic);} /* app_r_bye */

收到BYE消息时,回调函数调用app_i_bye()函数。它销毁呼叫句柄并释放存储操作上下文信息的相关内存。

void app_i_bye(int           status,char const   *phrase,nua_t        *nua,nua_magic_t  *magic,nua_handle_t *nh,nua_hmagic_t *hmagic,sip_t const  *sip,tagi_t        tags[])
{printf("call released\n");/* release operation handle */nua_handle_destroy(hmagic->handle);op->handle = NULL;/* release operation context information */su_free(appl->home, hmagic);} /* app_i_bye */

发一条消息

以下代码展示如何发一条SIP MESSAGE 消息。

send_message() 函数发出SIP MESSAGE。

void send_message(void)
{op_t *op;/* create operation context information */op = su_zalloc(appl->home, sizeof(op_t));if (op = NULL) {printf("cannot create operation context information\n");return;}/* how we create destination_address? *//* create operation handle */op->handle = nua_handle(appl->nua,op,NUTAG_URL(destination_address),TAG_END());if (op->handle == NULL) {printf("cannot create operation handle\n");return;}/* send MESSAGE */nua_message(op->handle,SIPTAG_CONTENT_TYPE_STR("text/plain"),SIPTAG_PAYLOAD_STR("Hello, world!"),/* other tags as needed ... */TAG_END());} /* send_message */

收到MESSAGE消息的应答时,回调函数调用app_r_message()函数:

void app_r_message(int           status,char const   *phrase,nua_t        *nua,nua_magic_t  *magic,nua_handle_t *nh,nua_hmagic_t *hmagic,sip_t const  *sip,tagi_t        tags[])
{printf("response to MESSAGE: %03d %s\n", status, phrase);
} /* app_r_message */

接收消息

以下代码展示收到SIP MESSAGE时是怎样处理的。

收到SIP MESSAGE时,回调函数调用 app_i_message():

void app_i_message(int status,
char const *phrase,
nua_t *nua,
nua_magic_t *magic,
nua_handle_t *nh,
nua_hmagic_t *hmagic,
sip_t const *sip,
tagi_t tags[])
{
printf("received MESSAGE: %03d %s\n", status, phrase);printf("From: %s%s" URL_PRINT_FORMAT "\n",
sip->sip_from->a_display ? sip->sip_from->a_display : "",
sip->sip_from->a_display ? " " : "",
URL_PRINT_ARGS(sip->sip_from->a_url));if (sip->sip_subject) {
printf("Subject: %s\n", sip->sip_subject->g_value);
}if (sip->sip_payload) {
fwrite(sip->sip_payload->pl_data, sip->sip_payload->pl_len, 1, stdout);
fputs("\n", stdout);
}
} /* app_i_message */

建立Presence Server

...application_t *app;operation_t   *oper;...oper->app = app;app->nua = nua_create(ssip->s_root,app_callback,app,TAG_NULL());
...oper->handle = nua_handle(app->nua, app,NUTAG_URL(to->a_url),SIPTAG_TO(to),ta_tags(ta));
...nua_notifier(oper->handle,SIPTAG_EXPIRES_STR("3600"),SIPTAG_EVENT_STR("presence"),SIPTAG_CONTENT_TYPE_STR("application/pidf-partial+xml"),NUTAG_SUBSTATE(nua_substate_pending),TAG_END());

在 nua_notifier对象,即presence server建立之后,返回一个nua_r_notifier。app_callback函数的状态及phrase值显示创建成功。

收到订阅消息时的鉴权可以由回调函数处理:

void app_callback(nua_event_t event,
int status, char const *phrase,
nua_t *nua, application_t *app,
nua_handle_t *nh, oper_t *op,
sip_t const *sip, tagi_t tags[])
{nea_sub_t *subscriber = NULL;switch (event) {case nua_i_subscription:tl_gets(tags,NEATAG_SUB_REF(subscriber),TAG_END());nua_authorize(nua_substate_active);default:break;
}

关停

以下函数展示应用程序是如何终止NUA栈的工作的。

shutdown()函数开始关停操作:

void shutdown(void)
{nua_shutdown(appl->nua);} /* shutdown */

无论NUA栈的停止是成功或失败,回调函数调用app_r_shutdown()函数。

void app_r_shutdown(int           status,char const   *phrase,nua_t        *nua,nua_magic_t  *magic,nua_handle_t *nh,nua_hmagic_t *hmagic,sip_t const  *sip,tagi_t        tags[])
{printf("shutdown: %d %s\n", status, phrase);if (status < 200) {/* shutdown in progress -> return */return;}/* end the event loop. su_root_run() will return */su_root_break(magic->root);} /* app_r_shutdown */

Sofia nua模块--高层UA库相关推荐

  1. 天津市职高高一计算机试题及答案,职高(中职)数学(基础模块)上册题库.doc

    职高(中职)数学(基础模块)上册题库.doc 文档编号:388585 文档页数:13 上传时间: 2018-08-30 文档级别:精品资源 文档类型:doc 文档大小:653.00KB 宁波至达教育 ...

  2. 手机微信ua大全,最新不重复的UA库

    手机微信ua大全,最新不重复的UA库,最新微信ua标识 有各种不同品牌的手机型号 微信内置浏览器和原生浏览器UA (手机微信 UA)常用UserAgent列表(去重共12833条) 类型 系统 设备 ...

  3. matlab对象浏览器模块隐,MATLAB 添加自定义的模块到simulink库浏览器

    EDA365欢迎您登录! 您需要 登录 才可以下载或查看,没有帐号?注册 x MATLAB 添加自定义的模块到simulink库浏览器' X) d- E" x' b; y" Y% ...

  4. Python—— 组合数据类型(模块5: jieba库的使用)(实例:基本统计值计算文本词频统计)

    前言 本篇主要介绍组合数据类型,以基本统计值计算为例,介绍函数使用和各种类型定义.以文本词频统计为例,介绍Jieba库的使用. (从本篇开始,出现的一些库中函数介绍以及部分简单代码都将以图片形式呈现) ...

  5. python使用osgeo库_Python使用内置urllib模块或第三方库requests访问网络资源

    前言 更多内容,请访问我的 个人博客. Python 访问网络资源有很多方法,urllib, urllib2, urllib3, httplib, httplib2, requests ,现介绍如下两 ...

  6. MATLAB 添加自定义的模块到simulink库浏览器

    在MATLAB开发环境中,Simulink仿真平 台可以建立用户自定义的库文件,并将它们显示在Library Browser窗口下,方便用户进行模块的操作.用户可以将一些平时使用比较频繁,或者自己建立 ...

  7. 03-requests模块携带UA请求头,携带参数,携带cookie,持久化存储

    reques模块的基础使用方法 chrome浏览器使用方法了解新建隐身窗口的目的,了解chrome中network的使用 1,新建隐身窗口(无痕窗口) 浏览器中直接打开网站,会自动带上之前网站保存的c ...

  8. Python学习笔记011_模块_标准库_第三方库的安装

    容器 -> 数据的封装 函数 -> 语句的封装 类 -> 方法和属性的封装 模块 -> 模块就是程序 , 保存每个.py文件 # 创建了一个hello.py的文件,它的内容如下 ...

  9. Android开发之ApiCloud模块开发之模块引用第三方库的问题

    因为现在第三方库比较多,所以很多人为了快速开发导致库用烂大街了,但是在模块开发中本人不建议使用第三方库的依赖会有很多问题,要么是资源图片找不到,要么是布局找不到啥的,但是有的需求只有第三方库怎么办呢? ...

最新文章

  1. amp 保留指定位c语言,C语言位运算符学习
  2. 随着计算机多媒体技术的产生和发展,多媒体技术及其应用与发展论文(本科)10...
  3. 怎么样设置关闭网页再次登录网页是正常登录状态_学籍系统出现“该账号已登录,不能重复登录”怎么办?...
  4. python微信跳一跳小游戏刷分
  5. Linux驱动小技巧 | 利用DRIVER_ATTR实现调用内核函数
  6. electron增加导航按钮_Electron发布6.0 Released版本
  7. github-仓库基本-下载-上传
  8. JS模块化开发_思维导图
  9. RabbitMQ学习之spring配置文件rabbit标签的使用
  10. java中import的使用
  11. html生成小窗口,用JS制作9种弹出小窗口(HTML)
  12. Vue插槽(solt)简单案例
  13. 最简单的基于FFMPEG的图像编码器(YUV编码为JPEG)
  14. C#中创建线程的四种方式
  15. flashplayer 10 的 p2p 基础
  16. android模拟器横屏显示,安卓模拟器bluestacks怎么横屏显示
  17. 【群晖】VMM安装ubuntu虚拟机+Jenkins
  18. 试题 算法提高 盾神与条状项链
  19. 【随笔】小记2014年东北四省赛
  20. REPL module

热门文章

  1. Python3+Selenium 实现自动登录淘宝+清空购物车
  2. 黑马程序员——黑马诗歌
  3. 绿色红色荧光标记PLGA,PLA,PVP,PVA,明胶纳米纤维膜定制
  4. PHP echo ‘\n‘ 无效 无法不能换行的 一个简单解决方法
  5. 蓝桥杯真题:九宫幻方
  6. 《真香编辑器—CSDN编辑器|CSDN编辑器测评》
  7. 2023青岛电博会启幕 京东方艺云创新显示科技大放异彩
  8. Git的简介与Git详细操作流程
  9. 建设银行app流水申请
  10. Kubernetes 开源负载均衡器 OpenELB 使用指南