Rev 983 | Rev 999 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | SVN | RSS feed
| Rev 983 | Rev 984 | ||
|---|---|---|---|
| Line 27... | Line 27... | ||
| 27 | { |
27 | { |
| 28 | myId = socketCount++; |
28 | myId = socketCount++; |
| 29 | mySocket = -1; |
29 | mySocket = -1; |
| 30 | myReconnectTimeout = 0; |
30 | myReconnectTimeout = 0; |
| 31 | myForceReconnect = false; |
31 | myForceReconnect = false; |
| 32 | - | ||
| 33 | Thread<AsyncSocket>(); |
- | |
| 34 | } |
32 | } |
| 35 | 33 | ||
| 36 | AsyncSocket::~AsyncSocket() |
34 | AsyncSocket::~AsyncSocket() |
| 37 | { |
35 | { |
| 38 | if (mySocket != -1) |
- | |
| 39 | { |
- | |
| 40 | ::close(mySocket); |
- | |
| 41 | } |
- | |
| 42 | - | ||
| 43 | stop(); |
36 | stop(); |
| - | 37 | silentClose(); |
|
| 44 | } |
38 | } |
| 45 | 39 | ||
| 46 | void AsyncSocket::run() |
40 | void AsyncSocket::run() |
| 47 | { |
41 | { |
| 48 |
|
42 | // If connection is already up then we should not connect again |
| 49 | - | ||
| 50 | if (!isConnected()) |
43 | if (!isConnected()) |
| 51 | { |
44 | { |
| - | 45 | // If we have choosen to not use automatic reconnect do not start reconnect loop |
|
| 52 | if (myReconnectTimeout == 0) |
46 | if (myReconnectTimeout == 0) |
| 53 | { |
47 | { |
| - | 48 | // Connect to somewhere |
|
| 54 | connect(); |
49 | connect(); |
| 55 | } |
50 | } |
| 56 | else |
51 | else |
| 57 | { |
52 | { |
| - | 53 | // Start the reconnect loop |
|
| - | 54 | reconnectLoop(); |
|
| - | 55 | } |
|
| - | 56 | } |
|
| - | 57 | ||
| - | 58 | bool loop = true; |
|
| - | 59 | try |
|
| - | 60 | { |
|
| - | 61 | while (loop) |
|
| - | 62 | { |
|
| - | 63 | // If we have triggered a forced reconnect do it here |
|
| - | 64 | if (myForceReconnect) |
|
| - | 65 | { |
|
| - | 66 | myForceReconnect = false; |
|
| 58 | reconnectLoop(); |
67 | reconnectLoop(); |
| 59 | } |
68 | } |
| - | 69 | ||
| - | 70 | // Receive data |
|
| - | 71 | loop = receiveData(); |
|
| - | 72 | } |
|
| - | 73 | } |
|
| - | 74 | catch (SocketException *e) |
|
| - | 75 | { |
|
| - | 76 | // Something bad happend and we can not continue |
|
| - | 77 | eventAdd(SocketEvent::TYPE_CONNECTION_DIED, e->getDescription()); |
|
| 60 | } |
78 | } |
| 61 | 79 | ||
| - | 80 | // Clean up socket if we would want to restart |
|
| - | 81 | silentClose(); |
|
| - | 82 | } |
|
| - | 83 | ||
| - | 84 | bool AsyncSocket::receiveData() |
|
| - | 85 | { |
|
| 62 | char |
86 | char buffer[MAXBUFFER + 1]; |
| 63 |
|
87 | memset(buffer, 0, MAXBUFFER + 1); |
| - | 88 | ||
| 64 | int status; |
89 | int status = ::recv(mySocket, buffer, MAXBUFFER, 0); |
| 65 | 90 | ||
| 66 |
|
91 | if (status == -1) |
| 67 | try |
- | |
| 68 | { |
- | |
| 69 | while (loop) |
- | |
| 70 | { |
92 | { |
| 71 | if (myForceReconnect) |
- | |
| 72 | { |
- | |
| 73 | reconnectLoop(); |
- | |
| 74 | } |
- | |
| 75 | - | ||
| 76 | memset(buf, 0, MAXBUFFER + 1); |
- | |
| 77 | status = ::recv(mySocket, buf, MAXBUFFER, 0); |
- | |
| 78 | - | ||
| 79 | if (status == -1) |
- | |
| 80 | { |
- | |
| 81 | switch (errno) |
93 | switch (errno) |
| 82 | { |
94 | { |
| 83 | case EAGAIN: |
95 | case EAGAIN: |
| 84 | throw new SocketException("The socket is marked non-blocking and the receive operation would block, or a receive timeout had been set and the timeout expired before data was received."); |
96 | throw new SocketException("The socket is marked non-blocking and the receive operation would block, or a receive timeout had been set and the timeout expired before data was received."); |
| 85 | break; |
- | |
| 86 | 97 | ||
| 87 | case EBADF: |
98 | case EBADF: |
| 88 | throw new SocketException("The argument s is an invalid descriptor."); |
99 | throw new SocketException("The argument s is an invalid descriptor."); |
| 89 | 100 | ||
| 90 | case ECONNREFUSED: |
101 | case ECONNREFUSED: |
| 91 | throw new SocketException("A remote host refused to allow the network connection (typically because it is not running the requested service)."); |
102 | throw new SocketException("A remote host refused to allow the network connection (typically because it is not running the requested service)."); |
| 92 | 103 | ||
| 93 | case EFAULT: |
104 | case EFAULT: |
| 94 | throw new SocketException("The receive buffer pointer(s) point outside the process's address space."); |
105 | throw new SocketException("The receive buffer pointer(s) point outside the process's address space."); |
| 95 | 106 | ||
| 96 | case EINTR: |
107 | case EINTR: |
| 97 | throw new SocketException("The receive was interrupted by delivery of a signal before any data were available; see signal(7)."); |
108 | throw new SocketException("The receive was interrupted by delivery of a signal before any data were available; see signal(7)."); |
| 98 | 109 | ||
| 99 | case EINVAL: |
110 | case EINVAL: |
| 100 | throw new SocketException("Invalid argument passed."); |
111 | throw new SocketException("Invalid argument passed."); |
| 101 | break; |
- | |
| 102 | 112 | ||
| 103 | case ENOMEM: |
113 | case ENOMEM: |
| 104 | throw new SocketException("Could not allocate memory for recvmsg()."); |
114 | throw new SocketException("Could not allocate memory for recvmsg()."); |
| 105 | 115 | ||
| 106 | case ENOTCONN: |
116 | case ENOTCONN: |
| Line 108... | Line 118... | ||
| 108 | 118 | ||
| 109 | case ENOTSOCK: |
119 | case ENOTSOCK: |
| 110 | throw new SocketException("The argument s does not refer to a socket."); |
120 | throw new SocketException("The argument s does not refer to a socket."); |
| 111 | 121 | ||
| 112 | case ECONNRESET: |
122 | case ECONNRESET: |
| 113 |
|
123 | eventAdd(SocketEvent::TYPE_CONNECTION_RESET); |
| - | 124 | ||
| - | 125 | // If we have automatic reconnect we want to start it now |
|
| 114 |
|
126 | if (myReconnectTimeout == 0) |
| 115 | { |
127 | { |
| 116 | reconnectLoop(); |
128 | reconnectLoop(); |
| 117 | } |
129 | } |
| 118 | else |
130 | else |
| 119 | { |
131 | { |
| - | 132 | // Otherwise we would like to end the main loop |
|
| 120 |
|
133 | return false; |
| 121 | } |
134 | } |
| 122 | break; |
135 | break; |
| 123 | 136 | ||
| 124 | default: |
137 | default: |
| 125 | throw new SocketException("Unknow exception: " + itos(errno)); |
138 | throw new SocketException("Unknow exception: " + itos(errno)); |
| 126 | break; |
139 | break; |
| 127 | } |
140 | } |
| 128 | } |
141 | } |
| 129 | else if (status == 0) |
142 | else if (status == 0) |
| 130 | { |
143 | { |
| - | 144 | // Remote host have done a normal shutdown |
|
| 131 |
|
145 | eventAdd(SocketEvent::TYPE_CONNECTION_CLOSED); |
| - | 146 | ||
| 132 | if (myReconnectTimeout > 0) |
147 | if (myReconnectTimeout > 0) |
| 133 | { |
148 | { |
| 134 | reconnectLoop(); |
149 | reconnectLoop(); |
| 135 | } |
150 | } |
| 136 | else |
151 | else |
| 137 | { |
152 | { |
| 138 |
|
153 | return false; |
| 139 | } |
154 | } |
| 140 | } |
155 | } |
| 141 | else if (status > 0) |
156 | else if (status > 0) |
| 142 | { |
157 | { |
| 143 |
|
158 | // We have received data |
| - | 159 | eventAdd(SocketEvent::TYPE_DATA, buffer); |
|
| 144 | 160 | } |
|
| 145 | //slog << "Received: " + data + "\n"; |
- | |
| 146 | 161 | ||
| 147 |
|
162 | return true; |
| 148 | setEvent(ASYNCSOCKET_EVENT_DATA); |
- | |
| 149 | } |
163 | } |
| 150 | } |
164 | |
| - | 165 | void AsyncSocket::silentClose() |
|
| 151 |
|
166 | { |
| 152 |
|
167 | if (mySocket != -1) |
| 153 | { |
168 | { |
| 154 |
|
169 | ::close(mySocket); |
| 155 |
|
170 | mySocket = -1; |
| - | 171 | } |
|
| 156 | } |
172 | } |
| 157 | 173 | ||
| - | 174 | void AsyncSocket::close() |
|
| - | 175 | { |
|
| 158 |
|
176 | silentClose(); |
| - | 177 | eventAdd(SocketEvent::TYPE_CONNECTION_CLOSED); |
|
| 159 | } |
178 | } |
| 160 | 179 | ||
| 161 | void AsyncSocket::reconnectLoop() |
180 | void AsyncSocket::reconnectLoop() |
| 162 | { |
181 | { |
| 163 | SyslogStream &slog = SyslogStream::getInstance(); |
- | |
| 164 | - | ||
| 165 | while ( |
182 | while (true) |
| 166 | { |
183 | { |
| 167 | try |
184 | try |
| 168 | { |
185 | { |
| 169 | connect(); |
186 | connect(); |
| 170 |
|
187 | return; |
| 171 | } |
188 | } |
| 172 | catch (SocketException *e) |
189 | catch (SocketException *e) |
| 173 | { |
190 | { |
| 174 |
|
191 | eventAdd(SocketEvent::TYPE_CONNECTION_FAILED, e->getDescription()); |
| 175 |
|
192 | eventAdd(SocketEvent::TYPE_WAITING_RECONNECT); |
| 176 | slog << "Could not connect: " + e->getDescription() + "\n";//FIXME: Remove these |
- | |
| 177 | slog << "Will try again in " + itos(myReconnectTimeout) + " seconds\n"; |
- | |
| 178 | sleep(myReconnectTimeout); |
193 | sleep(myReconnectTimeout); |
| 179 | } |
194 | } |
| 180 | } |
195 | } |
| 181 | } |
196 | } |
| 182 | 197 | ||
| 183 | void AsyncSocket::create() |
198 | void AsyncSocket::create() |
| 184 | { |
199 | { |
| 185 |
|
200 | silentClose(); |
| 186 | 201 | ||
| 187 | mySocket = ::socket(AF_INET, SOCK_STREAM, 0); |
202 | mySocket = ::socket(AF_INET, SOCK_STREAM, 0); |
| 188 | 203 | ||
| 189 | int on = 1; |
204 | int on = 1; |
| 190 | int status = setsockopt(mySocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on)); |
205 | int status = setsockopt(mySocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on)); |
| 191 | if (status == -1) |
206 | if (status == -1) |
| 192 | { |
207 | { |
| 193 |
|
208 | silentClose(); |
| 194 | throw new SocketException("Create:Reuseaddress: " + itos(errno)); |
209 | throw new SocketException("Create:Reuseaddress: " + itos(errno)); |
| 195 | } |
210 | } |
| 196 | } |
211 | } |
| 197 | 212 | ||
| 198 | void AsyncSocket::startListen() |
213 | void AsyncSocket::startListen() |
| 199 | { |
214 | { |
| 200 | create(); |
215 | create(); |
| 201 | 216 | ||
| 202 | myAddressStruct.sin_family = AF_INET; |
217 | myAddressStruct.sin_family = AF_INET; |
| Line 205... | Line 220... | ||
| 205 | 220 | ||
| 206 | int status = ::bind(mySocket, (struct sockaddr *)&myAddressStruct, sizeof(myAddressStruct)); |
221 | int status = ::bind(mySocket, (struct sockaddr *)&myAddressStruct, sizeof(myAddressStruct)); |
| 207 | 222 | ||
| 208 | if (status == -1) |
223 | if (status == -1) |
| 209 | { |
224 | { |
| 210 |
|
225 | silentClose(); |
| 211 | switch (errno) |
226 | switch (errno) |
| 212 | { |
227 | { |
| 213 | case EACCES: |
228 | case EACCES: |
| 214 | throw new SocketException("The address is protected, and the user is not the superuser."); |
229 | throw new SocketException("The address is protected, and the user is not the superuser."); |
| 215 | 230 | ||
| Line 263... | Line 278... | ||
| 263 | 278 | ||
| 264 | status = ::listen(mySocket, MAXCONNECTIONS); |
279 | status = ::listen(mySocket, MAXCONNECTIONS); |
| 265 | 280 | ||
| 266 | if (status == -1) |
281 | if (status == -1) |
| 267 | { |
282 | { |
| 268 |
|
283 | silentClose(); |
| 269 | switch (errno) |
284 | switch (errno) |
| 270 | { |
285 | { |
| 271 | case EADDRINUSE: |
286 | case EADDRINUSE: |
| 272 | throw new SocketException("Another socket is already listening on the same port."); |
287 | throw new SocketException("Another socket is already listening on the same port."); |
| 273 | 288 | ||
| Line 301... | Line 316... | ||
| 301 | return false; |
316 | return false; |
| 302 | } |
317 | } |
| 303 | 318 | ||
| 304 | void AsyncSocket::connect() |
319 | void AsyncSocket::connect() |
| 305 | { |
320 | { |
| 306 |
|
321 | eventAdd(SocketEvent::TYPE_CONNECTING); |
| 307 | 322 | ||
| 308 | create(); |
323 | create(); |
| 309 | 324 | ||
| 310 | memset(&myAddressStruct, 0, sizeof(myAddressStruct)); |
325 | memset(&myAddressStruct, 0, sizeof(myAddressStruct)); |
| 311 | 326 | ||
| 312 | myAddressStruct.sin_family = AF_INET; |
327 | myAddressStruct.sin_family = AF_INET; |
| 313 | myAddressStruct.sin_port = htons(myPort); |
328 | myAddressStruct.sin_port = htons(myPort); |
| 314 | - | ||
| 315 | //slog << "Trying to resolv " + myAddress + "\n"; |
- | |
| 316 | 329 | ||
| 317 | struct hostent *hptr = gethostbyname(myAddress.c_str()); |
330 | struct hostent *hptr = gethostbyname(myAddress.c_str()); |
| 318 | if (hptr == NULL) |
331 | if (hptr == NULL) |
| 319 | { |
332 | { |
| 320 |
|
333 | silentClose(); |
| 321 | throw new SocketException("Connect: Could not resolv ip address"); |
334 | throw new SocketException("Connect: Could not resolv ip address"); |
| 322 | } |
335 | } |
| 323 | - | ||
| 324 | 336 | ||
| 325 | memcpy(&myAddressStruct.sin_addr, hptr->h_addr, hptr->h_length); |
337 | memcpy(&myAddressStruct.sin_addr, hptr->h_addr, hptr->h_length); |
| 326 | 338 | ||
| 327 | int status = ::connect(mySocket, (sockaddr*)&myAddressStruct, sizeof(myAddressStruct)); |
339 | int status = ::connect(mySocket, (sockaddr*)&myAddressStruct, sizeof(myAddressStruct)); |
| 328 | 340 | ||
| 329 | if (status == -1) |
341 | if (status == -1) |
| 330 | { |
342 | { |
| 331 |
|
343 | silentClose(); |
| 332 | switch (errno) |
344 | switch (errno) |
| 333 | { |
345 | { |
| 334 | case EACCES: |
346 | case EACCES: |
| 335 | throw new SocketException("For Unix domain sockets, which are identified by pathname: Write permission is denied on the socket file, or search per- mission is denied for one of the directories in the path prefix. (See also path_resolution(7).)"); |
347 | throw new SocketException("For Unix domain sockets, which are identified by pathname: Write permission is denied on the socket file, or search per- mission is denied for one of the directories in the path prefix. (See also path_resolution(7).)"); |
| 336 | 348 | ||
| Line 379... | Line 391... | ||
| 379 | default: |
391 | default: |
| 380 | throw new SocketException("Other errors may be generated by the underlying protocol modules. : " + itos(errno)); |
392 | throw new SocketException("Other errors may be generated by the underlying protocol modules. : " + itos(errno)); |
| 381 | } |
393 | } |
| 382 | } |
394 | } |
| 383 | 395 | ||
| 384 | myForceReconnect = false; |
- | |
| 385 | - | ||
| 386 |
|
396 | eventAdd(SocketEvent::TYPE_CONNECTED); |
| 387 | } |
- | |
| 388 | - | ||
| 389 | void AsyncSocket::close() |
- | |
| 390 | { |
- | |
| 391 | if (mySocket != -1) |
- | |
| 392 | { |
- | |
| 393 | ::close(mySocket); |
- | |
| 394 | mySocket = -1; |
- | |
| 395 | setEvent(ASYNCSOCKET_EVENT_CLOSED); |
- | |
| 396 | } |
- | |
| 397 | } |
- | |
| 398 | - | ||
| 399 | bool AsyncSocket::isConnected() |
- | |
| 400 | { |
- | |
| 401 | return mySocket != -1; |
- | |
| 402 | } |
- | |
| 403 | - | ||
| 404 | void AsyncSocket::setReconnectTimeout(unsigned int timeout) |
- | |
| 405 | { |
- | |
| 406 | myReconnectTimeout = timeout; |
- | |
| 407 | } |
- | |
| 408 | - | ||
| 409 | void AsyncSocket::startEvent() |
- | |
| 410 | { |
- | |
| 411 | myEventSemaphore.lock(); |
- | |
| 412 | } |
- | |
| 413 | - | ||
| 414 | bool AsyncSocket::availableEvent() |
- | |
| 415 | { |
- | |
| 416 | return (myEventQueue.size() > 0); |
- | |
| 417 | } |
- | |
| 418 | - | ||
| 419 | int AsyncSocket::getEvent() |
- | |
| 420 | { |
- | |
| 421 | return myEventQueue.pop(); |
- | |
| 422 | } |
- | |
| 423 | - | ||
| 424 | void AsyncSocket::waitForEvent() |
- | |
| 425 | { |
- | |
| 426 | myEventSemaphore.wait(); |
- | |
| 427 | } |
- | |
| 428 | - | ||
| 429 | void AsyncSocket::stopEvent() |
- | |
| 430 | { |
- | |
| 431 | myEventSemaphore.unlock(); |
- | |
| 432 | } |
- | |
| 433 | - | ||
| 434 | void AsyncSocket::setAddress(string address, int port) |
- | |
| 435 | { |
- | |
| 436 | myAddress = address; |
- | |
| 437 | myPort = port; |
- | |
| 438 | } |
- | |
| 439 | - | ||
| 440 | void AsyncSocket::setPort(int port) |
- | |
| 441 | { |
- | |
| 442 | myPort = port; |
- | |
| 443 | } |
- | |
| 444 | - | ||
| 445 | void AsyncSocket::setSocket(int socket) |
- | |
| 446 | { |
- | |
| 447 | mySocket = socket; |
- | |
| 448 | } |
- | |
| 449 | - | ||
| 450 | int AsyncSocket::getSocket() |
- | |
| 451 | { |
- | |
| 452 | return mySocket; |
- | |
| 453 | } |
- | |
| 454 | - | ||
| 455 | bool AsyncSocket::availableData() |
- | |
| 456 | { |
- | |
| 457 | return (myInQueue.size() > 0); |
- | |
| 458 | } |
- | |
| 459 | - | ||
| 460 | string AsyncSocket::getData() |
- | |
| 461 | { |
- | |
| 462 | return myInQueue.pop(); |
- | |
| 463 | } |
397 | } |
| 464 | 398 | ||
| 465 |
|
399 | void AsyncSocket::sendData(string data) |
| 466 | { |
- | |
| 467 | //cout << "SendData on " + myAddress + ":" + itos(myPort) + ".\n"; |
- | |
| 468 | sendDataDirect(data); |
- | |
| 469 | //myOutQueue.push(data); |
- | |
| 470 | //mySemaphore.broadcast(); |
- | |
| 471 | return true; |
- | |
| 472 | } |
- | |
| 473 | - | ||
| 474 | void AsyncSocket::sendDataDirect(string data) |
- | |
| 475 | { |
400 | { |
| 476 | SyslogStream &slog = SyslogStream::getInstance(); |
- | |
| 477 | - | ||
| 478 | //slog << "Sending: " << data << "\n"; |
- | |
| 479 | - | ||
| 480 | int status = ::send(mySocket, data.c_str(), data.size(), 0); |
401 | int status = ::send(mySocket, data.c_str(), data.size(), 0); |
| 481 | 402 | ||
| 482 | //slog << "Status: " << status << "\n"; |
- | |
| 483 | - | ||
| 484 | try |
- | |
| 485 | { |
- | |
| 486 | if (status == -1) |
403 | if (status == -1) |
| 487 | { |
404 | { |
| 488 | switch (errno) |
405 | switch (errno) |
| 489 | { |
406 | { |
| 490 | case EACCES: |
407 | case EACCES: |
| 491 | throw new SocketException("(For Unix domain sockets, which are identified by pathname) Write permission is denied on the destination socket file, or search permission is denied for one of the directories the path prefix. (See path_resolution(7).)"); |
408 | throw new SocketException("(For Unix domain sockets, which are identified by pathname) Write permission is denied on the destination socket file, or search permission is denied for one of the directories the path prefix. (See path_resolution(7).)"); |
| 492 | 409 | ||
| 493 | case EAGAIN: |
410 | case EAGAIN: |
| 494 |
|
411 | throw new SocketException("The socket is marked non-blocking and the requested operation would block."); |
| 495 | break; |
- | |
| 496 | 412 | ||
| 497 | case EBADF: |
413 | case EBADF: |
| 498 | throw new SocketException("An invalid descriptor was specified."); |
414 | throw new SocketException("An invalid descriptor was specified."); |
| 499 | 415 | ||
| 500 | case ECONNRESET: |
416 | case ECONNRESET: |
| Line 535... | Line 451... | ||
| 535 | 451 | ||
| 536 | case EPIPE: |
452 | case EPIPE: |
| 537 | throw new SocketException("The local end has been shut down on a connection oriented socket. In this case the process will also receive a SIGPIPE unless MSG_NOSIGNAL is set."); |
453 | throw new SocketException("The local end has been shut down on a connection oriented socket. In this case the process will also receive a SIGPIPE unless MSG_NOSIGNAL is set."); |
| 538 | 454 | ||
| 539 | default: |
455 | default: |
| 540 |
|
456 | throw new SocketException("Unknow exception: " + itos(errno)); |
| 541 | break; |
- | |
| 542 | } |
- | |
| 543 | } |
457 | } |
| 544 | } |
- | |
| 545 | catch (SocketException *e) |
- | |
| 546 | { |
- | |
| 547 | slog << "sendDataDirect: Exception: " + e->getDescription() + "\n"; |
- | |
| 548 | } |
- | |
| 549 | } |
- | |
| 550 | - | ||
| 551 | void AsyncSocket::setEvent(int event) |
- | |
| 552 | { |
- | |
| 553 | myEventQueue.push(event); |
- | |
| 554 | myEventSemaphore.broadcast(); |
- | |
| 555 | } |
458 | } |
| - | 459 | } |
|
| 556 | 460 | ||
| 557 | void AsyncSocket:: |
461 | void AsyncSocket::eventAdd(unsigned int eventType) |
| 558 | { |
462 | { |
| 559 | ///FIXME: Threadsafe? |
- | |
| 560 |
|
463 | eventAdd(eventType, ""); |
| 561 | } |
464 | } |
| 562 | 465 | ||
| - | 466 | void AsyncSocket::eventAdd(unsigned int eventType, string eventData) |
|
| - | 467 | { |
|
| - | 468 | SocketEvent socketEvent(eventType, eventData); |
|
| - | 469 | myEventQueue.push(socketEvent); |
|
| - | 470 | myEventSemaphore.broadcast(); |
|
| - | 471 | } |
|