posts - 33, comments - 0, trackbacks - 0, articles - 0
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

2011年10月6日

      在第一篇时就说过框架要在URL上作文章,是的,本文就框架怎样充分利用url上作尽可能详细的说明。

      做web开发的不可能对url陌生,早在web1.0时代,url作为统一资源定位符,在对web中资源的如何获得上起到巨大作用。不论用户请求的时静态页面或者是各种图片、脚本文件,通过url总能从web网站获取要访问的资源。Web2.0更是常常使用url作为get请求时参数的传递,如http://xxx.xxx.xxx/xxx.jsp?user=admin。以及近几年很火restful web service 摒弃soap而使用url传递请求参数 都说明合理利用url的可行和流行。

      当然不止是使用了url就算好的实践,而是能够做到优雅的使用,保证层次分明和整体的简洁,这才算是好的方式,这也正是本框架对使用url 所追求的目标。

      首先来看几个例子:

http://www.cnblogs.com/p2
http://www.xxx.com/index.do?page=2

http://www.xxx.com/product/
http://www.xxx.com/channel.do?channel=product

http://www.xxx.com/product/mobile
http://www.xxx.com/channel.do?channel=product&&subChannel=mobile

      相信各位看官不用我说也能明白,这几组的实现肯定第一种实现的方式更佳。抛开它能屏蔽服务器端使用的技术这一特性不说,它还能够更好地说明动态网站的层次结构,让用户在访问时能明确知道在网站的什么位置,而不会觉得是陷入了一个迷宫。

      当然上面列举的例子是网站前端所使用的url表现方式,因为表现方式可以多种多样,个人喜好不同,本框架在设计时没有给指定前端url的表现方式,而是定义接口,把这个权利留给使用的用户。框架将考虑更多 通用性的东西而不是 个性 自由的东西。

 

      下面对框架里默认使用的url Router AMPPathRouter做详细的介绍,包括设计的思想和实现的方式。首先AMPPathRouter的用途定位为后台使用。为了理解快速的理解它的工作原理,先来和struts做一下对比。

      Struts关于请求的配置:

 

<action name="login" class="com.lscmjx.action.LoginAction" method="login">
<result name="success">
main.jsp
</result>
<result name="failure">
login.jsp
</result>
</action>

    它提交的url会是http://xxx.xxx.xxx/login,访问web服务器时会把此url传递到struts框架交给它处理,之后struts会在struts.xml中寻找login的相关的配置,像上面例子,struts会找到LoginAction的类,并且调用其login的方法。

      写到这里,我请问这是最好的方式吗?当然不是,至少我在使用struts时就认为这是相当撇脚的设计。上面例子只是列举一个login方法,假如一个系统中要对后台调用的方法是100个,那岂不是就需要在struts.xml中写100个与之类似的配置。想想都头大,这样繁琐的工作,应该是由框架自己去处理,而不是人工给配置。

 

      再来看实现相同功能的Unicorn web框架的配置。

 

<action class="com.mh.action.UserAction"></action>

      当然提交的url肯定需要包含多一些的信息,来保证能通过url正确调用框架Action里的方法。这里提交的url方式:http://xxx.xxx.xxx/UserAction/login/

      通过在url里附加调用的Action类的信息,可以省略为不同的方法都在xml里配置的麻烦。假如UserAction里有100个方法,框架也只需这一行的配置。

      有了大体的认识之后,来看框架的核心部分AMPPathRouter的具体实现。

 

  /**
* 检查url是否是此Router类要处理的,/Action/Method/Param 格式的将会被检查合格,返回true
*
@param relativeUri
*
@param actionMap
*
@return
*/
public boolean checkUrl(String relativeUri, Map<String, ActionSupport> actionMap) {
Pattern pattern = Pattern.compile("^/\\w+/\\w+/\\S*");
Matcher matcher = pattern.matcher(relativeUri);
if(matcher.matches()) {
String actionName = relativeUri.split("/")[1];
ActionSupport actionSupport = actionMap.get(actionName);
if(null != actionSupport) {
String actionMethodName = relativeUri.split("/")[2];
Class<?> actionClass = actionSupport.getClass();
Method[] methods = actionClass.getMethods();
for(int i = 0; i < methods.length; i++) {
Method method = methods[i];
String methodName = method.getName();
if(methodName.equals(actionMethodName)) {
return true;
}
}
} else {
return false;
}
}
return false;
}
/**
* 匹配规则为:
* 1、符合/Action/method/param格式,
* 2、并且Action在actionMap中的确存在
* 3、method在此Action中存在
*/
@Override
public boolean route(String relativeUri, UrlFilter urlFilter) {
Map<String, ActionSupport> actionMap = urlFilter.getActionMap();
if(!this.checkUrl(relativeUri, actionMap)) {
return false;
}
// 拦截Action/Method/Param方式的请求,并构建ActionSupport类的属性
String[] params = relativeUri.split("/");
try {
ActionSupport actionSupport = actionMap.get(params[1]);
Class<?> action = actionSupport.getClass();
Method method = action.getMethod(params[2], new Class[] {});
if(params.length > 3) {
this.boxingRequest(urlFilter.getRequest(), params[3]);
}
// 只要找到ActionSupport的子类,则初始化其所具有的属性
Object newInstance = action.newInstance();
this.initActionSupport(newInstance, urlFilter);
String result = (String) method.invoke(newInstance, new Object[] {});
if (null == result || ActionSupport.AJAX.equals(result) || ActionSupport.FORWARD.equals(result) || ActionSupport.WEB_SERVICE.equals(result)) {
return true;
}
if(ActionSupport.REDIRECT.equals(result)) {
urlFilter.getResponse().sendRedirect(result);
return true;
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 把url中得param加入到request的attribute里
*
@param request
*
@param parameter
*/
private void boxingRequest(HttpServletRequest request, String parameter) {
String[] parameters = parameter.split("&");
for (int i = 0; i < parameters.length; i++) {
String param = parameters[i];
String[] key_value = param.split("=");
if(key_value.length == 2) {
request.setAttribute(key_value[0], key_value[1]);
}
}
}
/**
* 初始化ActionSupport类中所需的request、response、session、application等对象
*
@param obj
*
@param urlFilter
*/
private void initActionSupport(Object obj, UrlFilter urlFilter) {
ActionSupport action = (ActionSupport) obj;
action.setRequest(urlFilter.getRequest());
action.setResponse(urlFilter.getResponse());
action.setSession(urlFilter.getSession());
action.setApplication(urlFilter.getApplication());
}

 

      这便是AMPPathRouter的全部内容,其中在把请求分发到ActionSupport的子类 并调用相关方法时 是通过反射实现,其他地方地方都是相当容易理解的。

      空说无凭,把框架应用到实战中才是硬道理:

 

      好了,下一篇介绍Action 和 json。

posted @ 2011-11-25 18:18 马航 阅读(260) | 评论 (0)编辑 收藏

      上篇说过,所有提交到web程序的url都被此UrlFilter拦截。拦截到请求后,UrlFilter则召集它的好多个得力干将Router 们, 询问他们:“谁能处理此URL啊 ?”

这时一位叫做AMPRouter 首当其冲 说:“这个url交给我了”。这时filter就会把此url全权交给AMPRouter来办,至于如何去处理,filter也不再过问,它觉得:“我把任务都交给你了,怎么解决是你的事”。

      根据单一职责的原则,UrlFilter就负责上面情景中的分发urlRouter中的差事,url如何分发交给Router处理。并且Router实际是一个接口,使用框架的用户完全可以自己实现Router,这样用户可以自主定义的url分发的策略。另外呢,框架初始化的一些操作它也是 推脱不掉的,像根据unicorn-config.xml初始化系统中的RouterAction'。下面是具体的代码:

 

@Override
public void init(FilterConfig config) throws ServletException {
	application = config.getServletContext();
	String loadPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
	String classPath = loadPath.substring(1, loadPath.length());
	ArrayList<String> actions = XMLReader.getNodeValues(classPath + "unicorn-config.xml", "actions");
	this.initActions(actions);
	ArrayList<String> routers = XMLReader.getNodeValues(classPath + "unicorn-config.xml", "routers");
	this.initRouters(routers);
}

 

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
			throws IOException, ServletException {
	HttpServletRequest request = (HttpServletRequest) servletRequest;
	String path = request.getContextPath();
	String uri = request.getRequestURI();
	String relativeUri = uri.substring(path.length(), uri.length());
	this.request = request;
	this.session = request.getSession();
	this.response = (HttpServletResponse) servletResponse;
	// 用户自定义的Router优先级最高,url先通过用户定义的
	Iterator<IPathRouter> iterator = routerList.iterator();
	while(iterator.hasNext()) {
		IPathRouter router = iterator.next();
		if(router.route(relativeUri, this)) {
			return ;
		}
	}
	// 拦截不到的继续访问
	filterChain.doFilter(servletRequest, servletResponse);
}

        其中Router类的初始化,Action类的初始化于这个类似:

private void initRouters(ArrayList<String> routers) {
	routerList = new ArrayList<IPathRouter>();
	for (int i = 0; i < routers.size(); i++) {
		String routerName = routers.get(i);
		try {
			Class<?> clz = Class.forName(routers.get(i));
			// 单例模式通过方法获取对象实例
			IPathRouter router = (IPathRouter) clz.newInstance();
			routerList.add(router);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		}
	}
	// 最后把框架默认的Router加入进来
	routerList.add(new AMPPathRouter());
}

        其中unicorn-config.xml文件的编写,拿其中我一个项目里的这个文件来举例:

<?xml version="1.0" encoding="UTF-8" ?>
<config>
<routers>
<router class="com.mh.router.MySessionCheckRouter"></router>
</routers>
<actions>
<action class="com.mh.action.UserAction"></action>
<action class="com.mh.action.InformationAction"></action>
<action class="com.mh.action.UploadInformationIconAction"></action>
<action class="com.mh.action.TempPicAction"></action>
<action class="com.mh.action.MobileAction"></action>
</actions>
</config>

      这里即定义了Action,也定义了自己的Router,并且从名称上可以看出,这个SessionCheckRouter是要判断所有提交到服务器的指定url的请求 是否已经登录过,没有登录,可能会把此请求遣送会登录页。以及初始化所有的Action,在Router处理完请求,分发给action时,可以从filter里面去取。

 


posted @ 2011-11-25 12:35 马航 阅读(265) | 评论 (0)编辑 收藏

      承接上篇的简单介绍,下面详细介绍整个框架的大致结构。

      先来看一下整个框架包的结构:

 

      可以看出框架包含的包很少,包的结构也超简单。这里 涉及FilterActionSupportRouter等三个概念,他们之间的关系,通过下图来表示:

 

      图也不规范,说不上来是哪个UML图,不过通过它也能看出一个请求到达时,框架基本的处理流程。首先由Filter拦截到所有请求,然后把请求交给所有注册的Router类,如果请求的Url正好是一个Router要拦截的,则把此请求交给这个Router,框架不再把请求向下传递。Router得到请求后,分析Url,通过Url里的信息把请求交给对应的ActionSupport的子类来处理。

      这里拦截采用Filter来处理,这跟多数的web框架一样,使用FilterServlet有更多的能力进行请求的分发。首先在一个web工程的web.xml文件中配置框架的UrlFilter类来拦截所有的请求。需要注意的一点是dispatcher 要设置为request,如果设置了forward的话,由框架内部进行的forward又会被框架拦截,从而造成无限的循环。Url-pattern设置为/*,表示所有的请求都会拦截,从而把对url分发的权利交由框架本身,而不是采用jsp规范里的url分发策略。框架在处理所有请求的url 时,依次交给各个Router类来处理,如果Router类判断是符合自己的url格式,则分发给 action 处理。如果不能处理再交给下一级的Router,最后url经由所有Router处理完,剩下的资源文件的url,如http://xxx.xxx.xxx.jpg,则框架调用filterdoChain()方法,通过filter的过滤去访问web里的资源。

<filter>

      <filter-name>unicornWeb</filter-name>

      <filter-class>com.mh.mvc.filter.UrlFilter</filter-class>

</filter>

<filter-mapping>

    <filter-name>unicornWeb</filter-name>

    <url-pattern>/*</url-pattern>

    <dispatcher>REQUEST</dispatcher>

</filter-mapping>

      大致的原理就是这样,在下篇介绍框架的详细实现。

posted @ 2011-11-25 11:43 马航 阅读(341) | 评论 (0)编辑 收藏

      我承认有点标题党了,不过题目中所说的几项技术确实有其相似之处,欲知事情原委,且听我详细道来。

      项目一开始只是不满 struts 庞大的体积,于是想自己根据其原理实现一个tiny 版。后来的开发中觉得,完全可以把上述的ajax、Restuful web service的一些思想加入进来。经过几周的努力,便开发出了一个基本成型的web 框架,暂且起名为unicorn(独角兽,吼吼)。下文开始便对这个自编写的框架做一些列的介绍,并且初步打算是将其开源,希望能一起交流和完善它。

      首先,为了能快速了解它是什么,先来看一下配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<config>
<routers>
<router class="com.mh.router.MySessionCheckRouter"></router>
</routers>
<actions>
<action class="com.mh.action.UserAction"></action>
<action class="com.mh.action.InformationAction"></action>
<action class="com.mh.action.MobileAction"></action>
</actions>
</config>

      上面就是整个工程的配置文件,可以看出需要配置的东西非常少,只需要制定action类 和 router类有哪些就Ok。框架奉行约定大于配置的思想,至于请求如何分发,这个不需要人工配置,框架自动解决。这里要介绍两个概念Action 和 Router,熟悉Struts的肯定都知道Action,Action替代Servlet、JSP时代的Servlet,所有提交的请求由struts分发给不同的Action来处理。这里道理也是一样的,Action就是经过框架处理后的请求接受者。再来说一下Router,字面意思路由器,学过计算机网络的都知道,ip数据包在网络上之所以能够顺利到达,就是因为路由器根据路由表来来确定出来传输的途径。这里Router也是这个作用,根据访问服务器的URL来制定分发策略。Router是完全可以自定义的,用户可以定义自己的Router来制定URL分发的策略,并且用户自定义的Router比系统默认的Router有更高的优先权。

二、URL上做文章

/UserAction/login/username=admin&&password=admin

      先来介绍系统MethodRouter的处理方式。上面的url根据"/"分为三个部分,第一部分是请求的Action类,第二部分是类中的方法Method,第三部分是提交的参数Param。这一点受上篇文章优酷的架构里URL设计的启发。

      经过这样的设计,就明白在上述配置文件中为何可以如此简单了。

      当然也可以不以这样的方式,框架提供自定义Router的支持。比如你想这样处理URL:/前台页面/子栏目/子栏目

      想实现上面的方式,就可以自己定义Router,在Router里面获取上述的URL,然后做处理、forward到相应的jsp页面。

三、使用Json传输数据

      Ajax请求很容易处理json数据,ajax可以与系统轻松交互。

      当初Web Service使用SOAP的xml格式传输数据,如今也有人指责这是大费周折。Restful方式提倡遵循HTTP语义,完全使用URL结合GET、POST、PUT、DELETE来传输请求,结果在roil阵营里广泛使用,认为是web service更优雅的方式。所以本框架也吸取他们的优点,也完全可以通过url传输请求的数据,如上述URL中的Param部分。不过没有遵循Restful强调的Http语义,全部使用Get和POST的请求方式,当然也可以制定为其他,这完全看你的心情,因为这对功能实现无关紧要。而且我觉得统一使用一种,更避免了需要指定请求方式的麻烦。

      数据的返回使用json格式,比SOAP更为轻量简洁和优雅,而且有更多的平台直接支持。如在android平台,本身就支持json格式的处理, 如果使用web service 的SOAP,你可能还要导入KSOAP的第三方库。

      在非浏览器的客户端,可以借助编写的工具类,来完成web service方式的操作,

public interface IWebService {
	public List<LiteInformationDTO> getInformationsOfOwnerApp(String ownerApp, int start, int limit) throws SocketTimeoutException;
}

      经过这样的封装,已经与使用web service毫无差别,而且还会更加高效,因为处理json总比处理SOAP的xml要容易。

 

      先简单写这么多,之后的续篇详细介绍。

posted @ 2011-11-22 16:22 马航 阅读(277) | 评论 (0)编辑 收藏

记得以前给大家介绍过视频网站龙头老大YouTube的技术架构,相信大家看了都会有不少的感触,互联网就是这么一个神奇的东西。今天我突然想到,优酷网在国内也算是视频网站的老大了,不知道他的架构相对于YouTube是怎么样的,于是带着这个好奇心去网上找了优酷网架构的各方面资料,虽然谈得没有YouTube那么详细,但多少还是挖掘了一点,现在总结一下,希望对喜欢架构的朋友有所帮助。

一、网站基本数据概览

  • 据2010年统计,优酷网日均独立访问人数(uv)达到了8900万,日均访问量(pv)更是达到了17亿,优酷凭借这一数据成为google榜单中国内视频网站排名最高的厂商。
  • 硬件方面,优酷网引进的戴尔服务器主要以 PowerEdge 1950与PowerEdge 860为主,存储阵列以戴尔MD1000为主,2007的数据表明,优酷网已有1000多台服务器遍布在全国各大省市,现在应该更多了吧。

二、网站前端框架

从一开始,优酷网就自建了一套CMS来解决前端的页面显示,各个模块之间分离得比较恰当,前端可扩展性很好,UI的分离,让开发与维护变得十分简单和灵活,下图是优酷前端的模块调用关系:

这样,就根据module、method及params来确定调用相对独立的模块,显得非常简洁。下面附一张优酷的前端局部架构图:

 

三、数据库架构

应该说优酷的数据库架构也是经历了许多波折,从一开始的单台MySQL服务器(Just Running)到简单的MySQL主从复制、SSD优化、垂直分库、水平sharding分库,这一系列过程只有经历过才会有更深的体会吧,就像MySpace的架构经历一样,架构也是一步步慢慢成长和成熟的。

1、简单的MySQL主从复制:

MySQL的主从复制解决了数据库的读写分离,并很好的提升了读的性能,其原来图如下:

其主从复制的过程如下图所示:

但是,主从复制也带来其他一系列性能瓶颈问题:

  1. 写入无法扩展
  2. 写入无法缓存
  3. 复制延时
  4. 锁表率上升
  5. 表变大,缓存率下降

那问题产生总得解决的,这就产生下面的优化方案,一起来看看。

 

2、MySQL垂直分区

如果把业务切割得足够独立,那把不同业务的数据放到不同的数据库服务器将是一个不错的方案,而且万一其中一个业务崩溃了也不会影响其他业务的正常进行,并且也起到了负载分流的作用,大大提升了数据库的吞吐能力。经过垂直分区后的数据库架构图如下:

然而,尽管业务之间已经足够独立了,但是有些业务之间或多或少总会有点联系,如用户,基本上都会和每个业务相关联,况且这种分区方式,也不能解决单张表数据量暴涨的问题,因此为何不试试水平sharding呢?

 

3、MySQL水平分片(Sharding)

这是一个非常好的思路,将用户按一定规则(按id哈希)分组,并把该组用户的数据存储到一个数据库分片中,即一个sharding,这样随着用户数量的增加,只要简单地配置一台服务器即可,原理图如下:

如何来确定某个用户所在的shard呢,可以建一张用户和shard对应的数据表,每次请求先从这张表找用户的shard id,再从对应shard中查询相关数据,如下图所示:

但是,优酷是如何解决跨shard的查询呢,这个是个难点,据介绍优酷是尽量不跨shard查询,实在不行通过多维分片索引、分布式搜索引擎,下策是分布式数据库查询(这个非常麻烦而且耗性能)

 

四、缓存策略

貌似大的系统都对“缓存”情有独钟,从http缓存到memcached内存数据缓存,但优酷表示没有用内存缓存,理由如下:

  1. 避免内存拷贝,避免内存锁
  2. 如接到老大哥通知要把某个视频撤下来,如果在缓存里是比较麻烦的

而且Squid 的 write() 用户进程空间有消耗,Lighttpd 1.5 的 AIO(异步I/O) 读取文件到用户内存导致效率也比较低下。

但为何我们访问优酷会如此流畅,与土豆相比优酷的视频加载速度略胜一筹?这个要归功于优酷建立的比较完善的内容分发网络(CDN),它通过多种方式保证分布在全国各地的用户进行就近访问——用户点击视频请求后,优酷网将根据用户所处地区位置,将离用户最近、服务状况最好的视频服务器地址传送给用户,从而保证用户可以得到快速的视频体验。这就是CDN带来的优势,就近访问,有关CDN的更多内容,请大家Google一下。

好了,就总结这么多了,有兴趣的同学接着补充,虽然很多资料图片都来自网络,但整理也不容易,欢迎转载,转载留个出处:青藤屋 原文链接

posted @ 2011-11-02 11:23 马航 阅读(280) | 评论 (0)编辑 收藏

windows系统使我们经常使用的操作系统怎么才能使用我们现在经常使用的操作系统不变的情况下继续我们的SVN之旅,我们在综合了好动种方法的同时感觉这些内容非常贴近我们SVN在Windows种的应用与配置.

1.下载文件,

下载最新版本subversion,我这里选择svn-1.4.5-setup.exe

下载 "Subversion Windows Service" 软件包

下载 TortoiseSVN shell integration utility

2.安装Subversion 服务器

由于我下载的是setup.exe版本,安装程序安装后会自动设置系统变量.如果你下载的是zip版就需要手动设置系统变量.

setup.exe版直接安装就可以了.安装到D:/Program Files/Subversion

首先创建SVN储存库(repository)

svnadmin create F:/svn/

repository创建完毕后会在目录下生成若干个文件和文件夹,dav目录是提供给Apache与mod_dav_svn使用的目录,让它们存储内部数据;db目录就是所有版本控制的数据文件;hooks目录放置hook脚本文件的目录;locks用来放置Subversion文件库锁定数据的目录,用来追踪存取文件库的客户端;format文件是一个文本文件,里面只放了一个整数,表示当前文件库配置的版本号;

3.配置SVN服务器

(这个位置就是在你建储存库的地方F:/svn)

打开/conf/目录,打开svnserve.conf找到一下两句:

# [general]

# password-db = passwd

# anon-access = none

# auth-access = write

去之每行开头的#,其中第二行是指定身份验证的文件名,即passwd文件.anon-access = none 是匿名用户不能访问,必须要有用户名和密码。(注意:问题就出在这,一定要注意格式去掉注释后要顶格不能有空)

同样打开passwd文件,将

# [users]

# harry = harryssecret

# sally = sallyssecret

       格式为“用户名 = 密码”,如可插入一行:admin = admin888,即为系统添加一个用户名为admin,密码为admin888的用户

4.运行SVN服务器

运行SVN服务

在命令行执行

svnserve --daemon --root F:/svn

服务启动,--daemon可简写为-d,--root可简写为-r,可以建立一个批处理文件并放在windows启动组中便于开机就运行SVN服务(注意:这是临时打开的服务,命令执行后不能关闭窗口)

也可以制定subversion工作的端口:svnserve -d -r f:/svn --listen-port 9999

用后台服务的方式可以设置开机自动执行。

D:/Program Files/Subversion/bin>sc create svnservice binpath= "C:/Program Files/Subversion/bin/svnserve.exe --service -r f:/svn  --listen-port 9999"

就可以用net svnservice stop 或者start来启动服务了 也可以在Sevices.msc来启动了。

5、用客户端访问

格式:svn://服务器IP

 

---------------------------------------------------------------------------------------------------------

 

基于svnserve的服务器,权限文件authz配置的常见问题及解答  
 最近在我用Subversion论坛(http://www.iUseSVN.com/bbs)经常有人提到这样的问题: 
为什么我的客户端没有写权限? 
为什么我的权限没有起作用?

总结他们的配置,发现 
都是用svnserve作为服务器, 
都在svnserve.conf中使用了authz-db选项

原因可能如下:

1,配置authz时,没有注意svnserve启动参数-r所指定的目录。 
这里有两种情况: 
A:-r直接指定到版本库(称之为单库svnserve方式) 
比如,有一个库project1,位于D:/svn/project1 
使用以下命令启动svnserve

[Copy to clipboard] [ - ]CODE: 
svnserve -d -r D:/svn/project1 
在这种情况下,一个svnserve只能为一个版本库工作 
authz文件如果配置成下面这样就是错的,

[Copy to clipboard] [ - ]CODE: 
[groups] 
admin=user1 
dev=user2 
[project1:/] 
@admin=rw 
@doc=r 
应该配置成

[Copy to clipboard] [ - ]CODE: 
[groups] 
admin=user1 
dev=user2 
[/] 
@admin=rw 
@doc=r 
因为[project1:/]表示库project1的根目录,而按上面的启动参数,是没有库的概念的。 
使用类似这样的URL:svn://192.168.0.1/ 即可访问project1

B:-r指定到版本库的上级目录(称之为多库svnserve方式) 
同样,有一个库project1,位于D:/svn/project1 
如果使用以下命令启动svnserve

[Copy to clipboard] [ - ]CODE: 
svnserve -d -r D:/svn 
这种情况,一个svnserve可以为多个版本库工作, 
这时如果想限制指定库的指定目录,就应该指定具体的库,像这样

[Copy to clipboard] [ - ]CODE: 
[groups] 
admin=user1 
dev=user2 
[project1:/] 
@admin=rw 
@doc=r 
如果此时你还用[/],则表示所有库的根目录,同理,[/src]表示所有库的根目录下的src目录 
使用类似这样的URL:svn://192.168.0.1/project1 即可访问project1 
这样的URL:svn://192.168.0.1/project2 即可访问project2

2,对中文目录进行权限控制时,没有将权限文件authz改为utf-8格式。

svn对于非英文文件名和目录名使用utf-8格式编码处理,要对中文目录进行正确控制, 
应该使用无BOM的utf-8格式,如何将默认的文件转为utf-8, 
我使用的是UltraEdit的菜单"ASCII to UTF-8 (Unicode Editing)"。在UltraEdit的配置中,可以设置有无BOM  

posted @ 2011-10-12 17:26 马航 阅读(417) | 评论 (0)编辑 收藏

SIP协议

 

SIP协议过程概念及分析

 

SIP入门开发之路(含SIP开发需要学习的资源及网址)

 

SIP揭密(中文版)

 

使用Java的SIP Servlet进行SIP开发

 

 

Asterisk:

 

Asterisk安装及测试

 

Asterisk十问十答

 

Asterisk入门教程

 

Asterisk介绍-Asterisk RealTime SIP

asterisk配置文件列表及常用指令

 

asterisk 官方文档

 

asterisk目录及配置说明

 

Asterisk功能整理

 

Asterisk使用ODBC实现语音信箱

 

使用Asterisk实现可视的语音交换

 

 

OpenSIPS

 

开源SIP服务器OpenSIPS应用介绍

 

Opensips 安装

 

Opensips 配置文件

 

Mediaproxy的安装及其在OpenSIPS中的配置

 

Opensips文档之MediaProxy模块

 

使用OpenSIPS构建电话通信系统-8媒体服务整合

 

使用OpenSIPS构建电话通信系统-4脚本及路由基础

 

Opensips文档之TM模块

 

Opensips文档之RR模块

 

Opensips文档之TEXTOPS 模块

 

Opensips文档之AVPOPS模块

 

 

NAT穿透(即SIP打洞)

 

使用OpenSIPS构建电话通信系统-SIP穿透NAT

 

NAT穿透问题探讨

 

完美的NAT穿透技术ICE介绍

 

ICE-SIP穿透NAT问题的终极解决方案

 

NAT穿透技术ICE基础教程

posted @ 2011-10-07 20:31 马航 阅读(443) | 评论 (0)编辑 收藏

     摘要: Android中有一控件是ExpandableListView,比ListView更高级,ExpandableListView的效果很实用,比如因为需要查看一堆文件的目录结构或者开发像QQ好友那样的界面,就应该使用Expandablelistview。 本文最终效果如下: 首先是Activity代码,实际开发中数据(包括父item,子item及图片,Expandablelistview...  阅读全文

posted @ 2011-10-06 09:37 马航 阅读(5461) | 评论 (0)编辑 收藏