select() blocks until keypress, then returns 1 thereafter

select() blocks until keypress, then returns 1 thereafter

Post by Todd M. Gri » Mon, 12 Oct 1998 04:00:00



I am writing this to make sure this info gets out there so that other
people can find the solution faster than I did. I truned up several
people with postings asking about this problem but no one with the
answer I needed.

THE PROBLEM

Running on Windows NT 4.0 using the Cygnus g++ (gnu c++) version b19, I
ran with two processes communicating with sockets. One is the server
(receiver in this case) and the other the client (sender).  My
receiving process was hanging on select() at the start even though
there was data. When a key (even shift) was pressed in the shell window
it would unhang and consume the already waiting data. The other problem
was that all calls to select() after that returned with a value of 1
even when there was no data on the socket. This made my program go
ahead and try a read which had the undesirable effect of blocking.

WHAT WAS HAPPENING

It turns out that at run-time or maybe at link time, the select() that
is used is the unix kind that that uses the bit mask to specify what
file descriptors are being watched. However, the include files I was
using use some kind of Windows version that uses a structure with an
array in it.  So, when the bitmask version tried to use the structure I
was passing it, it looked like a bitmask that wanted it to check stdin
all the time.

THE SOLUTION

Simply use the right include files with the bitmask macros (FD_SET,
FD_ZERO, etc.). That way what I build and pass really is the bitmask
and it works just fine.

DEMOS

Below are my simple demo programs that do it right now. These may be
useful to someone looking for a starting point or to see what they do
for includes that works.

// ---------------------------------------- begin file SimpleServer.C

// compile command: gcc -o SimpleServer SimpleServer.C -lstdc++
// on Solaris you may also need: -lsocket -lnsl

// using CenterLine C++:
//   /vol/CenterLine-2.1.1/bin/CC -o SimpleServer SimpleServer.C -lsocket

// test instructions:
//
// 1. compile both this file and the SimpleClient.C
// 2. run SimpleServer first in a bash or DOS window
// 3. run SimpleClient in another bash or DOS window
// 4. they connect and SimpleClient starts sending messages/sleeping

#include <stdlib.h>
#include <stdio.h>
#include <fstream.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#ifdef WINNT

// ***************** here's where the problem was *************
// BAD includes
//#define __attribute__(x)
//#include <Windows32/Base.h>
//#include <Windows32/Sockets.h>

// GOOD includes
#include <sys/socket.h>
#include <cygwin32/in.h>

// with the "GOOD" includes I get link
// errors without this
extern "C" {
  unsigned long  htonl(unsigned long  h);
  unsigned short htons(unsigned short h);

}

#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif

// constants
#define PORT 2002

// globals for this file
static int  fd = -1;

int non_block_select(void)
{
  struct fd_set fdbits;
  FD_ZERO(&fdbits);
  FD_SET(fd, &fdbits);

  struct timeval interval; interval.tv_sec  = 0; interval.tv_usec = 0;

  int fds_ready = select(fd+1, &fdbits, (fd_set*) NULL, (fd_set*) NULL, &interval);
  cerr << "NB-----------------------------------------------------select() returned " << fds_ready << endl;

  return fds_ready;

}

int timeout_select(int seconds)
{
  struct fd_set fdbits;
  FD_ZERO(&fdbits);
  FD_SET(fd, &fdbits);

  struct timeval interval; interval.tv_sec  = seconds; interval.tv_usec = 0;

  int fds_ready = select(fd+1, &fdbits, (fd_set*) NULL, (fd_set*) NULL, &interval);
  cerr << "TO-----------------------------------------------------select() returned " << fds_ready << endl;

  return fds_ready;

}

int block_select(void)
{
  struct fd_set fdbits;
  FD_ZERO(&fdbits);
  FD_SET(fd, &fdbits);

  int fds_ready = select(fd+1, &fdbits, (fd_set*) NULL, (fd_set*) NULL, (struct timeval *) NULL);
  cerr << "B------------------------------------------------------select() returned " << fds_ready << endl;

  return fds_ready;

}

void init(void)
{

  struct sockaddr_in addr;
  memset((char *) &addr, 0, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = htonl(INADDR_ANY);
  addr.sin_port = htons(PORT);
  cerr << "connecting via port " << PORT << endl;

  int sock = socket(AF_INET, SOCK_STREAM, 0);           // create socket
  if (sock<0) { perror("creating socket"); exit(-1); }

  if (bind(sock, (struct sockaddr*) &addr, sizeof(addr))) { perror ("binding socket"); close(sock); exit(-1); }

  if (listen(sock, 1)) { perror ("listening to socket"); close(sock); exit(-1); }

  struct sockaddr remote;
  int remote_len = sizeof(remote);
  if ((fd = accept(sock, &remote, &remote_len)) == -1) { perror("accepting socket"); close(sock); exit(-1); }
  cerr << "server side connected" << endl;
  close(sock);

  cerr << "fd=" << fd << endl;//zzz
  cerr << "fd modes=" << (void*) fcntl(fd, F_GETFL) << endl;//zzz

}

int main(int, char **)
{
  char buff[1000];
  init(); // open the socket
  while(1)
  {
    //cerr << "calling timeout_select(5)" << endl;
    //timeout_select(5);
    //cout << "back from timeout" << endl;

    cout << "calling block_select()" << endl;
    block_select();

    cerr << "back from \"select()\" and going on to read" << endl;
    int bytesread = read(fd, buff, (size_t) 999);
    if (bytesread == 0) break;
    buff[bytesread] = 0;
    cerr << "read this #" << buff << "#" << endl;
  }
  cerr << "got eof" << endl;

}

// ---------------------------------------- end file SimpleServer.C

// ---------------------------------------- begin file SimpleClient.C

// compile command: gcc -o SimpleClient SimpleClient.C -lstdc++
// on Solaris you may also need: -lsocket -lnsl

// see test instructions in SimpleSerevr.C

#include <stdlib.h>
#include <stdio.h>
#include <fstream.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#ifdef WINNT

// ***************** here's where the problem was *************
// BAD includes
//#define __attribute__(x)
//#include <Windows32/Base.h>
//#include <Windows32/Sockets.h>

// GOOD includes
#include <sys/socket.h>
#include <cygwin32/in.h>

#else
#include <sys/socket.h>
#include <netinet/in.h>
#endif

#include <arpa/inet.h>

// constants
#define PORT 2002

// globals for this file
static int fd;

void init(void)
{
  struct sockaddr_in addr;
  memset((char *) &addr, 0, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  addr.sin_port = htons(PORT);
  cerr << "connecting via port " << PORT << endl;

  fd = socket(AF_INET, SOCK_STREAM, 0);         // create socket
  if (fd<0) { perror("creating socket"); exit(-1); }

  if (connect(fd, (struct sockaddr*) &addr, sizeof(addr))) { perror ("socketPrep - connecting socket"); exit(-1); }
  cerr << "socketPrep - client side connected" << endl;

}

int main (int argc, char **argv)
{
  char buff[1000];
  buff[0] = 'A';
  buff[1] = 0;
  init(); // open the socket
  cerr << "sending first message in 3 seconds" << endl; sleep(3);

  while(1)
  {
    int len = strlen(buff);
    write(fd, buff, len);
    cerr << "sent this #" << buff << "#" << endl;
    buff[len]   = 'x';
    buff[len+1] = 0;
    cerr << "sleeping" << endl;
    sleep(10);
  }

}

// ---------------------------------------- end file SimpleClient.C
 
 
 

select() blocks until keypress, then returns 1 thereafter

Post by Todd M. Gri » Mon, 12 Oct 1998 04:00:00





>>It turns out that at run-time or maybe at link time, the select() that
>>is used is the unix kind that that uses the bit mask to specify what
>>file descriptors are being watched. However, the include files I was
>>using use some kind of Windows version that uses a structure with an
>>array in it.

>A structure with an array of integers in it is actually a common way of
>representing the fd_set. This is far from unique to Windows.

>> So, when the bitmask version tried to use the structure I
>>was passing it, it looked like a bitmask that wanted it to check stdin
>>all the time.

>>THE SOLUTION

>>Simply use the right include files with the bitmask macros (FD_SET,
>>FD_ZERO, etc.). That way what I build and pass really is the bitmask
>>and it works just fine.

>Of course! These FD_ functions are the only public interface for manipulating
>fd_set objects. The type fd_set should be treated as an opaque type!  If you
>directly access fd_set without using these functions/macros, you are on
>your own, since the various standards like POSIX no longer require things
>to work properly.

I hope I wasn't that unclear. I never meant to suggest that I did or
want others to operate on those data types directly without the macros.
I was simply saying that there are two sets of include files in the
Cygwin32 I am using. One set uses bit masks in those macros and the
other uses the arrays.

I compiled with the array kind and somehow linked with the bit mask
kind. I switched to including the bitmask kind and all is working now.
I didn't change my code, only the include statements.