summaryrefslogtreecommitdiff
path: root/docs/tutorials/004/client.cpp
blob: b32a5f8acb5f72f4494fac96783fa7b9eeed6e17 (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

// $Id$

/*
  We need the connector object & we also bring in a simple string class.
 */
#include "ace/SOCK_Connector.h"
#include "ace/SString.h"

/*
  In this tutorial, we extend SOCK_Stream by adding a few wrappers
  around the send_n() method.
 */
class Client : public ACE_SOCK_Stream
{

public:
  // Basic constructor
  Client(void);

  /*
    Construct and open() in one call.  This isn't generally a good
    idea because you don't have a clean way to inform the caller
    when open() fails.  (Unless you use C++ exceptions.)
   */
  Client( const char * server, u_short port );

  /*
    Open the connection to the server.  Notice that this mirrors
    the use of ACE_SOCK_Connector.  By providing our own open(),
    we can hide the connector from our caller & make it's interaction
    easier.
   */
  int open( const char * server, u_short port );

  /*
    These are necessary if you're going to use the constructor that
    invokes open().
   */
  inline int initialized(void) { return initialized_; }
  inline int error(void)       { return error_; }

  /*
    This is where the coolness lies.  Most C++ folks are familiar
    with "cout << some-data".  It's a very handy and easy way to
    toss data around.  By adding these method calls, we're able
    to do the same thing with a socket connection.
   */
  Client & operator<<( ACE_SString & str );
  Client & operator<<( char * str );
  Client & operator<<( int  n );

protected:
  unsigned char initialized_;
  unsigned char error_;

};

/*
  The basic constructor just sets our flags to reasonable values.
 */
Client::Client(void)
{
  initialized_ = 0;
  error_ = 0;
}

/*
  This constructor also sets the flags but then calls open().  If the
  open() fails, the flags will be set appropriately.  Use the two inline
  method calls initialized() and error() to check the object state after
  using this constructor.
 */
Client::Client( const char * server, u_short port )
{
  initialized_ = 0;
  error_ = 0;
  (void)open(server,port);
}

/*
  Open a connection to the server.  This hides the use of ACE_SOCK_Connector
  from our caller.  Since our caller probably doesn't care *how* we connect,
  this is a good thing.
 */
int Client::open( const char * server, u_short port )
{
  /*
    This is right out of Tutorial 3.  The only thing we've added is to set
    the initialized_ member variable on success.
   */

  ACE_SOCK_Connector connector;
  ACE_INET_Addr addr (port, server);

  if (connector.connect (*this, addr) == -1)
  {
    ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "open"), -1);
  }

  initialized_ = 1;

  return(0);
}

/*
  The first of our put operators sends a simple string object to the peer.
*/
Client & Client::operator<<( ACE_SString & str )
{
  /*
    We have to be able to allow:
      server << foo << bar << stuff;

    To accomplish that, every << operator must check that the object is
    in a valid state before doing work.
   */

  if( initialized() && ! error() )
  {
      /*
        Get the actual data held in the string object
       */
      char * cp = str.rep();

      /*
        Send that data to the peer using send_n() as before.  If we have
        a problem, we'll set error_ so that subsequent << operations won't
        try to use a broken stream.
       */
      if( this->send_n(cp,strlen(cp)) == -1 )
      {
          error_ = 1;
      }
  }
  else
  {
      /*
        Be sure that error_ is set if somebody tries to use us when
        we're not initialized.
       */
      error_ = 1;
  }

  /*
    We have to return a reference to ourselves to allow chaining of
    put operations (eg -- "server << foo << bar").  Without the reference,
    you would have to do each put operation as a statement.  That's OK
    but doesn't have the same feel as standard C++ iostreams.
   */
  return *this ;
}

/*
How do you put a char*?  We'll take an easy way out and construct an ACE_SString
from the char* and then put that.  It would have been more efficient to implement
this with the body of the operator<<(ACE_SString&) method and then express that
method in terms of this one.  There's always more than one way to do things!
 */
Client & Client::operator<< ( char * str )
{
  ACE_SString newStr(str);

  *this << newStr;

  return *this ;

  /*
    Notice that we could have been really clever and done:

      return *this << ACE_SString(str);

    That kind of thing just makes debugging a pain though!
   */
}

/*
  ACE_SString and char* are both about the same thing.  What do you do about
  different datatypes though?

  Do the same thing we did with char* and convert it to ACE_SString where we
  already have a << operator defined.
 */
Client & Client::operator<< ( int n )
{
  /*
    Create a character buffer large enough for the largest number.  That's
    a tough call but 1024 should be quite enough.
  */
  char buf[1024];

  /*
    Put the number into our buffer...
  */
  ACE_OS::sprintf(buf,"(%d)\n",n);

  /*
    And create the ACE_SString that we know how to put.
  */
  ACE_SString newStr(buf);

  /*
    Send it and...
  */
  *this << newStr;

  /*
    return ourselves as usual.
  */
  return *this;
}


/*
  Now we pull it all together.  Like Tutorial 3, we'll allow command line options.
 */
int main (int argc, char *argv[])
{
  const char *server_host = argc > 1 ? argv[1]        : ACE_DEFAULT_SERVER_HOST;
  u_short server_port     = argc > 2 ? ACE_OS::atoi (argv[2]) : ACE_DEFAULT_SERVER_PORT;
  int max_iterations      = argc > 3 ? ACE_OS::atoi (argv[3]) : 4;

  /*
    Use the basic constructor since the other isn't really very safe.
   */
  Client server;
  
  /*
    Open the server connection.  Notice how this is simpler than Tutorial 3
    since we only have to provide a host name and port value.
   */
  if( server.open(server_host,server_port) == -1 )
  {
    ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "open"), -1);
  }
  
  for (int i = 0; i < max_iterations; i++)
  {
    /*
      Tell the server which iteration we're on.  No more mucking aroudn with
      sprintf at this level!  It's all hidden from us.
     */
    server << "message = " << i+1;

    /*
      Everything OK?
     */
    if ( server.error() )
    {
      ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "send"), -1);
    }
    else
    {
      ACE_OS::sleep (1);
    }
  }

  if (server.close () == -1)
  {
    ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "close"), -1);
  }

  return 0;
}