第一部分,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进行操作清晰明了。
这是服务器启动的代码,关键在入on data的回调函数,我们利用系统提供的buff和socket对象,构建我们封装的BuffRead和BuffWrite就可以进行数据的读写了。
- 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");
- });