Threads can't communicate with each other and call methods on each other's objects...
[macgdbp.git] / Source / SocketWrapper.m
1 /*
2 * MacGDBp
3 * Copyright (c) 2002 - 2007, Blue Static <http://www.bluestatic.org>
4 *
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17 #import "SocketWrapper.h"
18 #include <sys/types.h>
19 #include <sys/socket.h>
20 #include <netinet/in.h>
21 #include <arpa/inet.h>
22 #include <unistd.h>
23
24 NSString *SocketWrapperNotificationConnection = @"debuggerconnection";
25 NSString *SocketDidAcceptNotification = @"socketdidaccept";
26
27 @implementation SocketWrapper
28
29 /**
30 * Initializes the socket wrapper with a host and port
31 */
32 - (id)initWithPort: (int)port
33 {
34 if (self = [super init])
35 {
36 _port = port;
37 }
38 return self;
39 }
40
41 /**
42 * Close our socket and clean up anything else
43 */
44 - (void)dealloc
45 {
46 close(_socket);
47
48 [super dealloc];
49 }
50
51 /**
52 * Returns the delegate
53 */
54 - (id)delegate
55 {
56 return _delegate;
57 }
58
59 /**
60 * Sets the delegate but does *not* retain it
61 */
62 - (void)setDelegate: (id)delegate
63 {
64 if (_delegate != nil)
65 {
66 [[NSNotificationCenter defaultCenter] removeObserver: _delegate];
67 }
68
69 _delegate = delegate;
70
71 [[NSNotificationCenter defaultCenter] addObserver: _delegate selector: @selector(socketDidAccept:) name: SocketDidAcceptNotification object: nil];
72 }
73
74 /**
75 * Connects to a socket on the port specified during init. This will dispatch another thread to do the
76 * actual waiting. Delegate notifications are posted along the way to let the client know what is going on.
77 */
78 - (void)connect
79 {
80 [NSThread detachNewThreadSelector: @selector(_connect:) toTarget: self withObject: nil];
81 }
82
83 /**
84 * This does the actual dirty work (in a separate thread) of connecting to a socket
85 */
86 - (void)_connect: (id)obj
87 {
88 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
89
90 // create an INET socket that we'll be listen()ing on
91 int socketOpen = socket(PF_INET, SOCK_STREAM, 0);
92
93 // create our address given the port
94 struct sockaddr_in address;
95 address.sin_family = AF_INET;
96 address.sin_port = htons(_port);
97 address.sin_addr.s_addr = htonl(INADDR_ANY);
98 memset(address.sin_zero, '\0', sizeof(address.sin_zero));
99
100 // bind the socket... and don't give up until we've tried for a while
101 int tries = 0;
102 while (bind(socketOpen, (struct sockaddr *)&address, sizeof(address)) < 0)
103 {
104 if (tries >= 5)
105 {
106 close(socketOpen);
107 [_delegate errorEncountered: nil];
108 }
109 NSLog(@"couldn't bind to the socket... trying again in 5");
110 sleep(5);
111 tries++;
112 }
113
114 // now we just have to keep our ears open
115 if (listen(socketOpen, 0) == -1)
116 {
117 [_delegate errorEncountered: nil];
118 }
119
120 // accept a connection
121 struct sockaddr_in remoteAddress;
122 socklen_t remoteAddressLen = sizeof(remoteAddress);
123 _socket = accept(socketOpen, (struct sockaddr *)&remoteAddress, &remoteAddressLen);
124 if (_socket < 0)
125 {
126 close(socketOpen);
127 [_delegate errorEncountered: nil];
128 }
129
130 // we're done listening now that we have a connection
131 close(socketOpen);
132
133 [self _postNotification: SocketDidAcceptNotification withObject: nil];
134
135 [pool release];
136 }
137
138 /**
139 * Reads from the socket and returns the result as a NSString (because it's always going to be XML). Be aware
140 * that the underlying socket recv() call will *wait* for the server to send a message, so be sure that this
141 * is used either in a threaded environment so the interface does not hang, or when you *know* the server
142 * will return something (which we almost always do).
143 *
144 * Data string returned is autorelease'd
145 */
146 - (void)receive
147 {
148 // create a buffer
149 char buffer[1024];
150
151 // do our initial recv() call to get (hopefully) all the data and the lengh of the packet
152 int recvd = recv(_socket, &buffer, sizeof(buffer), 0);
153
154 // take the received data and put it into an NSData
155 NSMutableData *data = [NSMutableData data];
156
157 // strip the length from the packet, and clear the null byte then add it to the NSData
158 char packetLength[8];
159 int i = 0;
160 while (buffer[i] != '\0')
161 {
162 packetLength[i] = buffer[i];
163 i++;
164 }
165
166 // we also want the null byte, so move us up 1
167 i++;
168
169 // the total length of the full transmission
170 int length = atoi(packetLength);
171
172 // move the packet part of the received data into it's own char[]
173 char packet[sizeof(buffer)];
174 memmove(packet, &buffer[i], recvd - i);
175
176 // convert bytes to NSData
177 [data appendBytes: packet length: recvd];
178
179 // check if we have a partial packet
180 if (length + i > sizeof(buffer))
181 {
182 while (recvd < length)
183 {
184 int latest = recv(_socket, &buffer, sizeof(buffer), 0);
185 if (latest < 1)
186 {
187 NSLog(@"socket closed or error");
188 }
189 [data appendBytes: buffer length: latest];
190 recvd += latest;
191 }
192 }
193
194 // convert the NSData into a NSString
195 [_delegate dataReceived: [[[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding] autorelease]];
196 }
197
198 /**
199 * Sends a given NSString over the socket
200 */
201 - (void)send: (NSString *)data
202 {
203 data = [NSString stringWithFormat: @"%@\0", data];
204 int sent = send(_socket, [data UTF8String], [data length], 0);
205 if (sent < 0)
206 {
207 NSLog(@"error in sending");
208 }
209 if (sent < [data length])
210 {
211 // TODO - do we really need to worry about partial sends with the lenght of our commands?
212 NSLog(@"FAIL: only partial packet was sent; sent %d bytes", sent);
213 }
214
215 [_delegate dataSent];
216 }
217
218 /**
219 * Helper method to simply post a notification to the default notification center with a given name and object
220 */
221 - (void)_postNotification: (NSString *)name withObject: (id)obj
222 {
223 NSDictionary *dict = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: _delegate, nil]
224 forKeys: [NSArray arrayWithObjects: SocketWrapperNotificationConnection, nil]];
225 [[NSNotificationCenter defaultCenter] postNotificationName: name object: obj userInfo: dict];
226 }
227
228 @end