-- non-blocking sockets object -- requires LuaSocket, stringStack. assert(stringStack, "stringstack is needed."); assert(select, "LuaSocket is needed."); -- Usage: -- To create a client socket from a server socket with a connection waiting, do -- myClient = clientSocket:accept(myServer); -- -- To poke some data into the send buffer for a client socket... -- myClient:send(string); -- -- To see if there is some data waiting for you... -- s = myClient:readyRead(pattern); -- where pattern is a strfind pattern to decide if a whole interesting thing has -- arrived. Returns it as a string if it is, returns an empty string when there -- is nothing waiting, and nil if the socket has been closed. -- -- To send data to a socket (when read from select())... -- n = myClient:readySend(); -- n is non-nil if the socket has been closed. clientSocket = { recBuff = {}, -- contains a stringStack with received chunks -- of data in it. When you use the readyRead() -- method, and the pattern passed to it matches, -- it concatanates the string, and returns it. sendBuff = {}, -- contains a list of strings that need to be -- sent to the client. This is used to send -- data when the readySend() method is called. socket = nil, -- LuaSocket's handle for this connection. writers = {}, -- contains luasocket handles that have data -- waiting to be written. toClose = nil, -- this socket needs to be closed once all data has -- been sent. accept = function(self, server) -- This function is called in the form clientSocket:accept(s), -- where 's' is the server socket which has just recieved a -- connection. It returns a table to represent that socket, -- which has all of the other functions in it. local r; r = { recBuff = stringStack:new(), sendBuff = {}, socket = accept(server), send = self.send, readySend = self.readySend, readyPeek = self.readyPeek, readyRead = self.readyRead, close = self.close, echo = nil, }; if (r.socket) then r.socket:timeout(0, "b"); -- make socket non-blocking return r; else return nil; end; end, new = function(self, ip, port) -- This function actually creates a connection to somewhere else... local r; local t = secs; r = { recBuff = stringStack:new(), sendBuff = {}, socket = nil, send = self.send, readySend = self.readySend, readyPeek = self.readyPeek, readyRead = self.readyRead, close = self.close, }; while ((secs < t + 3) and r.socket == nil) do r.socket = connect(ip, port); end; if (r.socket == nil) then return nil end; r.socket:timeout(0, "b"); -- make socket non-blocking return r; end, close = function(self) -- This function closes a socket, and tidies up. if (getn(self.sendBuff) > 0) then -- remove ourself from the writers list... foreachi(clientSocket.writers, function(i, v) if (v == %self.socket) then tremove(clientSocket.writers, i); return 1; end; end); end; self.sendBuff = {}; self.recBuff = {}; self.socket:close(); self.socket = nil; end, send = function(self, string) -- This function adds a string to the send table. if (self.toClose and self.toClose > 1) then return nil end; if (getn(self.sendBuff) == 0) then tinsert(clientSocket.writers, self.socket); end; tinsert(self.sendBuff, string); end, readySend = function(self) -- This function tries to send as much data in the send -- table to self.socket before it would block, and the -- returns, after updating the send buffer. It returns -- non-nil if the connection has closed. local err, sent, string, v; while (self.sendBuff.n > 0) do string = self.sendBuff[1]; err, sent = self.socket:send(string); if (err == "closed") then if (self.toClose == 1) then self.toClose = 2; end; return 1; end; if (sent < strlen(string)) then -- we didn't get to send it all... -- update this string to chop what we've sent, -- and then return. self.sendBuff[1] = strsub(string, sent + 1, -1); return nil; else -- we sent it all! yay! remove the entry. tremove(self.sendBuff, 1); end; end; -- we've sent all waiting data, remove us from the -- writers table. v = clientSocket.writers; for i=1, getn(v) do if (v[i] == self.socket) then tremove(v, i); break; end; end; if (self.toClose == 1) then self.toClose = 2; end; end, readyRead = function(self, pattern) -- This function reads the available data from the -- socket and adds it to the recieved string stack. -- If the string read matches 'pattern', the string -- is concatenated, returned, and the stringstack -- emptied. If no string is ready, it returns "", -- if the connection has been closed, it returns -- nil. local r, err, s, l; repeat r, err = self.socket:receive("*a"); if (err ~= "" and err ~= "timeout") then return nil end; if (not self.toClose) then if (self.echo ~= nil) then self:send(r); dataSent = dataSent + (strlen(r)); end; self.recBuff:add(r); -- Check what has just been read, and the last read too, since a peek -- may have appeneded our pattern to the stack if (self.recBuff.stack.n > 1 and r ~= nil) then r = self.recBuff.stack[self.recBuff.stack.n-1] .. r; end; if (strfind(r, pattern, 1) ~= nil) then s = self.recBuff:create(); self.recBuff = stringStack:new(); if (debugFile) then write(debugFile, "read data from ", tostring(self.socket), ": ", (r or "(no data)"), "\n") flush(debugFile) end return s or ""; end; end; until (err == "timeout"); return ""; end, readyPeek = function(self, pattern) -- This function reads the available data from the -- socket and adds it to the recieved string stack. -- If the string read matches 'pattern', the string -- is concatenated, returned, and the stringstack -- emptied. If no string is ready, it returns "", -- if the connection has been closed, it returns -- nil. local r, err, s, l; repeat r, err = self.socket:receive("*a"); if (err ~= "" and err ~= "timeout") then return nil end; if (not self.toClose) then self.recBuff:add(r); if (strfind(self.recBuff:create(), pattern, 1) ~= nil) then s = self.recBuff:create(); return s; end; end; until (err == "timeout"); return ""; end, };