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策略服务器实现: