posts - 33, comments - 0, trackbacks - 0, articles - 0
            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          2011年11月25日

                在第一篇時就說過框架要在URL上作文章,是的,本文就框架怎樣充分利用url上作盡可能詳細的說明。

                做web開發(fā)的不可能對url陌生,早在web1.0時代,url作為統(tǒng)一資源定位符,在對web中資源的如何獲得上起到巨大作用。不論用戶請求的時靜態(tài)頁面或者是各種圖片、腳本文件,通過url總能從web網(wǎng)站獲取要訪問的資源。Web2.0更是常常使用url作為get請求時參數(shù)的傳遞,如http://xxx.xxx.xxx/xxx.jsp?user=admin。以及近幾年很火restful web service 摒棄soap而使用url傳遞請求參數(shù) 都說明合理利用url的可行和流行。

                當然不止是使用了url就算好的實踐,而是能夠做到優(yōu)雅的使用,保證層次分明和整體的簡潔,這才算是好的方式,這也正是本框架對使用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

                相信各位看官不用我說也能明白,這幾組的實現(xiàn)肯定第一種實現(xiàn)的方式更佳。拋開它能屏蔽服務器端使用的技術(shù)這一特性不說,它還能夠更好地說明動態(tài)網(wǎng)站的層次結(jié)構(gòu),讓用戶在訪問時能明確知道在網(wǎng)站的什么位置,而不會覺得是陷入了一個迷宮。

                當然上面列舉的例子是網(wǎng)站前端所使用的url表現(xiàn)方式,因為表現(xiàn)方式可以多種多樣,個人喜好不同,本框架在設計時沒有給指定前端url的表現(xiàn)方式,而是定義接口,把這個權(quán)利留給使用的用戶??蚣軐⒖紤]更多 通用性的東西而不是 個性 自由的東西。

           

                下面對框架里默認使用的url Router AMPPathRouter做詳細的介紹,包括設計的思想和實現(xiàn)的方式。首先AMPPathRouter的用途定位為后臺使用。為了理解快速的理解它的工作原理,先來和struts做一下對比。

                Struts關(guān)于請求的配置:

           

          <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的相關(guān)的配置,像上面例子,struts會找到LoginAction的類,并且調(diào)用其login的方法。

                寫到這里,我請問這是最好的方式嗎?當然不是,至少我在使用struts時就認為這是相當撇腳的設計。上面例子只是列舉一個login方法,假如一個系統(tǒng)中要對后臺調(diào)用的方法是100個,那豈不是就需要在struts.xml中寫100個與之類似的配置。想想都頭大,這樣繁瑣的工作,應該是由框架自己去處理,而不是人工給配置。

           

                再來看實現(xiàn)相同功能的Unicorn web框架的配置。

           

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

                當然提交的url肯定需要包含多一些的信息,來保證能通過url正確調(diào)用框架Action里的方法。這里提交的url方式:http://xxx.xxx.xxx/UserAction/login/

                通過在url里附加調(diào)用的Action類的信息,可以省略為不同的方法都在xml里配置的麻煩。假如UserAction里有100個方法,框架也只需這一行的配置。

                有了大體的認識之后,來看框架的核心部分AMPPathRouter的具體實現(xiàn)。

           

            /**
          * 檢查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;
          }
          /**
          * 匹配規(guī)則為:
          * 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方式的請求,并構(gòu)建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的全部內(nèi)容,其中在把請求分發(fā)到ActionSupport的子類 并調(diào)用相關(guān)方法時 是通過反射實現(xiàn),其他地方地方都是相當容易理解的。

                空說無憑,把框架應用到實戰(zhàn)中才是硬道理:

           

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

          posted @ 2011-11-25 18:18 馬航 閱讀(269) | 評論 (0)編輯 收藏

                上篇說過,所有提交到web程序的url都被此UrlFilter攔截。攔截到請求后,UrlFilter則召集它的好多個得力干將Router 們, 詢問他們:“誰能處理此URL啊 ?”

          這時一位叫做AMPRouter 首當其沖 說:“這個url交給我了”。這時filter就會把此url全權(quán)交給AMPRouter來辦,至于如何去處理,filter也不再過問,它覺得:“我把任務都交給你了,怎么解決是你的事”。

                根據(jù)單一職責的原則,UrlFilter就負責上面情景中的分發(fā)urlRouter中的差事,url如何分發(fā)交給Router處理。并且Router實際是一個接口,使用框架的用戶完全可以自己實現(xiàn)Router,這樣用戶可以自主定義的url分發(fā)的策略。另外呢,框架初始化的一些操作它也是 推脫不掉的,像根據(jù)unicorn-config.xml初始化系統(tǒng)中的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優(yōu)先級最高,url先通過用戶定義的
          	Iterator<IPathRouter> iterator = routerList.iterator();
          	while(iterator.hasNext()) {
          		IPathRouter router = iterator.next();
          		if(router.route(relativeUri, this)) {
          			return ;
          		}
          	}
          	// 攔截不到的繼續(xù)訪問
          	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的請求 是否已經(jīng)登錄過,沒有登錄,可能會把此請求遣送會登錄頁。以及初始化所有的Action,在Router處理完請求,分發(fā)給action時,可以從filter里面去取。

           


          posted @ 2011-11-25 12:35 馬航 閱讀(273) | 評論 (0)編輯 收藏

                承接上篇的簡單介紹,下面詳細介紹整個框架的大致結(jié)構(gòu)。

                先來看一下整個框架包的結(jié)構(gòu):

           

                可以看出框架包含的包很少,包的結(jié)構(gòu)也超簡單。這里 涉及Filter、ActionSupport、Router等三個概念,他們之間的關(guān)系,通過下圖來表示:

           

                圖也不規(guī)范,說不上來是哪個UML圖,不過通過它也能看出一個請求到達時,框架基本的處理流程。首先由Filter攔截到所有請求,然后把請求交給所有注冊的Router類,如果請求的Url正好是一個Router要攔截的,則把此請求交給這個Router,框架不再把請求向下傳遞。Router得到請求后,分析Url,通過Url里的信息把請求交給對應的ActionSupport的子類來處理。

                這里攔截采用Filter來處理,這跟多數(shù)的web框架一樣,使用FilterServlet有更多的能力進行請求的分發(fā)。首先在一個web工程的web.xml文件中配置框架的UrlFilter類來攔截所有的請求。需要注意的一點是dispatcher 要設置為request,如果設置了forward的話,由框架內(nèi)部進行的forward又會被框架攔截,從而造成無限的循環(huán)。Url-pattern設置為/*,表示所有的請求都會攔截,從而把對url分發(fā)的權(quán)利交由框架本身,而不是采用jsp規(guī)范里的url分發(fā)策略??蚣茉谔幚硭姓埱蟮?/span>url 時,依次交給各個Router類來處理,如果Router類判斷是符合自己的url格式,則分發(fā)給 action 處理。如果不能處理再交給下一級的Router,最后url經(jīng)由所有Router處理完,剩下的資源文件的url,如http://xxx.xxx.xxx.jpg,則框架調(diào)用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>

                大致的原理就是這樣,在下篇介紹框架的詳細實現(xiàn)。

          posted @ 2011-11-25 11:43 馬航 閱讀(350) | 評論 (0)編輯 收藏

          2011年11月22日

                我承認有點標題黨了,不過題目中所說的幾項技術(shù)確實有其相似之處,欲知事情原委,且聽我詳細道來。

                項目一開始只是不滿 struts 龐大的體積,于是想自己根據(jù)其原理實現(xiàn)一個tiny 版。后來的開發(fā)中覺得,完全可以把上述的ajax、Restuful web service的一些思想加入進來。經(jīng)過幾周的努力,便開發(fā)出了一個基本成型的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??蚣芊钚屑s定大于配置的思想,至于請求如何分發(fā),這個不需要人工配置,框架自動解決。這里要介紹兩個概念Action 和 Router,熟悉Struts的肯定都知道Action,Action替代Servlet、JSP時代的Servlet,所有提交的請求由struts分發(fā)給不同的Action來處理。這里道理也是一樣的,Action就是經(jīng)過框架處理后的請求接受者。再來說一下Router,字面意思路由器,學過計算機網(wǎng)絡的都知道,ip數(shù)據(jù)包在網(wǎng)絡上之所以能夠順利到達,就是因為路由器根據(jù)路由表來來確定出來傳輸?shù)耐緩?。這里Router也是這個作用,根據(jù)訪問服務器的URL來制定分發(fā)策略。Router是完全可以自定義的,用戶可以定義自己的Router來制定URL分發(fā)的策略,并且用戶自定義的Router比系統(tǒng)默認的Router有更高的優(yōu)先權(quán)。

          二、URL上做文章

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

                先來介紹系統(tǒng)MethodRouter的處理方式。上面的url根據(jù)"/"分為三個部分,第一部分是請求的Action類,第二部分是類中的方法Method,第三部分是提交的參數(shù)Param。這一點受上篇文章優(yōu)酷的架構(gòu)里URL設計的啟發(fā)。

                經(jīng)過這樣的設計,就明白在上述配置文件中為何可以如此簡單了。

                當然也可以不以這樣的方式,框架提供自定義Router的支持。比如你想這樣處理URL:/前臺頁面/子欄目/子欄目

                想實現(xiàn)上面的方式,就可以自己定義Router,在Router里面獲取上述的URL,然后做處理、forward到相應的jsp頁面。

          三、使用Json傳輸數(shù)據(jù)

                Ajax請求很容易處理json數(shù)據(jù),ajax可以與系統(tǒng)輕松交互。

                當初Web Service使用SOAP的xml格式傳輸數(shù)據(jù),如今也有人指責這是大費周折。Restful方式提倡遵循HTTP語義,完全使用URL結(jié)合GET、POST、PUT、DELETE來傳輸請求,結(jié)果在roil陣營里廣泛使用,認為是web service更優(yōu)雅的方式。所以本框架也吸取他們的優(yōu)點,也完全可以通過url傳輸請求的數(shù)據(jù),如上述URL中的Param部分。不過沒有遵循Restful強調(diào)的Http語義,全部使用Get和POST的請求方式,當然也可以制定為其他,這完全看你的心情,因為這對功能實現(xiàn)無關(guān)緊要。而且我覺得統(tǒng)一使用一種,更避免了需要指定請求方式的麻煩。

                數(shù)據(jù)的返回使用json格式,比SOAP更為輕量簡潔和優(yōu)雅,而且有更多的平臺直接支持。如在android平臺,本身就支持json格式的處理, 如果使用web service 的SOAP,你可能還要導入KSOAP的第三方庫。

                在非瀏覽器的客戶端,可以借助編寫的工具類,來完成web service方式的操作,

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

                經(jīng)過這樣的封裝,已經(jīng)與使用web service毫無差別,而且還會更加高效,因為處理json總比處理SOAP的xml要容易。

           

                先簡單寫這么多,之后的續(xù)篇詳細介紹。

          posted @ 2011-11-22 16:22 馬航 閱讀(284) | 評論 (0)編輯 收藏

          2011年11月2日

          記得以前給大家介紹過視頻網(wǎng)站龍頭老大YouTube的技術(shù)架構(gòu),相信大家看了都會有不少的感觸,互聯(lián)網(wǎng)就是這么一個神奇的東西。今天我突然想到,優(yōu)酷網(wǎng)在國內(nèi)也算是視頻網(wǎng)站的老大了,不知道他的架構(gòu)相對于YouTube是怎么樣的,于是帶著這個好奇心去網(wǎng)上找了優(yōu)酷網(wǎng)架構(gòu)的各方面資料,雖然談得沒有YouTube那么詳細,但多少還是挖掘了一點,現(xiàn)在總結(jié)一下,希望對喜歡架構(gòu)的朋友有所幫助。

          一、網(wǎng)站基本數(shù)據(jù)概覽

          • 據(jù)2010年統(tǒng)計,優(yōu)酷網(wǎng)日均獨立訪問人數(shù)(uv)達到了8900萬,日均訪問量(pv)更是達到了17億,優(yōu)酷憑借這一數(shù)據(jù)成為google榜單中國內(nèi)視頻網(wǎng)站排名最高的廠商。
          • 硬件方面,優(yōu)酷網(wǎng)引進的戴爾服務器主要以 PowerEdge 1950與PowerEdge 860為主,存儲陣列以戴爾MD1000為主,2007的數(shù)據(jù)表明,優(yōu)酷網(wǎng)已有1000多臺服務器遍布在全國各大省市,現(xiàn)在應該更多了吧。

          二、網(wǎng)站前端框架

          從一開始,優(yōu)酷網(wǎng)就自建了一套CMS來解決前端的頁面顯示,各個模塊之間分離得比較恰當,前端可擴展性很好,UI的分離,讓開發(fā)與維護變得十分簡單和靈活,下圖是優(yōu)酷前端的模塊調(diào)用關(guān)系:

          這樣,就根據(jù)module、method及params來確定調(diào)用相對獨立的模塊,顯得非常簡潔。下面附一張優(yōu)酷的前端局部架構(gòu)圖:

           

          三、數(shù)據(jù)庫架構(gòu)

          應該說優(yōu)酷的數(shù)據(jù)庫架構(gòu)也是經(jīng)歷了許多波折,從一開始的單臺MySQL服務器(Just Running)到簡單的MySQL主從復制、SSD優(yōu)化、垂直分庫、水平sharding分庫,這一系列過程只有經(jīng)歷過才會有更深的體會吧,就像MySpace的架構(gòu)經(jīng)歷一樣,架構(gòu)也是一步步慢慢成長和成熟的。

          1、簡單的MySQL主從復制:

          MySQL的主從復制解決了數(shù)據(jù)庫的讀寫分離,并很好的提升了讀的性能,其原來圖如下:

          其主從復制的過程如下圖所示:

          但是,主從復制也帶來其他一系列性能瓶頸問題:

          1. 寫入無法擴展
          2. 寫入無法緩存
          3. 復制延時
          4. 鎖表率上升
          5. 表變大,緩存率下降

          那問題產(chǎn)生總得解決的,這就產(chǎn)生下面的優(yōu)化方案,一起來看看。

           

          2、MySQL垂直分區(qū)

          如果把業(yè)務切割得足夠獨立,那把不同業(yè)務的數(shù)據(jù)放到不同的數(shù)據(jù)庫服務器將是一個不錯的方案,而且萬一其中一個業(yè)務崩潰了也不會影響其他業(yè)務的正常進行,并且也起到了負載分流的作用,大大提升了數(shù)據(jù)庫的吞吐能力。經(jīng)過垂直分區(qū)后的數(shù)據(jù)庫架構(gòu)圖如下:

          然而,盡管業(yè)務之間已經(jīng)足夠獨立了,但是有些業(yè)務之間或多或少總會有點聯(lián)系,如用戶,基本上都會和每個業(yè)務相關(guān)聯(lián),況且這種分區(qū)方式,也不能解決單張表數(shù)據(jù)量暴漲的問題,因此為何不試試水平sharding呢?

           

          3、MySQL水平分片(Sharding)

          這是一個非常好的思路,將用戶按一定規(guī)則(按id哈希)分組,并把該組用戶的數(shù)據(jù)存儲到一個數(shù)據(jù)庫分片中,即一個sharding,這樣隨著用戶數(shù)量的增加,只要簡單地配置一臺服務器即可,原理圖如下:

          如何來確定某個用戶所在的shard呢,可以建一張用戶和shard對應的數(shù)據(jù)表,每次請求先從這張表找用戶的shard id,再從對應shard中查詢相關(guān)數(shù)據(jù),如下圖所示:

          但是,優(yōu)酷是如何解決跨shard的查詢呢,這個是個難點,據(jù)介紹優(yōu)酷是盡量不跨shard查詢,實在不行通過多維分片索引、分布式搜索引擎,下策是分布式數(shù)據(jù)庫查詢(這個非常麻煩而且耗性能)

           

          四、緩存策略

          貌似大的系統(tǒng)都對“緩存”情有獨鐘,從http緩存到memcached內(nèi)存數(shù)據(jù)緩存,但優(yōu)酷表示沒有用內(nèi)存緩存,理由如下:

          1. 避免內(nèi)存拷貝,避免內(nèi)存鎖
          2. 如接到老大哥通知要把某個視頻撤下來,如果在緩存里是比較麻煩的

          而且Squid 的 write() 用戶進程空間有消耗,Lighttpd 1.5 的 AIO(異步I/O) 讀取文件到用戶內(nèi)存導致效率也比較低下。

          但為何我們訪問優(yōu)酷會如此流暢,與土豆相比優(yōu)酷的視頻加載速度略勝一籌?這個要歸功于優(yōu)酷建立的比較完善的內(nèi)容分發(fā)網(wǎng)絡(CDN),它通過多種方式保證分布在全國各地的用戶進行就近訪問——用戶點擊視頻請求后,優(yōu)酷網(wǎng)將根據(jù)用戶所處地區(qū)位置,將離用戶最近、服務狀況最好的視頻服務器地址傳送給用戶,從而保證用戶可以得到快速的視頻體驗。這就是CDN帶來的優(yōu)勢,就近訪問,有關(guān)CDN的更多內(nèi)容,請大家Google一下。

          好了,就總結(jié)這么多了,有興趣的同學接著補充,雖然很多資料圖片都來自網(wǎng)絡,但整理也不容易,歡迎轉(zhuǎn)載,轉(zhuǎn)載留個出處:青藤屋 原文鏈接

          posted @ 2011-11-02 11:23 馬航 閱讀(288) | 評論 (0)編輯 收藏

          2011年10月12日

          windows系統(tǒng)使我們經(jīng)常使用的操作系統(tǒng)怎么才能使用我們現(xiàn)在經(jīng)常使用的操作系統(tǒng)不變的情況下繼續(xù)我們的SVN之旅,我們在綜合了好動種方法的同時感覺這些內(nèi)容非常貼近我們SVN在Windows種的應用與配置.

          1.下載文件,

          下載最新版本subversion,我這里選擇svn-1.4.5-setup.exe

          下載 "Subversion Windows Service" 軟件包

          下載 TortoiseSVN shell integration utility

          2.安裝Subversion 服務器

          由于我下載的是setup.exe版本,安裝程序安裝后會自動設置系統(tǒng)變量.如果你下載的是zip版就需要手動設置系統(tǒng)變量.

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

          首先創(chuàng)建SVN儲存庫(repository)

          svnadmin create F:/svn/

          repository創(chuàng)建完畢后會在目錄下生成若干個文件和文件夾,dav目錄是提供給Apache與mod_dav_svn使用的目錄,讓它們存儲內(nèi)部數(shù)據(jù);db目錄就是所有版本控制的數(shù)據(jù)文件;hooks目錄放置hook腳本文件的目錄;locks用來放置Subversion文件庫鎖定數(shù)據(jù)的目錄,用來追蹤存取文件庫的客戶端;format文件是一個文本文件,里面只放了一個整數(shù),表示當前文件庫配置的版本號;

          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,即為系統(tǒng)添加一個用戶名為admin,密碼為admin888的用戶

          4.運行SVN服務器

          運行SVN服務

          在命令行執(zhí)行

          svnserve --daemon --root F:/svn

          服務啟動,--daemon可簡寫為-d,--root可簡寫為-r,可以建立一個批處理文件并放在windows啟動組中便于開機就運行SVN服務(注意:這是臨時打開的服務,命令執(zhí)行后不能關(guān)閉窗口)

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

          用后臺服務的方式可以設置開機自動執(zhí)行。

          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的服務器,權(quán)限文件authz配置的常見問題及解答  
           最近在我用Subversion論壇(http://www.iUseSVN.com/bbs)經(jīng)常有人提到這樣的問題: 
          為什么我的客戶端沒有寫權(quán)限? 
          為什么我的權(quán)限沒有起作用?

          總結(jié)他們的配置,發(fā)現(xiàn) 
          都是用svnserve作為服務器, 
          都在svnserve.conf中使用了authz-db選項

          原因可能如下:

          1,配置authz時,沒有注意svnserve啟動參數(shù)-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的根目錄,而按上面的啟動參數(shù),是沒有庫的概念的。 
          使用類似這樣的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,對中文目錄進行權(quán)限控制時,沒有將權(quán)限文件authz改為utf-8格式。

          svn對于非英文文件名和目錄名使用utf-8格式編碼處理,要對中文目錄進行正確控制, 
          應該使用無BOM的utf-8格式,如何將默認的文件轉(zhuǎn)為utf-8, 
          我使用的是UltraEdit的菜單"ASCII to UTF-8 (Unicode Editing)"。在UltraEdit的配置中,可以設置有無BOM  

          posted @ 2011-10-12 17:26 馬航 閱讀(423) | 評論 (0)編輯 收藏

          2011年10月7日

          SIP協(xié)議

           

          SIP協(xié)議過程概念及分析

           

          SIP入門開發(fā)之路(含SIP開發(fā)需要學習的資源及網(wǎng)址)

           

          SIP揭密(中文版)

           

          使用Java的SIP Servlet進行SIP開發(fā)

           

           

          Asterisk:

           

          Asterisk安裝及測試

           

          Asterisk十問十答

           

          Asterisk入門教程

           

          Asterisk介紹-Asterisk RealTime SIP

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

           

          asterisk 官方文檔

           

          asterisk目錄及配置說明

           

          Asterisk功能整理

           

          Asterisk使用ODBC實現(xiàn)語音信箱

           

          使用Asterisk實現(xiàn)可視的語音交換

           

           

          OpenSIPS

           

          開源SIP服務器OpenSIPS應用介紹

           

          Opensips 安裝

           

          Opensips 配置文件

           

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

           

          Opensips文檔之MediaProxy模塊

           

          使用OpenSIPS構(gòu)建電話通信系統(tǒng)-8媒體服務整合

           

          使用OpenSIPS構(gòu)建電話通信系統(tǒng)-4腳本及路由基礎

           

          Opensips文檔之TM模塊

           

          Opensips文檔之RR模塊

           

          Opensips文檔之TEXTOPS 模塊

           

          Opensips文檔之AVPOPS模塊

           

           

          NAT穿透(即SIP打洞)

           

          使用OpenSIPS構(gòu)建電話通信系統(tǒng)-SIP穿透NAT

           

          NAT穿透問題探討

           

          完美的NAT穿透技術(shù)ICE介紹

           

          ICE-SIP穿透NAT問題的終極解決方案

           

          NAT穿透技術(shù)ICE基礎教程

          posted @ 2011-10-07 20:31 馬航 閱讀(451) | 評論 (0)編輯 收藏

          2011年10月6日

               摘要: Android中有一控件是ExpandableListView,比ListView更高級,ExpandableListView的效果很實用,比如因為需要查看一堆文件的目錄結(jié)構(gòu)或者開發(fā)像QQ好友那樣的界面,就應該使用Expandablelistview。 本文最終效果如下: 首先是Activity代碼,實際開發(fā)中數(shù)據(jù)(包括父item,子item及圖片,Expandablelistview...  閱讀全文

          posted @ 2011-10-06 09:37 馬航 閱讀(5469) | 評論 (0)編輯 收藏

          2011年10月3日

          前言:做完了手機全能播放器的項目, 又要告別幾個月來并肩作戰(zhàn),即將去北京發(fā)展的Manager zhu。準備把做過的3GP/FLV/AVI格式整理一遍, 算是對幾個月辛苦成果的總結(jié), 也為后來者提供一些參考。

          1. 概述

          流行的文件格式背后都有大公司的支持。FLV得益于ADOBE公司推動的網(wǎng)絡視頻分享風潮,而AVI則是MICROSOFT首創(chuàng)的RIFF即視頻和音頻交織在一起同步播放。 3GP/MP4是APPLE提出并得到ISO標準支持作為NOKIA等手機的默認視頻格式。3GP是MP4格式在手機上的簡化版。MP4的codec組合一般是mpeg4 + AAC, 3GP則按版本演進分為3gpp r5(h.263/mpeg4 + AMR-NB/AMR WB), 3gpp r6(增加h.264視頻和aacPlus音頻支持)。

          有人會把MP4和MPEG4搞混, 前者是文件容器(container),后者是視頻編碼格式, 容器的作用是把壓縮編碼后的視頻和音頻數(shù)據(jù)盡可能緊湊的排布,就好像阿甘的巧克力盒子,你并不知道盒子里有什么, 但你可以按照既定的線索解開文件,取出你需要的數(shù)據(jù)。

          文件格式一般包括以下三要素:

          header: 標記文件類型,音視頻碼流的基本屬性信息
          index: 索引表,每個frame有對應的offset,size,timestamp.
          stream: 真正的音視頻流數(shù)據(jù)。
          任何文件格式都應該有以上3要素。 當然AVI視頻沒有索引也能播放,但不能拖放seek,需要自己重建索引。解析器(demuxer)根據(jù)frame_id找到其在文件中的offset和size,然后讀取出來解碼并播放。

          2. 文件格式分析

          下面來分析一下3GP/MP4文件格式。APPLE的格式有2個特點,1. 排布緊湊幾乎沒有冗余數(shù)據(jù)(AVI則有很多junk數(shù)據(jù)),2.音視頻碼流數(shù)據(jù)可隨意存放而不需按時間順序排布。

          3gp文件由一系列的box(atom)組成。每個box的結(jié)構(gòu)都是4字節(jié)的size,4字節(jié)的type, 還有一些data數(shù)據(jù)。用mp4info查看3gp文件的數(shù)據(jù)排布如下圖:

          如上圖, ftyp是表示文件的版本信息, mdat存放文字,音視頻等數(shù)據(jù)。你可能要問,這些音視頻數(shù)據(jù)怎么找到呢? 是通過moov box里的子box trak,里面存放著音視頻的屬性描述以及每個sample的索引。

          3. 關(guān)于sample atoms

             video和audio的碼流屬性(如視頻width/height,codec id, 音頻采樣率聲道數(shù)等)存放在stsd box里; 下面著重介紹MP4高效壓縮的精華:stts,stss,stsc,stsz,stco五個box。對比AVI的索引表是每個sample都有對應的id,flag,offset,size,3GP的高效索引方式可以把AVI轉(zhuǎn)碼成同碼率的MP4后,文件size減小成原來的20-30%!

          1. stts atom(time to sample atoms,見quicktime format 文檔圖2-28 標準文檔點擊下載): 存儲了sample的時間信息。stts能讓很方便的根據(jù)timestamp找到對應的sample,或者獲取某個sample對應的timestamp. sttstable記錄著有相同duration的sample的數(shù)量count和時長dutation。

          2. stss atom(sync sample atom,見文檔圖2-31): 存儲了每個關(guān)鍵幀的sample id。 stss能讓你很方便的找到當前幀最近的關(guān)鍵幀。

          3. stsc atom(sample to chunk atom): sample存放在chunk里為了允許優(yōu)化的數(shù)據(jù)讀取。比如音頻sample size都很?。╝mr-nb sample size為32字節(jié)), 每次讀取一個sample開銷太大, 可一次性讀所在chunk里一堆sample。

          4. stsz atom(sample size atom): stsz可以描述每個sample的size.

          5. stco atom(chunk offset atoms): stco描述了每個chunk在文件中的絕對偏移位置。該offset可以是32位的

          也可以是64位的,后者用來支持處理超大文件。

          4 .使用sample atoms來處理播放流程

          · 查找sample         

          1.確定時間,相對于媒體時間坐標系統(tǒng)

          2.檢查time-to-sample atom來確定給定時間的sample序號。

          3.檢查sample-to-chunk atom來發(fā)現(xiàn)對應該sample的chunk。

          4.從chunk offset atom中提取該trunk的偏移量。

          5.利用sample size atom找到sample在trunk內(nèi)的偏移量和sample的大小。

          例如,如果要找第1秒的視頻數(shù)據(jù),過程如下:

          1. 第1秒的視頻數(shù)據(jù)相對于此電影的時間為600

          2. 檢查time-to-sample atom,得出每個sample的duration是40,從而得出需要尋找第600/40 = 15 + 1 = 16個sample

          3. 檢查sample-to-chunk atom,得到該sample屬于第5個chunk的第一個sample,該chunk共有4個sample

          4. 檢查chunk offset atom找到第5個trunk的偏移量是20472

          5. 由于第16個sample是第5個trunk的第一個sample,所以不用檢查sample size atom,trunk的偏移量即是該sample的偏移量20472。如果是這個trunk的第二個sample,則從sample size atom中找到該trunk的前一個sample的大小,然后加上偏移量即可得到實際位置。

          6. 得到位置后,即可取出相應數(shù)據(jù)進行解碼,播放

          ·       查找關(guān)鍵幀      

          查找過程與查找sample的過程非常類似,只是需要利用sync sample atom來確定key frame的sample序號

          確定給定時間的sample序號 
          檢查sync sample atom來發(fā)現(xiàn)這個sample序號之后的key frame 
          檢查sample-to-chunk atom來發(fā)現(xiàn)對應該sample的chunk 
          從chunk offset atom中提取該trunk的偏移量 
          利用sample size atom找到sample在trunk內(nèi)的偏移量和sample的大小


          5 .3GP/MP4相關(guān)資源

               quicktime file format specification: 最權(quán)威的格式文檔 點擊下載
               開源的3GP/MP4解析器: ffmpeg, GPAC, helix, google opencore等 

          posted @ 2011-10-03 10:54 馬航 閱讀(694) | 評論 (0)編輯 收藏

          Android開發(fā)又將帶來新一輪熱潮,很多開發(fā)者都投入到這個浪潮中去了,創(chuàng)造了許許多多相當優(yōu)秀的應用。其中也有許許多多的開發(fā)者提供了應用開 源項目,貢獻出他們的智慧和創(chuàng)造力。學習開源代碼是掌握技術(shù)的一個最佳方式。下面推薦幾個應用開源項目,這些項目不僅提供了優(yōu)秀的創(chuàng)意,也可以直接掌握 Android內(nèi)核的接口使用:

          1、Android團隊提供的示例項目

          如果不是從學習Android SDK中提供的那些樣例代碼開始,可能沒有更好的方法來掌握在Android這個框架上開發(fā)。由Android的核心開發(fā)團隊提供了15個優(yōu)秀的示例項目,包含了游戲、圖像處理、時間顯示、開始菜單快捷方式等。
          地址:http://code.google.com/p/apps-for-android/

          2、 Remote Droid

          RemoteDroid是一個Android應用,能夠讓用戶使用自己的無線網(wǎng)絡使用無線鍵盤、觸摸屏操作手機。這個項目為開發(fā)者提供了如網(wǎng)絡連接、觸摸屏手指運動等很好的樣例。
          地址:http://code.google.com/p/remotedroid/

          3、 TorProxy和Shadow

          TorProxy應用實現(xiàn)了Android手機無線電電傳通訊(TOR),和Shadow應用一起使用,可以使用手機匿名上網(wǎng)。從該項目源代碼中,可以掌握socket連接、管理cookie等方法。
          地址:http://www.cl.cam.ac.uk/research/dtg/code/svn/android-tor/

          4、 Android SMSPopup

          SMSPopup可以截獲短信內(nèi)容顯示在一個泡泡形狀的窗口中。從這個項目中可以掌握到如何使用內(nèi)置的短信SMS接口。
          地址:http://code.google.com/p/android-smspopup/

          5、 Standup Timer

          Standup Timer應用用于控制站立會議時間,類似秒表倒計時,可以提醒每個人的講話時間已到,從而保證每個與會者使用時間一樣。從該項目的代碼中,可以學會如何使用時間函數(shù)。另外,這個項目的代碼是采用視圖view、模型model嚴格分離的設計思路。
          地址:http://github.com/jwood/standup-timer

          6、 Foursquare

          是Foursquare.com的一個客戶端應用,該應用主要分為兩個模塊:API(com.joelapenna.foursquare)和界面前端 (com.joelapenna.foursquared)兩部分。從該項目代碼中,可以學會如何同步、多線程、HTTP連接等技術(shù)。
          地址:http://code.google.com/p/foursquared/

          7、 Pedometer

          Pedometer應用用于記錄你每天走路步數(shù)的。盡管記錄不一定精準,但是從這個項目中,可以學習幾個不同的技術(shù):加速器交互、語音更新、后臺運行服務等。
          地址:http://code.google.com/p/pedometer/

          8、 OpenSudoku-android

          OpenSudoku是一個簡單的九宮格數(shù)獨游戲。從代碼中可以學習到如何在視圖中顯示表格數(shù)據(jù),以及如何和一個網(wǎng)站交互等技術(shù)。
          地址:http://code.google.com/p/opensudoku-android/

          9、 ConnectBot

          ConnectBot是Android平臺的一個客戶端安全殼應用。從該項目代碼中,可以學習到很多Android安全方面的內(nèi)容,這些是你在開發(fā)應用時經(jīng)常需要考慮的安全問題。
          地址:http://code.google.com/p/connectbot/

          10、 WordPress的Android應用

          當然在最后不能不提WordPress的Android應用了,這是WordPress官方開發(fā)團隊提供的一個項目。從代碼中可以學習到XMLRPC調(diào)用(當然還有更多的優(yōu)秀內(nèi)容)。
          地址:http://android.svn.wordpress.org/trunk/

          posted @ 2011-10-03 09:47 馬航 閱讀(479) | 評論 (0)編輯 收藏

          主站蜘蛛池模板: 当涂县| 尉犁县| 凉山| 金坛市| 吴忠市| 林州市| 邵武市| 新乡县| 沙坪坝区| 白城市| 荥阳市| 香格里拉县| 称多县| 泾源县| 合江县| 广昌县| 漳浦县| 大石桥市| 拜泉县| 全椒县| 玛多县| 怀集县| 焦作市| 赞皇县| 綦江县| 阿拉善盟| 易门县| 开封县| 米林县| 伊川县| 青川县| 彭阳县| 富锦市| 塔城市| 秭归县| 九寨沟县| 德州市| 南郑县| 宜兰市| 肇州县| 赤峰市|