as3 Socket
Socket 套接字连接允许Flash播放器通过指定的端口与服务器通信,socket连接与其他通信技术
最大的不同是socket连接在数据传输完成后不会自动关闭。
当socket连接创建后,连接会一直保持,直到客户端(Flash播放器)和服务端主动关闭,因此
服务器可在任何时间不用客户端请求即可发送数据给客户端。
Socket连接被普遍用于创建多用户应用程序,比如说一个在线聊天室,它有一个服务端程序和
无数个Flash客户端组成。每次客户端发送消息给服务器,服务器检测那些用户可以收到这些消
息并把消息传给指定客户端,这种情况下接收客户端并没有提前请求数据而是通过服务器主动
推送数据的。当客户端关闭时,服务端提示其他客户端某客户端已离线。
Flash播放器提供了两种类型的socket连接。一种是早期版本就有的XMLSocket,Flash播放器9增
加了二进制socket连接。
使用flash.net.XMLSocket类创建XML数据格式的socket连接,使用flash.net.Socket类创建二进制数
据格式socket连接。
XMLsocket连接以XML数据报交换数据,二进制socket连接是ActionScript3.0新增的功能,相比
之下更低级,但功能很强大,几乎可以连接任意类型的socket服务端程序。例如二进制sockets可
连接邮件服务端程序(POP3,SMTP,和IMAP),新闻服务器(NNTP),聊天室服务器或远程桌面
VNC服务器(RFB)。
不管是哪种类型的socket连接,其通信方式都是异步的,也就是说你不能直接从socket连接中
读取数据,而是通过事件处理函数进行读取处理。
24.1.
连接Socket
服务器
问题
我想连接socket服务器
解决办法
使用Socket.connect()或XMLSocket.connect()方法建立连接并监听connect事件确定连接是否建
立。
讨论
要连接socket服务器,首先要知道域名或IP地址,还要知道端口,不管是使用Socket还是
XMLSocket,连接步骤是一样的,都是用connect()方法进行连接,该方法接受两个参数:
host
指定域名或IP地址,如www.example.com或192.168.1.101。
port
数字,指定连接的端口号,必须大于1024,如果小于1024则需服务器提供策略文件允许。
因为是异步通信,connect()方法不会等待结果而是继续执行下面的语句,因此需要注册事件监
听器来获取连接结果。
注册事件监听器必须在调用connect()方法之前,当连接成功时connect事件就会触发,下面的例
子演示连接本机2900端口:
package { import flash.display.Sprite; import flash.events.*; import flash.net.Socket; public class SocketExample extends Sprite { private var socket:Socket; public function SocketExample( ) { socket = new Socket( ); // Add an event listener to be notified when the connection // is made socket.addEventListener( Event.CONNECT, onConnect ); // Connect to the server socket.connect( "localhost", 2900 ); } private function onConnect( event:Event ):void { trace( "The socket is now connected..." ); } } } 如果使用XMLSocket,代码也基本上相同,代码如下: package { import flash.display.Sprite; import flash.events.*; import flash.net.XMLSocket; public class SocketExample extends Sprite { private var socket:XMLSocket; public function SocketExample( ) { socket = new XMLSocket( ); // Add an event listener to be notified when the connection is made socket.addEventListener( Event.CONNECT, onConnect ); // Connect to the server socket.connect( "localhost", 2900 ); } private function onConnect( event:Event ):void { trace( "The xml socket is now connected..." ); } } }
如果连接失败,可能的异常有:runtimeerror,ioError,securityError,
记住,当用socket连接主机时,要遵循FlashPlayer安全沙漏规则:
swf和主机必须在同一个域;
网络上的swf不能连接本地服务器;
本地的swf不能访问任何网络资源;
要允许域名交叉访问或连接低于1024的端口,需要提供cross-domain策略文件。
如果Socket或XMLSocket对象要使用cross-domain策略文件,可通过方法
flash.system.Security.loadPolicyFile()读取:
Security.loadPolicyFile("http://www.rightactionscript.com/crossdomain.xml"); cross-domain策略文件例子: <?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domainpolicy. dtd"> <cross-domain-policy> <allow-access-from domain="*" to-ports="80,110" /> </cross-domain-policy>
24.2.
发送数据
问题
我要发送数据给socket服务器
解决办法
对于Socket对象,是使用write方法(writeByte(),writeUTFBytes(),等等)把数据写入到缓冲区,
再通过flush()方法发送数据,对于XMLSocket对象使用send()方法。
讨论
Socket和XMLSocket类各自定义了不同的APIs来发送数据,首先看一下Socket的API:
当使用Socket对象发送数据时必须先把数据写到缓冲区,Socket类定义了一系列方法用于写数
据,每个方法都是写入不同类型的数据,这些方法是writeBoolean(),writeByte(),writeBytes(),
writeDouble(),writeFloat(),writeInt(),writeMultiByte(),writeObject(),writeShort(),write-
UnsignedInt(),writeUTF(),和writeUTFBytes()。每个方法都接受一种类型的参数,如
writeBoolean()接受布尔型参数,writeByte(),writeDouble(),writeFloat(),writeInt(),writeShort(),
和writeUnsignedInt()接受数字参数,writeObject()方法接受对象参数,writeBytes()方法允许传递
一个ByteArray类型参数以及偏移量和长度,如下面的写法:
socket.writeBytes(byteArray,0,byteArray.length);
writeUTF()和writeUTFBytes()方法写入字符串,每个方法接受一个字符串参数,writeUTFBytes()
方法写入字节形式的字符串,writeUTF()方法写入字节数字。
writeMultiByte()方法也是写入字符串数据,但可以不使用默认字符集,该方法接受两个参数:
字符串和指定字符集编码,下面的例子写入Unicode编码的字符串:
socket.writeMultiByte("example","unicode");
利用Socket对象,完全可以用ActionScript写出一个Telnet和POP客户端,这两个协议都是以ASCII
字符为基础的,例如,连接一个POP服务器后,可用下面的代码执行USER命令:
//POPserversexpectanewline(\n)toexecutetheprecedingcommand.
socket.writeUTFBytes("USERexampleUsername\n");
写入的数据实际上还没发送到服务器上,每个方法都死把数据累积到Socket对象上,例如下面的
四个代码并没有把数据发送出去:
socket.writeByte(1); socket.writeByte(5); socket.writeByte(4); socket.writeByte(8);
当要发送数据时,必须调用flush()方法,flush()方法发送所有的数据并清空缓冲区:
socket.flush();
XMLSocket类发送数据就比较简单了,发送数据的方法为send(),send()方法接受任意类型的数
据类型,它会把参数转换为字符串并发送给服务器,一般这个参数是一个XML对象:
xmlSocket.send(xml);
实际上发送的数据类型是由服务器所决定的,如果服务器接受XML数据,那必须发送XML数
据,如果服务器接受URL-编码数据,则必须发送URL-编码数据。
24.3.
接收数据
问题
我想接收socket服务器发送来的数据
解决办法
对于Socket实例可通过ProgressEvent.SOCKET_DATA事件处理函数中读取数据,可用readByte()
或readInt()方法
对于XMLSocket实例可通data事件处理函数中读取XML数据
讨论
从socket中接收数据的方法取决于你使用socket类型,Socket和XMLSocket都可以接收数据,但是
两者实现方法有些不同,让我们先看看Socket类是如何做的。
正如前面所讲到的,Flash提供的socket通信方式是异步通信,也就是说仅仅创建socket连接并试图
读取数据这是不可能的,read方法读取数据时并不会等待数据传输过来而立即返回,如果数据还
没准备好而去读取数据会导致异常。
当数据准备好了时,socketData事件就会触发,通过注册该事件处理函数,当数据发送过来时就
会触发,因此可通过该处理函数读取数据。
为了读取服务器发送来的数据,Socket类提供了一系列read方法来读取不同类型的数据,例如
通过readByte()方法读取一个字节,readUnsignedInt()方法读取一个无符号整数,具体看下面的
表格:
Table
24-1.
Socket
read
methods
for
various
datatypes
还有两个方法没有在上面的表格里,它们是readBytes()和readUTFBytes(),readBytes()方法没有
返回值,它接受三个参数:
bytes
一个flash.util.ByteArray实例填充读取的数据
offset
一个uint值指定读取数据的偏移量,默认为0
length
一个uint值表示读取的字节数,默认为0,表示所有的数据
readUTFBytes()方法只接受一个参数,表示读取的UTF-8字节数,返回一个字符串。
下面的例子代码连接一个socket服务器并读取和显示服务器发送的数据:
package { import flash.display.Sprite; import flash.events.ProgressEvent; import flash.net.Socket; public class SocketExample extends Sprite { private var socket:Socket; public function SocketExample( ) { 方法:返回类型描述字节数 readBoolean():Boolean 读取布尔型数据1 readByte():int 读取一个字节数据1 readDouble():Number 读取IEEE 754 双精度浮点数8 readFloat():Number 读取IEEE 754 单精度浮点数4 readInt():in 读取32 位整数4 readObject():* 读取AMF 格式对象N readShort():int 读取16 位整数2 readUnsignedByte():uint 读取无符号字节1 readUnsignedInt():uint 读取无符号32 位整数4 readUnsignedShort():uint 读取无符号16 位整数2 readUTF():String 读取UTF-8 字符串n socket = new Socket( ); // Listen for when data is received from the socket server socket.addEventListener( ProgressEvent.SOCKET_DATA, onSocketData ); // Connect to the server socket.connect( "localhost", 2900 ); } private function onSocketData( event:ProgressEvent ):void { trace( "Socket received " + socket.bytesAvailable + " byte(s) of data:" ); // Loop over all of the received data, and only read a byte if there // is one available while ( socket.bytesAvailable ) { // Read a byte from the socket and display it var data:int = socket.readByte( ); trace( data ); } } } }
上面的例子中,如果socket服务器发送"Hello"字符串,则输出:
//Socket接收5字节的数据:
72
101
108
108
111
Socket对象接收的数据都是ASCII编码的文本,我们可以用readUTFBytes()方法重新构造字符串,
readUTFBytes()方法需要知道有多少个字节需要转换,用bytesAvailable属性指定字节数:
varstring:String=socket.readUTFBytes(socket.bytesAvailable);
XMLSocket类和Socket类基本类似,两者都要注册监听器检测数据是否接收完毕,但是两者读取
数据的方式是不同的。
当数据准备好时XMLSocket实例发出data事件,事件类型为flash.events.DataEvent.DATA,它
其中的data属性包含接收过来的数据。
从服务器返回的数据都是原始的数据,如果你希望以XML进行处理则需先把数据转换为XML
实例。
下面的代码例子用XMLSocket连接本地服务器,端口为2900,连接成功后,发送<test>消息给服
务器,onData事件处理函数处理服务器返回的数据,返回数据为<response><test
success='true'/></response>,注意到该事件的data属性的内容只是字符串数据,需用XML构造器
转换位XML实例,最后通过E4X语法输出XML:
package { import flash.display.Sprite; import flash.events.Event; import flash.events.DataEvent; import flash.net.XMLSocket; public class SocketExample extends Sprite { private var xmlSocket:XMLSocket; public function SocketExample( ) { xmlSocket = new XMLSocket( ); // Connect listener to send a message to the server // after we make a successful connection xmlSocket.addEventListener( Event.CONNECT, onConnect ); // Listen for when data is received from the socket server xmlSocket.addEventListener( DataEvent.DATA, onData ); // Connect to the server xmlSocket.connect( "localhost", 2900 ); } private function onConnect( event:Event ):void { xmlSocket.send( "<test/>" ); } private function onData( event:DataEvent ):void { // The raw string returned from the server. // It might look something like this: // <response><test success='true'/></response> trace( event.data ); // Convert the string into XML var response:XML = new XML( event.data ); // Using E4X, access the success attribute of the "test" // element node in the response. // Output: true trace( response.test.@success ); } } }
24.4.
与socket
服务器的状态信号交换
问题
我想与服务器进行信号交换以便知道读取的数据内容是什么以及如何进行处理。
解决办法
创建不同的常量来表示协议状态,用这些常量映射与之对应的状态处理函数,在socketData事件
处理函数中通过状态映射表调用对应的状态处理函数。
讨论
连接socket服务器需要经过一个完整的信号交换,通常服务器初始化后发送数据给客户端,客户
端进行回应,服务器再回应,这个完整的处理过程一直重复直到信号交换完成,这个连接才算
建好了。
就像典型的HTTP连接一样,HTTP协议定义了一系列状态代表不同的传输数据,我们现在建立
的socket连接也是最原始的无状态的,同样需要建立各种状态以及维护各种状态的函数功能。
解决办法就是创建不同的状态常量代表服务器发送的不同类型的内容,每个状态关联不同的状
态处理函数。
连接一个socket服务器需要的信号交换可能有:
当客户端连接到服务器时,服务器立即回应,发送一个整数代表服务器所支持的协议版本。
客户端返回一个整数表示可以通信的协议版本。
服务器发送8自己的认证询问。
客户端发送认证给服务器。
如果客户端的回应不合法或协议不一致或不是规定的信息则关闭连接。
要实现这个信号交换过程,首先要创建代表不同状态的常量,例如建立如下常量:
publicconstDETERMINE_VERSION:int=0;
publicconstRECEIVE_CHALLENGE:int=1;
publicconstNORMAL:int=2;
常量设置为什么值并不重要,重要的是这些常量值都应该不同,下一步就是创建代表不同状态
的处理函数,如创建readVersion(),readChallenge(),和readNormalProtocol(),接着要把这些函
数与状态常量相关联,如下面的代码:
stateMap=newObject();
stateMap[DETERMINE_VERSION]=readVersion;
stateMap[RECEIVE_CHALLENGE]=readChallenge;
stateMap[NORMAL]=readNormalProtocol;
最后就是在socketData事件处理函数中根据当前的状态调用相应的状态处理函数了,创建
currentState变量代表当前状态,根据其值调用对应的处理函数:
varprocessFunc:Function=stateMap[currentState];
processFunc();//Invoketheappropriateprocessingfunction
完整代码如下:
package { import flash.display.Sprite; import flash.events.ProgressEvent; import flash.net.Socket; import flash.utils.ByteArray; public class SocketExample extends Sprite { // The state constants to describe the protocol public const DETERMINE_VERSION:int = 0; public const RECEIVE_CHALLENGE:int = 1; public const NORMAL:int = 2; // Maps a state to a processing function private var stateMap:Object; // Keeps track of the current protocol state private var currentState:int; private var socket:Socket; public function SocketExample( ) { // Initialzes the states map stateMap = new Object( ); stateMap[ DETERMINE_VERSION ] = readVersion; stateMap[ RECEIVE_CHALLENGE ] = readChallenge; stateMap[ NORMAL ] = readNormalProtocol; // Initialze the current state currentState = DETERMINE_VERSION; // Create and connect the socket socket = new Socket( ); socket.addEventListener( ProgressEvent.SOCKET_DATA, onSocketData ); socket.connect( "localhost", 2900 ); } private function onSocketData( event:ProgressEvent ):void { // Look up the processing function based on the current state var processFunc:Function = stateMap[ currentState ]; processFunc( ); } private function readVersion( ):void { // Step 1 - read the version from the server var version:int = socket.readInt( ); // Once the version is read, the next state is receiving // the challenge from the server currentState = RECEIVE_CHALLENGE; // Step 2 - write the version back to the server socket.writeInt( version ); socket.flush( ); } private function readChallenge( ):void { // Step 3 - read the 8 byte challenge into a byte array var bytes:ByteArray = new ByteArray( ); socket.readBytes( bytes, 0, 8 ); // After the challenge is received, the next state is // the normal protocol operation currentState = NORMAL; // Step 4 - write the bytes back to the server socket.writeBytes( bytes ); socket.flush( ); } private function readNormalProtocol( ):void { // Step 5 - process the normal socket messages here now that // that handshaking process is complete } } }
24.5.
断开与Socket
服务器的连接
问题
我想断开与服务器的连接或通知服务器断开
解决办法
调用Socket.close()或XMLSocket.close()方法关闭连接,或者监听close事件
讨论
当我们连接一个socket连接后,用完时应该关闭连接,释放资源,如果不关闭这会导致占用端口
使其它连接无法建立。
Socket和XMLSocket关闭socket连接的方法是一样的,都为close()方法:
//AssumesocketisaconnectedSocketinstance
socket.close();//Disconnectfromtheserver
调用XMLSocket实例的方法:
//AssumexmlSocketisaconnectedXMLSocketinstance
xmlSocket.close();//Disconnectfromtheserver
close()方法通知服务器客户端已经断开连接,如果服务器先关闭呢,那客户端该怎么知道呢,
可通过监听客户端的close事件,注册事件类型为Event.CLOSE:
varsocket:Socket=newSocket();
socket.addEventListener(Event.CLOSE,onClose);
24.6.
处理Socket
异常
问题
使用socket如何处理可能引发的异常
解决办法
使用try/catch处理I/O和(EOF)异常
讨论
Socket和XMLSocket类处理异常的方式基本类似,当调用connect()方法时,如遇到下面的情况
Socket和XMLSocket对象都抛出SecurityError:
.swf被认为本地非安全
端口号高于655535.
当调用send()(XMLSocket)或flush()(Socket),如果没有事先连接好,则抛出IOError,可先通
过socket对象的connected属性是否为true,再调用send()或flush()。如下面的代码:
if ( socket.connected ) { try { socket.flush( ); } catch( error:IOError ) { logInstance.write( "socket.flush error\n" + error ); } } else { connectToSocketServer( ); //进行socket连接 }
Socket所有的read方法都可能抛出EOFError和IOError,但没有数据可读而读取时引发EOF异
常,当socket已关闭而去读取时引发I/O 异常。