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

第一部分,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[n] = 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,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_ZSTRING]      = 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"),}
  
  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,writeInt32: function(int32) {
        var buff = new Buffer(4);
        buff.writeInt32LE(int32,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就可以进行数据的读写了。

相关文章

    本文实践自 RayWenderlich、Ali Hafizji 的文章《...
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@1...
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从C...
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发...
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《...
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试...