OSDN Git Service

Adding iPhone support (since iPhone doesn't currently support IPv6)
[syncr/master.git] / CocoaAsyncSocket / CocoaAsyncSocket-4.4.1 / AsyncSocketSample / EchoMain.m
1 #import <Foundation/Foundation.h>
2 #import <CoreFoundation/CFStream.h>
3 #import "../AsyncSocket.h"
4 #include <fcntl.h>
5 #include <unistd.h>
6
7 #pragma mark Declarations
8
9 @interface Echo : NSObject
10 {
11         BOOL shouldExitLoop;
12         AsyncSocket *socket;
13         NSMutableString *text;
14 }
15 -(id)init;
16 -(void)dealloc;
17 -(void)runLoop;
18 -(void)stopRunLoop;
19 -(void)readFromServer;
20 -(void)readFromStdIn;
21 -(void)doTextCommand;
22 -(void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err;
23 -(void)onSocketDidDisconnect:(AsyncSocket *)sock;
24 -(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port;
25 -(void)onSocket:(AsyncSocket *)sock didReadData:(NSData*)data withTag:(long)t;
26 @end
27
28 void showHelp();
29
30 #pragma mark -
31 #pragma mark Implementation
32
33 @implementation Echo : NSObject
34
35
36 /*
37  This method creates the socket. Echo reuses this one socket throughout its life.
38  Echo also sets up the input. While a command-line app is waiting for input, it
39  is usually blocked; I make the input non-blocking so that the run-loop remains
40  active.
41 */
42 - (id)init
43 {
44         self = [super init];
45
46         // Create socket.
47         NSLog (@"Creating socket.");
48         socket = [[AsyncSocket alloc] initWithDelegate:self];
49         
50         // Create command buffer.
51         text = [[NSMutableString alloc] init];
52         
53         // Set up stdin for non-blocking.
54         if (fcntl (STDIN_FILENO, F_SETFL, O_NONBLOCK) == -1)
55         {
56                 NSLog (@"Can't make STDIN non-blocking.");
57                 exit(1);
58         }
59         
60         return self;
61 }
62
63
64 /*
65  I release allocated resources here, including the socket. The socket will close
66  any connection before releasing itself, but it will not need to. I explicitly
67  close any connections in the "quit" command handler.
68 */
69 - (void)dealloc
70 {
71         [socket release];
72         [text release];
73         [super dealloc];
74 }
75
76
77 /*
78  Echo spends one second handling any run-loop activity (i.e. socket activity)
79  and then comes up for air to check if any new commands have been entered and,
80  if so, executing them. Wash, rinse, repeat.
81
82  Note the use of the shouldExitLoop flag to control when the run-loop ends and
83  the app quits. I could have just called exit(), but this allows the app to clean
84  up after itself properly. You should use a similar technique if you create a
85  thread for socket activity and processing.
86 */
87 - (void)runLoop
88 {
89         shouldExitLoop = NO;
90         while (!shouldExitLoop)
91         {
92                 [self readFromStdIn];
93                 [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
94         }
95 }
96
97
98 /* This method just abstracts the stop-run-loop operation. */
99 - (void)stopRunLoop
100 {
101         shouldExitLoop = YES;
102 }
103
104
105 /*
106  This method simply abstracts the read-from-server operation. It is called
107  from -onSocket:didReadData:withTag: to set up another read operation. If it did
108  not set up another read operation, AsyncSocket would not do anything with any
109  further packets from Echo Server.
110  
111  You should not use "\n" as a packet delimiter in your own code. I explain why
112  in EchoServerMain.c.
113 */
114 - (void)readFromServer
115 {
116         NSData *newline = [@"\n" dataUsingEncoding:NSASCIIStringEncoding];
117         [socket readDataToData:newline withTimeout:-1 tag:0];
118 }
119
120
121 /*
122  This method queues up a message that will be sent immediately upon connecting
123  to Echo Server. Note that the message consists of two write requests. They will
124  be sent consecutively, though, and will appear as one packet to Echo Server,
125  because Echo Server looks for the "\n" to determine when the packet ends.
126 */
127 - (void)prepareHello
128 {
129         // Yes, you can call methods on NSString constants.
130         
131         NSData *message =
132                 [@"Hello, Echo Server! " dataUsingEncoding:NSASCIIStringEncoding];
133
134         NSData *newline =
135                 [@"I am a new client.\n" dataUsingEncoding:NSASCIIStringEncoding];
136
137         [socket writeData:message withTimeout:-1 tag:0];
138         [socket writeData:newline withTimeout:-1 tag:0];
139 };
140
141
142 /*
143  I only runs the run-loop for a second at a time, because I need to receive and
144  act on input from the user (which won't result in run-loop activity).
145  That happens here.
146 */
147 - (void)readFromStdIn
148 {
149         Byte c;
150         while (read (STDIN_FILENO, &c, 1) == 1)
151         {
152                 [text appendString: [NSString stringWithFormat:@"%c", c]];
153                 if (c == '\n') [self doTextCommand];
154         }
155 }
156
157
158 /*
159  This method sends text to Echo Server, or executes a command.
160 */
161 - (void)doTextCommand
162 {
163         NSArray *params = [text componentsSeparatedByString:@" "];
164         if ([text hasPrefix: @"quit"] || [text hasPrefix: @"exit"])
165         {
166                 // I don't technically need to call -disconnect here. When the app quits,
167                 // the socket will disconnect itself if needed. But it is easier to keep
168                 // track of here. Note that -onSocket:willDisconnectWithError: will NOT
169                 // be called here, though -onSocketDidDisconnect: will be.
170                 
171                 // I stop the run loop like a gentleman instead of simply calling exit()
172                 // so that the app exits cleanly. This is also technically unnecessary;
173                 // the OS will destroy the socket connection when it cleans up after the
174                 // process.  
175                 
176                 NSLog (@"Disconnecting & exiting.");
177                 [socket disconnect];
178                 [self stopRunLoop];
179         }
180         else if ([text hasPrefix: @"disconnect"])
181         {
182                 NSLog (@"Disconnecting.");
183                 [socket disconnect];
184         }
185         else if ([text hasPrefix: @"connect"])
186         {
187                 UInt16 port;
188                 NSString *host;
189                 if ([params count] == 3)
190                 {
191                         port = [[params objectAtIndex:2] intValue];
192                         host = [params objectAtIndex:1];
193                 }
194                 else
195                 {
196                         port = [[params objectAtIndex:1] intValue];
197                         host = @"localhost";
198                 }
199
200                 // This starts to establish a connection to the server.
201                 // The connection will not be finished until later, when
202                 // -onSocket:didConnectToPort:host: is called. But even so, you can
203                 // immediately queue a read or write operation here. It will be
204                 // performed when the connection is established.
205                 //
206                 // Note that I enclose the connect method call in a @try block. An
207                 // exception will be thrown if the socket is already connected. Usually,
208                 // if an exception was thrown, that indicates programmer error. In this
209                 // case, an exception would indicate that I did not forsee a user trying
210                 // to make a new connection when one already exists. I could and should
211                 // have used -isConnected to check for that case in advance, but I wanted
212                 // to demonstrate the exception-handling.
213                 //
214                 // Note also, that I call -prepareHello (which writes to the socket)
215                 // before the socket is actually connected. The write request will be
216                 // queued up and transmitted as soon as the connection is complete.
217                 
218                 @try
219                 {
220                         NSError *err;
221                         
222                         if ([socket connectToHost:host onPort:port error:&err])
223                         {
224                                 NSLog (@"Connecting to %@ port %u.", host, port);
225                                 [self prepareHello];
226                         }
227                         else
228                         {
229                                 NSLog (@"Couldn't connect to %@ port %u (%@).", host, port, err);
230                         }
231                 }
232                 @catch (NSException *exception)
233                 {
234                         NSLog ([exception reason]);
235                 }
236         }
237         else if ([text hasPrefix: @"dump"])
238         {
239                 // This demonstrates AsyncSocket's -description method.
240                 NSLog (@"%@", socket);
241         }
242         else if ([text hasPrefix: @"help"])
243         {
244                 showHelp();
245         }
246         else
247         {
248                 // Anything other than a command is sent to Echo Server. Note that data
249                 // will include the final press of the Return key, which is "\n". That
250                 // is why I use "\n" verbatim as the packet delimiter, instead of
251                 // specifying CRLF or something.
252                 
253                 NSData *data = [text dataUsingEncoding:NSASCIIStringEncoding];
254                 [socket writeData:data withTimeout:-1 tag:0];
255         }
256         [text setString:@""];
257 }
258
259
260 #pragma mark -
261 #pragma mark AsyncSocket Delegate Methods
262
263
264 /*
265  This will be called whenever AsyncSocket is about to disconnect. In Echo Server,
266  it does not do anything other than report what went wrong (this delegate method
267  is the only place to get that information), but in a more serious app, this is
268  a good place to do disaster-recovery by getting partially-read data. This is
269  not, however, a good place to do cleanup. The socket must still exist when this
270  method returns.
271  */
272 -(void) onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err
273 {
274         if (err != nil)
275                 NSLog (@"Socket will disconnect. Error domain %@, code %d (%@).",
276                            [err domain], [err code], [err localizedDescription]);
277         else
278                 NSLog (@"Socket will disconnect. No error.");
279 }
280
281
282 /*
283  Normally, this is the place to release the socket and perform the appropriate
284  housekeeping and notification. But I intend to re-use this same socket for
285  other connections, so I do nothing.
286 */
287 -(void) onSocketDidDisconnect:(AsyncSocket *)sock
288 {
289         NSLog (@"Disconnected.");
290 }
291
292
293 /*
294  This method is called when Echo has connected to Echo Server. I immediately
295  wait for incoming data from the server, but I already have two write requests
296  queued up (from -prepareHello), and will also be sending data when
297  the user gives me some to send.
298 */
299 -(void) onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port;
300 {
301         NSLog (@"Connected to %@ %u.", host, port);
302         [self readFromServer];
303 }
304
305
306 /*
307  This method is called when Echo has finished reading a packet from Echo Server.
308  It prints it out and immediately calls -readFromServer, which will queue up a
309  read operation, waiting for the next packet.
310
311  You'll note that I do not implement -onSocket:didWriteDataWithTag:. That is
312  because Echo does not care about the data once it is transmitted. AsyncSocket
313  will still send the data, but will not notify Echo when that it done.
314 */
315 -(void) onSocket:(AsyncSocket *)sock didReadData:(NSData*)data withTag:(long)t
316 {
317         NSString *str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
318         printf ([str cString]);
319         fflush (stdout);
320         [str release];
321         [self readFromServer];
322 }
323
324
325 @end
326
327 #pragma mark -
328 #pragma mark C Functions
329
330 void showHelp()
331 {
332         printf ("ECHO by Dustin Voss copyright 2003. Sample code for using AsyncSocket.");
333         printf ("\nReads and writes text to an ECHO SERVER. The following commands are available:");
334         printf ("\n\tquit, exit -- exit the program");
335         printf ("\n\thelp -- display this message");
336         printf ("\n\tconnect host port -- connects to the server on the given host and port");
337         printf ("\n\tdisconnect -- disconnects from the current server");
338         printf ("\n\tdump -- displays the socket's status");
339         printf ("\nAnything else gets transmitted to the server. Begin!\n");
340         fflush (stdout);
341 }
342
343 int main (int argc, const char * argv[])
344 {
345         showHelp();
346         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
347         Echo *e = [[Echo alloc] init];
348         [e runLoop];
349         [e release];
350         [pool release];
351         return 0;
352 }