00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 #include <stdio.h>
00024 #include <stdlib.h>
00025 #include <string.h>
00026 #include <unistd.h>
00027 #include <limits.h>
00028 #include <errno.h>
00029 #include <signal.h>
00030 #include <time.h>
00031 #include <sched.h>
00032 #include <netdb.h>
00033 #include <sys/socket.h>
00034 #include <sys/types.h>
00035
00036 #include <sys/mman.h>
00037 #include <netinet/in.h>
00038 #include <netinet/ip.h>
00039 #include <netinet/tcp.h>
00040 #include <arpa/inet.h>
00041 #include <pth.h>
00042
00043 #include "Config.h"
00044 #include "SoundSrv.hh"
00045
00046
00047 clSoundSrv *SoundSrv;
00048
00049
00050 void *Input(void *vpNone)
00051 {
00052 SoundSrv->InputExec();
00053 return NULL;
00054 }
00055
00056
00057 void *WaitConnect(void *vpNone)
00058 {
00059 SoundSrv->WaitConnectExec();
00060 return NULL;
00061 }
00062
00063
00064 void *ServeClient(void *vpHandle)
00065 {
00066 SoundSrv->ServeClientExec(vpHandle);
00067 return NULL;
00068 }
00069
00070
00071 int main(int argc, char *argv[])
00072 {
00073 bool bDaemon = false;
00074 int iArgCntr;
00075 char cpDevice[_POSIX_PATH_MAX + 1];
00076 #ifndef BSDSYS
00077 uid_t uidCurrent;
00078 struct sched_param sSchedParam;
00079 #endif
00080
00081 signal(SIGPIPE, SIG_IGN);
00082 signal(SIGFPE, SIG_IGN);
00083 #ifndef BSDSYS
00084 uidCurrent = getuid();
00085 setuid(0);
00086 sSchedParam.sched_priority = SS_SCHED_PRIORITY;
00087 sched_setscheduler(0, SCHED_FIFO, &sSchedParam);
00088 setuid(uidCurrent);
00089 #endif
00090 if (pth_init() < 0)
00091 {
00092 fprintf(stderr, "fatal: pth_init() failed\n");
00093 exit(1);
00094 }
00095 strcpy(cpDevice, SS_SND_DEVICE);
00096 if (argc >= 2)
00097 {
00098 for (iArgCntr = 1; iArgCntr < argc; iArgCntr++)
00099 {
00100 if (strcmp(argv[iArgCntr], "-D") == 0)
00101 {
00102 bDaemon = true;
00103 }
00104 else if (strcmp(argv[iArgCntr], "--version") == 0)
00105 {
00106 printf("SoundSrv v%i.%i.%i\n", SS_VERSMAJ, SS_VERSMIN,
00107 SS_VERSPL);
00108 printf("Copyright (C) 1999-2001 Jussi Laako\n");
00109 return 0;
00110 }
00111 else if (strcmp(argv[iArgCntr], "--help") == 0)
00112 {
00113 printf("%s [-D|--version|--help|device]\n\n", argv[0]);
00114 printf("-D start as daemon\n");
00115 printf("--version display version information\n");
00116 printf("--help display this help\n");
00117 printf("device use device (eg. dsp0)\n");
00118 return 0;
00119 }
00120 else
00121 {
00122 strcpy(cpDevice, argv[iArgCntr]);
00123 }
00124 }
00125 }
00126 if (bDaemon)
00127 {
00128 if (fork() != 0)
00129 {
00130 exit(0);
00131 }
00132 setsid();
00133 freopen("/dev/null", "r+", stderr);
00134 freopen("/dev/null", "r+", stdin);
00135 freopen("/dev/null", "r+", stdout);
00136 }
00137 SoundSrv = new clSoundSrv(argv[0], cpDevice);
00138 SoundSrv->Exec();
00139 delete SoundSrv;
00140 pth_kill();
00141 return 0;
00142 }
00143
00144
00145 clSoundSrv::clSoundSrv(const char *cpName, const char *cpAudioDev)
00146 {
00147 int iBits;
00148 int iLoopCntr;
00149 int iOSSVersion;
00150 long lPthVersion;
00151 char cpCfgName[_POSIX_PATH_MAX + 1];
00152 char cpLogName[_POSIX_PATH_MAX + 1];
00153 char cpDevName[_POSIX_PATH_MAX + 1];
00154 char cpLogTxt[256];
00155
00156 bRun = true;
00157 for (iLoopCntr = 0; iLoopCntr < SS_MAXCLIENTS; iLoopCntr++)
00158 {
00159 ipClientH[iLoopCntr] = -1;
00160 }
00161 strcpy(cpProgName, cpName);
00162 sprintf(cpCfgName, "soundsrv.%s.cfg", cpAudioDev);
00163 CfgFile = new clCfgFile(cpCfgName);
00164 sprintf(cpLogName, "%s.%s", SS_LOGFILE, cpAudioDev);
00165 LogFile = new clLogFile(cpLogName);
00166 LogFile->Add('*', "Starting");
00167 lPthVersion = pth_version();
00168 sprintf(cpLogTxt, "Pth version %lu.%lu.%lu (compiled with %u.%u.%u)",
00169 ((lPthVersion & 0xf00000) >> 20),
00170 ((lPthVersion & 0x0ff000) >> 12),
00171 (lPthVersion & 0x0000ff),
00172 SS_PTH_MAJ, SS_PTH_MIN, SS_PTH_PL);
00173 LogFile->Add(' ', cpLogTxt);
00174 sprintf(cpDevName, "/dev/%s", cpAudioDev);
00175 if (!CfgFile->GetInt("SampleRate", &iAudioSr))
00176 {
00177 iAudioSr = SS_SND_SAMPLERATE;
00178 }
00179 if (!CfgFile->GetInt("Channels", &iAudioCh))
00180 {
00181 iAudioCh = SS_SND_CHANNELS;
00182 }
00183 if (CfgFile->GetInt("Bits", &iBits))
00184 {
00185 switch (iBits)
00186 {
00187 case 8:
00188 iAudioFrmt = AFMT_U8;
00189 iAudioTypeSize = 1;
00190 break;
00191 case 16:
00192 iAudioFrmt = AFMT_S16_NE;
00193 iAudioTypeSize = 2;
00194 break;
00195 #ifndef USE_OSSLITE
00196 case 24:
00197 case 32:
00198 iAudioFrmt = AFMT_S32_NE;
00199 iAudioTypeSize = 4;
00200 break;
00201 #endif
00202 default:
00203 iAudioFrmt = SS_SND_FORMAT;
00204 iAudioTypeSize = SS_SND_FORMAT_SIZE;
00205 }
00206 }
00207 else
00208 {
00209 iAudioFrmt = SS_SND_FORMAT;
00210 iAudioTypeSize = SS_SND_FORMAT_SIZE;
00211 }
00212 sprintf(cpLogTxt, "Request %s fs %i ch %i fmt %xh", cpAudioDev,
00213 iAudioSr, iAudioCh, iAudioFrmt);
00214 LogFile->Add(' ', cpLogTxt);
00215 Audio = new clAudio(cpDevName, &iAudioFrmt, &iAudioSr, &iAudioCh,
00216 AUDIO_READ);
00217 sprintf(cpLogTxt, "Open %s fs %i ch %i fmt %xh", cpAudioDev,
00218 iAudioSr, iAudioCh, iAudioFrmt);
00219 LogFile->Add(' ', cpLogTxt, Audio->GetError());
00220 iOSSVersion = Audio->GetVersion();
00221 sprintf(cpLogTxt, "OSS version %i.%i.%i (compiled with %i.%i.%i)",
00222 ((iOSSVersion >> 16) & 0xff), ((iOSSVersion >> 8) & 0xff),
00223 (iOSSVersion & 0xff),
00224 ((SOUND_VERSION >> 16) & 0xff), ((SOUND_VERSION >> 8) & 0xff),
00225 (SOUND_VERSION & 0xff));
00226 LogFile->Add(' ', cpLogTxt);
00227 sprintf(cpLogTxt, "Fragment size %i bytes", Audio->GetFragmentSize());
00228 LogFile->Add(' ', cpLogTxt);
00229 iSampleCount = Audio->GetFragmentSize() / iAudioTypeSize;
00230 iOutBufSize = iSampleCount * sizeof(GDT);
00231 cpOutBuf = (char *) malloc(iOutBufSize);
00232 if (cpOutBuf == NULL)
00233 {
00234 LogFile->Add('!', "OUT OF MEMORY");
00235 exit(1);
00236 }
00237 mlock(cpOutBuf, iOutBufSize);
00238 }
00239
00240
00241 clSoundSrv::~clSoundSrv()
00242 {
00243 delete Audio;
00244 LogFile->Add('*', "Shutdown");
00245 delete LogFile;
00246 delete CfgFile;
00247 munlock(cpOutBuf, iOutBufSize);
00248 if (cpOutBuf != NULL) free(cpOutBuf);
00249 }
00250
00251
00252 void clSoundSrv::Exec()
00253 {
00254 unsigned int iInputStackSize;
00255 int iSigNum;
00256 void *vpReturnValue;
00257 pth_attr_t pthaInput;
00258 pth_attr_t pthaWaitConnect;
00259 pth_event_t ptheInputDead;
00260 pth_event_t ptheWaitConnectDead;
00261 pth_event_t ptheThreadDead;
00262
00263 sigemptyset(&sigsetQuit);
00264 sigaddset(&sigsetQuit, SIGINT);
00265 sigaddset(&sigsetQuit, SIGHUP);
00266 sigaddset(&sigsetQuit, SIGTERM);
00267 iInputStackSize = 1024 * 32;
00268 pthaInput = pth_attr_new();
00269 pth_attr_set(pthaInput, PTH_ATTR_NAME, "Input");
00270 pth_attr_set(pthaInput, PTH_ATTR_STACK_SIZE, iInputStackSize);
00271 tidInput = pth_spawn(pthaInput, Input, NULL);
00272 pthaWaitConnect = pth_attr_new();
00273 pth_attr_set(pthaWaitConnect, PTH_ATTR_NAME, "WaitConnect");
00274 tidWaitConnect = pth_spawn(PTH_ATTR_DEFAULT, WaitConnect, NULL);
00275 ptheInputDead = pth_event(PTH_EVENT_TID|PTH_UNTIL_TID_DEAD, tidInput);
00276 ptheWaitConnectDead = pth_event(PTH_EVENT_TID|PTH_UNTIL_TID_DEAD,
00277 tidWaitConnect);
00278 ptheThreadDead = pth_event_concat(ptheInputDead, ptheWaitConnectDead,
00279 NULL);
00280 LogFile->Add(' ', "Running");
00281 pth_sigwait_ev(&sigsetQuit, &iSigNum, ptheThreadDead);
00282 if (pth_event_occurred(ptheThreadDead))
00283 {
00284 LogFile->Add('!', "Dead of thread detected");
00285 Quit();
00286 }
00287 else
00288 {
00289 Quit(iSigNum);
00290 }
00291 pth_cancel(tidInput);
00292 pth_join(tidInput, &vpReturnValue);
00293 pth_join(tidWaitConnect, &vpReturnValue);
00294 LogFile->Add(' ', "Stopping");
00295 }
00296
00297
00298 void clSoundSrv::Quit()
00299 {
00300 bRun = false;
00301 }
00302
00303
00304 void clSoundSrv::Quit(int iSigNum)
00305 {
00306 bRun = false;
00307 switch (iSigNum)
00308 {
00309 case SIGHUP:
00310 LogFile->Add(' ', "Received SIGHUP");
00311 break;
00312 case SIGINT:
00313 LogFile->Add(' ', "Received SIGINT");
00314 break;
00315 default:
00316 LogFile->Add('!', "Received unknown signal");
00317 }
00318 }
00319
00320
00321 void clSoundSrv::InputExec()
00322 {
00323 int iDevH;
00324 int iInBufSize;
00325 int iConvBufSize;
00326 int iBytesRead = 0;
00327 int iInputErrors = 0;
00328 void *pInBuf;
00329 GDT *pConvBuf;
00330
00331 iInBufSize = iSampleCount * iAudioTypeSize;
00332 iConvBufSize = iSampleCount * sizeof(GDT);
00333 pInBuf = malloc(iInBufSize);
00334 pConvBuf = (GDT *) malloc(iConvBufSize);
00335 if (pInBuf == NULL || pConvBuf == NULL)
00336 {
00337 LogFile->Add('!', "OUT OF MEMORY!");
00338 return;
00339 }
00340 mlock(pInBuf, iInBufSize);
00341 mlock(pConvBuf, iConvBufSize);
00342 pth_cancel_state(PTH_CANCEL_DEFERRED, NULL);
00343 iDevH = SoundSrv->Audio->GetHandle();
00344 LogFile->Add(' ', "Input thread running");
00345
00346
00347 iBytesRead = read(iDevH, pInBuf, iInBufSize);
00348 while (bRun)
00349 {
00350 if (access(SS_SHUTDOWNFILE, F_OK) == 0)
00351 {
00352 unlink(SS_SHUTDOWNFILE);
00353 Quit();
00354 break;
00355 }
00356 iBytesRead += pth_read(iDevH, pInBuf, iInBufSize - iBytesRead);
00357 if (!bRun) break;
00358 if (iBytesRead == iInBufSize)
00359 {
00360 switch (iAudioTypeSize)
00361 {
00362 case 1:
00363 DSP.Convert(pConvBuf, (unsigned char *) pInBuf,
00364 iSampleCount);
00365 break;
00366 case 2:
00367 DSP.Convert(pConvBuf, (signed short *) pInBuf,
00368 iSampleCount, false);
00369 break;
00370 case 4:
00371 DSP.Convert(pConvBuf, (signed int *) pInBuf,
00372 iSampleCount, true);
00373 break;
00374 }
00375 MutexThis.Wait();
00376 SoundMsg.SetData(cpOutBuf, pConvBuf, iSampleCount);
00377 CondDataAvail.Notify(TRUE);
00378 MutexThis.Release();
00379 iBytesRead = 0;
00380 }
00381 else if (iBytesRead > iInBufSize)
00382 {
00383 LogFile->Add('!',
00384 "pth_read() overflowed input buffer, process unstable");
00385 Quit();
00386 }
00387 else if (iBytesRead < iInBufSize && iBytesRead >= 0)
00388 {
00389 iInputErrors++;
00390 LogFile->Add('!', "pth_read() input buffer underrun");
00391 }
00392 else
00393 {
00394 iInputErrors++;
00395 LogFile->Add('!', "input device pth_read() error", errno);
00396 }
00397 if (iInputErrors >= SS_MAXERRORS)
00398 {
00399 LogFile->Add('!', "Too many errors on input device");
00400 Quit();
00401 }
00402 }
00403 munlock(pInBuf, iInBufSize);
00404 munlock(pConvBuf, iOutBufSize);
00405 free(pInBuf);
00406 free(pConvBuf);
00407 LogFile->Add(' ', "Input thread ending");
00408 }
00409
00410
00411 void clSoundSrv::WaitConnectExec()
00412 {
00413 int iLoopCntr;
00414 int iListenH;
00415 int iPort;
00416 socklen_t iAddrLen;
00417 char cpLogEntry[256];
00418 void *vpReturnValue;
00419 struct protoent *spProtocol = NULL;
00420 struct servent *spService = NULL;
00421 struct sockaddr_in sServAddr;
00422 struct sockaddr_in sClieAddr;
00423 pth_attr_t pthaServeClient;
00424 pth_event_t ptheInputDead;
00425
00426 LogFile->Add(' ', "WaitConnect thread running");
00427 pthaServeClient = pth_attr_new();
00428 pth_attr_set(pthaServeClient, PTH_ATTR_NAME, "ServeClient");
00429 ptheInputDead = pth_event(PTH_EVENT_TID|PTH_UNTIL_TID_DEAD, tidInput);
00430 spProtocol = getprotobyname("tcp");
00431 if (spProtocol != NULL)
00432 {
00433 iListenH = socket(AF_INET, SOCK_STREAM, spProtocol->p_proto);
00434 }
00435 else
00436 {
00437 iListenH = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
00438 LogFile->Add('#',
00439 "Warning: no entry returned for TCP by getprotobyname()");
00440 }
00441 if (iListenH < 0)
00442 {
00443 LogFile->Add('!', "socket() failed for listening fd", errno);
00444 return;
00445 }
00446 memset(&sServAddr, 0x00, sizeof(sServAddr));
00447 sServAddr.sin_family = AF_INET;
00448 sServAddr.sin_addr.s_addr = htonl(INADDR_ANY);
00449 if (CfgFile->GetInt("Port", &iPort))
00450 {
00451 sServAddr.sin_port = htons(iPort);
00452 }
00453 else
00454 {
00455 spService = getservbyname(cpProgName, "tcp");
00456 if (spService != NULL)
00457 {
00458 sServAddr.sin_port = spService->s_port;
00459 }
00460 else
00461 {
00462 sServAddr.sin_port = htons(SS_DEFAULT_PORT);
00463 }
00464 }
00465 if (bind(iListenH, (struct sockaddr *) &sServAddr, sizeof(sServAddr)) < 0)
00466 {
00467 LogFile->Add('!', "bind() failed for listening fd", errno);
00468 close(iListenH);
00469 return;
00470 }
00471 if (listen(iListenH, SS_MAXCLIENTS) < 0)
00472 {
00473 LogFile->Add('!', "listen() failed for listening fd", errno);
00474 close(iListenH);
00475 return;
00476 }
00477 sprintf(cpLogEntry, "Listening for connections on port %i",
00478 ntohs(sServAddr.sin_port));
00479 LogFile->Add(' ', cpLogEntry);
00480 while (bRun)
00481 {
00482 iClientIdx = FindFreeClient();
00483 memset(&sClieAddr, 0x00, sizeof(sClieAddr));
00484 iAddrLen = sizeof(sClieAddr);
00485 ipClientH[iClientIdx] = pth_accept_ev(iListenH,
00486 (struct sockaddr *) &sClieAddr, &iAddrLen, ptheInputDead);
00487 if (!pth_event_occurred(ptheInputDead))
00488 {
00489 if (ipClientH[iClientIdx] >= 0)
00490 {
00491 sprintf(cpLogEntry, "%s connected",
00492 inet_ntoa(sClieAddr.sin_addr));
00493 LogFile->Add('+', cpLogEntry);
00494 ptidServeClient[iClientIdx] =
00495 pth_spawn(pthaServeClient, ServeClient,
00496 &ipClientH[iClientIdx]);
00497 }
00498 }
00499 }
00500 for (iLoopCntr = 0; iLoopCntr < SS_MAXCLIENTS; iLoopCntr++)
00501 {
00502 if (ipClientH[iLoopCntr] >= 0)
00503 pth_join(ptidServeClient[iLoopCntr], &vpReturnValue);
00504 }
00505 pth_event_free(ptheInputDead, PTH_FREE_THIS);
00506 LogFile->Add(' ', "WaitConnect thread ending");
00507 }
00508
00509
00510 void clSoundSrv::ServeClientExec(void *vpHandle)
00511 {
00512 bool bThreadRun = true;
00513 char cpFirstMsg[GLOBAL_HEADER_LEN];
00514 char *cpLocalOutBuf;
00515 int iHandleIdx;
00516 const int iSockH = *((int *) vpHandle);
00517 int iSockBufSize;
00518
00519 int iSockNagle;
00520 int iSockTOS;
00521 int iBytesSent;
00522 int iLastRes;
00523 int iWriteErrno;
00524 socklen_t iSockOptLen;
00525 stSoundStart sSndStart;
00526 pth_event_t ptheInputDead;
00527
00528 LogFile->Add(' ', "ServeClient thread running");
00529 iHandleIdx = FindThisHandle(iSockH);
00530 if (iHandleIdx < 0)
00531 {
00532 LogFile->Add('!', "FindThisHandle() returned error");
00533 return;
00534 }
00535 cpLocalOutBuf = (char *) malloc(iOutBufSize);
00536 if (cpLocalOutBuf == NULL)
00537 {
00538 LogFile->Add('!', "malloc() for local output buffer failed");
00539 return;
00540 }
00541 mlock(cpLocalOutBuf, iOutBufSize);
00542 ptheInputDead = pth_event(PTH_EVENT_TID|PTH_UNTIL_TID_DEAD, tidInput);
00543 sSndStart.iChannels = iAudioCh;
00544 sSndStart.dSampleRate = iAudioSr;
00545 sSndStart.iFragmentSize = iSampleCount;
00546 SoundMsg.SetStart(cpFirstMsg, &sSndStart);
00547 if (pth_write_ev(iSockH, cpFirstMsg, GLOBAL_HEADER_LEN, ptheInputDead) <
00548 GLOBAL_HEADER_LEN)
00549 {
00550 LogFile->Add('?', "Unable to send data header message to client",
00551 errno);
00552 close(iSockH);
00553 ipClientH[iHandleIdx] = -1;
00554 return;
00555 }
00556 iSockOptLen = sizeof(iSockBufSize);
00557 if (getsockopt(iSockH, SOL_SOCKET, SO_SNDBUF, &iSockBufSize,
00558 &iSockOptLen) < 0)
00559 {
00560 LogFile->Add('!', "getsockopt() error getting send buffer size",
00561 errno);
00562 }
00563 if (iSockBufSize < (SS_SOCKET_BUF_FRAGS * iOutBufSize))
00564 {
00565 iSockBufSize = SS_SOCKET_BUF_FRAGS * iOutBufSize;
00566 if (setsockopt(iSockH, SOL_SOCKET, SO_SNDBUF, &iSockBufSize,
00567 sizeof(iSockBufSize)) < 0)
00568 {
00569 LogFile->Add('!', "setsockopt() error setting send buffer size",
00570 errno);
00571 }
00572 }
00573
00574
00575
00576
00577
00578
00579
00580
00581
00582
00583
00584
00585
00586
00587
00588
00589
00590 iSockNagle = 1;
00591 if (setsockopt(iSockH, IPPROTO_TCP, TCP_NODELAY, &iSockNagle,
00592 sizeof(iSockNagle)) < 0)
00593 {
00594 LogFile->Add('!', "setsockopt() error disabling nagle algorithm",
00595 errno);
00596 }
00597 iSockTOS = IPTOS_LOWDELAY;
00598 if (setsockopt(iSockH, IPPROTO_IP, IP_TOS, &iSockTOS,
00599 sizeof(iSockTOS)) < 0)
00600 {
00601 LogFile->Add('!', "setsockopt() error setting TOS flag",
00602 errno);
00603 }
00604 while (bRun && bThreadRun)
00605 {
00606 MutexThis.Wait();
00607 CondDataAvail.Wait(MutexThis.GetPtr(), ptheInputDead);
00608 memcpy(cpLocalOutBuf, cpOutBuf, iOutBufSize);
00609 MutexThis.Release();
00610 if (!pth_event_occurred(ptheInputDead))
00611 {
00612 iBytesSent = 0;
00613 do {
00614 iLastRes = pth_write_ev(iSockH, &cpLocalOutBuf[iBytesSent],
00615 iOutBufSize - iBytesSent, ptheInputDead);
00616 iBytesSent += iLastRes;
00617 if (pth_event_occurred(ptheInputDead))
00618 {
00619 bThreadRun = false;
00620 break;
00621 }
00622 } while (iBytesSent < iOutBufSize && iLastRes >= 0);
00623 if (!bThreadRun) break;
00624 iWriteErrno = (iLastRes < 0) ? errno : 0;
00625 if (iWriteErrno != 0)
00626 {
00627 if (iWriteErrno == EPIPE)
00628 {
00629 LogFile->Add('-', "Client disconnect");
00630 }
00631 else
00632 {
00633 LogFile->Add('-',
00634 "pth_write() returned error on client socket",
00635 iWriteErrno);
00636 }
00637 bThreadRun = false;
00638 }
00639 }
00640 else break;
00641 }
00642 close(iSockH);
00643 ipClientH[iHandleIdx] = -1;
00644 pth_event_free(ptheInputDead, PTH_FREE_THIS);
00645 munlock(cpLocalOutBuf, iOutBufSize);
00646 free(cpLocalOutBuf);
00647 LogFile->Add(' ', "ServeClient thread ending");
00648 }
00649
00650
00651 int inline clSoundSrv::FindFreeClient()
00652 {
00653 int iLoopCntr;
00654
00655 for (iLoopCntr = 0; iLoopCntr < SS_MAXCLIENTS; iLoopCntr++)
00656 {
00657 if (ipClientH[iLoopCntr] < 0)
00658 {
00659 return iLoopCntr;
00660 }
00661 }
00662 return -1;
00663 }
00664
00665
00666 int inline clSoundSrv::FindThisHandle(int iFindH)
00667 {
00668 int iLoopCntr;
00669
00670 for (iLoopCntr = 0; iLoopCntr < SS_MAXCLIENTS; iLoopCntr++)
00671 {
00672 if (ipClientH[iLoopCntr] == iFindH)
00673 {
00674 return iLoopCntr;
00675 }
00676 }
00677 return -1;
00678 }
00679