After some more testing, sniffing and stuff I've checked that the UDP packets are actually being sent, at least when I connect to a remote server.
My problem right now looks evident: the "select()" call for the UDP socket is not working. It times out and does not receive anything at all as it should (as I've checked that the info is being sent).
If I use only ony socket everything goes fine, but when I set up a second socket (in this case for the UDP packets) it won't receive anything.
I'm using two independent select() calls, one for each socket, and I don't know if that may be causing all the trouble. I'll post some code now.
This is the class delcaration:
#define PACKET_BUFFER_SIZE 512
#define PACKET_MAX_SIZE 512
#define IS_TIMEOUT 5
// Definition for our buffer datatype
struct buffer
{
char buffer[PACKET_BUFFER_SIZE]; // Packet buffer - 512 should be more than enough
unsigned int bytes; // Number of bytes currently in buffer
};
/**
* CInsim class to manage the Insim connection and processing of the packets
*/
class CInsim
{
private:
SOCKET sock; // TCP Socket (most packets)
SOCKET sockudp; // UDP Socket (if requested, for NLP and MCI)
byte using_udp; // 1 if we are using UDP for NLP or MCI packets
struct buffer gbuf; // Our global buffer
struct buffer lbuf; // Our local buffer
char packet[PACKET_MAX_SIZE]; // A buffer where the current packet is stored
fd_set readfd, exceptfd; // File descriptor watches
struct timeval select_timeout; // timeval struct for the select() call
struct buffer udp_gbuf; // (for NLP and MCI packets via UDP) Our global buffer
struct buffer udp_lbuf; // (for NLP and MCI packets via UDP) Our local buffer
char udp_packet[PACKET_MAX_SIZE]; // (for NLP and MCI packets via UDP) A buffer where the current packet is stored
fd_set udp_readfd, udp_exceptfd; // (for NLP and MCI packets via UDP) File descriptor watches
struct timeval udp_select_timeout; // (for NLP and MCI packets via UDP) timeval struct for the select() call
ofstream logfile;
public:
CInsim::CInsim(); // Constructor
// "int init(...)" Establishes connection with the socket and insim.
//+ The last argument ch_ver is a pointer to a IS_VER struct. If it's used an IS_VER packet
//+ will be returned. If ch_ver is not used in the call no IS_VER will be requested/returned.
int init (char *addr, word port, char *product, char *admin, struct IS_VER *pack_ver = NULL, byte prefix = 0, word flags = 0, word interval = 0, word udpport = 0);
int isclose(); // Closes connection from insim and from the socket
int next_packet(); // Gets next packet ready into "char packet[]"
char peek_packet(); // Returns the type of the current packet
void* get_packet(); // Returns a pointer to the current packet. Must be casted
int send_packet(void* packet); // Sends a packet to the host
int udp_next_packet(); // (UDP) Gets next packet ready into "char udp_packet[]"
char udp_peek_packet(); // (UDP) Returns the type of the current packet
void* udp_get_packet(); // (UDP) Returns a pointer to the current packet. Must be casted
int udp_send_packet(void* packet); // (UDP) Sends a packet to the host
};
This is the code used for establishing/connecting the two sockets and the connection to InSim:
(any var used not defined here is because it's a private member)
/**
* Initialize the socket and the Insim connection
* If "struct IS_VER *pack_ver" is set it will contain an IS_VER packet after returning. It's an optional argument
*/
int CInsim::init (char *addr, word port, char *product, char *admin, struct IS_VER *pack_ver, byte prefix, word flags, word interval, word udpport)
{
// Initialise WinSock
// Only required on Windows
WSADATA wsadata;
if (WSAStartup(0x202, &wsadata) == SOCKET_ERROR)
{
WSACleanup();
return -1;
}
// Create the TCP socket - this defines the type of socket
sock = 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 (sock == INVALID_SOCKET)
{
closesocket(sock);
WSACleanup();
return -1;
}
// Resolve the IP address
struct sockaddr_in saddr;
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
struct hostent *hp;
hp = gethostbyname(addr);
if (hp != NULL)
saddr.sin_addr.s_addr = *((unsigned long*)hp->h_addr);
else
saddr.sin_addr.s_addr = inet_addr(addr);
// Set the port number in the socket structure - we convert it from host byte order, to network
saddr.sin_port = htons(port);
// Now the socket address structure is full, lets try to connect
if (connect(sock, (struct sockaddr *) &saddr, sizeof(saddr)) < 0)
{
closesocket(sock);
WSACleanup();
return -1;
}
// If the user asked for NLP or MCI packets and defined an udpport
if ((udpport) && (flags >= ISF_NLP))
{
// Create the UDP socket - this defines the type of socket
sockudp = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
// Could we get the socket handle? If not the OS might be too busy or have run out of available socket descriptors
if (sockudp == INVALID_SOCKET)
{
closesocket(sock);
closesocket(sockudp);
WSACleanup();
return -1;
}
// Resolve the IP address
struct sockaddr_in udp_saddr;
memset(&udp_saddr, 0, sizeof(udp_saddr));
udp_saddr.sin_family = AF_INET;
struct hostent *udp_hp;
udp_hp = gethostbyname(addr);
if (udp_hp != NULL)
udp_saddr.sin_addr.s_addr = *((unsigned long*)udp_hp->h_addr);
else
udp_saddr.sin_addr.s_addr = inet_addr(addr);
// Set the UDP port number in the UDP socket structure - we convert it from host byte order, to network
udp_saddr.sin_port = htons(port);
// Connect the UDP using the same address as in the TCP socket
if (connect(sockudp, (struct sockaddr *) &udp_saddr, sizeof(udp_saddr)) < 0)
{
closesocket(sock);
closesocket(sockudp);
WSACleanup();
return -1;
}
// We are using UDP!
using_udp = 1;
}
// Ok, so we're connected. First we need to let LFS know we're here by sending the IS_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 = ISP_ISI;
if (pack_ver != NULL) // We request an ISP_VER if the caller asks for it
isi_p.ReqI = 1;
isi_p.Prefix = prefix;
isi_p.UDPPort = udpport;
isi_p.Flags = flags;
isi_p.Interval = interval;
memcpy(isi_p.IName, product, sizeof(isi_p.IName)-1);
memcpy(isi_p.Admin, admin, 16);
// Send the initialization packet
if(send_packet(&isi_p) < 0)
{
if (using_udp)
closesocket(sockudp);
closesocket(sock);
WSACleanup();
return -1;
}
// If an IS_VER packet was requested
if (pack_ver != NULL)
{
if (next_packet() < 0) // Get next packet, supposed to be an IS_VER
{
if (isclose() < 0)
{
if (using_udp)
closesocket(sockudp);
closesocket(sock);
WSACleanup();
return -1;
}
return -1;
}
switch (peek_packet()) // Check if the packet returned was an IS_VER
{
case ISP_VER: // It was, get it!
memcpy(pack_ver, (struct IS_VER*)get_packet(), sizeof(struct IS_VER));
break;
default: // It wasn't, something went wrong. Quit
if (isclose() < 0)
{
if (using_udp)
closesocket(sockudp);
closesocket(sock);
WSACleanup();
}
return -1;
}
}
return 0;
}
Here's the code for retrieving TCP packets. This seems to work fine in all cases:
/**
* Get next packet ready
* This function also keeps the connection alive as long as you keep calling it
*/
int CInsim::next_packet()
{
unsigned char oldp_size, p_size;
bool alive = true;
while (alive) // Keep the connection alive!
{
alive = false;
oldp_size = (unsigned char)*lbuf.buffer;
if ((lbuf.bytes > 0) && (lbuf.bytes >= oldp_size)) // There's an old packet in the local buffer, skip it
{
// Copy the leftovers from local buffer to global buffer
memcpy(gbuf.buffer, lbuf.buffer+oldp_size, lbuf.bytes-oldp_size);
gbuf.bytes = lbuf.bytes - oldp_size;
// Copy from global buffer back to the beginning of local buffer
memset(lbuf.buffer, 0, PACKET_BUFFER_SIZE);
memcpy(lbuf.buffer, gbuf.buffer, gbuf.bytes);
lbuf.bytes = gbuf.bytes;
}
p_size = (unsigned char)*lbuf.buffer;
while ((lbuf.bytes < p_size) || (lbuf.bytes < 1)) // Read until we have a full packet
{
// Set the timeout period
select_timeout.tv_sec = IS_TIMEOUT;
select_timeout.tv_usec = 0;
// 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(sock, &readfd);
FD_SET(sock, &exceptfd);
int rc = select(sock + 1, &readfd, NULL, &exceptfd, &select_timeout);
if (rc == 0) // Timeout
continue;
if (rc < 0) // An error occured
return -1;
if (FD_ISSET(sock, &readfd)) // We got data!
{
// Recieve any waiting bytes
int retval = recv(sock, lbuf.buffer + lbuf.bytes, PACKET_BUFFER_SIZE - lbuf.bytes, 0);
// Deal with the results
if (retval == 0) // Connection has been closed at the other end
return -2;
if (retval < 0) // An error ocurred
return -1;
p_size = *lbuf.buffer;
lbuf.bytes += retval;
}
else if (FD_ISSET(sock, &exceptfd)) // An exception occured - we want to quit
return -1;
}
memset(packet, 0, PACKET_MAX_SIZE);
memcpy(packet, lbuf.buffer, p_size);
logfile << "UDP, ISP_" << (int)peek_packet() << ", " << (float)clock()/CLOCKS_PER_SEC << ", recv" << endl;
if ((peek_packet() == ISP_TINY) && (*(packet+3) == TINY_NONE))
{
alive = true;
struct IS_TINY keepalive;
keepalive.Size = sizeof(struct IS_TINY);
keepalive.Type = ISP_TINY;
keepalive.ReqI = 0;
keepalive.SubT = TINY_NONE;
// Send it back
if (send_packet(&keepalive) < 0)
return -1;
}
}
return 0;
}
And here's the code for retrieving UDP packets. This is the function that is giving me problems:
/**
* Get next UDP packet ready
*/
int CInsim::udp_next_packet()
{
unsigned char oldp_size, p_size;
oldp_size = (unsigned char)*udp_lbuf.buffer;
if ((udp_lbuf.bytes > 0) && (udp_lbuf.bytes >= oldp_size)) // There's an old packet in the local buffer, skip it
{
// Copy the leftovers from local buffer to global buffer
memcpy(udp_gbuf.buffer, udp_lbuf.buffer+oldp_size, udp_lbuf.bytes-oldp_size);
udp_gbuf.bytes = udp_lbuf.bytes - oldp_size;
// Copy from global buffer back to the beginning of local buffer
memset(udp_lbuf.buffer, 0, PACKET_BUFFER_SIZE);
memcpy(udp_lbuf.buffer, udp_gbuf.buffer, udp_gbuf.bytes);
udp_lbuf.bytes = udp_gbuf.bytes;
}
p_size = (unsigned char)*udp_lbuf.buffer;
while ((udp_lbuf.bytes == 0) || (udp_lbuf.bytes < p_size)) // Read until we have a full packet
{
// Set the timeout period
udp_select_timeout.tv_sec = IS_TIMEOUT;
udp_select_timeout.tv_usec = 0;
// Clear them
FD_ZERO(&udp_readfd);
FD_ZERO(&udp_exceptfd);
// Set them to watch our socket for data to read and exceptions that maybe thrown
FD_SET(sockudp, &udp_readfd);
FD_SET(sockudp, &udp_exceptfd);
int rc = select(sockudp + 1, &udp_readfd, NULL, &udp_exceptfd, &udp_select_timeout);
if (rc == 0) // Timeout
continue;
if (rc < 0) // An error occured
return -1;
if (FD_ISSET(sockudp, &udp_readfd)) // We got data!
{
// Recieve any waiting bytes
int retval = recv(sockudp, udp_lbuf.buffer + udp_lbuf.bytes, PACKET_BUFFER_SIZE - udp_lbuf.bytes, 0);
// Deal with the results
if (retval < 0) // An error ocurred
{
return -1;
}
p_size = *udp_lbuf.buffer;
udp_lbuf.bytes += retval;
}
else if (FD_ISSET(sockudp, &udp_exceptfd)) // An exception occured - we want to quit
return -1;
}
memset(udp_packet, 0, PACKET_MAX_SIZE);
memcpy(udp_packet, udp_lbuf.buffer, p_size);
logfile << "UDP, ISP_" << (int)udp_peek_packet() << ", " << (float)clock()/CLOCKS_PER_SEC << ", recv" << endl;
return 0;
}
I'm not sure if I'm doing something wrong with the sockets. If anyone is willing to check the code to help me I can provide tha full example and library code, executables, dlls and whatever you might want to give it a try.