SVN二次开发——让SVN、TSVN(TortoiseSVN)支持windows的访问控制模型、NTFS ADS(可选数据流、NTFS的安全属性)
SVN二次开发
——让SVN、TSVN(TortoiseSVN)支持windows的访问控制模型、NTFS ADS
(可选数据流、NTFS的安全属性)
SVN secondary development:Let svn(subversion),TSVN(TortoiseSVN) support NTFS ADS(NTFS alternate data streams), NTFS Security Properties(NTFS Security attributes,NTFS Extended Attributes),Windows access control model
前两年和北京的一家信息安全公司合作对svn1.6.16进行了二次开发,今天有时间在博客中概要的整理记录一下。
Windows操作系统支持NTFS格式的文件系统,NTFS和FAT相比怎加了附加属性或叫扩展属性(NTFS Extended Attributes),也称为:ADS(Alternate Data Streams, 可选数据流、附加数据流、交换数据流)、NTFS安全属性(NTFS Security Properties,NTFS Security attributes),NTFS是一个有安全性质的文件系统,除了windows操作系统支持的windows访问控制模型(Windows access control model)的安全属性外,客户还可利用ADS实现一些用户专门应用,如设置密级属性页,存放专门的保密数据等。然而,SVN(Subversion)不支持NTFS的扩展属性(安全属性、ADS),这给使用扩展属性(安全属性、ADS)且又使用SVN的用户带来不便,限制了SVN的适用范围。前两年和北京一家信息安全公司合作对svn1.6.16进行了二次开发,使之增加了对NTFS安全属性(即NTFS的扩展属性、NTFS ADS)的支持,大大方便了用户的使用,扩展了SVN的适用范围。
subversion-1.6.16核心源代码26.7M,加上支撑环境源代码,编译后达407M,使之支持NTFS安全属性的二次开发涉及客户端和服务端核心代码,工作量很大,收集的相关资料高达7G,项目的总体设计、详细设计、开发笔记等纸质文档就高达400多页,如果用博客把各个细节记录下来,就要写成多集长篇了,因时间所限只能简单的整理成本文,它包含windows的访问控制模型、SVN的简介、二次开发的总体框图、SVN源代码分析概要。有需要详细资料和支持NTFS扩展属性(NTFS安全属性)的SVN、TSVN网友请和我联系:QQ:1561724180,0311-87024917.
一、windows的访问控制模型 (NTFS的安全属性)
Windows NT的最初设计目标就是提供一个为操作系统提供安全实现的层,为它的对象实现安全保护,其实现方法是通过访问控制模型(Access Control Model)来实现的 。Windows访问控制模型是Windows安全性的基础构件。访问控制模型有两个主要的组成部分,访问令牌(Access Token)和安全描述符(SD:Security Descriptor),它们分别是访问者和被访问者拥有的东西。通过访问令牌和安全描述符的内容,Windows可以确定持有令牌的访问者能否访问持有 安全描述符的对象。
访问令牌是与特定的Windows账户关联的。当一个Windows账户登录的时候,系统会从内部数据库里读取该账户的信息,然后使用这些信息生成一个访问令牌。在该账户环境下启动的进程,都会获得这个令牌的一个副本,进程中的线程默认持有这个令牌。线程要想去访问某个对象,或者执行某些系统管理相关的操作时,Windows就会使用这个线程持有的令牌进行访问检查。
安全描述符是与被访问对象关联的,比如:文件、目录,它包含:
- 版本号。
- 这个对象所有者的OwnerSID和GroupSID(SID:Security IDentifiers)。
- 自由访问控制列表DACL(Discretionary Access Control List),它包含由对象的所有者定义的对象访问控制项(ACE,Access Control Entry),每个访问控制项的内容描述了允许或拒绝特定账户对这个对象执行特定操作。
- 系统访问控制列表SACL(System Access Control List,有时称为审核 ACE)是一种控制与资源关联的审核消息的机制。与DACL相似,SACL包含定义指定资源的审核规则的ACE。通过审核ACE,可以记录访问资源的成功尝试或失败尝试,但与DACE不同的是,审核ACE不控制哪些账户可以使用某个资源。例如,可以创建一个ACE并将其应用于某个文件的SACL,以记录打开该文件的所有成功尝试。
- 还有其自身的一些控制位。
该结构体在WRK中定义如下:
typedef struct _SECURITY_DESCRIPTOR {
UCHAR Revision;
UCHAR Sbz1;
SECURITY_DESCRIPTOR_CONTROL Control;
PSID Owner;
PSID Group;
PACL Sacl;
PACL Dacl;
} SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;
下图说明了,安全对象和DACL以及访问者之间的联系(来源于MSDN)。注意,DACL表中的每个ACE的顺序是有意义的,如果前面的Allow(或denied)ACE通过了,那么,系统就不会检查后面的ACE了。
windows的文件、目录的安全描述符SD保存在NTFS文件系统的扩展属性中,对于FAT文件系统因为不支持扩展属性,所以不能进行安全控制。如果你的操作系统是NTFS,那么,你可以看到你创建出来的文件的安全属性的样子。
二、windows安全属性API
文件和目录的的安全属性不能直接操作,但可以通过微软提供的API函数进行读写,这些API函数有:
- AddAccessDeniedAce,加入一个Access-Denied 的ACE。
- DeleteAce,删除一个ACE。
- IsValidAcl,检查你所设置的ACL是否合法。
- IsValidSecurityDescriptor,检查SD的合法性。
- MakeAbsoluteSD和MakeSelfRelativeSD,两个函数可以在两种SD的格式中进行转换。
- SetSecurityDescriptorDacl 和 SetSecurityDescriptorSacl,把ACL设置到SD中。
- 使用GetSecurityDescriptorDacl or GetSecurityDescriptorSacl,取得SD中的ACL结构。
这些和SD/ACL/ACE相关的API函数叫作Low-Level Security Descriptor Functions,其详细信息还请参看MSDN。
关于如何使用这些函数在程序中操作NTFS的安全属性网上例子很多,这里不再赘述,有需要的网友可以联系我:0311-87024917,QQ:1561724180
三、SVN(Subversion)、TortoiseSVN
SVN,即Subversion,是一个自由开源的版本控制系统,可以将数据恢复到早期版本,或者检查数据修改的历史,这些数据可以是包括源代码在内的任何其他类型的文件。
Subversion 是一个自由/开源的版本控制系统。也就是说,在 Subversion 管理下,文件和目录可以超越时空。也就是 Subversion 允许你数据恢复到早期版本,或者是检查数据修改的历史。正因为如此,许多人将版本控制系统当作一种神奇的“时间机器”。
SVN确实可以像一个时间机器一样,回到任意时刻的版本,查看任意两个时刻的版本变动,不止在协同开发中,即使在个人开发过程中,这种特性都是非常非常有用的,我曾经有过这种经历,对代码进行很多的修改,发现修改的想法根本是错误的,而这时我已经修改了多个文件,要想回退是非常纠结的事情,而现在可以使用SVN轻松做到这一点。
某些版本控制系统本身也是软件配置管理(SCM)系统,这种系统经过精巧的设计,专门用来管理源代码树,并且具备许多与软件开发有关的特性—比如,对编程语言的支持,或者提供程序构建工具。不过 Subversion 并不是这样的系统。它是一个通用系统,可以管理任何类型的文件集。对你来说,这些文件这可能是源程序,而对别人,则可能是一个货物清单或者是数字电影。
SVN总体架构如下图所示,图中的一端是保存所有版本数据的 Subversion 版本库,另一端是Subvesion 的客户程序,管理着所有版本数据的本地影射(称为“工作副本”),在这两极之间是各种各样的版本库访问(RA)层,某些使用电脑网络通过网络服务器访问版本库,某些则绕过网络服务器直接访问版本库。
DAV是Apache HTTP服务器的一个插件,使版本库可以通过网络访问。DAV的意思是“Distributed Authoring and Versioning”。最初的WebDAV标准得到了广泛的成功,所有的现代操作系统拥有内置的(后面有详细资料)对普通WebDAV的支持,许多流行的应用程序(如: Microsoft Office、Dreamweaver和Photoshop)也可以使用WebDAV。在服务器方面,Apache从1998年就开始支持WebDAV,并被认为是一个事实上的开源标准,也有许多商业的WebDAV服务器,例如Microsoft的IIS。
TortoiseSVN是Windows下的GUI客户端,TortoiseSVN 与Windows 外壳(例如资源管理器)无缝集成,安装后右击文件或文件夹即可通过右键菜单使用。
通常我们保存一个文件的不同版本是保存各个本版的本身,SVN不是这样,在版本库中同一个文件的数个连续修订版本以增量式的方式保存,Subversion在保存修订版本时,仅保存与前一个修订版本之间的差异,通过这些差异足以从前一个修订版本中重建当前的修订版本。换句话说,在保存文件中的每一个修订版本仅包含这个修订版本作出的修改。这个规则的唯一一个例外是当前第一个修订版本。
Subversion用一个二进制差异算法描述文件的变化,对于文本(可读)和二进制 (不可读)文件其操作方式是一致的。
Subversion在版本库中只能保存文件和目录的普通属性,而NTFS扩展属性(安全属性)则不支持,这正是我们下面要解决的问题。
Subversion服务端支持Berkeley DB和FSFS,我们的二次开发针对FSFS,网上有个FSFS的英文资料《Subversion FSFS》,因开发需要我们把它翻译成了中文,形成了一个中英文对照版,有需要的可以和我们联系(联系方式见最后)。
三、subversion二次开发总体方案图
在访问层客户端一侧增加NTFS安全属性的读出、设置、发送、接收模块,在客户端执行import、export、checkout、update、commit等命令时,访问层通过新增的模块读出或设置相应文件或文件夹的NTFS的安全属性。
在版本库层增加NTFS安全属性(NTFS附加属性)的发送、接收、保存模块,完成客户端传输过来的相应文件、文件夹的NTFS安全属性的保存工作,或把相应的功能直接嵌入原来的相应的功能模块。
支持NTFS安全属性的SVN二次开发总体方案图:
四、源代码分析:
开发过程中形成了数百页的源代码分析资料,这里只做简单的介绍,展现一个思路,有需要详细资料的网友可和我们联系,QQ:1561724180
1、总体概念
编辑器(Editor): include\svn_delta.h
所谓的编辑器就是一个全是回调函数的结构(struct svn_delta_editor_t),增量源将调用这些函数产生增量。见include\svn_delta.h
编组和解组编辑器操作的通用框架: libsvn_ra_svn\editorp.c
SVN协议的客户端和服务器需要驱动和消费编辑器。对于提交,客户端驱动和服务器消耗;对于更新/交换/状态/差异命令(update/switch/status/diff),服务器驱动和客户端消耗。 editorp.c提供了一个在SVN连接上编组和解组编辑器操作的通用框架;服务器和客户端两端都使用。
树增量的生产者和消费者:
在Subversion中有各种树增量的生产者和消费者。下面举一个提交命令处理的例子:
- 客户端检查其工作副本数据,并产生用于提交的描述变化的树增量。
- 客户端网络库消耗这个增量,并以等效的系列网络请求通过线路发送他们(例如,以ra_svn协议流发送到svnserve,或以WebDAV命令发送到Apache httpd服务器)
- 服务器收到这些请求,并生产树增量(如果顺利的话,等同于上面客户端生产那个)。Subversion服务器模块消费该增量,并向文件系统提交一个相应的事务。
其他的情况详见include\svn_delta.h中的注释。
2、访问层数据流
数据流的构建
svn访问层数据流有两种socket和file,分别用于网络传输和本地文件传输,它们都使用svn_ra_svn__stream_t结构来实现。下面的两个函数用于构建两种数据流。详见libsvn_ra_svn\streams.c。
libsvn_ra_svn\streams.c
svn_ra_svn__stream_t * svn_ra_svn__stream_from_files(
apr_file_t *in_file,
apr_file_t *out_file,
apr_pool_t *pool) svn_ra_svn__stream_t * svn_ra_svn__stream_from_sock(apr_socket_t *sock,
apr_pool_t *pool)
对win socket接口的调用
对于windows下的socket数据流sock_read_cb、sock_write_cb会安照下面的路径最终调用win Socket的recv、send函数从socket套接口接收和发送数据。下面值列出了sock_read_cb的调用路径,sock_write_cb也一样,可参照理解。
sock_read_cb libsvn_ra_svn\streams.c
↓
apr_socket_recv APR\network_io\win32\sendrecv.c。
↓ 注:APR\network_io内有beos、os2、unix、win32四种os
WSARecv or recv win socket接口
网络访问数据流的总体流程
服务端 客户端
svnserve\main.c libsvn_ra_svn\client.c
↓ ↓
+--->--------\/-------<---+
↓
svn_ra_svn_create_conn libsvn_ra_svn\marshal.c
↓
svn_ra_svn__stream_from_sock libsvn_ra_svn\streams.c
↓
svn_ra_svn__stream_create(b, libsvn_ra_svn\streams.c
<---- sock_read_cb, 创建svn_ra_svn__stream_t并
↓<--- sock_write_cb, 使用这里的参数初始化他,并返
↓ sock_timeout_cb, 回他的句柄。
↓ sock_pending_cb, pool)
↓
↓---->----
↓
apr_socket_recv apr\network_io\win32\sendrecv.c
apr_socket_send
↓
recv or WSARecv win socket
send or WSASend
这种机制作的非常灵活,对于文件操作、串口操作、USB读写操作等,只需要在调用svn_ra_svn__stream_create函数时传递相应的读写函数就可以了。
3、Subversion服务端import的处理过程和调用关系
main()主流程
↓
serve(conn, ¶ms, connection_pool) svnserve\serve.c
↓
svn_ra_svn_handle_commands2(......) libsvn_ra_svn\marshal.c
循环处理客户端发过来的各个命令,命令及其处理函数在svnserve\serve.c文件中定义在report_commands[]、main_commands[]数组。见上面。
对于svn import处理的命令有:check_path、commit
↓
(*command->handler)(conn, iterpool, params, baton)
↓ 下面以处理commit为例,即command->cmdname==“commit”
commit() svnserve\serve.c
↓
svn_repos_get_commit_editor5 libsvn_repos\commit.c
↓ 设置:e->add_file=add_file,处理add_file编辑命令使用该函数
svn_ra_svn_drive_editor libsvn_ra_svn\editorp.c
↓
svn_ra_svn_drive_editor2 libsvn_ra_svn\editorp.c
循环处理客户端发过来的各个editor命令,如:open-root、add-file、apply-textdelta、textdelta-chunk、textdelta-end、close-file、close-dir、close-edit
ra_svn_edit_cmds[]赋值初始化定义在editorp.c的后半部分
↓
(*ra_svn_edit_cmds[i].handler)(conn, subpool, params, &state)
↓ 对于"add-file"命令
ra_svn_handle_add_file libsvn_ra_svn\editorp.c
通过客户端传过来的令牌token在ra_svn_driver_state_t *ds中找到操作的版本库。
↓
ds->editor->add_file(path, entry->baton, copy_path, copy_rev, ds->file_pool, &file_entry->baton))
在(libsvn_repos\commit.c的) svn_repos_get_commit_editor5函数中设置:e->add_file=add_file,见上面对svn_repos_get_commit_editor的调用
↓
add_file libsvn_repos\commit.c
(const char *path, void *parent_baton, const char *copy_path,
svn_revnum_t copy_revision, apr_pool_t *pool, void **file_baton)
↓
svn_fs_make_file(eb->txn_root, full_path, subpool) libsvn_fs\fs-loader.c
↓
fs_make_file(svn_fs_root_t *root, libsvn_fs_fs\tree.c
const char *path,
apr_pool_t *pool)
该函数将完成在版本中增加一个文件,但这个文件只有在执行close-edit命令后才会真正出现在版本仓库中。
4、Subversion客户端import的处理过程和调用关系
- 服务端:
版本仓库位置:c:\svn\t t为版本仓库名称
服务端启动参数:F:svnserve -d -r c:\svn
- 客户端:
要导入的文件位置:f:\svn-ntfs\kf\test
客户端要导入的文件内容:
f:\svn-ntfs\kf\test
| 1.txt
\---test_dir
2.txt
启动:C:>svn import f:\svn-ntfs\kf\test svn://192.168.0.3/t/ -m "Test"
注:不要丢掉-m "Test"参数,否则需要指定编辑器,如果不指定,则不能正常运行。网上有资料说,windows下可用gvim.exe或Posix Tools vi.exe。祥见《windows下subversion的编译与调试.doc》。
main() svn\main.c
↓
(*subcommand->cmd_func)(os, &command_baton, pool);
↓该调用在main()函数的最后。各子命令设置在子命令表svn_cl__cmd_table[]
svn_cl__import svn\import-cmd.c
↓在svn_cl__import函数内用命令行参数设置到处路径path,path= ""为当前目录。
svn_client_import3(...,path,...) libsvn_client\commit.c
↓
get_ra_editor libsvn_client\commit.c
↓------------------ 建立网络连接 --------------------------
svn_client__open_ra_session_internal libsvn_client\ra.c
↓ ↓
svn_ra_open3 libsvn_ra\ra_loader.c
↓ ↓
vtable->open_session(......) 在svn_ra_open3()函数内
↓ ↓ 虚函数表vtable定义在libsvn_ra_svn\client.c内
ra_svn_open libsvn_ra_svn\client.c
↓ ↓
open_session libsvn_ra_svn\client.c
↓ ↓
make_connection-->apr_socket_connect-->win Socket的conn()建立连接
↓ ↓ libsvn_ra_svn\client.c
svn_ra_svn_create_conn libsvn_ra_svn\marshal.c
↓----------------------- 网络连接已建立 -------------------
svn_ra_check_path libsvn_ra\ra_loader.c
↓ 给服务端发送“check_path”命令。用于确认版本库URL存在。
↓ ↓
vtable->check_path
↓ ↓ 虚函数表vtable定义在libsvn_ra_svn\client.c内
ra_svn_check_path libsvn_ra_svn\client.c
↓ ↓
svn_ra_svn_write_cmd(conn, pool, "check-path",...)
↓
svn_ra_get_commit_editor3 libsvn_ra\ra_loader.c
↓ 给服务端发送“commit”命令。告诉服务端开始提交。
↓ ↓
session->vtable->get_commit_editor
↓ ↓ 虚函数表vtable定义在libsvn_ra_svn\client.c内
ra_svn_commit libsvn_ra_svn\client.c
↓ ↓
svn_ra_svn_write_tuple(conn,..., "commit",...);
↓ ↓●
svn_ra_svn_get_editor libsvn_ra_svn\editorp.c
↓ 设置编辑器使用的各个函数
↓
import 递归的向版本仓库导入文件或目录 libsvn_client\commit.c
↓
import_dir-->import_file or 递归调用import_dir
or libsvn_client\commit.c
import_file
↓ ↓
editor->add_file
↓ ↓ ↓
在(libsvn_ra_svn\editorp.c的)svn_ra_svn_get_editor
↓ ↓ 函数中设置:ra_svn_editor->add_file = ra_svn_add_file
ra_svn_add_file libsvn_ra_svn\editorp.c
↓ ↓ ↓
svn_ra_svn_write_cmd(b->conn, pool, "add-file", ...)
↓ ↓ libsvn_ra_svn\editorp.c
↓ ↓
send_file_contents(path, file_baton, ...) libsvn_client\commit.c
该函数中使用的svn_stream_t 结构定义在libsvn_subr\stream.c
↓ ↓ ↓
editor->apply_textdelta
↓ ↓ ↓ 该函数指针在上面的●处设置
ra_svn_apply_textdelta libsvn_ra_svn\editorp.c
↓ ↓ ↓ ↓★
svn_stream_set_write(diff_stream, ra_svn_svndiff_handler)
↓ ↓ ↓ ↓ ↓
svn_stream_set_write(..., svn_write_fn_t write_fn)
↓ ↓ ↓ ↓ stream->write_fn = write_fn libsvn_ra_svn\editorp.c
即:stream->write_fn = ra_svn_svndiff_handler
↓ ↓ ↓ ↓
svn_txdelta_to_svndiff2 libsvn_delta\svndiff.c
↓ ↓ ↓ 设置:*handler = window_handler
↓ ↓ ↓
svn_stream_open_readonly libsvn_subr\stream.c
↓ ↓ ↓ 打开要发送的文件
svn_txdelta_send_stream libsvn_delta\text_delta.c
↓ ↓ ↓
svn_txdelta_send_txstream libsvn_delta\text_delta.c
↓ ↓ ↓
(*handler)(window, handler_baton)
↓ ↓ ↓ 该函数指针在上面的★处设置
window_handler libsvn_delta\text_delta.c
↓ ↓ ↓
svn_stream_write libsvn_subr\stream.c
↓ ↓ ↓
stream->write_fn(stream->baton, data, len)
↓ ↓ ↓ 该函数指针在上面的★处设置
ra_svn_svndiff_handler libsvn_ra_svn\editorp.c
↓ ↓ ↓
svn_ra_svn_write_cmd(...,"textdelta-chunk","cs", b->token, &str)
↓ ↓
editor->close_file
↓ ↓
↓ 在(libsvn_ra_svn\editorp.c的)svn_ra_svn_get_editor函数中设置:
ra_svn_editor->close_file = ra_svn_close_file
↓ ↓
ra_svn_close_file libsvn_ra_svn\editorp.c
↓ ↓
svn_ra_svn_write_cmd(b->conn, pool, "close-file", "c(?c)", b->token, text_checksum);
↓ 祥见“命令的发送”
↓
editor->close_edit(edit_baton, pool)
↓
ra_svn_close_edit libsvn_ra_svn\editorp.c
↓
svn_ra_svn_write_cmd libsvn_ra_svn\marshal.c
↓ 见“发送一个命令”
svn_ra_svn_read_cmd_response libsvn_ra_svn\marshal.c
↓ ↓读命令的回应
svn_ra_svn_read_tuple libsvn_ra_svn\marshal.c
↓ ↓
svn_ra_svn_read_item libsvn_ra_svn\marshal.c
↓
eb->callback(eb->callback_baton)
(svn_error_t *ra_svn_end_commit) libsvn_ra_svn\client.c
↓
import函数结束
SVN二次开发——让SVN、TSVN(TortoiseSVN)支持windows的访问控制模型、NTFS ADS(可选数据流、NTFS的安全属性)的更多相关文章
- SVNKit学习——svn二次开发背景和闲谈(一)
开发背景: 简述现有流程:代码的合并.提交是以任务为最小单元的.例如A和B两个同学开发不同的任务,那就是两个任务号.合并的时候可能会先合并A的代码,在合并B的代码. 需求:SVN合并程序开发——一款能 ...
- ADT通过svn进行团队开发,svn插件不好使的解决方案
在使用ADT的svn插件的时候老是会出现各种异常,所以就干脆不用svn插件了,直接将adt的工作空间建在svn上面,以保证团队成员共用一套代码,节约宝贵的整合时间. 使用步骤: 1.首先需要安装好sv ...
- 版本控制工具 svn 二
一.图标 忽略图标 实例 二.版本 回滚 tortoisesvn ——> 版本更新——>一般情况下使用 “显示日子” 回滚 三.版本冲突 版本冲突产生原因 多人先后提交文件,每个人提交的文 ...
- 阿里云服务器Linux CentOS安装配置(二)yum安装svn
阿里云服务器Linux CentOS安装配置(二)yum安装svn 1.secureCRT连接服务器 2.先创建一个文件夹,用来按自己的习惯来,用来存放数据 mkdir /data 3.yum安装sv ...
- 【转】协同开发中SVN使用规范试用
转自:http://www.cnblogs.com/BraveCheng/archive/2012/07/02/2573617.html 协同开发中SVN使用规范试用 目标,要求 本次svn提交规范主 ...
- eclipse 导入tortoiseSVN检出项目,不显示svn信息(eclipse安装svn插件)
eclipse 导入tortoiseSVN检出项目,不显示svn信息(eclipse安装svn插件) CreateTime--2018年5月10日14:10:35 Author:Marydon 1 ...
- SVN入门 服务器VisualSVN Server和客户端TortoiseSVN安装
Subversion是一个版本控制系统,相对于的RCS.CVS,采用了分支管理系统,它的设计目标就是取代CVS.互联网上免费的版本控制服务多基于Subversion. 一.SVN工作原理 SVN(Su ...
- 协同开发中SVN的使用建议
协同开发中SVN的使用建议 1. 注意个人账户密码安全 各员工需牢记各自的账户和密码,不得向他人透漏,严禁使用他人账户进行SVN各项操作(主要考虑每个SVN账号的使用者的权限范围问题).如有忘记,请 ...
- iOS开发XCODE5 SVN配置 使用办法 (转) 收藏一下
标签: xcode5svn xcodesvn使用 xcode自带的svn xcodesvn版本操作 xcode自带svn版本 这两天响应老板要求,把所有代码放到公司的SVN服务器上,按照我的想法肯 ...
随机推荐
- phpcms9添加301跳转
在做301跳转时遇到了"错误 310 (net::ERR_TOO_MANY_REDIRECTS):重定向过多."的问题,小编在这里把处理方法简单给大家写出来希望可以帮助到大家,另外 ...
- HTML&CSS基础学习笔记1.3-HTML的标签语法
HTML标签语法 1. 标签由英文尖括号<和>括起来,如<html>就是一个标签. 2. HTML中的标签一般都是成对出现的,分开始标签和结束标签.结束标签比开始标签多了一个/ ...
- Builder模式的几个要点
1.Builder模式主要用于“分步骤构建一个复杂的对象”.在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化.2.变化点在哪里,封装哪里——Builder模式主要在于应对“复杂对象各 ...
- NSIS操作系统环境变量
手头有个项目需要修改PATH变量 需要!include "EnvVarUpdate.nsh" 以下是NSIS脚本代码 ; Script generated by the HM NI ...
- cocos2d-x Loading界面实现资源加载
有时候场景中的资源加载过多的话就会引起游戏进入的时候很卡,因为那是边加载边显示.在tests例子里面有一个很好的例子叫做TextureCacheTest,里面讲解了如何写loading. #inclu ...
- Android String 转 MD5
/** * 将字符串转成16 位MD5值 * * @param string * @return */ public static String MD5(String string) { byte[ ...
- 价格更低、SLA 更强的全新 Azure SQL 数据库服务等级将于 9 月正式发布
继上周公告之后,很高兴向大家宣布更多好消息,作为我们更广泛的数据平台的一部分, 我们将在 Azure 上提供丰富的在线数据服务.9 月,我们将针对 Azure SQL 数据库推出新的服务等级.Azur ...
- 玩转指针(Playing with Pointers)
Question: What is a Pointer? What are its limitations? What are its benefits? How do we use it? What ...
- cocos2dx-lua绑定自定义c++类(二)
在 cocos2dx-lua绑定自定义c++类(一)中介绍了如何产生绑定文件. 现在,来看看怎么在工程里使用这个cpp文件.像普通源码文件一样,导入到工程里,看到 LuaTest.h文件里有一个函数入 ...
- javascript笔记5之流程控制语句
/* var box = 100; //单行语句 var age = 20; //另一条单行语句 { //用花括号包含的语句集合,叫做复合语句,单位一个 //一对花括号,表示一个复合语句,处理时候,可 ...