-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver-iterative.cpp
404 lines (331 loc) · 10.4 KB
/
server-iterative.cpp
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
/*********************************************************** -- HEAD -{{{1- */
/* Echo Server for Network API Lab: Part I in Internet Technology 2011.
*
* Iterative server capable of accpeting and processing a single connection
* at any given time. Data received from the connection is simply sent back
* unmodified ("echoed").
*
* Build the server using e.g.
* $ g++ -Wall -Wextra -o server-iter server-iterative.cpp
*
* Start using
* $ ./server-iter
* or
* $ ./server-iter 31337
* to listen on a port other than the default 5703.
*/
/******************************************************************* -}}}1- */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <vector>
#include <algorithm>
//--//////////////////////////////////////////////////////////////////////////
//-- configurables ///{{{1///////////////////////////////////////////
// Set VERBOSE to 1 to print additional, non-essential information.
#define VERBOSE 1
// If NONBLOCKING is set to 1, all sockets are put into non-blocking mode.
// Use this for Part II, when implementing the select() based server, where
// no blocking operations other than select() should occur. (If an blocking
// operation is attempted, a EAGAIN or EWOULDBLOCK error is raised, probably
// indicating a bug in the code!)
#define NONBLOCKING 0
// Default port of the server. May be overridden by specifying a different
// port as the first command line argument to the server program.
const int kServerPort = 5703;
// Second parameter to listen().
// Note - this parameter is (according to the POSIX standard) merely a hint.
// The implementation may choose a different value, or ignore it altogether.
const int kServerBacklog = 8;
// Size of the buffer used to transfer data. A single read from the socket
// may return at most this much data, and consequently, a single send may
// send at most this much data.
const size_t kTransferBufferSize = 64;
//-- constants ///{{{1///////////////////////////////////////////
/* Connection states.
* A connection may either expect to receive data, or require data to be
* sent.
*/
enum EConnState
{
eConnStateReceiving,
eConnStateSending
};
//-- structures ///{{{1///////////////////////////////////////////
/* Per-connection data
* In the iterative server, there is a single instance of this structure,
* holding data for the currently active connection. A concurrent server will
* need an instance for each active connection.
*/
struct ConnectionData
{
EConnState state; // state of the connection; see EConnState enum
int sock; // file descriptor of the connections socket.
// items related to buffering.
size_t bufferOffset, bufferSize;
char buffer[kTransferBufferSize+1];
};
//-- prototypes ///{{{1///////////////////////////////////////////
/* Receive data and place it in the connection's buffer.
*
* Requires that ConnectionData::state is eConnStateReceiving; if not, an
* assertation fault is generated.
*
* If _any_ data is received, the connection's state is transitioned to
* eConnStateSending.
*
* Returns `true' if the connection remains open and further processing is
* required. Returns `false' to indicate that the connection is closing or
* has closed, and the connection should not be processed further.
*/
static bool process_client_recv( ConnectionData& cd );
/* Send data from the connection's buffer.
*
* Requires that ConnectionData::state is eConnStateSending; if not, an
* asseration fault is generated.
*
* When all data is sent, the connection's state is transitioned to
* eConnStateReceiving. If data remains in the buffer, no state-transition
* occurs.
*
* Returns `true' if the connection remains open and further processing is
* required. Returns `false' to indicate that the connection is closing or
* has closed, and the connection should not be processed further.
*/
static bool process_client_send( ConnectionData& cd );
/* Places the socket identified by `fd' in non-blocking mode.
*
* Returns `true' if successful, and `false' otherwise.
*/
static bool set_socket_nonblocking( int fd );
/* Returns `true' if the connection `cd' has an invalid socket (-1), and
* `false' otherwise.
*/
static bool is_invalid_connection( const ConnectionData& cd );
/* Sets up a listening socket on `port'.
*
* Returns, if successful, the new socket fd. On error, -1 is returned.
*/
static int setup_server_socket( short port );
//-- main() ///{{{1///////////////////////////////////////////
int main( int argc, char* argv[] )
{
int serverPort = kServerPort;
// did the user specify a port?
if( 2 == argc )
{
serverPort = atoi(argv[1]);
}
# if VERBOSE
printf( "Attempting to bind to port %d\n", serverPort );
# endif
// set up listening socket - see setup_server_socket() for details.
int listenfd = setup_server_socket( serverPort );
if( -1 == listenfd )
return 1;
// loop forever
while( 1 )
{
sockaddr_in clientAddr;
socklen_t addrSize = sizeof(clientAddr);
// accept a single incoming connection
int clientfd = accept( listenfd, (sockaddr*)&clientAddr, &addrSize );
if( -1 == clientfd )
{
perror( "accept() failed" );
continue; // attempt to accept a different client.
}
# if VERBOSE
// print some information about the new client
char buff[128];
printf( "Connection from %s:%d -> socket %d\n",
inet_ntop( AF_INET, &clientAddr.sin_addr, buff, sizeof(buff) ),
ntohs(clientAddr.sin_port),
clientfd
);
fflush( stdout );
# endif
# if NONBLOCKING
// enable non-blocking sends and receives on this socket
if( !set_socket_nonblocking( clientfd ) )
continue;
# endif
// initialize connection data
ConnectionData connData;
memset( &connData, 0, sizeof(connData) );
connData.sock = clientfd;
connData.state = eConnStateReceiving;
// Repeatedly receive and re-send data from the connection. When
// the connection closes, process_client_*() will return false, no
// further processing is done.
bool processFurther = true;
while( processFurther )
{
while( processFurther && connData.state == eConnStateReceiving )
processFurther = process_client_recv( connData );
while( processFurther && connData.state == eConnStateSending )
processFurther = process_client_send( connData );
}
// done - close connection
close( connData.sock );
}
// The program will never reach this part, but for demonstration purposes,
// we'll clean up the server resources here and then exit nicely.
close( listenfd );
return 0;
}
//-- process_client_recv() ///{{{1///////////////////////////////////////
static bool process_client_recv( ConnectionData& cd )
{
assert( cd.state == eConnStateReceiving );
// receive from socket
ssize_t ret = recv( cd.sock, cd.buffer, kTransferBufferSize, 0 );
if( 0 == ret )
{
# if VERBOSE
printf( " socket %d - orderly shutdown\n", cd.sock );
fflush( stdout );
# endif
return false;
}
if( -1 == ret )
{
# if VERBOSE
printf( " socket %d - error on receive: '%s'\n", cd.sock,
strerror(errno) );
fflush( stdout );
# endif
return false;
}
// update connection buffer
cd.bufferSize += ret;
// zero-terminate received data
cd.buffer[cd.bufferSize] = '\0';
// transition to sending state
cd.bufferOffset = 0;
cd.state = eConnStateSending;
return true;
}
//-- process_client_send() ///{{{1///////////////////////////////////////
static bool process_client_send( ConnectionData& cd )
{
assert( cd.state == eConnStateSending );
// send as much data as possible from buffer
ssize_t ret = send( cd.sock,
cd.buffer+cd.bufferOffset,
cd.bufferSize-cd.bufferOffset,
MSG_NOSIGNAL // suppress SIGPIPE signals, generate EPIPE instead
);
if( -1 == ret )
{
# if VERBOSE
printf( " socket %d - error on send: '%s'\n", cd.sock,
strerror(errno) );
fflush( stdout );
# endif
return false;
}
// update buffer data
cd.bufferOffset += ret;
// did we finish sending all data
if( cd.bufferOffset == cd.bufferSize )
{
// if so, transition to receiving state again
cd.bufferSize = 0;
cd.bufferOffset = 0;
cd.state = eConnStateReceiving;
}
return true;
}
//-- setup_server_socket() ///{{{1///////////////////////////////////////
static int setup_server_socket( short port )
{
// create new socket file descriptor
int fd = socket( AF_INET, SOCK_STREAM, 0 );
if( -1 == fd )
{
perror( "socket() failed" );
return -1;
}
// bind socket to local address
sockaddr_in servAddr;
memset( &servAddr, 0, sizeof(servAddr) );
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(port);
if( -1 == bind( fd, (const sockaddr*)&servAddr, sizeof(servAddr) ) )
{
perror( "bind() failed" );
close( fd );
return -1;
}
// get local address (i.e. the address we ended up being bound to)
sockaddr_in actualAddr;
socklen_t actualAddrLen = sizeof(actualAddr);
memset( &actualAddr, 0, sizeof(actualAddr) );
if( -1 == getsockname( fd, (sockaddr*)&actualAddr, &actualAddrLen ) )
{
perror( "getsockname() failed" );
close( fd );
return -1;
}
char actualBuff[128];
printf( "Socket is bound to %s %d\n",
inet_ntop( AF_INET, &actualAddr.sin_addr, actualBuff, sizeof(actualBuff) ),
ntohs(actualAddr.sin_port)
);
// and start listening for incoming connections
if( -1 == listen( fd, kServerBacklog ) )
{
perror( "listen() failed" );
close( fd );
return -1;
}
// allow immediate reuse of the address (ip+port)
int one = 1;
if( -1 == setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int) ) )
{
perror( "setsockopt() failed" );
close( fd );
return -1;
}
# if NONBLOCKING
// enable non-blocking mode
if( !set_socket_nonblocking( fd ) )
{
close( fd );
return -1;
}
# endif
return fd;
}
//-- set_socket_nonblocking() ///{{{1////////////////////////////////////
static bool set_socket_nonblocking( int fd )
{
int oldFlags = fcntl( fd, F_GETFL, 0 );
if( -1 == oldFlags )
{
perror( "fcntl(F_GETFL) failed" );
return false;
}
if( -1 == fcntl( fd, F_SETFL, oldFlags | O_NONBLOCK ) )
{
perror( "fcntl(F_SETFL) failed" );
return false;
}
return true;
}
//-- is_invalid_connection() ///{{{1////////////////////////////////////
static bool is_invalid_connection( const ConnectionData& cd )
{
return cd.sock == -1;
}
//--///}}}1//////////////// vim:syntax=cpp:foldmethod=marker:ts=4:noexpandtab: