http://duker.iteye.com/blog/209040上面提到flash socket的策略服务器问题如何用MINA2来解决。另外,在http://code.google.com/p/hudo/上的hudo as3 网络通信框架也提出使用一个简单的java程序监听843端口。网上介绍的解决方法很多,例如http://bbs.9ria.com/viewthread.php?tid=15182。
这个问题源于Actionscript3网络通信的特殊性——在通信之前必须(哪怕你代码里没有明显的程序表现)进行一次的策略请求。类似于html协议的策略文件和js的策略文件,只不过用tcp协议。SGS是开源的网游服务器引擎,但没有解决此问题的直接方法,我觉得用mina1.1.7解决这个问题比较方便,就试着写写,觉得还可行(至少hudo的demo可以用这种方法)。
我的思路是,添加一个SGS服务,然后开mina服务器监听843端口,如果有合适的输入就返回crossdomain.xml开放端口。
声明几点,一,我开一个SGS所以不会出现843端口冲突问题,二,我不知道代码是否经得起高并发——没有跨域策略文件,flash将无法进行任何socket连接,那是非常致命的问题。
用hudo的demo来说:
首先,修改SGS的配置文件conf\sgsApp.properties
# This is the properties file for running the MUDServer application com.sun.sgs.app.name=thServer com.sun.sgs.app.root=data/thServer com.sun.sgs.app.port=23 com.sun.sgs.impl.transport.tcp.listen.port=23 com.sun.sgs.app.listener=thgame.GameChannels # SGS 0.9.5.1-r3730 # see com.sun.sgs.impl.kernel.ServiceConfigRunner:fetchServices() # My own service , use ":" to split #com.sun.sgs.app.services=thgame.FlashPolicyServiceImpl # Managers may be "", but CANNOT delete this line #com.sun.sgs.app.managers= # SGS 0.9.9 # see com.sun.sgs.impl.kernel.Kernel:fetchServices() # My own service , use ":" to split com.sun.sgs.services=thgame.FlashPolicyServiceImpl # Managers may be "", but CANNOT delete this line com.sun.sgs.managers=
注意,0.9.5和0.9.9不同,com.sun.sgs.app.* 和 com.sun.sgs.*,而且managers都不可以省略,详细可以参考SGS源代码。然后就是写那个thgame.FlashPolicyServiceImpl类了
package thgame; import com.sun.sgs.kernel.ComponentRegistry; import com.sun.sgs.service.Service; import com.sun.sgs.service.TransactionProxy; import java.util.Properties; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.charset.Charset; import org.apache.mina.common.DefaultIoFilterChainBuilder; import org.apache.mina.common.IoAcceptor; import org.apache.mina.common.IoAcceptorConfig; import org.apache.mina.filter.LoggingFilter; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.textline.TextLineCodecFactory; import org.apache.mina.transport.socket.nio.SocketAcceptor; import org.apache.mina.transport.socket.nio.SocketAcceptorConfig; /** * Simple Service implementation. <p> */ public class FlashPolicyServiceImpl implements Service { /** Choose your favorite port number. */ private static final int PORT = 843; public FlashPolicyServiceImpl(Properties properties, ComponentRegistry systemRegistry, TransactionProxy txnProxy) throws Exception { System.out.println("FlashPolicyServiceImpl construct"); //退出函数后线程仍存在 IoAcceptor acceptor = new SocketAcceptor(); IoAcceptorConfig config = new SocketAcceptorConfig(); DefaultIoFilterChainBuilder chain; chain = config.getFilterChain(); /** * 启用/禁用 SO_REUSEADDR 套接字选项。 * 超过一个的套接字绑定到相同的套接字地址。 * 如果此功能不受支持,则 getReuseAddress() 将始终返回 false */ //((SocketAcceptorConfig)config).setReuseAddress(true); //System.out.println("getReuseAddress:" + ((SocketAcceptorConfig)config).isReuseAddress()); /** * 重复加logger会抛异常 */ //chain.addLast("logger", new LoggingFilter()); /** * 文本模式 */ //chain.addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory())); // Bind try { acceptor.bind(new InetSocketAddress(PORT), new FlashPolicyProtocolHandler(), config); System.out.println("FlashPolicyServiceImpl Listening on port " + PORT); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return; } public String getName() { System.out.println("FlashPolicyServiceImpl ready"); return toString(); } public void ready() { return; } public void shutdown() { System.out.println("FlashPolicyServiceImpl shutdown"); } }
再加上最为重要的handler就大功告成
package thgame; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import org.apache.mina.common.IoHandler; import org.apache.mina.common.IoHandlerAdapter; import org.apache.mina.common.IoSession; import org.apache.mina.common.ByteBuffer; public class FlashPolicyProtocolHandler extends IoHandlerAdapter { private static String POLICY_FILE = "<cross-domain-policy>" + "<site-control permitted-cross-domain-policies=\"all\"/>" + "<allow-access-from domain=\"*\" to-ports=\"*\" />" + "</cross-domain-policy>"; private Charset ch = Charset.forName("utf-8"); private CharsetDecoder decoder = ch.newDecoder(); private CharsetEncoder encoder = ch.newEncoder(); public void exceptionCaught(IoSession session, Throwable cause) { //cause.printStackTrace(); // Close connection when unexpected exception is caught. session.close(); } @Override public void messageReceived(IoSession session, Object message) { try { //System.out.println("received..."); if(! (message instanceof ByteBuffer)) { session.close(); return ; } ByteBuffer rb = (ByteBuffer) message; String str = rb.getString(decoder); //System.out.println(str); if(str.equals("<policy-file-request/>")){ //System.out.println("<policy-file-request/>"); ByteBuffer wb = ByteBuffer.allocate(POLICY_FILE.length()); wb.putString(POLICY_FILE, encoder); /** * ByteBuffer必须flip */ wb.flip(); session.write(wb); } } catch (Exception e) { e.printStackTrace(); } finally { session.close(); } } }
个人认为flash虽然对策略文件很重视,但通常只会在通信前读一次,以后不会再读,所以对服务器性能影响不会太大。
----------------------------------------------
补注:
由于这个问题是SGS上(试验性质)。
真实世界中,有人遇到端口连接过多而无法读出策略xml的情况(在广州交流会上听到)
所以这个问题在高并发情况下还挺复杂,并不是简单写个adapter类就能解决。
---------------------------------------------------
20100927补注:
除了策略文件外,想起来还遗漏掉flash开发中一个非常重要的问题,就是本地信任:
http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html
如果使用fd的话,还会出现一种情况,你直接打开是本地文件不信任,但用FD会本地信任,而且还会影响到直接打开——以后打开swf也变成本地信任。原因是有个配置文件在起作用,它记录下曾经用FD打开过的swf,致使它们本地信任(可以访问本地文件)
假设使用Administrator用户,则在Windows XP的目录
C:\Documents and Settings\Administrator\Application Data\Macromedia\Flash Player\#Security\FlashPlayerTrust
的xxx.cfg就是造成本地信任的原因!
如果想模拟真实的用户系统(没有调试过flash),必须把此目录下的文件全部删除!否则不信任对话框不弹出!
详细请参考:
http://www.cc-space.com/?p=401
------------------------------------------------------
assql有更详细的关于策略服务器的搭建方法(使用各种编程语言),可以参考:
http://code.google.com/p/assql/wiki/SecurityInformation
另外socket的策略文件除了可以在843端口读,还可以在连接中的端口读取,可以根据项目需要选择。
--------------------------------------------------------
mina2以后的线程池可能需要手工关闭(或者用System.exit(0))
下面代码仅供参考
import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.log4j.Logger; import org.apache.mina.core.service.SimpleIoProcessorPool; import org.apache.mina.filter.logging.LoggingFilter; import org.apache.mina.transport.socket.nio.NioProcessor; import org.apache.mina.transport.socket.nio.NioSession; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; ... /** * 服务器实例 * @author Administrator * */ public final class PackServer { private static final Logger LOG = Logger.getLogger(PackServer.class); private static ExecutorService executorService = Executors.newCachedThreadPool(); private static SimpleIoProcessorPool<NioSession> pool = new SimpleIoProcessorPool<NioSession>( NioProcessor.class, executorService, Runtime.getRuntime().availableProcessors() + 1); private static NioSocketAcceptor acceptor = new NioSocketAcceptor(pool); public static void startSever() throws IOException { acceptor.setHandler(new PackServerHandler()); acceptor.setReuseAddress(true); acceptor.bind(new InetSocketAddress(GlobalConfig.DEFAULT_PORT)); LOG.info("Listening on port " + GlobalConfig.DEFAULT_PORT); } public static void stopServer() { if (acceptor != null) { //关闭线程池,否则无法退出 executorService.shutdown(); //关闭服务器路由总开关 acceptor.unbind(); acceptor.dispose(); } } // --------------------------------------------- private PackServer() { } }
-----------------------------------------------
这里有个jboss netty版的flash策略服务器实现: