views:

686

answers:

12

It's basically one app that is installed on multiple PC's, each install maintaining it's own database which is sync'd with other's as & when they are up (connected to the same network) at the same time.

I've tested this using simple socket connections and custom buffers, but want to make the comms between the apps conform to accepted standards and also to be secure/robust, and not try to re-invent the wheel.

What is the normal/standard way of doing this app-to-app comms & where do I find out more?

Also, what techniques are/can be used to announce and find the other apps on a network?


edit: (refining my problem)

The pub/sub model pointed to by gimel below seems to be along the lines of what I need. It however covers a lot of ground & I don't really know what to take away & use from all that.

It also looks like I need to establish a P2P connection once two or more apps found each other - how do I do that?

If there are examples/tutorials available, please point them out. Small open source projects/modules that implements something like what I need would also serve.

My platform of choice is Linux, but Windows-based examples would also be very usable.


edit [09-01-06]:

I am currently looking at the following options:

  1. multicasting (TLDP-Howto) - this seems workable, but I need to study it some more.
  2. using free dynamic DNS servers, although this seems a bit dicey...
  3. using some free email facility, e.g. gmail/yahoo/..., and send/read mail from there to find other app's IP's (can work, but feels dirty)
  4. webservices has been suggested, but I don't know how they work & will have to study it up

I would appreciate your opinion on these options and if there are any examples out there. I unfortunately do NOT have the option of using a central server or website (unless it can be guaranteed to be free and permanent).

[Edit 2009-02-19]

(Wish I could accept two/three answers! The one I've accepted because it provides lines of thought and possibilities, while others came with fixed, but applicable, solutions. Thanks to all who answered, all of it helps.)

As & when I find/implement my solution, I will update this question, and should the solution be adequate I'll create a sourceforge project for it. (It is in any case a small problem within a far larger project.)

+5  A: 

See Publish / Subscribe asynchronous messaging paradigm.

An example implementaion is Apache ActiveMQ:

Apache ActiveMQ is fast, supports many Cross Language Clients and Protocols, comes with easy to use Enterprise Integration Patterns and many advanced features while fully supporting JMS 1.1 and J2EE 1.4.

gimel
wooof!! - u pointed me at pandora's box here... but pub/sub is essentially what I need, without a dedicated broker. I need more pointed directions: (the)standard/protocols/techniques/examples(favorite!)
slashmais
Examples are usually platform-specific. Please specify.
gimel
slashmais
Maybe the source for a Microsoft production system will help: http://www.codeplex.com/pubsub (probably not a good fit for Linux).
gimel
A: 

Sounds like you need a distributed cache or offline db functionality - depending on your language (java/c#/...) there are various options open to you...

Chris Kimpton
+2  A: 

Do you want this to be totally P2P or are you planning on having a central server for doing anything more then being a directory?

For communication security, SSL should be fine. Java supports these in a pretty straightforward way, if that's what you're using. here's the reference for SSL in java 6

Chad Okere
slashmais
+3  A: 

I designed a similar app to what you are describing several years ago. I designed an "Advertise Server" that ran on each desktop, and would use UDP to broadcast it's statuses to any other program running on the network. Now, this has it's own set of issues, depending on how you plan to run this application... But, here's the quick and dirty of how it worked...

I setup a 'listener' on a port, that was a picked by hashing the database server, and database the application was connected to. This would ensure that anyone I got a broadcast from, was using the same database as I was, and allowed multiple instances of the app to run on the desktop (A design requirement).

Then, I setup various "BroadcastMessage()" functions that would broadcast certain events. I even went as far as allowed developers using my API the ability to create custom events, with custom payload data, and then have the program register a listener, for that event, which would notify the registrar when that event came in, and pass it the data that came with it.

For example, when the app started, it would broadcast an "I'm here" message, and anyone listening could eat the message, ignore it or reply to it. In the "I'm here", it contained the IP address of the running application, so that any clients COULD connect to it via a TCP connection for further data updates, that HAD to be delivered.

I chose UDP, because it was not a REQUIREMENT that these broadcasts be seen by all the other running instances. It was more of a convenience than anything... If someone had added a record to the DB while you were on the same screen, the new record would just 'appear' on your desktop.

It also came in handy that if an admin changed permissions of a user while they were running the app, the user did not have to exit and re-enter the app, the update was received, and processed right there, and the user could do what they needed to.

It's really easy to setup a listener on a thread that just listens for these types of messages... If you need example code, I can provide that too, but it is in C++, and designed for Windows, but it uses the raw wsock32.lib, so it SHOULD transfer over to any Unix platform pretty easy. (Just need to typedef DWORD, as I used that a lot..).

LarryF
Does sound along the lines of what I want to do. Would be good if you can add some of your sample-code - just enough to make the logic of the comms-process clear. TIA
slashmais
Oh, yes, the languages I use are C/C++ and assembler, but am not hooked on it, nor on bound to any OS (although Linux provides the most freedom)
slashmais
A: 

Amazon and Microsoft both have hosted queues you can use as a rendezvous point between an arbitrary number of connected, cooperating applications. Amazon's is commercial, not free. Microsoft's is currently free, but not guaranteed to be so, forever. It solves exactly the problem you are facing. Provides a pub/sub model for connected clients.

Cheeso
A: 

I may have missed something here, but I failed to see your choice of a programming language. In a windows-based environment, using .Net framework, the best choice would be to use WCF, which allows you to add security/robustness with a simple configuration. If you want a solution with windows based computers which is not .Net oriented, I would look into using MSMQ, which is a communication framework built to these standards.

dotmad
+4  A: 

Hmm,

This is a bit like a math problem. The question of how two computers establish a connection once they find each other is fairly straightforward. You can use any number of P2P or client-server protocols. SSL is almost universally available but you could also serve SSH, run Freenet or whatever. Once you establish a connection through one of these protocols, a publish/Subscribe model for exchanging data could work well. But there is

The question of how the computers FIND each other is where things get tricky. There are essentially three cases:

  1. All your computers will be on your own local area network. In this case, any computer that goes online can broadcast a message that it is online and get message back concerning what other machines are online. Remember any broadcast/multicast message has to every machine on the network since it doesn't know what its target. You cannot do this on the internet since you cannot send a message to every machine on the net.

  2. Your computers are on arbitrary nodes on the internet ** and ** there will always be at least one of your computers connected to the web ** and ** all machine go online on a regular basis. In this case, each machine can keep a running list of IP addresses. When a machine that's been offline for a while comes back online, it checks for the known addresses, connecting to the first valid address - this is how the emule protocol finds servers.

  3. Your computers are on arbitrary nodes on the internet ** and ** all machines offline together ** or **quite a few go offline for long periods while otheers switch IP addresses. Here, I don't think that you can demonstrate that there is no way for new machines coming on to find each other without some central server to links to common IPs. There is no way to broadcast a message throughout the Internet because it is big. In these circumstances, computers have no identifying information for the other machines coming online so you need to use a central, common resource: the email address you mentioned, a website, an ftp server, an IRC channel. A dynamic DNS is just one more instance of a centralized information store. If you must use such a store, naturally you have to evaluate all the stores available for their reliability, speed and permanence. Unless you give more information about what your application needs in this regard, I don't think anyone else can decide which permanent store you need.

I think this covers all the possibilities. What you have to do is decide which of these general cases your application falls under and then decide on one of protocols which fits this case.

Edit:

From feedback, it seems case 3 applies. From the reasoning you can see above, it should be clear that there is no way to avoid some form of external server - the only way to find a needle in a haystack is by keeping track of where it is. The qualities one would want in such a provider are:

  • Reliability: How often is the thing up in a given day
  • Speed: How quickly does the thing respond
  • Permanence: How long do you expect the thing to last? Will you lose access to it as the Internet evolves?

As mentioned, there are many easily available resources that more or less fit this bill. Email servers (as you are presently using), web servers, FTP servers, DNS servers, IRC channels, Twitter accounts, web forums....

The problem of applications that come to life after a while and require updating without a central server is a common problem but it common mostly among virus writer - just about any organization which has the resources to create a distributed application also has resources to maintain a central server. That said, standard solutions over the years included email, http servers, ftp servers, IRC channels and dynamic DNS servers. Different servers in each of these categories vary in their speed, reliability and permanence so the task of choosing one goes back to your judgment. IRC channels deserve mention because they fast and easy to set up but these might indeed fade as the internet evolves.

As an example of distribution application which uses a variety of "client finding" techniques, you can download the source to BO2K, a, uh "remote administration utility". This might provide some insight into all of the functionality of your remote updating client.

Just to repeat. I imagine there are three parts to your problem:

  1. The machines finding each other (see above)
  2. The machines establishing a connection (again, SSL, SSH and others are readily available)
  3. The machines exchanging data. You could use a "publish/subscribe" model or just roll your own simple protocol. I worked for company which had an auto-updating client and that's what we did. The reasons for creating your own protocol are 1) in even the simplest situations, the requirements for speed and reliability will vary as will the data exchanged, 2. The simplest exchange of data requires just a few lines of code and so no one bothers following protocols for really simple data exchange, 3. Since so different applications use different methods and languages, no protocol for simple data exchange is dominant. For more complex situations, there indeed a whole forest of protocols but their vary complexity would make them awkward to use for simple data exchange. The way that the git scm sends data is one example of a updating protocol. If it is just so happens that you database resembles the source code that git sends, you could use git to maintain your database. But the chances are your updating approach won't resemble what git does that closely. Another example protocol is one or another version of web services such as SOAP. These protocols just wrap the process of calling a function on one machine using xml and http. If you can already establish socket communication between your applications, then there's no reason to do this. Remember, to implement web services you'd need to run an run an http server and parse the xml that an http clients into raw data. Considering you can send your data directly through a socket, there's no reason to do this. So you are back to rolling your own.

Anyway, an example of a simple protocol might be:

  • one app first sends an index of the data it has as an array of indexes.
  • the other app sends a list of the items which are new it
  • and then the first app sends those actual items.

Then the apps change roles and exchange data the other way. A given "handshake" in your protocol would look like this in pseudo code:

void update_database(in_stream, out_stream) {
  Get_Index_Of_Other_Machines_Items(in_stream);
  Send_Index_Of_Items_You_Need(out_stream);
  Get_Items_You_Need(in_stream);
}

With another function to implement the opposite side of these data exchanges.

A further consideration is that if each entry in the database is being generated independently, you will need to generate an ID which is unique to this without being able to reference all of the items in the distributed database. You can generate a GUID or just a large random number for this purpose.

You will no doubt have to tweak and develop this further if you are going to use it.

Keep in mind, however, that if your applications are all only occasionally updating, there will no way of being certain that any given instances will have any data item. For example, suppose, in a day, half the machines only go online after 5:00pm and the other half only go online before 5:00 pm. In this case, the two groups of machines will share no data whatsoever if they directly updating each other. If, on the other hand, your machines really do on at even distributed times (rather than according to pattern like I described), it is reasonably likely that each machine will eventually get all of the updates (at least all of the old updates).

Given all this, I think you should consider exactly how infrequently your applications will be connecting and how important it is for all the data to be fully synchronized. In situations of extreme infrequency, you may need to use an external server to move your data as well as finding your clients. Regular email is natural solution to this problem. It would be one thing to use if no computer is ever online when another is online. If you are worried about access to a particular email accounts, you could start each app with a list of multiple email addresses, with all the addresses being checked and you having the option of updating the application with even more addresses as given addresses fail. Using email for everything would have the virtue of simplicity. You could use

On a lighter side note, numbers stations are a pre-Internet effort to solve the information updating problem but they also wouldn't fit your case (they do broadcast to a large portion of the world, however).

Joe Soul-bringer
Case 3 applies. There are make-works available, but I would have thought this not to be such a unique problem. Surely there must be techniques available? or am I seeking snowballs in hell...
slashmais
See the editing comment
Joe Soul-bringer
+1  A: 

Ok. As promised, here is some of the sample code I ripped from my application. This isn't expected to compile and run, this is an example of how I did it. You may have to do yours completely different. Plus, this was written for Windows, and as you'll see in the code, it uses Windows Messages to send data across between the server thread and the main application, but that all depends on how YOU plan to use it. I left some of the more interesting parts in for you to reference.

As for the security part, well, I figure you can handle that part. That's just a simple matter of encrypting the data before it goes over the wire, using some well know cipher, so I didn't think I had to include any of that. For example, you can see how I constructed the packet headers, and then there is a payload that usually consists of another structure. So, encrypt that structure, send it as data, and then decrypt it on the other end, and copy it to the proper structure.

// Some defines that you may see in the code, all of which are user defined...
#define ADVERTISE_SERVER           0x12345678 // Some unique ID for your advertisement server
#define ACTIVITY_NONE              0x00000000
#define ACTIVITY_LOGON             0x00000001
#define ACTIVITY_LOGOFF            0x00000002
#define ACTIVITY_RUNNING           0x00000004
#define ACTIVITY_IDLE              0x00000005
#define ACTIVITY_SPECIFIC          0x00000006


enum Advertisements {
   ADVERTISE_SHUTDOWN,
   ADVERTISE_MESSAGE,
   ADVERTISE_DEBUG,
   ADVERTISE_OVERLAPPED,
   ADVERTISE_BROADCAST_IDENTITY,
   ADVERTISE_IDENTITY,
   ADVERTISE_PARAMETER_CHANGE
};

struct TAdvertiseServerPacket {
   UINT     uiAdvertisePacketType;
   DWORD    dwPacketLength;
   bool     bRequestReply;
   UINT     uiReplyType;
   bool     bOverlappedResult;
   int      iPacketId;
   bool     bBroadcast;
   char     GuidHash[35];
   BYTE     PacketData[1024];
};

struct TAdvertiseIdentity {
   TCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
   char  szUserName[LEN_APPL_USERNAME + 1];
   char  szDatabase[MAX_PATH];
   char  szConfiguration[MAX_PATH];
   char  szVersion[16];
   long  nUserId;
   char  szApplication[MAX_PATH];
   char  szActivity[33];
   UINT  uiStartupIndc;
};

struct TAdvertiseMessage {
   char              MessageFrom[LEN_APPL_USERNAME + 1];
   char              MessageText[512];
};

struct TAdvertiseItemUpdate {
   NMHDR             pNMHDR;
   long              nItemId;
   long              nItemTypeId;
   char              szItemName[LEN_ITEM_NAME + 1];
   bool              bState;
};

struct TAdvertiseItemUpdateEx {
   NMHDR             pNMHDR;
   long              nItemId;
   bool              bState;
   bool              bBroadcast;
   DWORD             dwDataSize;
   void              *lpBuffer;
};

struct TOverlappedAdvertisement {
   int               iPacketId;
   BYTE              Data[1020];
};

DWORD WINAPI CAdvertiseServer::Go(void* tptr)
{
   CAdvertiseServer *pThis = (CAdvertiseServer*)tptr;

   /* Used and reused for Overlapped results, */
   DWORD BufferSize       = 0;
   BYTE *OverlappedBuffer = NULL;
   bool bOverlapped       = false;
   int  iOverlappedId     = 0;
   DWORD BufferPosition   = 0;
   DWORD BytesRecieved    = 0;
   TAdvertiseItemUpdateEx *itemex = NULL;
   UINT uiPacketNumber    = 0;

   bool Debug = false;
#ifdef _DEBUG
   Debug = true;
#endif
   {
      DWORD dwDebug = 0;
      dwDebug = GetParameter(ADVERTISE_SERVER_DEBUG); // GetParameter is part of the main program used to store running config values.
      if(dwDebug > 0)
      {
         Debug = true;
      }
   }
   WSAData wsaData;
   WSAStartup(MAKEWORD(1,1), &wsaData);
   ServerSocket = socket(PF_INET, SOCK_DGRAM, 0);
   if(ServerSocket == INVALID_SOCKET)
   {
      CLogging Log("Client.log");
      ServerSocket = NULL;
      Log.Log("Could not create server advertisement socket: %d", GetLastError());
      return -1;
   }
   sockaddr_in sin;
   ZeroMemory(&sin, sizeof(sin));
   sin.sin_family = AF_INET;
   sin.sin_port = htons(Port);
   sin.sin_addr.s_addr = INADDR_ANY;
   if(bind(ServerSocket, (sockaddr *)&sin, sizeof(sin)) != 0)
   {
      CLogging Log("Client.log");
      Log.Log("Could not bind server advertisement socket on port: %d Error: %d", Port, GetLastError());
      DWORD dwPort = 0;
      dwPort = GetParameter(ADVERTISE_SERVER_PORT); // Again, used to set the port number, if one could not be figured out.
      if(dwPort > 0)
      {
         return -1;
      }
      Port = 36221;
      sin.sin_port = htons(Port);
      if(bind(ServerSocket, (sockaddr *)&sin, sizeof(sin)) != 0)
      {
         CLogging Log("Client.log");
         Log.Log("Could not bind server advertisement socket on port: %d Error: %d Could not start AdvertiseServer after two attempts.  Server failed.", Port, GetLastError());
         return -1;
      }
   }

   SECURITY_ATTRIBUTES sa;
   sa.bInheritHandle = TRUE;
   sa.lpSecurityDescriptor = NULL;
   sa.nLength = sizeof(SECURITY_ATTRIBUTES);
   HANDLE mutex = CreateMutex(NULL, FALSE, "Client.Mutex"); // Used to keep and eye on the main program, if it shuts down, or dies, we need to die.
   while (1)
   {
      TAdvertiseServerPacket ap;
      sockaddr_in sin;
      int fromlen = sizeof(sin);
      fd_set fds;
      FD_ZERO(&fds);
      FD_SET(ServerSocket, &fds);
      timeval tv;
      tv.tv_sec = 15;
      tv.tv_usec = 0;
      int err = select(0, &fds, NULL, NULL, &tv);
      if(err == SOCKET_ERROR)
      {
         CLogging Log("Client.log");
         Log.Log("Advertise: Winsock error: %d", WSAGetLastError());
         Beep(800, 100);
         break;
      }
      if(err == 0)
      {
         if(WaitForSingleObject(mutex, 0) != WAIT_OBJECT_0)
         {
            continue; // Main app is still running
         }
         else
         {
            Beep(800, 100); // Main app has died, so exit our listen thread.
            break;
         }
      }

      int r = recvfrom(ServerSocket, (char *)&ap, sizeof(ap), 0, (sockaddr *)&sin, &fromlen);

      if(r != sizeof(TAdvertiseServerPacket))
      {
         continue;
      }
      switch(ap.uiAdvertisePacketType)
      {
         // This is where you respond to all your various broadcasts, etc.
         case ADVERTISE_BROADCAST_IDENTITY:
         {
            // None of this code is important, however you do it, is up to you.
            CDataAccess db(CDataAccess::DA_NONE);
            TCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
            ZeroMemory(ComputerName, sizeof(ComputerName));
            DWORD len = MAX_COMPUTERNAME_LENGTH;
            GetComputerName(ComputerName, &len);
            if(pThis->szActivity) {
               CAdvertiseServer::AdvertiseIdentity(ComputerName, CDataAccess::GetLoggedInUserName(), CDataAccess::DatabaseConfiguration(), CDataAccess::DatabaseConfiguration(), ACTIVITY_SPECIFIC, pThis->szActivity, false);
            } else {
               CAdvertiseServer::AdvertiseIdentity(ComputerName, CDataAccess::GetLoggedInUserName(), CDataAccess::DatabaseConfiguration(), CDataAccess::DatabaseConfiguration(), ACTIVITY_RUNNING, NULL, false);
            }
         }
         case ADVERTISE_IDENTITY:
         {
            TAdvertiseIdentity ident;
            memcpy((void*)&ident, (void*)ap.PacketData, ap.dwPacketLength);
            Listener::iterator theIterator;
            theIterator = pThis->m_Listeners.find(ap.uiAdvertisePacketType);
            if(theIterator == pThis->m_Listeners.end())
            {

               //We got an Identity Broadcast, but we're not listening for them.
               continue;
            }
            {
               itemex = new TAdvertiseItemUpdateEx;
               ZeroMemory(itemex, sizeof(TAdvertiseItemUpdateEx));
               memcpy((void*)&ident, ap.PacketData, ap.dwPacketLength);
               itemex->pNMHDR.code     = (*theIterator).first;
               itemex->pNMHDR.hwndFrom = (*theIterator).second;
               itemex->pNMHDR.idFrom   = ADVERTISE_SERVER;
               itemex->dwDataSize      = sizeof(TAdvertiseIdentity);
               itemex->lpBuffer        = (void*)&ident;
               SendMessage((*theIterator).second, WM_NOTIFY, 0, (LPARAM)itemex);
               delete itemex;
            }
         }
         case ADVERTISE_SHUTDOWN:
         {
            TCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
            ZeroMemory(ComputerName, sizeof(ComputerName));
            DWORD len = MAX_COMPUTERNAME_LENGTH;
            GetComputerName(ComputerName, &len);
            CString guid;
            guid.Format("%s%s", CDataAccess::DatabaseConfiguration(), ComputerName);
            if(stricmp(ap.GuidHash, CDataAccess::HashPassword(guid)) == 0)
            {
               return 1;
            }
         }
         case ADVERTISE_MESSAGE:
         {
            TAdvertiseMessage msg;
            memcpy((void*)&msg, (void*)ap.PacketData, ap.dwPacketLength);
            CString msgtext;
            msgtext.Format("Message from: %s\r\n\r\n%s", msg.MessageFrom, msg.MessageText);
            ::MessageBox(NULL, msgtext, "Broadcast Message", MB_ICONINFORMATION | MB_SYSTEMMODAL);
            break;
         }
         case ADVERTISE_OVERLAPPED:
         {
            // I left this code in here, as it's a good example of how you can send large amounts of data over a UDP socket, should you need to do it.
            BufferPosition = (1020 * ((ap.uiReplyType - 1) - 1));
            if(BufferPosition > BufferSize) {
               BufferPosition -= 1020;
            }
            TOverlappedAdvertisement item;
            ZeroMemory(&item, sizeof(TOverlappedAdvertisement));
            memcpy((void*)&item, (void*)ap.PacketData, ap.dwPacketLength);
            if(item.iPacketId == iOverlappedId)
            {
               DWORD ToCopy = (sizeof(item.Data) > (BufferSize - BytesRecieved) ? BufferSize - BytesRecieved : sizeof(item.Data));
               memcpy((void*)&OverlappedBuffer[BufferPosition], (void*)item.Data, ToCopy);
               BytesRecieved += ToCopy;
               if(BytesRecieved < BufferSize)
               {
                  continue;
               }
            }
         }
         default:
         {
            // What do we do if we get an advertisement we don't know about?
            Listener::iterator theIterator;
            if(bOverlapped == false)
            {
               theIterator = pThis->m_Listeners.find(ap.uiAdvertisePacketType);
               if(theIterator == pThis->m_Listeners.end())
               {
                  continue;
               }
            }

            // Or it could be a data packet
            TCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
            ZeroMemory(ComputerName, sizeof(ComputerName));
            DWORD len = MAX_COMPUTERNAME_LENGTH;
            GetComputerName(ComputerName, &len);
            CString guid;
            guid.Format("%s%s", CDataAccess::DatabaseConfiguration(), ComputerName);
            bool FromUs = stricmp(ap.GuidHash, CDataAccess::HashPassword(guid)) == 0;
            if(((FromUs && Debug) || !FromUs) || ap.bBroadcast)
            {
               if(ap.bOverlappedResult)
               {
                  if(ap.uiReplyType == 1)
                  {
                     itemex = new TAdvertiseItemUpdateEx;
                     ZeroMemory(itemex, sizeof(TAdvertiseItemUpdateEx));
                     memcpy(itemex, ap.PacketData, ap.dwPacketLength);
                     OverlappedBuffer = (BYTE*)malloc(itemex->dwDataSize);
                     BufferSize = itemex->dwDataSize;
                     ZeroMemory(OverlappedBuffer, itemex->dwDataSize);
                     bOverlapped = true;
                     iOverlappedId = ap.iPacketId;
                     uiPacketNumber = ap.uiReplyType;
                  }
                  continue;
               }
               if(bOverlapped)
               {
                  itemex->pNMHDR.code     = (*theIterator).first;
                  itemex->pNMHDR.hwndFrom = (*theIterator).second;
                  itemex->pNMHDR.idFrom   = ADVERTISE_SERVER;
                  itemex->dwDataSize      = BufferSize;
                  itemex->lpBuffer        = (void*)OverlappedBuffer;
                  SendMessage((*theIterator).second, WM_NOTIFY, 0, (LPARAM)itemex);
                  delete itemex;
                  free(OverlappedBuffer);
                  BufferSize       = 0;
                  OverlappedBuffer = NULL;
                  bOverlapped      = false;
                  iOverlappedId    = 0;
                  BufferPosition   = 0;
                  BytesRecieved    = 0;
                  itemex           = NULL;
                  uiPacketNumber   = 0;
                  break;
               }
               TAdvertiseItemUpdate *item = new TAdvertiseItemUpdate;
               ZeroMemory(item, sizeof(TAdvertiseItemUpdate));
               memcpy(item, ap.PacketData, ap.dwPacketLength);

               item->pNMHDR.code     = (*theIterator).first;
               item->pNMHDR.hwndFrom = (*theIterator).second;
               item->pNMHDR.idFrom   = ADVERTISE_SERVER;
               SendMessage((*theIterator).second, WM_NOTIFY, 0, (LPARAM)item);
               delete item;
            }
            break;
         }
      }
   }
   try {
      ResetEvent(ServerMutex);
      CloseHandle(pThis->ServerMutex);
      closesocket(ServerSocket);
      return 0;
   }
   catch(...) {
      closesocket(ServerSocket);
      return -2;
   }
}

// Here's a couple of the helper functions that do the sending...
bool CAdvertiseServer::SendAdvertisement(TAdvertiseServerPacket packet)
{
   TCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
   ZeroMemory(ComputerName, sizeof(ComputerName));
   DWORD len = MAX_COMPUTERNAME_LENGTH;
   GetComputerName(ComputerName, &len);
   CString guid;
   guid.Format("%s%s", CDataAccess::DatabaseConfiguration(), ComputerName);

   strcpy(packet.GuidHash, CDataAccess::HashPassword(guid));

   bool bRetval = false;
   SOCKET s = socket(PF_INET, SOCK_DGRAM, 0);
   if(s != INVALID_SOCKET)
   {
      BOOL tru = TRUE;
      setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char *)&tru, sizeof(tru));
      sockaddr_in sin;
      ZeroMemory(&sin, sizeof(sin));
      sin.sin_family = PF_INET;
      sin.sin_port = htons(Port);
      sin.sin_addr.s_addr = INADDR_BROADCAST;
      if(sendto(s, (char *)&packet, sizeof(packet), 0, (sockaddr *)&sin, sizeof(sin)) > 0)
      {
         bRetval = true;
         if(packet.bRequestReply)
         {
           // Here is where your work comes in, in setting up a reply, or making a TCP connection back to the other client.
         }
      }
      closesocket(s);
   }
   return bRetval;
}

bool CAdvertiseServer::Advertise(UINT uiAdvertisement, long nItemId, bool bState, void *lpBuffer, DWORD dwDataSize, bool bBroadcast)
{
   TAdvertiseServerPacket packet;
   ZeroMemory(&packet, sizeof(packet));
   TAdvertiseItemUpdateEx   item;
   ZeroMemory(&item, sizeof(item));

   UINT packetnum = 1;
   packet.bOverlappedResult = true;
   packet.bRequestReply = false;
   packet.uiAdvertisePacketType = uiAdvertisement;
   packet.dwPacketLength = sizeof(item);
   packet.uiReplyType = packetnum;
   packet.bBroadcast = bBroadcast;
   item.nItemId = nItemId;
   item.bState  = bState;
   item.dwDataSize = dwDataSize;
   memcpy((void*)packet.PacketData, (void*)&item, sizeof(item));
   packet.iPacketId = GetTickCount();
   if(SendAdvertisement(packet))
   {
      BYTE *TempBuf = new BYTE[dwDataSize];
      memcpy(TempBuf, lpBuffer, dwDataSize);

      DWORD pos = 0;
      DWORD BytesLeft = dwDataSize;
      while(BytesLeft)
      {
         TOverlappedAdvertisement item;
         packet.uiAdvertisePacketType = ADVERTISE_OVERLAPPED;
         packet.bOverlappedResult = BytesLeft > 1020;
         item.iPacketId = packet.iPacketId;
         memcpy((void*)item.Data, (void*)&TempBuf[pos], (BytesLeft >= 1020 ? 1020 : BytesLeft));
         memcpy((void*)packet.PacketData, (void*)&item, sizeof(item));
         packet.dwPacketLength = sizeof(item);
         packet.uiReplyType++;
         if(SendAdvertisement(packet))
         {
            if(BytesLeft >= 1020)
            {
               BytesLeft -= 1020;
               pos += 1020;
            }
            else
            {
               BytesLeft = 0;
            }
         }
      }
      delete TempBuf;
   }
   return true;
}

void CAdvertiseServer::Shutdown()
{
   TAdvertiseServerPacket packet;
   packet.uiAdvertisePacketType = ADVERTISE_SHUTDOWN;
   SendAdvertisement(packet);
}
LarryF
slashmais
Wish I could accept two answers: or at least be able to share the bounty over multiple answers.
slashmais
+3  A: 

I've solved this problem a few times being in network management. Your basic concern seems to be "Discovery", how do your apps discover each other.

Honestly the easiest way is to know your IP address & a mask (most are class c), and to try to connect to each machine in that class c.

If you default to class C, that means that it'll just about always work for most networks. You can then allow overrides where you add in either specific IP addresses to connect to, or additional subnets.

To discover a class C, you just figure out your IP address (let's say 192.168.2.77), then iterate over everything in 192.168.2.(1-254), trying to open a connection to each.

I've done it with multiple threads (you can ping all the devices at once that way and have good results within 3 seconds. I discovered a class B network in like 5 minutes with a few hundred threads!), or you can just go from one to the next in a single thread--but if you do that make sure your timeout is really low (1/2 a second or so) otherwise it will take forever--even at 1/2 second it will take a minute to make the rounds.

You probably also want to leave the discovery thread running in the background at a lower speed, always searching for new mates.

And cache your "Known good" IP addresses for quicker startup.

It's not a hard problem, but it's not trivial either. Just expect to do a little legwork.

Also, you probably want to be able to add a new IP/Mask to scan an external subnet. There is just no way around this if you want to contact devices on the internet (although once one PC discovers a net, it can send the address to all the others if you want, and that could grow really big really quick!)

Bill K
I don't think your approach is very sound. You can't attempt to connect to EVERY machine in your class. Using my method above to do a UDP "Identity Broadcast" is the best way, as any machine RUNNING the app will respond with "I'm here, and I'm IP x.x.x.x", and then you can directly connect...
LarryF
I did it quite a bit, like I said, even a class B (65000 addresses) can be discovered in a reasonable amount of time. UDP broadcasts tend to be blocked by some switches and don't scale outside a subnet (and aren't always implemented)
Bill K
Yes, that IS one of the drawbacks to using the datagram like I show above. But, even that can be worked around if necessary... Hell, you COULD just run a dispatch service on one of your known servers, and have all apps report to it when they are running.
LarryF
I guess we were interested in making stuff that just worked. We're making $10,000,000 packages for telcoms and other content providers (cable, network) to use to manage their entire networks--so having stuff that Just worked in a consistent, reliable manner was a big plus.
Bill K
+1  A: 

Ok - so MQ and that type of thing sound like over-kill.

My understanding of your app:

Desktop app running on multiple machines on the same network - have their own databases, need to discover each other.

Why not:

1) UDP broadcast / listen on a regular basis to "find other machines on the same network" - example in Java: http://java.sun.com/docs/books/tutorial/networking/datagrams/index.html

2) Use SSL sockets for actual communication after discovery:
http://stilius.net/java/java_ssl.php....
http://www.exampledepot.com/egs/javax.net.ssl/Client.html

yes, this is where it looks like i must go ...
slashmais
ps: i don't really do java ... :-o
slashmais
A: 

There is good article about P2P with WCF here http://msdn.microsoft.com/en-us/magazine/cc188685.aspx. It provides code but assumes .Net3, Wcf, Vista and above

judek
+1  A: 

Have you considered using a Bittorrent typed setup?

The communication principals used should give you a fairly solid base for building your application. All you need is for two nodes to know about each other and then it builds from there. I use MonoTorrent to run a private (100-node) data network, an RSS feed to announce what files need to be where (modified version of Wordpress) and doing everything in SSH tunnels. I do have a central server which manages the network, but that could easily live on any one of my 100 nodes. Using a dynamic DNS service, the first node alive sets up it's own tracker as a backup if my server goes down.

You can use XML files as your messaging scheme, or modify the transmission of the Bittorrent network to transmit data packets directly to your app. I think the concept of what you are looking for is in Bittorrent. The first node to fire up would reclaim the Dynamic DNS entry (DynDNS has a fairly easy to use API) if there wasn't an active host on the network. (There is a downside... I've run into sync issues when two trackers fire up within the TTL window)

There are quite a few references to SSH tunneling out there, I just use this one because of the fun diagrams. SSH tunneling isn't the most efficient method available but it is a very nice alternative to having to programatically wrap your communications in an SSL tunnel.

I know the thoughts are kind of jumbled, I just hope it gives you a little help in pointing yourself in the right direction. PS... for a fully portable solution you could run this in Java or .Net (running under Mono.. I have AppleTVs running Mono even). Then OS could even a flexible part of your operation.

thaBadDawg