Managing Socket Pools

There are many times when I need to manage a set of network connections. One situation is when I’m the ‘client’ side of a connection. Ideally, I would have a pool of connections ready to go at a moment’s notice, because TCP/IP startup times can take a while. Additionally, I’d like to reuse connections that have already been established, for the same reason. So, what to do.

I have this SocketPool construct, which manages a set of network streams (tcp/ip connections). The code looks like this:

local Collections = require "Collections"
local NetStream = require "NetStream"

local SocketPool_t = {}
local SocketPool_mt = {
  __index = SocketPool_t;
}

local SocketPool = function(params)
  params = params or {host="localhost", port=80, reserve=2, timeout=60*2}

  local obj = {
    Connections = Collections.Queue.new();
    Hostname = params.host or "localhost";
    Port = tonumber(params.port) or 80;
    Reserve = params.reserve or 2;
    Timeout = params.timeout,
  }
  setmetatable(obj, SocketPool_mt);

  obj:Cycle()  -- Reserve initial set of connections
  return obj
end

function SocketPool_t:CreateNewConnection()
  local connection, err = NetStream.Open(self.Hostname, self.Port)

  if not connection then
    return nil, err
  end
  connection:SetIdleInterval(self.Timeout);
  self:AddConnection(connection);
  
  return connection
end

function SocketPool_t:CleanoutSockets()
  local qLen = self.Connections:Len()

  -- Check each connection to see if it's still connected
  -- and if it has run past its idle time
  while qLen > 0 do
    local connection = self.Connections:Dequeue()
    self:AddConnection(connection)

    qLen = qLen - 1
  end
end

function SocketPool_t:Cycle()
  self:CleanoutSockets();

  -- Fill back up to the reserve level
  while self.Connections:Len() < self.Reserve do
    local connection, err = self:CreateNewConnection()
    if not connection then
      return false, err
    end
  end
  return true
end

function SocketPool_t:AddConnection(connection)
  if not connection:IsConnected() or connection:IsIdle() then
    return false
  end
  self.Connections:Enqueue(connection)
  return true
end

function SocketPool_t:GetConnection()
  self:Cycle();
  return self.Connections:Dequeue()
end

return SocketPool

Well, that’s a bit of a mouthful, but really it’s fairly straight forward. The usage is like this:

local sockpool = SocketPool({host="www.google.com", port=80, reserve=2, timeout=120})

while running do
  local conn = sockpool:GetConnection();
  conn:Send("Hello, World");
  sockpool:AddConnection(conn);
end

Basically, create an instance of a socket pool, connecting to ‘www.google.com:80’. Each time through the loop, I get a connection from the pool, do something with it, and return the connection back to the pool.

This is really convenient because the pool will take care of getting rid of the socket if it has already been closed, by either end, and creating new sockets when needed. In this particular case, I have setup the pool such that I will always have 2 sockets in reserve. That means that as long as I’m asking for one at a time, I’m always going to get a socket that’s already connected and ready to go.

I have put in a timeout of two minutes (120 seconds). That way, when I go to get a new socket, the pool will first get rid of those sockets that have been sitting around for too long. If it has fallen below the reserve mark (2), then it will refill with fresh new connections. The timeout is very useful to have because when you’re using tcp/ip based sockets out on the internet, there are numerous reasons why having a stale socket sitting around is not a good thing. So, dumping them out of your usage pool with some frequency ensures that what remains is fresh and will not suffer from various ailments such as being silently closed by some intervening router.

I’ve used a couple of things here like the NetStream, and the Queue, which aren’t standard fare, but you can get the general idea.

There is one little item that I found to be challening to really do correctly, on Win32 at least. The function: connection:IsConnected() is supposed to determine whether a tcp/ip socket is currently connected or not. On Windows, this is a surprisingly non-obvious thing to determine.

Here’s the socket code for it:

GetConnectionTime = function(self)
  local poptvalue = ffi.new('int[1]')
  local poptsize = ffi.new('int[1]',ffi.sizeof('int'))
  local size = ffi.sizeof('int')

  local success, err = WinSock.getsockopt(self.Handle, SOL_SOCKET, SO_CONNECT_TIME, poptvalue, poptsize)
		
  if not success then
    return nil, err
  end

  return poptvalue[0];		
end,

IsConnected = function(self)
  success, err = self:GetConnectionTime()
  if success and success >= 0 then
    return true
  end

  return false
end,

The key is the GetConnectionTime() function. This call will tell you how long a socket has been connected. There are two ways in which this call can fail. The first is that the socket was never actually connected to anything. In this case, getsockopt() will succeed, but the connection time will be ‘-1’. The second case is that the socket was connected at some point, but subsequently disconnected. In this case, the getsockopt() will return nil, WSAENOTSOCK (or some other error).

The IsConnected() function takes advantage of these return values, assuming the only valid state is that the connection time is greater than or equal to 0. All other cases indicate the socket is not connected.

This is a fairly quick and easy test. Perhaps a more robust situation might be to use io completion ports, and catch the state transition there. But, this is a nice quick and dirty mechanism that you can use at any time, without having to get your feet into the io completion world.

So, there you have it. Fairly easy management of client side tcp/ip connections. By using such a mechanism, my code is much more compact and clean, and I don’t have to worry about managing my socket connections.

Advertisements


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s