众所周知Flash的功能比较强大,但是有两个功能到目前为止始终无法实现
1是swf的点对点连接
2是swf读写操作文件
别想了,靠Flash本身是不可能的,虽然有传说中的MMSave();等一些隐藏函数,但是毕竟这些未公开的函数了解的人并不多,用起来也不方便。
那么究竟有没有其他办法可以扶助实现呢?答案当然是肯定的。在这里我们需要请出当前最热的两门名副其实的编程语言C++/Java,靠他们来实现你要实现的功能吧。
在这里我用Java举例。
首先我们必须了解 AS如何让Java做事?对于双方来讲唯一应用性最高的途径就是Socket了。
AS1->XMLSocket->send()->Java.Socket->InputStream
AS2->XMLSocket->send()->Java.Socket->InputStream
AS3->Socket->writh()->flush()->Java.Socket->InputStream
as1&2只支持以字符串形式发送socket,而as3支持真正意义上的流,在这里为了兼容和教学简单,我均以字符串形式来实现相互间的通信。
1->AS与Java最基本的通信
1.1 简单Java服务器
首先我们来做最简单的单线程Java服务器
CODE:
import java.io.*;
import java.net.*;
public class Server extends ServerSocket{
//服务端口号常量(as要求必须大于1024,小于65535)
private static final int SERVER_PORT = 10086;
//申明流的空间
private Socket client;
private BufferedReader in;
private PrintWriter out;
private String src;
//构造函数
public Server() throws IOException{
super(SERVER_PORT);
//监听连接,初始流在进来后读取前,和写入后发送前所存放的空间
Socket socket = accept();
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
out = new PrintWriter(client.getOutputStream(),true);
//循环等待读取信息
while(true){
//读取行信息,注意是以换行符结束的
src = in.readLine();
//如果发送"close",就跳出循环(断开连接)
if(src.equals("close")){
break;
}
//在收到的信息前加是标识并发回(注意结尾加"/0",这是as的XMLSocket读入每条信息的条件.as3的Socket不需要)
out.println("rev: "+src+"/0");
//以上out.println("xx")相当于out.write("xx");out.flush();的执行效果
System.out.println("msg is "+src)
}
//关闭连接
close();
}
//入口函数...
public static void main (String[] args) throws IOException{
new Server();
}
}
import java.net.*;
public class Server extends ServerSocket{
//服务端口号常量(as要求必须大于1024,小于65535)
private static final int SERVER_PORT = 10086;
//申明流的空间
private Socket client;
private BufferedReader in;
private PrintWriter out;
private String src;
//构造函数
public Server() throws IOException{
super(SERVER_PORT);
//监听连接,初始流在进来后读取前,和写入后发送前所存放的空间
Socket socket = accept();
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
out = new PrintWriter(client.getOutputStream(),true);
//循环等待读取信息
while(true){
//读取行信息,注意是以换行符结束的
src = in.readLine();
//如果发送"close",就跳出循环(断开连接)
if(src.equals("close")){
break;
}
//在收到的信息前加是标识并发回(注意结尾加"/0",这是as的XMLSocket读入每条信息的条件.as3的Socket不需要)
out.println("rev: "+src+"/0");
//以上out.println("xx")相当于out.write("xx");out.flush();的执行效果
System.out.println("msg is "+src)
}
//关闭连接
close();
}
//入口函数...
public static void main (String[] args) throws IOException{
new Server();
}
}
这样一个Java的服务器就建好了,Java是同步的事件的驱动是等待的,这个AS是不同的,所以方法对与as区别还是比较大的,具体功能已经注释的非常清楚。至于具体Java的特性我不是专业的,为了对读者负责我就不做详细介绍了,有兴趣的朋友可以查阅Java相关书籍。
接下来呢就是AS的访问了:
1.2 AS1 与 Java 通信
Action Script 1
CODE:
var SERVER_PORT = 10086;
var SERVER_IP = "127.0.0.1";
var conn = new XMLSocket();
conn.connect(SERVER_IP,SERVER_PORT);
conn.onConnect = socketConnect;
conn.onData = socketData;
function socketConnect(success){
trace("connect"+success);
if(success){
this.send("hello world/r");
}
}
function socketData(src){
trace(src);
}
var SERVER_IP = "127.0.0.1";
var conn = new XMLSocket();
conn.connect(SERVER_IP,SERVER_PORT);
conn.onConnect = socketConnect;
conn.onData = socketData;
function socketConnect(success){
trace("connect"+success);
if(success){
this.send("hello world/r");
}
}
function socketData(src){
trace(src);
}
接触了相对生疏的Java后现在回到AS,感觉就是爽,嘿嘿
前三行定义了三个变量,由于as1没有强制类型,所以千万别加上类型修饰符啊,反而会出错,请注意。
conn.connect(SERVER_IP,SERVER_PORT);连接到socket的服务器。
如果连接成功 Java中的 ServerSocket.accept();会返回一个对象,并向下执行。
as的连接成功呢则触发了socketConnect(success)的事件,并且把true的参数传如函数。当然连不到就是false了。
成功后就会发送一个"hello world/r"的字符串,"/r"是回车符,因为Java里我用的是readLine();所以需要看到行的结束。
当有数据进来的时候呢就会出发socketData函数了,这里把信息输出。
好了用as1的朋友到此已经成功与Java程序通讯了。
1.3 AS2 与 Java 通信
Action Script 2
CODE:
var SERVER_PORT:Number = 10086;
var SERVER_IP:String = "127.0.0.1";
var conn:XMLSocket = new XMLSocket();
conn.connect(SERVER_IP,SERVER_PORT);
conn.onConnect = socketConnect;
conn.onData = socketData;
function socketConnect(success:Boolean){
trace("connect"+success);
if(success){
this.send("hello world/r");
}
}
function socketData(src:String){
trace(src);
}
var SERVER_IP:String = "127.0.0.1";
var conn:XMLSocket = new XMLSocket();
conn.connect(SERVER_IP,SERVER_PORT);
conn.onConnect = socketConnect;
conn.onData = socketData;
function socketConnect(success:Boolean){
trace("connect"+success);
if(success){
this.send("hello world/r");
}
}
function socketData(src:String){
trace(src);
}
以上是fla版,和as1唯一的区别就是有类型定义,这样做无论是时间执行效率还是空间执行效率都会有明显提高。
接下去看看真正的Action Script 2代码,将以下代码保存成Socket.as文件,和fla文件放在一起。
CODE:
class Socket extends XMLSocket{
public function Socket(){
super();
}
public function onConnect(success:Boolean){
trace("connect"+success);
if (success){
this.send("hello world/r");
}
}
public function onData(src:String){
trace(src);
}
}
public function Socket(){
super();
}
public function onConnect(success:Boolean){
trace("connect"+success);
if (success){
this.send("hello world/r");
}
}
public function onData(src:String){
trace(src);
}
}
fla里在帧上写
CODE:
var SERVER_PORT:Number = 10086;
var SERVER_IP:String = "127.0.0.1";
var conn:Socket = new Socket();
conn.connect(SERVER_IP,SERVER_PORT);
var SERVER_IP:String = "127.0.0.1";
var conn:Socket = new Socket();
conn.connect(SERVER_IP,SERVER_PORT);
这才是真正的as2,当然你可以更好的修改Socket,让他符合Server的要求,比如在类里加上一个常量,并写入符合Server要求的方法。
CODE:
public var msg:String = "";
public function write(src){
msg += src;
}
public function flush(){
this.send(msg+"/r");
msg = "";
}
public function write(src){
msg += src;
}
public function flush(){
this.send(msg+"/r");
msg = "";
}
这样在fla里发送的方式就改为
CODE:
conn.write("hello world");
conn.flush();
conn.flush();
这样是不是又规范又符合Java的要求了呢?我只是举是一个简单例子,你可以按要求自己再修改。
1。4 AS3 与 Java 通信
Action Script 3
最后是传说中的as3了,这里我们用Socket中的writeUTFBytes();来写字符串。首先来看fla版的
CODE:
var SERVER_PORT:Number = 10086;
var SERVER_IP:String = "127.0.0.1";
var conn:Socket = new Socket(SERVER_IP,SERVER_PORT);
conn.addEventListener("connect",socketConnect);
conn.addEventListener("socketData",socketData);
function socketConnect(event:Event){
event.target.writeUTFBytes("hello world");
event.target.writeByte(10);
event.target.flush();
}
function socketData(event:ProgressEvent){
trace(event.target.readUTFBytes(event.target.bytesAvailable));
}
var SERVER_IP:String = "127.0.0.1";
var conn:Socket = new Socket(SERVER_IP,SERVER_PORT);
conn.addEventListener("connect",socketConnect);
conn.addEventListener("socketData",socketData);
function socketConnect(event:Event){
event.target.writeUTFBytes("hello world");
event.target.writeByte(10);
event.target.flush();
}
function socketData(event:ProgressEvent){
trace(event.target.readUTFBytes(event.target.bytesAvailable));
}
看看这个fla版本的是不是在代码上思路更加清晰呢?所有事件已经全部改为监听的方式,并且所有事件将把事件作为参数传入函数。
bytesAvailable为字节长度,而readUTFBytes的参数是从当前指向的位置读取到参数位置,这样写就是读完。
在as2里,如果在事件触发的函数里写this指向的是触发事件的实例,而as3永远指向所在类的实力,触发事件的实例被记录在传入事件参数的target对象中。
as3更加注重的是oop,在fla里已经体现出来了。接下来就来看看DocmentClass的方法
CODE:
package{class Run{
private var SERVER_PORT:Number = 10086;
private var SERVER_IP:String = "127.0.0.1";
public function Run(){
//在这里写代码并在flash里设置该类为文档类,和直接写上帧上其实没有区别,入口函数
new ClientSocket(SERVER_IP,SERVER_PORT);
}
}}
private var SERVER_PORT:Number = 10086;
private var SERVER_IP:String = "127.0.0.1";
public function Run(){
//在这里写代码并在flash里设置该类为文档类,和直接写上帧上其实没有区别,入口函数
new ClientSocket(SERVER_IP,SERVER_PORT);
}
}}
当然还有一个ClientSocket.as的文件放这个socket客户端类
CODE:
package {
import flash.net.Socket;
import flash.events.ProgressEvent;
import flash.events.Event;
import flash.events.IOErrorEvent;
public class ClientSocket extends Socket {
public function ClientSocket(ip:String,port:uint) {
super(ip,port);
addEventListener("cennect",socketConnect);
addEventListener("socketData",socketData);
addEventListener("ioError",ioError);
}
public function send(src:String) {
writeUTFBytes(src);
writeByte(10);
flush();
}
private function socketConnect(event:Event) {
send("hello world");
}
private function socketData(event:ProgressEvent) {
trace(readUTFBytes(bytesAvailable));
}
private function ioError(event:IOErrorEvent) {
trace("connect error");
}
}
}
import flash.net.Socket;
import flash.events.ProgressEvent;
import flash.events.Event;
import flash.events.IOErrorEvent;
public class ClientSocket extends Socket {
public function ClientSocket(ip:String,port:uint) {
super(ip,port);
addEventListener("cennect",socketConnect);
addEventListener("socketData",socketData);
addEventListener("ioError",ioError);
}
public function send(src:String) {
writeUTFBytes(src);
writeByte(10);
flush();
}
private function socketConnect(event:Event) {
send("hello world");
}
private function socketData(event:ProgressEvent) {
trace(readUTFBytes(bytesAvailable));
}
private function ioError(event:IOErrorEvent) {
trace("connect error");
}
}
}
入口函数创建了一个客户端的对象,而具体的类的内部构造如上代码所示:
首先创建父类构造函数,再为自己添加监听,当连接时执行socketConnect();发送字符串,注意writeByte(10)是换行符。
在as3的socket类里还有其他的事件,这里因为教学原因所以没有过多的举例,相关可以查阅socket的帮助
close 在服务器关闭套接字连接时调度。
connect 在建立网络连接后调度。(这个监听在教程里已经实现)
deactivate Flash Player 失去操作系统焦点并变为非活动状态时调度。
ioError 在出现输入/输出错误并导致发送或加载操作失败时调度。(以前是在onConnect传入false,而这里是直接引发ioError事件,更规范)
securityError 若对 Socket.connect() 的调用尝试连接到调用方安全沙箱外部的服务器或端口号低于 1024 的端口,则进行调度。
socketData 在套接字接收到数据后调度。 (这个监听在教程里已经实现)
2 Java多线程服务器
基本的通信做到以后就是修改代码增加功能了,这里我们必须让Java能支持多个线程的连接,这才是服务器呀。
CODE:
import java.io.*;
import java.net.*;
public class Server extends ServerSocket{
//服务端口号常量(as要求必须大于1024,小于65535)
private static final int SERVER_PORT = 10086;
//构造函数
public Server() throws IOException{
super(SERVER_PORT);
//监听新连接,为每个连接分配一个线程,将新的连接传入独立线程
Socket socket = accept();
new SocketThread(socket);
}
//入口函数...
public static void main (String[] args) throws IOException{
new Server();
}
//建一个类,该类属于独立的线程,他的每个实例都会在独立的线程里运行
class SocketThread extends Thread{
//申明流的空间在独立线程里了,因为他属于传送时所需,主线程只是监听有没有新连接
private Socket client;
private BufferedReader in;
private PrintWriter out;
private String src;
//构造函数
public SocketThread(Socket socket) throws IOException{
//初始客户端的连接为该线程传入的连接
client = socket;
//初始流在进来后读取前,和写入后发送前所存放的空间
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
out = new PrintWriter(client.getOutputStream(),true);
//准备工作完毕,启动该线程
start();
}
public void run(){try {
//循环等待读取信息
while(true){
//读取行信息,注意是以换行符结束的
src = in.readLine();
//如果发送"close",就跳出循环(断开连接)
if(src.equals("close")){
break;
}
//在收到的信息前加是标识并发回(注意结尾加"/0",这是as的XMLSocket读入每条信息的条件.as3的Socket不需要)
out.println("rev: "+src+"/0");
//以上out.println("xx")相当于out.write("xx");out.flush();的执行效果
System.out.println("msg is "+src)
}
//关闭连接
close();
}catch(IOException e){
}catch(NullPointerException e){
System.out.println("client closed");
}}
}
}
import java.net.*;
public class Server extends ServerSocket{
//服务端口号常量(as要求必须大于1024,小于65535)
private static final int SERVER_PORT = 10086;
//构造函数
public Server() throws IOException{
super(SERVER_PORT);
//监听新连接,为每个连接分配一个线程,将新的连接传入独立线程
Socket socket = accept();
new SocketThread(socket);
}
//入口函数...
public static void main (String[] args) throws IOException{
new Server();
}
//建一个类,该类属于独立的线程,他的每个实例都会在独立的线程里运行
class SocketThread extends Thread{
//申明流的空间在独立线程里了,因为他属于传送时所需,主线程只是监听有没有新连接
private Socket client;
private BufferedReader in;
private PrintWriter out;
private String src;
//构造函数
public SocketThread(Socket socket) throws IOException{
//初始客户端的连接为该线程传入的连接
client = socket;
//初始流在进来后读取前,和写入后发送前所存放的空间
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
out = new PrintWriter(client.getOutputStream(),true);
//准备工作完毕,启动该线程
start();
}
public void run(){try {
//循环等待读取信息
while(true){
//读取行信息,注意是以换行符结束的
src = in.readLine();
//如果发送"close",就跳出循环(断开连接)
if(src.equals("close")){
break;
}
//在收到的信息前加是标识并发回(注意结尾加"/0",这是as的XMLSocket读入每条信息的条件.as3的Socket不需要)
out.println("rev: "+src+"/0");
//以上out.println("xx")相当于out.write("xx");out.flush();的执行效果
System.out.println("msg is "+src)
}
//关闭连接
close();
}catch(IOException e){
}catch(NullPointerException e){
System.out.println("client closed");
}}
}
}
修改后的Java主线程监听是否有新的连接,如果有就把这个连接分配到新的线程,让他去监听消息,而自己继续监听连接,这样的思路是不是很清晰呢?
仔细看看其实不是很难,大部分的代码都和前面的一样,只是循环监听信息的代码被放到的独立的线程里面。
关于多线程和一些Java的技术问题,为了对大家负责,我依然谨慎言语,以免误导大家。
现在你可以启动多个as对Java进行连接,并且他们之间互不干扰。一个线程对应一个连接,底层的工作非常透明,管理非常容易。
3 AS 通过Java数据转发实现P2P通信
谈到这里,其实要实现这步就非常容易了,原理上只要将本来Java收到后返回的信息,发到别人这里去,就可以了。原理如下所示
flash_1 -> Java -> flash_2
flash_2 -> Java -> flash_3
flash_3 -> Java -> flash_1
这样任何两个flash之间都能通过java转发了,现在只有一个问题,就是Java怎么知道我的信息要发给谁?其实很简单我们在每个连接连入的时候发送一条注册信息,让Java知道我的名字,而别人只要在字符串前加上我的名字就可以了。
那么在Java里 我们需要两个功能
1个是增加和删除自己的标识
2是识别字符串中哪些是名字,哪些是信息
这里我们用到方法是<空格>
"r s1";这样的一条信息过去,Java服务器要处理为该连接添加一个标识s1,而在这以后任何客户端只要发送
"s1 Hello";就会把"Hello"这个字符串发送给标识为s1的客户端,这样两个flash之间就完全实现了数据互通
"u s1";当离开的时候可以用这个代码来删除s1的标识符
为了简单和安全,我们可以暂设为标识必须是两位,以字母开头,这样的组合已经超过千种,绝对够用了。
这样只要判断空个所出现的位置即可,如果是第二位就是注册或卸载标识,如果是第三位就是字符转发了,如果都没有,那就是错误信息。
把上面的Run里的out.println("rev: "+src+"/0");改成以下信息就可以判断信息是注册还是卸载了
CODE:
//屏蔽所有长度小于4的信息,不做处理.
if(line.length()<4){
out.write("error: length<4");
out.flush();
line = in.readLine();
continue;
}
//命令字符
if (line.charAt(1)==' '){
//相应客户端命令请求
name = line.substring(2);
switch (line.charAt(0)) {
case 'r':
//注册客户端
if (!registered && name.length()==2){
//为自己加一个ID
Server.clientID.put(name,client);
registered = true;
//返回注册成功
out.write("registeration successed");
out.flush();
}
break;
case 'u':
//删除客户端
Server.clientID.remove(name);
registered = false;
break;
}
}else if (line.charAt(2)==' '){
//这里是转发代码;
}
if(line.length()<4){
out.write("error: length<4");
out.flush();
line = in.readLine();
continue;
}
//命令字符
if (line.charAt(1)==' '){
//相应客户端命令请求
name = line.substring(2);
switch (line.charAt(0)) {
case 'r':
//注册客户端
if (!registered && name.length()==2){
//为自己加一个ID
Server.clientID.put(name,client);
registered = true;
//返回注册成功
out.write("registeration successed");
out.flush();
}
break;
case 'u':
//删除客户端
Server.clientID.remove(name);
registered = false;
break;
}
}else if (line.charAt(2)==' '){
//这里是转发代码;
}
当然这里用的一些API在前面还要声明过。在线程类里增加两条申明
CODE:
private String name;
private boolean registered;
private boolean registered;
构造函数里
CODE:
registered = false;
当然最重要的是还要在Server的主线程里声明一个放ID的容器
CODE:
public static HashMap clientID = new HashMap();
这样每个连接都有自己的ID了
现在要做的是在转发了,在上面的转发代码的地方写
CODE:
send(line.substring(0,2),line.substring(3));
当然send函数还没有定义呢,现在定义
CODE:
public boolean send(String id, String src) throws IOException {
//读取标识的地址
Socket socket = (Socket)Server.clientID.get(id);
if (socket != null){
out = new PrintWriter(socket.getOutputStream(), true);
out.write(src);
out.flush();
return true;
}else{
return false;
}
}
//读取标识的地址
Socket socket = (Socket)Server.clientID.get(id);
if (socket != null){
out = new PrintWriter(socket.getOutputStream(), true);
out.write(src);
out.flush();
return true;
}else{
return false;
}
}
这里我加入了判断id是否存在,当不存在就没有任何操作,并且将操作结果成功与否返回,在send的时候就可以分类成功或不成功分别做什么了。
马上来开两个AS吧,用到我们刚才第一章节里自己封装的as2,把加载成功的地方改成send("r c1/r");和send("r c2/r");
Action Script 2
CODE:
//file1
var SERVER_PORT:Number = 10086;
var SERVER_IP:String = "127.0.0.1";
conn:Socket = new Socket();
conn.connect(SERVER_IP,SERVER_PORT);
conn.onConnect = function (success:Boolean){
if (success){
write("r c1");
flush();
}
}
//file2
var SERVER_PORT:Number = 10086;
var SERVER_IP:String = "127.0.0.1";
conn:Socket = new Socket();
conn.connect(SERVER_IP,SERVER_PORT);
conn.onConnect = function (success:Boolean){
if (success){
write("c2 hello");
flush();
}
}
var SERVER_PORT:Number = 10086;
var SERVER_IP:String = "127.0.0.1";
conn:Socket = new Socket();
conn.connect(SERVER_IP,SERVER_PORT);
conn.onConnect = function (success:Boolean){
if (success){
write("r c1");
flush();
}
}
//file2
var SERVER_PORT:Number = 10086;
var SERVER_IP:String = "127.0.0.1";
conn:Socket = new Socket();
conn.connect(SERVER_IP,SERVER_PORT);
conn.onConnect = function (success:Boolean){
if (success){
write("c2 hello");
flush();
}
}
依次运行file1,file2看看,file2运行的时候是不是成功收到了一个hello呢?嘿嘿。
这样做虽然底层还是没有实现P2P,但是效果已经达到,如果还是觉得不够理想,那么可以 每个客户端都绑定一个Java的ServerSocket,负责收信息,并转发给同绑定Flash,这样虽然没有实现swf的p2p,但是在 底层已经p2p了。
[完毕... 答疑和下载 在11楼]
<1 AS与Java最基本的通信> .. OK
<2 Java多线程服务器> .. OK
<3 AS 通过Java数据转发实现P2P通信> .. OK
<4 将P2P方式用类封装资源免费下载> .. OK