Play源代码分析:Server启动过程

Play是个Rails风格的Java Web框架,需要了解背景请看:

  1. Play Framework介绍1--主要概念
  2. Play Framework介绍2—Helloworld

如何调试请看此处。以下进入正题^_^

Server启动过程主要涉及三个地方:

  1. play.Play类:代表Play本身业务模型。
  2. play.server.Server类:负责服务器启动。
  3. play.classloading包:负责.java文件读取、编译和加载。

总体流程:

Play代码分析-Server.Main

Server.main为入口方法:

复制代码
  
  
public static void main(String[] args) throws Exception { … Play.init(root, System.getProperty( " play.id " , "" )); if (System.getProperty( " precompile " ) == null ) { new Server(); } else { Logger.info( " Done. " ); } }
复制代码

做两件事:

  1. Play.init
  2. 然后创建Server对象。

Play.init

复制代码
  
  
public static void init(File root, String id) { … readConfiguration(); Play.classes = new ApplicationClasses(); … // Build basic java source path VirtualFile appRoot = VirtualFile.open(applicationPath); roots.add(appRoot); javaPath = new ArrayList < VirtualFile > ( 2 ); javaPath.add(appRoot.child( " app " )); javaPath.add(appRoot.child( " conf " )); // Build basic templates path templatesPath = new ArrayList < VirtualFile > ( 2 ); templatesPath.add(appRoot.child( " app/views " )); // Main route file routes = appRoot.child( " conf/routes " ); … // Load modules loadModules(); … // Enable a first classloader classloader = new ApplicationClassloader(); // Plugins loadPlugins(); // Done ! if (mode == Mode.PROD || preCompile() ) { start(); } … }
复制代码

主要做:

  1. 加载配置
  2. new ApplicationClasses();加载app、views和conf路径到VirtualFile中,VirtualFile是Play内部的统一文件访问接口,方便后续读取文件
  3. 加载route
  4. 加载Module,Play的应用扩展组件。
  5. 加载Plugin,Play框架自身的扩展组件。
  6. 工作在产品模式则启动Play.

关键步骤为new ApplicationClasses(),执行computeCodeHashe(),后者触发目录扫描,搜索.java文件。相关过程简化代码如下:

复制代码
  
  
public ApplicationClassloader() { super (ApplicationClassloader. class .getClassLoader()); // Clean the existing classes for (ApplicationClass applicationClass : Play.classes.all()) { applicationClass.uncompile(); } pathHash = computePathHash(); … }
复制代码
复制代码
  
  
int computePathHash() { StringBuffer buf = new StringBuffer(); for (VirtualFile virtualFile : Play.javaPath) { scan(buf, virtualFile); } return buf.toString().hashCode(); }
复制代码
复制代码
  
  
void scan(StringBuffer buf, VirtualFile current) { if ( ! current.isDirectory()) { if (current.getName().endsWith( " .java " )) { Matcher matcher = Pattern.compile( " \\s+class\\s([a-zA-Z0-9_]+)\\s+ " ).matcher(current.contentAsString()); buf.append(current.getName()); buf.append( " ( " ); while (matcher.find()) { buf.append(matcher.group( 1 )); buf.append( " , " ); } buf.append( " ) " ); } } else if ( ! current.getName().startsWith( " . " )) { for (VirtualFile virtualFile : current.list()) { scan(buf, virtualFile); } } }
复制代码

Start流程

Play.Start过程

简化代码如下:

复制代码
  
  
public static synchronized void start() { try { ... // Reload configuration readConfiguration(); ... // Try to load all classes Play.classloader.getAllClasses(); // Routes Router.detectChanges(ctxPath); // Cache Cache.init(); // Plugins for (PlayPlugin plugin : plugins) { try { plugin.onApplicationStart(); } catch (Exception e) { if (Play.mode.isProd()) { Logger.error(e, " Can't start in PROD mode with errors " ); } if (e instanceof RuntimeException) { throw (RuntimeException)e; } throw new UnexpectedException(e); } } ... // Plugins for (PlayPlugin plugin : plugins) { plugin.afterApplicationStart(); } } catch (PlayException e) { started = false ; throw e; } catch (Exception e) { started = false ; throw new UnexpectedException(e); } }
复制代码

关键步骤为执行Play.classloader.getAllClasses()加载app目录中的类型。简化代码如下:

复制代码
  
  
public List < Class > getAllClasses() { if (allClasses == null ) { allClasses = new ArrayList < Class > (); if (Play.usePrecompiled) { ... } else { List < ApplicationClass > all = new ArrayList < ApplicationClass > (); // Let's plugins play for (PlayPlugin plugin : Play.plugins) { plugin.compileAll(all); } for (VirtualFile virtualFile : Play.javaPath) { all.addAll(getAllClasses(virtualFile)); } List < String > classNames = new ArrayList < String > (); for ( int i = 0 ; i < all.size(); i ++ ) { if (all.get(i) != null && ! all.get(i).compiled) { classNames.add(all.get(i).name); } } Play.classes.compiler.compile(classNames.toArray( new String[classNames.size()])); for (ApplicationClass applicationClass : Play.classes.all()) { Class clazz = loadApplicationClass(applicationClass.name); if (clazz != null ) { allClasses.add(clazz); } } ... } } return allClasses; }
复制代码

主要步骤:

  1. plugin.compileAll,给所有plugin一次机会进行自定义编译。
  2. Play.classes.compiler.compile(classNames.toArray(new String[classNames.size()]));编译所有.java文件。编译后的.class存储在ApplicationClass中。内部使用了eclipse的JDT编译器。
  3. loadApplicationClass,取出ApplicationClass中的.class加入List<Class>中返回。

到此完成.java的加载。相关对象关系如下图:

Play代码分析

接着new Server()启动HTTP服务,监听请求

简化代码如下:

复制代码
  
  
public Server() { ... if (httpPort == - 1 && httpsPort == - 1 ) { httpPort = 9000 ; } ... InetAddress address = null ; try { if (p.getProperty( " http.address " ) != null ) { address = InetAddress.getByName(p.getProperty( " http.address " )); } else if (System.getProperties().containsKey( " http.address " )) { address = InetAddress.getByName(System.getProperty( " http.address " )); } } catch (Exception e) { Logger.error(e, " Could not understand http.address " ); System.exit( - 1 ); } ServerBootstrap bootstrap = new ServerBootstrap( new NioServerSocketChannelFactory( Executors.newCachedThreadPool(), Executors.newCachedThreadPool()) ); try { if (httpPort != - 1 ) { bootstrap.setPipelineFactory( new HttpServerPipelineFactory()); bootstrap.bind( new InetSocketAddress(address, httpPort)); bootstrap.setOption( " child.tcpNoDelay " , true ); if (Play.mode == Mode.DEV) { if (address == null ) { Logger.info( " Listening for HTTP on port %s (Waiting a first request to start) ... " , httpPort); } else { Logger.info( " Listening for HTTP at %2$s:%1$s (Waiting a first request to start) ... " , httpPort, address); } } else { if (address == null ) { Logger.info( " Listening for HTTP on port %s ... " , httpPort); } else { Logger.info( " Listening for HTTP at %2$s:%1$s ... " , httpPort, address); } } } } catch (ChannelException e) { Logger.error( " Could not bind on port " + httpPort, e); System.exit( - 1 ); } ... }
复制代码

主要步骤:

  1. 设置端口,地址
  2. new ServerBootstrap,创建jboss netty服务器。Play1.1.1使用了netty作为底层通讯服务器。
  3. new HttpServerPipelineFactory(),设置netty所需的请求处理管道工厂。它负责当请求到达时提供处理者。
  4. bootstrap.bind(new InetSocketAddress(address, httpPort),绑定地址,端口。

原文:http://www.cnblogs.com/Chaos/archive/2011/04/17/2018500.html

Live555是一款开源的多媒体流服务器,支持实时流媒体的传输。下面是部分Live555源代码分析: 1. MediaSession类 MediaSession类是Live555中最核心的类之一,它表示了一个媒体会话,包括了媒体流的传输协议、媒体编码格式、传输方式等信息。MediaSession类中的成员变量包括: - fServerMediaSession:代表了一个媒体服务器会话,负责提供媒体流的传输。 - fSubsessions:代表了一个或多个媒体流的传输会话,可以是RTP/RTCP、HTTP/RTSP等协议。 - fSdpLines:代表了SDP协议中的信息,可以是媒体流的编码格式、传输方式等信息。 MediaSession类中的核心方法包括: - createNew:用于创建一个新的媒体会话。 - addSubsession:用于添加一个媒体流的传输会话。 - generateSdpDescription:用于生成SDP协议描述信息。 - startStream:用于开始媒体流的传输。 - pauseStream:用于暂停媒体流的传输。 - seekStream:用于跳转媒体流的传输。 2. MediaSubsession类 MediaSubsession类表示了一个媒体流的传输会话,包括了媒体流的传输协议、媒体编码格式、传输方式等信息。MediaSubsession类中的成员变量包括: - fParentSession:代表了父级MediaSession类的实例。 - fRTPSink:代表了RTP数据的发送器。 - fRTCPInstance:代表了RTCP数据的发送器。 - fTrackNumber:代表了媒体流的轨道编号。 - fCodecName:代表了媒体流的编码格式。 - fMediumName:代表了媒体流的传输方式。 MediaSubsession类中的核心方法包括: - initiate:用于初始化媒体流的传输。 - startStream:用于开始媒体流的传输。 - pauseStream:用于暂停媒体流的传输。 - seekStream:用于跳转媒体流的传输。 3. RTSPServer类 RTSPServer类是Live555中实现RTSP协议的服务器类。RTSPServer类中的成员变量包括: - fServerMediaSession:代表了一个媒体服务器会话,负责提供媒体流的传输。 - fHTTPServerPort:代表了HTTP服务器的端口号。 - fRTSPServerPort:代表了RTSP服务器的端口号。 RTSPServer类中的核心方法包括: - createNew:用于创建一个新的RTSP服务器。 - start:用于启动RTSP服务器。 - stop:用于停止RTSP服务器。 - incomingConnectionHandler:用于处理RTSP客户端的连接请求。 - handleCmd_DESCRIBE:用于处理DESCRIBE命令。 - handleCmd_SETUP:用于处理SETUP命令。 - handleCmd_PLAY:用于处理PLAY命令。 - handleCmd_PAUSE:用于处理PAUSE命令。 - handleCmd_TEARDOWN:用于处理TEARDOWN命令。 以上是Live555源代码的部分分析,希望对你有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值