既然选择了原生态的SOCKET,那么首先要面对的问题就是数据的打包/解包问题。
对于服务器端,我们使用struct作为数据打包/解包工具,并对struct作了简单了解(相关资料:Python模块学习 ---- struct 数据格式转换)。
在Flash客户端,我们数据处理使用的是ByteArray类。
简单统计一下,我们在开发过程中可能用到的数据类型,在struct中的表现手法,以及相对于的Python数据类型和Flash数据类型,还有Flash中数据解包读取方式
数据类型 | Python | Flash | Flash读取方法 | struct标识符 | 自定义标识符 |
---|---|---|---|---|---|
字符串 | String | String | readUTFBytes(len:uint):String | *s | s |
16bit有符合整型 | int | int | readShort():int | h | h |
16bit无符号整型 | int | uint | readUnsignedShort():uint | H | H |
32bit有符号整型 | int | int | readInt():int | i | i |
32bit无符号整型 | int | uint | readUnsignedInt():uint | I | I |
64bit浮点型 | float | Number | readFloat():Number | d | d |
需要提一下的是,所有的字符串采用的是UTF-8编码。
数据包定义
数据长度+协议号+数据段
其中:
数据长度:长度为4字节的无符号整形数据,用来指定数据段长度
协议号:长度为2字节的ushort数字
数据段:用来存放需要传递的二进制数据
对于Python服务器端,我们发现struct使用起来还是挺不方便的,特别是字符串和包格式定义符。所以对它进行简单封装
import struct
import struct
datafmt = {
101 : 'sH', # redirect address
102 : 's', # server message
1001 : 'ss', # login : name|password
2001 : 'H', # login result: 0 success or fail
2002 : 'Is', # user enter : user id | user name
2003 : 'HH', # animal load : type | animal model id
# type : 1 2 3 4 5 6 7 8
# animal : 象 狮 虎 豹 犬 狼 猫 鼠
2004 : 'Hsdsis',# Test
}
def pack(comid, data):
print comid, data
global datafmt
if comid not in datafmt:
print cmd_id, ' not in ', datafmt
return ''
fmtStr = datafmt[comid]
fmtStrRes = []
idx = 0
fixString ={}
for k in fmtStr:
# 计算字符串长度
if k == 's':
_strLength = len(data[idx])
fixString[idx] = _strLength
fmtStrRes.append('H'+ str(_strLength) +'s')
else:
fmtStrRes.append(k)
idx = idx + 1
# 指定BIG_ENDIAN模式
fmt = '>H' + ''.join(fmtStrRes)
# 插入字符串长度值
if len(fixString)>0:
idx = 0
for k,v in fixString.items():
data.insert(k+idx,v)
idx = idx + 1
# 加入协议号
data.insert(0, comid)
# 打包结果
result = struct.pack(fmt, *data)
# 返回结果,加上数据长度
return struct.pack('>I', len(result) - 2) + result
def unpack(comid, data):
print 'unpack----------', len(data)
global datafmt
if comid not in datafmt:
print cmd_id, ' not in ', datafmt
return None
fmtStr = datafmt[comid]
fmtStrRes = []
_data_index = 0
for k in fmtStr:
if k == 'h' or k == 'H':
_data_index += 2
fmtStrRes.append(k)
elif k == 'i' or k == 'I':
_data_index += 4
fmtStrRes.append(k)
elif k == 'd':
_data_index += 8
fmtStrRes.append(k)
elif k == 's':
_string_length = data[_data_index:_data_index + 2]
print repr(_string_length)
_strlen, = struct.unpack('>H', _string_length)
print _strlen
data = data[:_data_index] + data[_data_index + 2:];
_data_index += _strlen
fmtStrRes.append(str(_strlen) + 's')
# 指定BIG_ENDIAN模式
fmt = '>' + ''.join(fmtStrRes)
print 'unpack fmt=', fmt
# 获取结果
print 'data.len = ', len(data)
return struct.unpack(fmt, data)
我们定义了pack和unpack两个函数,分别用于打包和解包。相应的测试代码如下:
if __name__=='__main__':
# Test
_pack_result = pack(2004, [1, '123456', 5.0, '789', 1000, '0'])
if(len(_pack_result) > 6):
# 获取数据段长度
_data_len = struct.unpack('>I', _pack_result[:4])[0]
print 'data len = ', _data_len
if(len(_pack_result) >= _data_len + 6):
# 获取协议号
_comid = struct.unpack('>H', _pack_result[4:6])[0]
print 'command id = ', _comid
# 获取数据段
_data = _pack_result[6:_data_len + 6]
# 解包
print unpack(_comid, _data)
# 源数据截除
_pack_result = _pack_result[_data_len + 6:]
print 'left len = ', len(_pack_result)
对于Flash客户端,我们一样需要定义pack和unpack方法
package
{
import flash.utils.ByteArray;
public class NetCommand
{
private static var s_Format:Object =
{
101 : 'sH', // redirect address
102 : 's', // server message
1001 : 'ss', // login : name|password
2001 : 'H', // login result: 0 success or fail
2002 : 'Is', // user enter : user id | user name
2003 : 'HH', // animal load : type | animal model id
// type : 1 2 3 4 5 6 7 8
// animal : 象 狮 虎 豹 犬 狼 猫 鼠
2004 : 'Hsdsis'// Test
}
public static function Pack(comid:uint, raw_data:Array) : ByteArray
{
// 检查协议号
if(! comid in s_Format)
{
throw new Error("出现未知协议号:" + comid);
return null;
}
// 构建数据段
var data : ByteArray = new ByteArray;
for(var i:int = 0; i < s_Format[comid].length; ++i)
{
trace(s_Format[101]);
trace(s_Format[comid]);
var flag:String = s_Format[comid].charAt(i);
if(flag == 'h')
{
data.writeShort(raw_data[i]);
}
else if(flag == 'H')
{
data.writeShort(raw_data[i]);
}
else if(flag == 'i')
{
data.writeInt(raw_data[i]);
}
else if(flag == 'I')
{
data.writeUnsignedInt(raw_data[i]);
}
else if(flag == 'd')
{
data.writeDouble(raw_data[i]);
}
else if(flag == 's')
{
data.writeUTF(raw_data[i]);
}
}
// 构建结果
var pack:ByteArray = new ByteArray;
// 写入数据段长度
pack.writeUnsignedInt(data.length);
// 写入协议号
pack.writeShort(comid);
// 写入数据段
pack.writeBytes(data);
return pack;
}
public static function Unpack(comid:uint, data:ByteArray) : Array
{
// 检查协议号
if(! comid in s_Format)
{
throw new Error("出现未知协议号:" + comid);
return null;
}
// 解析数据
var raw_data:Array = new Array;
for(var i:int = 0; i < s_Format[comid].length; ++i)
{
var flag:String = s_Format[comid].charAt(i);
if(flag == 'h')
{
raw_data.push(data.readShort());
}
else if(flag == 'H')
{
raw_data.push(data.readUnsignedShort());
}
else if(flag == 'i')
{
raw_data.push(data.readInt());
}
else if(flag == 'I')
{
raw_data.push(data.readUnsignedInt());
}
else if(flag == 'd')
{
raw_data.push(data.readDouble());
}
else if(flag == 's')
{
raw_data.push(data.readUTF());
}
}
return raw_data;
}
}
}
测试代码如下:
// Test
var arr:Array = new Array;
arr.push(1);
arr.push("123456");
arr.push(5.0);
arr.push('789');
arr.push(1000);
arr.push('0');
trace(arr);
var result:ByteArray = NetCommand.Pack(2004, arr);
result.position = 0;
if(result.bytesAvailable > 6)
{
var data_len:uint = result.readUnsignedInt();
var comid:uint = result.readUnsignedShort();
if(result.bytesAvailable >= data_len)
{
var raw_data:Array = NetCommand.Unpack(comid, result);
trace(raw_data);
}
}
最后,我们需要对Python服务器端和Flash客户端进行联机测试。
Python服务器端代码
def dataReceived(self, data):
if(len(data) > 6):
# 获取数据段长度
_data_len = struct.unpack('>I', data[:4])[0]
if(len(data) >= _data_len + 6):
# 获取协议号
_comid = struct.unpack('>H', data[4:6])[0]
# 获取数据段
_data = data[6:_data_len + 6]
# 解包
_raw_data = unpack(_comid, _data)
print '_raw_data = ', _raw_data
# 构建回发数据
_data = []
for d in _raw_data:
print '_data = ', _data
_data.insert(len(_data), d)
# 回发数据
_pack_result = pack(_comid, _data)
self.transport.write(_pack_result)
else:
print '数据不完整,请等待...'
else:
print '目前没有可用数据...'
Flash客户端代码
private function onMouseClick(e:MouseEvent):void
{
// 发送数据
var arr:Array = new Array;
arr.push(1);
arr.push("123456");
arr.push(5.0);
arr.push('789');
arr.push(1000);
arr.push('0');
trace(arr);
var result:ByteArray = NetCommand.Pack(2004, arr);
m_Socket.writeBytes(result);
this.m_Socket.flush();
}
private function socketDataHandler(event:ProgressEvent):void
{
this.readBytes(this.m_ReadBuff, this.m_ReadBuff.length);
if(m_ReadBuff.bytesAvailable > 6)
{
var data_len:uint = m_ReadBuff.readUnsignedInt();
var comid:uint = m_ReadBuff.readUnsignedShort();
if(m_ReadBuff.bytesAvailable >= data_len)
{
var raw_data:Array = NetCommand.Unpack(comid, m_ReadBuff);
trace(raw_data);
}
}
}