您的位置:首页 > 移动开发 > Cocos引擎

cocos2dx 3.x 中 Lua socket 和 node.js 利用scoket互相通信读写二进制数据

2014-11-14 16:25 881 查看
第一部分,Lua socket如何读写二进制数据。

cocos2dx 3.x 版本已经集成了lua socket所以可以直接使用无需自己集成。首先需要初始化lua socket 如下:

socket = require("socket");
tcp    = socket.connect("127.0.0.1", 1024);

-- non-blocking
tcp:settimeout(0);


这里connect的两个参数就是,链接地址和端口号。settimeout设置为0 是让等待数据的时候不需要阻塞,这里我们使用lua并没有加入线程的支持所以lua是单线程。如果不设置为非阻塞,那么在等待socket数据的时候冻结lua的执行能力。这样scoket就连接上,等待着数据的读和写,我们这里读和写都使用同一个socket对象。

那么,socket如何得知有数据需要读和写呢? 如下:

-- check readable and writable
local reads, writes = socket.select({tcp}, {tcp}, 0);

if #reads == 1 then
-- data can read
end

if #request > 0 and #writes == 1 then
-- data can write
end


我们看到,select的方法放入我们connect返回的tcp对象,会返回reads 和 writes 就是可读和可写的表。具体select的参数和返回值参看lua socket API。reads和writes表的长度说明了是否有数据需要读写。这个段代码需要定时检测以确保一旦有数据就可以被及时的处理。

接下来就是如何进行数据的读写了。lua没有读写二进制数据的方法,所以我们需要引入一个扩展lpack.c,是c实现的lua二进制数据打包解包的功能。但是我找到了一个用lua 翻译这个c版本的库。如下。

-- lpack.c
-- a Lua library for packing and unpacking binary data
-- Luiz Henrique de Figueiredo <lhf@tecgraf.puc-rio.br>
-- 29 Jun 2007 19:27:20
-- This code is hereby placed in the public domain.
-- with contributions from Ignacio Castaño <castanyo@yahoo.es> and
-- Roberto Ierusalimschy <roberto@inf.puc-rio.br>.

-- Conversion from C to lua by Angelo Yazar, 2013.

local ffi      = require "ffi";
local bit      = require "bit";
local C        = ffi.C;
local tonumber = tonumber;
local string   = string;
local assert   = assert;

ffi.cdef [[
int isdigit( int ch );
]]

local OP_ZSTRING      = 'z'; --/* zero-terminated string */
local OP_BSTRING      = 'p'; --/* string preceded by length byte */
local OP_WSTRING      = 'P'; --/* string preceded by length word */
local OP_SSTRING      = 'a'; --/* string preceded by length size_t */
local OP_STRING       = 'A'; --/* string */
local OP_FLOAT        = 'f'; --/* float */
local OP_DOUBLE       = 'd'; --/* double */
local OP_NUMBER       = 'n'; --/* Lua number */
local OP_CHAR         = 'c'; --/* char */
local OP_BYTE         = 'b'; --/* byte = unsigned char */
local OP_SHORT        = 'h'; --/* short */
local OP_USHORT       = 'H'; --/* unsigned short */
local OP_INT          = 'i'; --/* int */
local OP_UINT         = 'I'; --/* unsigned int */
local OP_LONG         = 'l'; --/* long */
local OP_ULONG        = 'L'; --/* unsigned long */
local OP_LITTLEENDIAN = '<'; --/* little endian */
local OP_BIGENDIAN    = '>'; --/* big endian */
local OP_NATIVE       = '='; --/* native endian */
local OP_NONE         = function() end;

function badcode(c)
assert(false, "bad character code: '" .. tostring(c) .. "'");
end

local function isLittleEndian()
local x = ffi.new("short[1]", 0x1001);
local e = tonumber(( ffi.new("char[1]", x[0]) )[0]);

if e == 1 then
return true;
end

return false;
end

function doendian(c)
local e = isLittleEndian();

if c == OP_LITTLEENDIAN then
return not e;
elseif c == OP_BIGENDIAN then
return e;
elseif c == OP_NATIVE then
return false;
end

return false;
end

function doswap(swap, a, T)
if T == "byte" or T == "char" then
return a;
end

if swap then
-- if T == "double" or T == "float" then
-- this part makes me unhappy --

a         = ffi.new(T .. "[1]", a);

local m   = ffi.sizeof(T);
local str = ffi.string(a, m):reverse();

ffi.copy(a, str, m);

return tonumber(a[0]);
--else
--  return bit.bswap( a )
--end
end

return a;
end

function isdigit(c)
return C.isdigit(string.byte(c)) == 1;
end

function l_unpack(s, f, init)
local len    = #s;
local i      = (init or 1);
local n      = 1;
local N      = 0;
local cur    = OP_NONE;
local swap   = false;
--lua_pushnil(L);

local values = {}

local function push(value)
values
= value;
n = n + 1;
end

local function done()
return i, unpack(values);
end

local endianOp = function(c)
swap = doendian(c);
-- N = 0 -- I don't think this is needed
end

local stringOp = function(c)
if i + N - 1 > len then
return done;
end

push(s:sub(i, i + N - 1));
i = i + N;
N = 0;
end

local zstringOp = function(c)
local l = 0;

if i >= len then
return done;
end

local substr = s:sub(i);
l            = substr:find('\0');

push(substr:sub(0, l));
i = i + l;
end

function unpackNumber(T)
return function()
local m = ffi.sizeof(T) ;

if i + m - 1 > len then
return done;
end

local a = ffi.new(T.."[1]");
ffi.copy(a, s:sub(i,i+m), m);
push(doswap(swap, tonumber(a[0]), T));
i = i + m;
end
end

function unpackString(T)
return function()
local m = ffi.sizeof(T);
if i + m > len then
return done;
end

local l = ffi.new(T .. "[1]");
ffi.copy(l, s:sub(i), m);
l = doswap(swap, tonumber(l[0]), T);

if i + m + l - 1 > len then
return done;
end

i = i + m;
push(s:sub(i, i + l - 1));
i = i + l;
end
end

local unpack_ops = {
[OP_LITTLEENDIAN] = endianOp,
[OP_BIGENDIAN]    = endianOp,
[OP_NATIVE]       = endianOp,
[OP_ZSTRING]      = zstringOp,
[OP_STRING]       = stringOp,
[OP_BSTRING]      = unpackString("unsigned char"),
[OP_WSTRING]      = unpackString("unsigned short"),
[OP_SSTRING]      = unpackString("size_t"),
[OP_NUMBER]       = unpackNumber("double"),
[OP_DOUBLE]       = unpackNumber("double"),
[OP_FLOAT]        = unpackNumber("float"),
[OP_CHAR]         = unpackNumber("char"),
[OP_BYTE]         = unpackNumber("unsigned char"),
[OP_SHORT]        = unpackNumber("short"),
[OP_USHORT]       = unpackNumber("unsigned short"),
[OP_INT]          = unpackNumber("int"),
[OP_UINT]         = unpackNumber("unsigned int"),
[OP_LONG]         = unpackNumber("long"),
[OP_ULONG]        = unpackNumber("unsigned long"),
[OP_NONE]         = OP_NONE,
[' ']             = OP_NONE,
[',']             = OP_NONE,
}

for c in (f .. '\0'):gmatch('.') do
if not isdigit(c) then
if cur == OP_STRING then

if N == 0 then
push("");
elseif stringOp(cur) == done then
return done();
end

else
if N == 0 then
N = 1;
end

for k = 1, N do
if unpack_ops[cur] then
if unpack_ops[cur](cur) == done then
return done();
end
else
badcode(cur);
end
end

end

cur = c;
N   = 0;
else
N = 10 * N + tonumber(c);
end
end

return done();
end

function l_pack(f, ...)
local args = {f, ...};
local i    = 1;
local N    = 0;
local swap = false;
local b    = "";
local cur  = OP_NONE;

local pop = function()
i = i + 1;
return args[i];
end

local endianOp = function(c)
swap = doendian(c);
-- N = 0 -- I don't think this is needed
end

local stringOp = function(c)
b = b .. pop();

if c == OP_ZSTRING then
b = b .. '\0';
end
end

function packNumber(T)
return function()
local a = pop()
a       = doswap(swap, a, T);
a       = ffi.new(T .. "[1]", a);
b       = b .. ffi.string(a, ffi.sizeof(T));
end
end

function packString(T)
return function()
local a  = pop();
local l  = #a;
local ll = doswap(swap, l, T);
ll       = ffi.new(T .. "[1]", ll);
b        = b .. ffi.string(ll, ffi.sizeof(T));
b        = b .. a;
end
end

local pack_ops = {
[OP_LITTLEENDIAN] = endianOp,
[OP_BIGENDIAN]    = endianOp,
[OP_NATIVE]       = endianOp,
[OP_ZSTRING]      = stringOp,
[OP_STRING]       = stringOp,
[OP_BSTRING]      = packString("unsigned char"),
[OP_WSTRING]      = packString("unsigned short"),
[OP_SSTRING]      = packString("size_t"),
[OP_NUMBER]       = packNumber("double"),
[OP_DOUBLE]       = packNumber("double"),
[OP_FLOAT]        = packNumber("float"),
[OP_CHAR]         = packNumber("char"),
[OP_BYTE]         = packNumber("unsigned char"),
[OP_SHORT]        = packNumber("short"),
[OP_USHORT]       = packNumber("unsigned short"),
[OP_INT]          = packNumber("int"),
[OP_UINT]         = packNumber("unsigned int"),
[OP_LONG]         = packNumber("long"),
[OP_ULONG]        = packNumber("unsigned long"),
[OP_NONE]         = OP_NONE,
[' ']             = OP_NONE,
[',']             = OP_NONE,
}

for c in (f .. '\0'):gmatch('.') do
if not isdigit(c) then
if N == 0 then
N = 1;
end

for k = 1, N do
if pack_ops[cur] then
pack_ops[cur](cur);
else
badcode(cur);
end
end

cur = c;
N   = 0;
else
N = 10 * N + tonumber(c);
end
end

return b;
end

string.pack   = l_pack;
string.unpack = l_unpack;


那么借助这个库我们可以这么做:

function Socket.readInt8()
local next, val = string.unpack(tcp:receive(1), "b")
return tonumber(val);
end

function Socket.readInt16()
local next, val = string.unpack(tcp:receive(2), "h");
return tonumber(val);
end

function Socket.readInt32()
local next, val = string.unpack(tcp:receive(4), "i");
return tonumber(val);
end

-- Server string data must end of "\n"
function Socket.readString()
return tostring(tcp:receive());
end

-- fmt: one or more letter Codes string
-- A  : string
-- c  : char
-- b  : byte (unsigned char)
-- h  : short
-- H  : unsigned short
-- i  : int
-- I  : unsigned int
-- l  : long
-- L  : unsigned long
function Socket.send(fmt, ...)
tcp:send(string.pack(fmt, ...));
end


读取数据我们使用lua socket的receive方法截取数据,以后再用解包函数解包,以后再强转成我们需要的类型。读取数据直接把数据按照类型打包,send出去即可。

第二部分,node.js的数据读写。

node.js 我只是使用了原生的socket API 并没有使用任何框架。封装了一个数据读写的模块如下:

var BufferRead = function(buff) {
var offset = 0;

return {
readInt8: function() {
var int8 = buff.readInt8(offset);
offset += 1;
return int8;
},

readInt16: function() {
var int16 = buff.readInt16LE(offset);
offset += 2;
return int16;
},

readInt32: function() {
var int32 = buff.readInt32LE(offset);
offset += 4;
return int32;
},

readString: function(len) {
var str = buff.toString("utf8", offset, offset + len);
offset += len;
return str;
}
};
}

var BufferWrite = function(socket) {
return {
writeInt8: function(int8) {
var buff = new Buffer(1);
buff.writeInt8(int8, 0);
socket.write(buff);
},

writeInt16: function(int16) {
var buff = new Buffer(2);
buff.writeInt16LE(int16, 0);
socket.write(buff);
},

writeInt32: function(int32) {
var buff = new Buffer(4);
buff.writeInt32LE(int32, 0);
socket.write(buff);
},

writeString: function(str) {
socket.write(str);
},

/**
* fmt is format string
* A  : string
* b  : byte (unsigned char)
* h  : short
* i  : int
*/
write: function(fmt) {
for (var i = 0; i < fmt.length; i++) {
switch (fmt.charAt(i)) {
case 'A':
this.writeString(arguments[i + 1]);
break;

case 'b':
this.writeInt8(arguments[i + 1]);
break;

case 'h':
this.writeInt16(arguments[i + 1]);
break;

case 'i':
this.writeInt32(arguments[i + 1]);
break;
}
}
}
};
}

module.exports = {
BufferRead:  BufferRead,
BufferWrite: BufferWrite
};


读写数据只是利用node.js提供的Buff对象打包了数据以后用socket进行操作清晰明了。

var protocal = require("./Protocol.js");
var net      = require("net");
var buffData = require("./BufferData.js");

var server   = net.createServer();

server.listen(1024, function() {
console.log('Server start local host at port 1024');
});

server.on("connection", function(socket) {
console.log("server socket connected");

socket.on('data', function(buff) {
var buffRead  = new buffData.BufferRead(buff);
var buffWrite = new buffData.BufferWrite(socket);
var reqCode   = buffRead.readInt32();
protocal.handlers[reqCode](socket, buffRead, buffWrite);
socket.pipe(socket);
});

socket.on('end', function() {
console.log('server socket disconnected');
socket.destroy();
});

socket.on('error', function(error) {
console.log("Client error: %s", error.toString());
socket.destroy();
});

});

server.on("error", function (error) {
console.log("Server error code = %s", error.toString());
});

server.on("close", function() {
console.log("Server closed");
});


这是服务器启动的代码,关键在入on data的回调函数,我们利用系统提供的buff和socket对象,构建我们封装的BuffRead和BuffWrite就可以进行数据的读写了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐