Assuming you're just writing for Windows, there are a few options you can go down for writing an InSim application based around C++. You can go down the route of using the WSA* functions, which provide a higher level abstraction to using sockets, you can use whats known as the 'berkeley socket functions' which are designed to be compatible with the berkeley socket api (which was introduced in non-Windows OS' a long time ago and then later brought into Windows when networking first appeared to help port programs across), or you could use someone else's socket abstraction.
Unfortunately there aren't any publically available InSim libraries out there for C++, that I'm aware of. lib_insim, which I created for use in luaLFS and a few other projects I've not released is probably closer than anything else, however it is written in C and might be a tad tricky to understand. It's also not 100% complete. (Un)Fortunately I, currently, steer well clear of C++ so anything I produce might not compile for whatever small reason.
A very simple, inline insim client, for Windows using the berkeley functions, might look something like the following.
Warning: This is completely untested, uncompiled (at least in this form). I cannot be held accountable for any and all damage that may or may not occur through any cock up of mine or your own in relation to this code.
#include <windows.h>
#include <time.h>
#include <stdio.h>
// insim.h is the actual InSim.txt file renamed - it's actually a C++ header file
#include "insim.hpp"
#define IS_ADDR "localhost"
#define IS_PORT 29999
#define IS_ADMIN "AdminPwd"
#define IS_TIMEOUT 5
#define PRODUCT_FULL "This app"
#define PACKET_BUFFER_SIZE 512
struct buffer_t
{
// Packet buffer - 512 should be more than enough
char buffer[PACKET_BUFFER_SIZE];
// Number of bytes currently in buffer
unsigned int bytes;
};
int main (int argc, char *argv[])
{
// Our global buffer
struct buffer_t gbuf;
// We need to zero it out
memset(gbuf.buffer, 0, PACKET_BUFFER_SIZE);
gbuf.bytes = 0;
// Our socket fd
SOCKET s;
struct sockaddr_in saddr;
// Initialise WinSock
WSADATA wsadata;
if (WSAStartup(0x202, &wsadata) == SOCKET_ERROR)
{
WSACleanup();
return WSAGetLastError();
}
// Create the socket - this defines the type of socket - in this instance a TCP one
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Could we get the socket handle? If not the OS might be too busy or have run out of available socket descriptors
if (s == INVALID_SOCKET)
return -1;
// Clear the socket address structure for later
memset(&saddr, 0, sizeof(saddr));
// Set the type of socket we're using
saddr.sin_family = AF_INET;
// We find the address to connect to the InSim client
struct hostent *hp;
// We first try to use DNS (i.e. localhost would resolve to 127.0.0.1)
hp = gethostbyname(IS_ADDR);
// We managed to resolve it, lets put the binary representation into the socket address structure
// If not, we assume that someone put in an IP - lets convert that
if (hp != NULL)
saddr.sin_addr.s_addr = *((unsigned long*)hp->h_addr);
else
saddr.sin_addr.s_addr = inet_addr(IS_ADDR);
// Set the port number in the socket structure - we convert it from host byte order, to network
// We do this for compatibility reasons
// Ideally, we should do this for all numbers that get transmitted over the network
// However, in the instance of LFS, this isn't really applicable as LFS only runs natively on
// little endian processors
// Sorry if this is a little technical, but it's worth pointing out
saddr.sin_port = htons(IS_PORT);
// Now the socket address structure is full, lets try to connect
if (connect(s, (struct sockaddr *) &saddr, sizeof(saddr)) < 0)
{
fprintf(stderr, "Connection to '%s' failed\n", IS_ADDR);
return -1;
}
// Ok, so we're connected. First we need to let LFS know we're here by sending the
// ISI packet
struct IS_ISI isi_p;
memset(&isi_p, 0, sizeof(struct IS_ISI));
isi_p.size = sizeof(struct IS_ISI);
isi_p.type = 1;
isi_p.udpport = 0;
isi_p.flags = 0;
isi_p.interval = 0;
memcpy(isi_p.iname, PRODUCT_FULL, strlen(PRODUCT_FULL));
memcpy(isi_p.admin, IS_ADMIN, 16);
if (send(s, (const char *)&isi_p, sizeof(struct IS_ISI), 0) < 0)
return -1;
// And now the version packet
struct IS_TINY is_v;
memset(&is_v, 0, sizeof(struct IS_TINY));
is_v.size = sizeof(struct IS_TINY);
is_v.type = ISP_TINY;
is_v.subT = TINY_VER;
is_v.reqI = 1;
if (send(s, (const char *)&is_v, sizeof(struct IS_TINY), 0) < 0)
return -1;
// Now this is where it gets complex - We need to do a lot of shifting twiddling that might be
// a little complicated to follow
int ok = 1;
while (ok > 0)
{
int rc;
// Set the timeout period
struct timeval select_timeout = IS_TIMEOUT;
// Setup the file descriptor watches
fd_set readfd, exceptfd;
// Clear them
FD_ZERO(&readfd);
FD_ZERO(&exceptfd);
// Set them to watch our socket for data to read and exceptions that maybe thrown
FD_SET(s, &readfd);
FD_SET(s, &exceptfd);
// Select will watch the sockets specificed in each FD_SET, and trigger after a timeout
rc = select(s + 1, &readfd, NULL, &exceptfd, &select_timeout);
// Fire any prerecv stuff here that you want wish to do
// Select output handling
if (rc < 0)
{
// An error occured
ok = -1;
}
else if (rc > 0)
{
// We got data or an exception
if (FD_ISSET(s, &readfd))
{
// We got data!
// make local temp buffer
char tbuf[PACKET_BUFFER_SIZE];
memset(&tbuf, 0, PACKET_BUFFER_SIZE);
// bytes read
unsigned int bread = 0;
// copy any half packet into the local buffer
if (gbuf.bytes > 0)
{
bread = gbuf.bytes;
memcpy(tbuf, gbuf.buffer, gbuf.bytes);
//gbuf.bytes = 0;
// clear left overs
memset(gbuf.buffer, 0, PACKET_BUFFER_SIZE);
}
// recv waiting bytes
int retval = recv(s + 1, tbuf + bread, PACKET_BUFFER_SIZE - (unsigned int)bread, 0);
// deal with recv
if (retval == 0)
{
// Connection has been closed at the other end
return -1;
}
else if (retval > 0)
{
// This is where it will get very tricky to follow, if you don't understand pointers
// The packetisation process is:
// read size of packet
// check to see if buffer doesnt exceed the limits, and has a packet size. loop until not satisfied
// read packet into lua
// increase number of bytes read
// check remaining sizes and
// copy remaining into global buffer and set number of bytes
char *p = tbuf;
bread = 0;
// packet size
unsigned char psize;
unsigned int tbytes = gbuf.bytes + retval;
gbuf.bytes = 0;
while ((PACKET_BUFFER_SIZE >= (tbytes - bread - (unsigned char)*p)) && ((tbytes - bread - (unsigned char)*p) >= 0) && ((unsigned char)*p > 0))
{
psize = (unsigned char)*p;
// Shouldn't be possible
if (psize == 0)
return -1;
// Deal with your packets here
unsigned int packet_id = *((char *)p+1);
if (packet_id == ISP_TINY)
{
// It's an ISP_TINY!
struct IS_TINY *t = data;
if ((t->reqI == 0) && (t->subT == TINY_NONE))
{
// It's a keepalive packet! We need to respond
printf("Ping? ");
struct IS_TINY keepalive_response;
memset(&keepalive_response, 0, sizeof(struct IS_TINY));
keepalive_response.size = sizeof(struct IS_TINY);
keepalive_response.type = ISP_TINY;
// Send it back
if (send(s, (const char *)&keepalive_response, sizeof(struct IS_TINY), 0) < 0)
ok = -1;
printf("Pong!\n");
}
}
// Increment the pointer and the number of bytes we've read
p += psize;
bread += psize;
}
// How many bytes have we left?
int tout = (unsigned char)(tbytes - bread);
// Oh dear, there's been a cock up - This SHOULD NOT be possible unless the TCP stream got
// screwed about
if (tout < 0)
ok = -1;
// This should not be possible either
if (tout > PACKET_BUFFER_SIZE)
ok = -1;
// Set the number of bytes in the global buffer
gbuf.bytes = (unsigned char)(tout);
// Copy the bytes from the local buffer into the global one
// Note: We do not zero it first for a slight speed increase on very busy
// servers and because we know how many true bytes we should have because we set it previously
memcpy(gbuf.buffer, tbuf+bread, gbuf.bytes);
}
}
else if (FD_ISSET(s, &exceptfd))
{
// An exception occured - we want to quit
ok = -1;
}
}
}
// We've been disconnected - in this example we don't know if this was because of a network
// error, or because we quit, but lets send the InSim close anyway
struct IS_TINY is_close;
memset(&p, 0, sizeof(struct IS_TINY));
is_close.size = sizeof(struct IS_TINY);
is_close.type = ISP_TINY;
is_close.reqI = 0;
is_close.subT = 2;
if (send(s, (const char *)&is_close, sizeof(struct IS_TINY), 0) < 0)
return -1;
// Clean up
closesocket(s);
WSACleanup();
return 0;
}
Now be aware that:
1. I have not tested this specific source - it is mostly a rip out of lib_insim and into inline code - it should work, but there may be things that I've missed. I apologise if I did, but I've been up for a very long time today
2. I've not angled it towards C++ specifically. You'd really want to turn this lot into objects and so on
3. Theres nothing in there about checking the version of InSim
4. Theres nothing in there about checking to see if we get any response from LFS at all
5. You need to include InSim.txt (by renaming it to insim.hpp) - other fiddling might be required
6. Things like the address, port number, etc. are hard coded - really they should be user input-able
7. There is no GUI
In simple terms it is about as basic as you can get - in C-ish (ignoring the fact that the stock insim header will not compile under a strict C compiler, but thats another, long, story) - that will "work" (see the above notes).
If you want to see something more sensible around this code you might want to look at the "recent" release to luaLFS (source is included, but not as well commented).
Now you might find this overwhelming, especially if you're more familiar with the MFC or something similar, in which case I apologise as it is a little heavy. However, if you choose to use something low level like C, C++, etc. you will have to deal with things like pointers. On the other hand you have to do practically no work to actually construct the packets, as you're talking the same native language as LFS (french! No.. Wait... Why did I pick french. Bugger. Maybe I've taken this analogy too far. Time to ditch it I'm afraid.).
Edit: I will point out that I was working on something called "modInSim" at one point a few months ago. This is basically like luaLFS; a dumb client that just sits there doing nothing but connecting and responding to keepalive packets. The client would beable to load multiple modules, dll/so files, which would beable to talk to LFS by sending packets, subscribing to the data received from LFS. If anyone is interested I may restart this project as you may find it interesting to use - if you're using C, or some other lower level language. It would also be possible to quite easily inject third party languages, much like I did with luaLFS, into modInSim using the current model. The only problem was configuration syntax and allowing modules to read the config data.