summaryrefslogtreecommitdiff
path: root/tests/IOStream_Test.cpp
blob: 7966f9e4082984f7ff5afd80b3fd36332378391c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
// $Id$

// ============================================================================
//
// = LIBRARY
//    tests
//
// = FILENAME
//    IOStream_Test.cpp
//
// = DESCRIPTION
//    This is a simple test of the IOStream class that illustrates
//    how to use iostream operations on almost arbitrary I/O classes.
//
// = AUTHOR
//    James CE Johnson <jcej@lads.com>
//
// ============================================================================

#include "test_config.h"
#include "ace/Thread.h"
#include "ace/Acceptor.h"
#include "ace/SOCK_Connector.h"
#include "ace/SOCK_Acceptor.h"
#include "ace/IOStream.h"
#include "ace/OS_NS_sys_wait.h"

ACE_RCSID (tests, IOStream_Test, "$Id$")

#if !defined (ACE_LACKS_ACE_IOSTREAM)
#  include "ace/OS_NS_unistd.h"
#  include "ace/os_include/os_ctype.h"  // Needed for isspace() function

typedef ACE_IOStream<ACE_SOCK_Stream> ACE_SOCK_IOStream;

/* The biggest drawback to an iostream is that it generally
   eats up whitespace when performing a get (>>) operation.

   That may be good if you're reading non-textual data but
   if you're trying to read a stream of words with embedded
   whitespace, it isn't going to be pleasant.

   If you've been blessed with the GNU String class, I've
   already provided a derived class, QuotedString, that
   makes dealing with strings very easy.

   If you're stuck with an array of characters then you
   will probably need somthing like I have below.

   On the other hand, one of the biggest advantages to an
   iostream is that it eats up whitespace :-)

   If you put (<<) your non-textual data to the iostream
   with any number of whitespace between the data then
   you can easily get (>>) the data from the iostream
   without having to worry about delimeters and such.

   The main thing to keep in mind when using an iostream
   between peers is that you MUST keep the data "fields"
   in sync.  That is, if the "putter" puts an int followed
   by a float followed by a double, you must make sure
   that the "getter" will be attempting to get an int
   then a float then a double.
 */

// Since I can't rely on GNU's String class being everywhere (yet),
// here's a simple class that will work with quoted strings.  Use at
// your own risk!  It is very incomplete!

class qchar
{
public:
  qchar (void) { c_ = '\0'; }

  qchar (char c) : c_ (c) { };

  operator char () const { return c_; }

  qchar operator= (char c) { return c_ = c; }

  bool operator== (char c) { return c_ == c; }

  friend ACE_SOCK_IOStream &operator>> (ACE_SOCK_IOStream & stream, qchar * buf);
  friend ACE_SOCK_IOStream &operator<< (ACE_SOCK_IOStream & stream, qchar * buf);
  friend ostream &operator<< (ostream & stream, qchar * buf);

private:
  char c_;
};

// This is taken almost directly from the QuotedString object that has
// been derived from GNU's String class.  The advantage to using
// QuotedString is that it is MUCH more robust than qchar will every
// be.

ACE_SOCK_IOStream &
operator>> (ACE_SOCK_IOStream & stream, qchar *buf)
{
  char c;

  *buf = '\0';  // Initialize the string

  stream.get (c);

  if (!stream) // eat space up to the first char
    return stream;

  // if we don't have a quote, append until we see space
  if (c != '"')
    for (*buf++ = c;
#ifdef CHORUS
         stream.get (c) && !isspace (c);
#else
 (void *) stream.get (c) && !isspace (c);
#endif /* CHORUS */
         *buf++ = c)
      continue;
  else
#ifdef CHORUS
    for (; stream.get (c) && c != '"'; *buf++ = c)
#else
    for (; (void *) stream.get (c) && c != '"'; *buf++ = c)
#endif /* CHORUS */
      if (c == '\\')
        {
          stream.get (c);
          if (c != '"')
            *buf++ = '\\';
        }

  *buf = '\0';

  return stream;
}

ACE_SOCK_IOStream &
operator<< (ACE_SOCK_IOStream &stream, qchar *buf)
{
  stream.put ('"');

  while (*buf)
    {
      if (*buf == '"')
        stream.put ('\\');

      stream.put ((char) *buf++);
    }

  stream.put ('"');

  return stream;
}

ostream &
operator<< (ostream &stream, qchar *buf)
{
  while (*buf)
    stream.put ((char) *buf++);

  return stream;
}

// Our client thread will initiate the test by sending some data to
// the server.

static void *
client (void *arg = 0)
{
  ACE_UNUSED_ARG (arg);

  // We don't _need_ to dynamically allocate the ACE_SOCK_IOStream.
  // But if we don't, it doesn't get destroyed on some platforms,
  // e.g., g++ 2.7.2.1 and Sun C++ 4.2 on Solaris 2.5.1. (It does work
  // on Linux, so the code seems fine.)  If we manage the storage
  // ourselves, we _will_ destroy it at the end of this function.
  ACE_SOCK_IOStream server;

  ACE_INET_Addr *remote_addr = (ACE_INET_Addr *) arg;
  ACE_INET_Addr addr (remote_addr->get_port_number (),
                      ACE_DEFAULT_SERVER_HOST);
  ACE_SOCK_Connector connector;

  if (connector.connect (server, addr) == -1)
    ACE_ERROR_RETURN ((LM_ERROR,
                       ACE_TEXT (" (%t) %p\n"),
                       ACE_TEXT ("Failed to connect to server thread")),
                      0);

  // Send a string to the server which it can interpret as a qchar[]
  const char *str = "\"This is a test     string.\"";
  ACE_DEBUG ((LM_DEBUG, ACE_TEXT (" (%P|%t) Client Sending: (%s)\n"),
              ACE_TEXT_TO_TCHAR_IN (str)));
  server << str << endl;

  // Allow the server to get the string and echo it to the user. (The
  // iostream doesn't need this, but humans do :)
  ACE_OS::sleep (2);

  // Send another string but this time the server will read it as a
  // char[].  Notice how the server's output doesn't include all of
  // the spaces sent by the client.

  str = "\"THIS IS A     TEST STRING.\"";
  ACE_DEBUG ((LM_DEBUG,
              ACE_TEXT (" (%P|%t) Client Sending: (%s)\n"),
              str));
  server << str << endl;

  // Again, give the server time to display the happenings to the
  // user.
  ACE_OS::sleep (2);

  // Read from the server an int, float, long, float double.  The
  // iostream will pull them out by using the whitespace provided by
  // the server.

  ACE_DEBUG ((LM_DEBUG,
              ACE_TEXT (" (%P|%t) Client Receiving\n")));

  ACE_Time_Value timeout (2);
  ACE_Time_Value *timeoutp = &timeout;

  server >> timeoutp;

  int i;
  float f1, f2;
  long l;
  double d;

  while (! (server >> i))
    {
      int eof = server.eof ();
      if (eof)
        {
          ACE_DEBUG ((LM_DEBUG,
                      ACE_TEXT (" (%P|%t) Unrecoverable stream error/eof\n")));
          break;
        }
      else
        {
          ACE_DEBUG ((LM_DEBUG,
                      ACE_TEXT (" (%P|%t) Recoverable stream error/timed out)\n")));
          server.clear (0);
        }
    }

  server >> f1;
  server >> l;
  server >> f2;
  server >> d;

  ACE_DEBUG ((LM_DEBUG,
              ACE_TEXT (" (%P|%t) Client Received: int %d float %f long %d float %f double %f\n"),
              i,
              f1,
              (int) l,
              f2,
              d));

  // Check for proper received values.
  ACE_ASSERT (i == 1  && (f1 >= 0.123420 && f1 <= 0.123422)
              && l == 666555444  && (f2 >= 23.44 && f2 <= 23.46)
              && (d >= -47.1e+9 && d <= -45.9e+9));
  // Reset the precision to limit ourselves to two significant digits.
  server.precision (2);

  // Now, make a little change & send 'em back.
  i  *= -1; server << i  << " ";
  f1 *= -1.0; server << f1 << " ";
  l  *= -1; server << l  << " ";
  f2 *= -1.0; server << f2 << " ";
  d  *= -1; server << d  << " ";
  server << endl;

  // Shut down the test.
  server.close ();

  return 0;
}

// Test the server's ability to receive data from the client and then
// begin a two-way conversation.

static void *
server (void *arg = 0)
{
  // We don't _need_ to dynamically allocate the ACE_SOCK_IOStream.
  // But if we don't, it doesn't get destroyed on some platforms,
  // e.g., g++ 2.7.2.1 and Sun C++ 4.2 on Solaris 2.5.1. (It does work
  // on Linux, so the code seems fine.)  If we manage the storage
  // ourselves, we _will_ destroy it at the end of this function.
  ACE_SOCK_IOStream client_handler;

  ACE_INET_Addr server_addr;
  ACE_SOCK_Acceptor *acceptor =
    reinterpret_cast<ACE_SOCK_Acceptor *> (arg);

  if (acceptor->get_local_addr (server_addr) == -1)
    ACE_ERROR_RETURN ((LM_ERROR,
                       ACE_TEXT ("%p\n"),
                       ACE_TEXT ("get_local_addr")),
                      0);

#if defined (ACE_HAS_THREADS)
  if (ACE_Thread_Manager::instance ()->spawn (ACE_THR_FUNC (client),
                                              (void *) &server_addr,
                                              THR_NEW_LWP | THR_DETACHED) == -1)
    ACE_ERROR_RETURN ((LM_ERROR,
                       ACE_TEXT (" (%t) %p\n"),
                       ACE_TEXT ("spawing client thread")),
                      0);
#endif /* ACE_HAS_THREADS */

  if (acceptor->accept (client_handler) == -1)
    ACE_ERROR_RETURN ((LM_ERROR,
                       ACE_TEXT (" (%P|%t) Failed to accept new client_handler")),
                      0);

  // Read a qbuf[] from the client.  Notice that all of the client's
  // whitespace is preserved.
  qchar qbuf[BUFSIZ];
  ACE_OS::memset (qbuf, 0, sizeof qbuf);
  client_handler >> qbuf;

  ACE_DEBUG ((LM_DEBUG,
              ACE_TEXT (" (%P|%t) Server Received: (\"%s\")\n"),
              ACE_TEXT_TO_TCHAR_IN ((char *) qbuf)));

  // Give the client time to announce the next test to the user.
  ACE_OS::sleep (2);

  // Now we try to use a char[] to get a string from the client.
  // Compared to the method above, this is quite messy.  Notice also
  // that whitespace is lost.

#if defined (ACE_HAS_STRING_CLASS) && defined (ACE_HAS_STANDARD_CPP_LIBRARY)
  ACE_IOStream_String buf;
  ACE_DEBUG ((LM_DEBUG,
              " (%P|%t) Server Received: ("));

  while (client_handler &&
 (buf.length () == 0 || buf[buf.length () - 1] != '"'))
    {
      if (! (client_handler >> buf))
        break;

      if (buf.length () > 0)
        ACE_DEBUG ((LM_DEBUG,
                    "%s ",
                    buf.c_str ()));
    }

  ACE_DEBUG ((LM_DEBUG,
              ")\n"));
#else
  char buf[BUFSIZ];
  ACE_OS::memset (buf, 0, sizeof buf);
  ACE_DEBUG ((LM_DEBUG,
              ACE_TEXT (" (%P|%t) Server Received: (")));

  while (ACE_OS::strlen (buf) == 0
         || buf[ACE_OS::strlen (buf) - 1] != '"')
    {
      if (! (client_handler >> buf))
        break;
      ACE_DEBUG ((LM_DEBUG,
                  ACE_TEXT ("%s "),
                  ACE_TEXT_TO_TCHAR_IN (buf)));
    }

  ACE_DEBUG ((LM_DEBUG,
              ACE_TEXT (")\n")));
#endif /* ACE_HAS_STRING_CLASS */

  // Send some non-textual data to the client.  We use a single
  // character to separate the fields but we could have used any valid
  // whitespace.  The data will be sent if the iostream's buffer gets
  // filled or when we flush it with an explicit client.sync ()
  // command or the implicit <<endl.

  ACE_DEBUG ((LM_DEBUG,
              ACE_TEXT (" (%P|%t) Server sleeping\n")));
  ACE_OS::sleep (5);

  ACE_DEBUG ((LM_DEBUG,
              ACE_TEXT (" (%P|%t) Server Sending:  1 .12342134 666555444 23.45 -46.5e9 \n")));
  client_handler << 1 << " ";
  client_handler << .12342134 << " ";
  client_handler << 666555444 << " ";
  client_handler << 23.45 << " ";
  client_handler << -46.5e9 << " ";
  client_handler << endl;

  // The client will have changed the sign of each data field and sent
  // 'em all back to us.  At the same time, the client used the
  // precision () function to change the significant digits for
  // non-integer values.
  int i;
  float f1, f2;
  long l;
  double d;
  client_handler >> i >> f1 >> l >> f2 >> d;

  ACE_DEBUG ((LM_DEBUG,
              ACE_TEXT (" (%P|%t) Server Received: int %d float %f long %d float %f double %f\n"),
              i,
              f1,
              (int) l,
              f2,
              d));

  // check for proper received values
  ACE_ASSERT (i == -1  && (f1 >= -0.13 && f1 <= -0.11)
              && l == -666555444  && (f2 >= -24.0 && f2 <= -22.0)
              && (d >= 45e+9 && d <= 47e+9));

  client_handler.close ();

  return 0;
}

static int
spawn (void)
{
  // Acceptor;
  ACE_SOCK_Acceptor acceptor;

  if (acceptor.open (ACE_sap_any_cast (const ACE_INET_Addr &)) == -1)
    ACE_ERROR_RETURN ((LM_ERROR,
                       ACE_TEXT (" (%P|%t) %p\n"),
                       ACE_TEXT ("open")),
                      -1);
#if defined (ACE_HAS_THREADS)
  else if (ACE_Thread_Manager::instance ()->spawn (ACE_THR_FUNC (server),
                                                  &acceptor,
                                                  THR_NEW_LWP | THR_DETACHED) == -1)
    ACE_ERROR_RETURN ((LM_ERROR,
                       ACE_TEXT ("%p\n"),
                       ACE_TEXT ("spawning server thread")),
                      -1);

  // Wait for the client and server thread to exit.
  ACE_Thread_Manager::instance ()->wait ();

#elif !defined (ACE_LACKS_FORK)

  switch (ACE_OS::fork ("child"))
    {
    case -1:
      ACE_ERROR ((LM_ERROR,
                  ACE_TEXT ("%p\n%a"),
                  ACE_TEXT ("fork failed")));
      ACE_OS::_exit (-1);
    case 0: // In child
      {
        ACE_APPEND_LOG ("IOStream_Test-children");
        ACE_INET_Addr server_addr;

        if (acceptor.get_local_addr (server_addr) == -1)
          ACE_ERROR ((LM_ERROR,
                      ACE_TEXT ("%p\n"),
                      ACE_TEXT ("get_local_addr")));
        else
          client ((void *) &server_addr);
        ACE_END_LOG;
        break;
      }
    default: // In parent
      server (&acceptor);

      // Allow the client to exit, then remove the Process_Mutex.
      ACE_OS::wait ();
      break;
    }
#else
  ACE_ERROR_RETURN ((LM_INFO,
                     ACE_TEXT ("threads *and* processes not supported on this platform\n")),
                    -1);
#endif /* ACE_HAS_THREADS */

  acceptor.close ();

  return 0;
}
#endif /* !ACE_LACKS_ACE_IOSTREAM */

int
run_main (int, ACE_TCHAR *[])
{
  ACE_START_TEST (ACE_TEXT ("IOStream_Test"));

#if !defined (ACE_LACKS_ACE_IOSTREAM)
  ACE_INIT_LOG (ACE_TEXT ("IOStream_Test-children"));
  spawn ();
#else
  ACE_ERROR ((LM_INFO,
              ACE_TEXT ("ACE_IOSTREAM not supported on this platform\n")));
#endif /* !ACE_LACKS_ACE_IOSTREAM */
  ACE_END_TEST;
  return 0;
}