live555--testRTSPClient学习心得
来源:互联网 发布:sql语句格式化工具 编辑:程序博客网 时间:2024/06/04 19:11
最近因为公司项目的缘故,开始学习live555项目,关注live media client端和server端的逻辑分析,在网上看了很多资料,都是大致的讲一些,对于其内部的一些逻辑还是没有详细解释,现在把自己在学习过程中的一些心得与大家分享,如有问题,可以回站内信给我,大家共同探讨一下。
下载live555开源包之后,解压,testRTSPClient所在目录为testRTSPClient.cpp目录中,我采用的是sourceInsight来阅读代码, 个人感觉还是比较方便的,从main函数开始,中间一些小细节大家明白的就不多说了。
int main(int argc, char** argv) { // Begin by setting up our usage environment: TaskScheduler* scheduler = BasicTaskScheduler::createNew(); UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler); // We need at least one "rtsp://" URL argument: if (argc < 2) { usage(*env, argv[0]); return 1; } // There are argc-1 URLs: argv[1] through argv[argc-1]. Open and start streaming each one: for (int i = 1; i <= argc-1; ++i) { openURL(*env, argv[0], argv[i]); } // All subsequent activity takes place within the event loop: env->taskScheduler().doEventLoop(&eventLoopWatchVariable);}
首先是环境的初始化,每个项目都一样,因此不多说了,代码如下:
TaskScheduler* scheduler = BasicTaskScheduler::createNew(); UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);
然后是进入openURL函数,将环境参数和控制台输入参数传入,对其进行解析处理,openURL函数如下:
void openURL(UsageEnvironment& env, char const* progName, char const* rtspURL) { RTSPClient* rtspClient = ourRTSPClient::createNew(env, rtspURL, RTSP_CLIENT_VERBOSITY_LEVEL, progName); if (rtspClient == NULL) { env << "Failed to create a RTSP client for URL \"" << rtspURL << "\": " << env.getResultMsg() << "\n"; return; } ++rtspClientCount;
// Next, send a RTSP "DESCRIBE" command, to get a SDP description for the stream. // Note that this command - like all RTSP commands - is sent asynchronously; we do not block, waiting for a response. // Instead, the following function call returns immediately, and we handle the RTSP response later, from within the event loop: rtspClient->sendDescribeCommand(continueAfterDESCRIBE); }
在openURL函数中,因为是client端服务,首先创建rtspClient,将url信息,项目名称信息,写入RTSPClient类中,这个可以查看ourRTSPClient::createNew函数和RTSPClient类定义进行获知,这里不多阐述;然后通过sendDescribeCommand函数,设置回调函数continueAfterDESCRIBE,回调函数在sendRequest中用来提示错误的发生;sendDescribeCommand如下定义:
unsigned RTSPClient::sendDescribeCommand(responseHandler* responseHandler, Authenticator* authenticator) { if (authenticator != NULL) fCurrentAuthenticator = *authenticator; return sendRequest(new RequestRecord(++fCSeq, "DESCRIBE", responseHandler));}
authenticator为鉴权认证信息,因为开源项目未涉及,关注responseHandler,这里传递的是回调函数指针,作为response句柄,在其内部调用sendRequest函数,sendRequest之前首先创建一个RequestRecord对象,new RequestRecord(++fCSeq, "DESCRIBE", responseHandler),构造函数在其类的初始化列表中将DESCRIBE作为commandName赋予对象,回调函数指针作为句柄传入fHandler,这样RequestRecord对象就含有fCSeq,“DESCRIBE”和responseHandler属性,进入sendRequest函数,如下:
unsigned RTSPClient::sendRequest(RequestRecord* request) { char* cmd = NULL; do { Boolean connectionIsPending = False; if (!fRequestsAwaitingConnection.isEmpty()) { // A connection is currently pending (with at least one enqueued request). Enqueue this request also: connectionIsPending = True; } else if (fInputSocketNum < 0) { // we need to open a connection int connectResult = openConnection(); if (connectResult < 0) break; // an error occurred else if (connectResult == 0) {// A connection is pending connectionIsPending = True; } // else the connection succeeded. Continue sending the command. } if (connectionIsPending) {//如果连接在等待,则将其加入等待队列 fRequestsAwaitingConnection.enqueue(request); return request->cseq(); } // If requested (and we're not already doing it, or have done it), set up the special protocol for tunneling RTSP-over-HTTP: if (fTunnelOverHTTPPortNum != 0 && strcmp(request->commandName(), "GET") != 0 && fOutputSocketNum == fInputSocketNum) { if (!setupHTTPTunneling1()) break; fRequestsAwaitingHTTPTunneling.enqueue(request); return request->cseq(); } // Construct and send the command: // First, construct command-specific headers that we need: char* cmdURL = fBaseURL; // by default Boolean cmdURLWasAllocated = False; char const* protocolStr = "RTSP/1.0"; // by default char* extraHeaders = (char*)""; // by default Boolean extraHeadersWereAllocated = False; char* contentLengthHeader = (char*)""; // by default Boolean contentLengthHeaderWasAllocated = False; char const* contentStr = request->contentStr(); // by default if (contentStr == NULL) contentStr = ""; unsigned contentStrLen = strlen(contentStr); if (contentStrLen > 0) { char const* contentLengthHeaderFmt ="Content-Length: %d\r\n"; unsigned contentLengthHeaderSize = strlen(contentLengthHeaderFmt)+ 20 /* max int len */; contentLengthHeader = new char[contentLengthHeaderSize]; sprintf(contentLengthHeader, contentLengthHeaderFmt, contentStrLen); contentLengthHeaderWasAllocated = True; } if (strcmp(request->commandName(), "DESCRIBE") == 0) { extraHeaders = (char*)"Accept: application/sdp\r\n"; } else if (strcmp(request->commandName(), "OPTIONS") == 0) { } else if (strcmp(request->commandName(), "ANNOUNCE") == 0) { extraHeaders = (char*)"Content-Type: application/sdp\r\n"; } else if (strcmp(request->commandName(), "SETUP") == 0) { MediaSubsession& subsession = *request->subsession(); Boolean streamUsingTCP = (request->booleanFlags()&0x1) != 0; Boolean streamOutgoing = (request->booleanFlags()&0x2) != 0; Boolean forceMulticastOnUnspecified = (request->booleanFlags()&0x4) != 0; char const *prefix, *separator, *suffix; constructSubsessionURL(subsession, prefix, separator, suffix); char const* transportFmt; if (strcmp(subsession.protocolName(), "UDP") == 0) {suffix = "";transportFmt = "Transport: RAW/RAW/UDP%s%s%s=%d-%d\r\n"; } else {transportFmt = "Transport: RTP/AVP%s%s%s=%d-%d\r\n"; } cmdURL = new char[strlen(prefix) + strlen(separator) + strlen(suffix) + 1]; cmdURLWasAllocated = True; sprintf(cmdURL, "%s%s%s", prefix, separator, suffix); // Construct a "Transport:" header. char const* transportTypeStr; char const* modeStr = streamOutgoing ? ";mode=receive" : ""; // Note: I think the above is nonstandard, but DSS wants it this way char const* portTypeStr; portNumBits rtpNumber, rtcpNumber; if (streamUsingTCP) { // streaming over the RTSP connectiontransportTypeStr = "/TCP;unicast";portTypeStr = ";interleaved";rtpNumber = fTCPStreamIdCount++;rtcpNumber = fTCPStreamIdCount++; } else { // normal RTP streamingunsigned connectionAddress = subsession.connectionEndpointAddress(); Boolean requestMulticastStreaming = IsMulticastAddress(connectionAddress) || (connectionAddress == 0 && forceMulticastOnUnspecified);transportTypeStr = requestMulticastStreaming ? ";multicast" : ";unicast";portTypeStr = ";client_port";rtpNumber = subsession.clientPortNum();if (rtpNumber == 0) { envir().setResultMsg("Client port number unknown\n"); delete[] cmdURL; break;}rtcpNumber = rtpNumber + 1; } unsigned transportSize = strlen(transportFmt)+ strlen(transportTypeStr) + strlen(modeStr) + strlen(portTypeStr) + 2*5 /* max port len */; char* transportStr = new char[transportSize]; sprintf(transportStr, transportFmt, transportTypeStr, modeStr, portTypeStr, rtpNumber, rtcpNumber); // When sending more than one "SETUP" request, include a "Session:" header in the 2nd and later commands: char* sessionStr = createSessionString(fLastSessionId); // The "Transport:" and "Session:" (if present) headers make up the 'extra headers': extraHeaders = new char[transportSize + strlen(sessionStr)]; extraHeadersWereAllocated = True; sprintf(extraHeaders, "%s%s", transportStr, sessionStr); delete[] transportStr; delete[] sessionStr; } else if (strcmp(request->commandName(), "GET") == 0 || strcmp(request->commandName(), "POST") == 0) { // We will be sending a HTTP (not a RTSP) request. // Begin by re-parsing our RTSP URL, just to get the stream name, which we'll use as our 'cmdURL' in the subsequent request: char* username; char* password; NetAddress destAddress; portNumBits urlPortNum; if (!parseRTSPURL(envir(), fBaseURL, username, password, destAddress, urlPortNum, (char const**)&cmdURL)) break; if (cmdURL[0] == '\0') cmdURL = (char*)"/"; delete[] username; delete[] password; protocolStr = "HTTP/1.0"; if (strcmp(request->commandName(), "GET") == 0) {// Create a 'session cookie' string, using MD5:struct { struct timeval timestamp; unsigned counter;} seedData;gettimeofday(&seedData.timestamp, NULL);seedData.counter = ++fSessionCookieCounter;our_MD5Data((unsigned char*)(&seedData), sizeof seedData, fSessionCookie);// DSS seems to require that the 'session cookie' string be 22 bytes long:fSessionCookie[23] = '\0';char const* const extraHeadersFmt = "x-sessioncookie: %s\r\n" "Accept: application/x-rtsp-tunnelled\r\n" "Pragma: no-cache\r\n" "Cache-Control: no-cache\r\n";unsigned extraHeadersSize = strlen(extraHeadersFmt) + strlen(fSessionCookie);extraHeaders = new char[extraHeadersSize];extraHeadersWereAllocated = True;sprintf(extraHeaders, extraHeadersFmt,fSessionCookie); } else { // "POST"char const* const extraHeadersFmt = "x-sessioncookie: %s\r\n" "Content-Type: application/x-rtsp-tunnelled\r\n" "Pragma: no-cache\r\n" "Cache-Control: no-cache\r\n" "Content-Length: 32767\r\n" "Expires: Sun, 9 Jan 1972 00:00:00 GMT\r\n";unsigned extraHeadersSize = strlen(extraHeadersFmt) + strlen(fSessionCookie);extraHeaders = new char[extraHeadersSize];extraHeadersWereAllocated = True;sprintf(extraHeaders, extraHeadersFmt,fSessionCookie); } } else { // "PLAY", "PAUSE", "TEARDOWN", "RECORD", "SET_PARAMETER", "GET_PARAMETER" // First, make sure that we have a RTSP session in progress if (fLastSessionId == NULL) {envir().setResultMsg("No RTSP session is currently in progress\n");break; } char const* sessionId; float originalScale; if (request->session() != NULL) {// Session-level operationcmdURL = (char*)sessionURL(*request->session());sessionId = fLastSessionId;originalScale = request->session()->scale(); } else {// Media-level operationchar const *prefix, *separator, *suffix;constructSubsessionURL(*request->subsession(), prefix, separator, suffix);cmdURL = new char[strlen(prefix) + strlen(separator) + strlen(suffix) + 1];cmdURLWasAllocated = True;sprintf(cmdURL, "%s%s%s", prefix, separator, suffix);sessionId = request->subsession()->sessionId();originalScale = request->subsession()->scale(); } if (strcmp(request->commandName(), "PLAY") == 0) {// Create "Session:", "Scale:", and "Range:" headers; these make up the 'extra headers':char* sessionStr = createSessionString(sessionId);char* scaleStr = createScaleString(request->scale(), originalScale);char* rangeStr = createRangeString(request->start(), request->end(), request->absStartTime(), request->absEndTime());extraHeaders = new char[strlen(sessionStr) + strlen(scaleStr) + strlen(rangeStr) + 1];extraHeadersWereAllocated = True;sprintf(extraHeaders, "%s%s%s", sessionStr, scaleStr, rangeStr);delete[] sessionStr; delete[] scaleStr; delete[] rangeStr; } else {// Create a "Session:" header; this makes up our 'extra headers':extraHeaders = createSessionString(sessionId);extraHeadersWereAllocated = True; } } char* authenticatorStr = createAuthenticatorString(request->commandName(), fBaseURL); char const* const cmdFmt = "%s %s %s\r\n" "CSeq: %d\r\n" "%s" "%s" "%s" "%s" "\r\n" "%s"; unsigned cmdSize = strlen(cmdFmt) + strlen(request->commandName()) + strlen(cmdURL) + strlen(protocolStr) + 20 /* max int len */ + strlen(authenticatorStr) + fUserAgentHeaderStrLen + strlen(extraHeaders) + strlen(contentLengthHeader) + contentStrLen; cmd = new char[cmdSize]; sprintf(cmd, cmdFmt, request->commandName(), cmdURL, protocolStr, request->cseq(), authenticatorStr, fUserAgentHeaderStr, extraHeaders, contentLengthHeader, contentStr); delete[] authenticatorStr; if (cmdURLWasAllocated) delete[] cmdURL; if (extraHeadersWereAllocated) delete[] extraHeaders; if (contentLengthHeaderWasAllocated) delete[] contentLengthHeader; if (fVerbosityLevel >= 1) envir() << "Sending request: " << cmd << "\n"; if (fTunnelOverHTTPPortNum != 0 && strcmp(request->commandName(), "GET") != 0 && strcmp(request->commandName(), "POST") != 0) { // When we're tunneling RTSP-over-HTTP, we Base-64-encode the request before we send it. // (However, we don't do this for the HTTP "GET" and "POST" commands that we use to set up the tunnel.) char* origCmd = cmd; cmd = base64Encode(origCmd, strlen(cmd)); if (fVerbosityLevel >= 1) envir() << "\tThe request was base-64 encoded to: " << cmd << "\n\n"; delete[] origCmd; } if (send(fOutputSocketNum, cmd, strlen(cmd), 0) < 0) { char const* errFmt = "%s send() failed: "; unsigned const errLength = strlen(errFmt) + strlen(request->commandName()); char* err = new char[errLength]; sprintf(err, errFmt, request->commandName()); envir().setResultErrMsg(err); delete[] err; break; } // The command send succeeded, so enqueue the request record, so that its response (when it comes) can be handled. // However, note that we do not expect a response to a POST command with RTSP-over-HTTP, so don't enqueue that. int cseq = request->cseq(); if (fTunnelOverHTTPPortNum == 0 || strcmp(request->commandName(), "POST") != 0) { fRequestsAwaitingResponse.enqueue(request); } else { delete request; } delete[] cmd; return cseq; } while (0); // An error occurred, so call the response handler immediately (indicating the error): delete[] cmd; handleRequestError(request); delete request; return 0;}
因为在初始化对象时RTSPClient的fInputSocketNum为-1,因此进入openConnection()函数,如下:
int RTSPClient::openConnection() { do { // Set up a connection to the server. Begin by parsing the URL: char* username; char* password; NetAddress destAddress; portNumBits urlPortNum; char const* urlSuffix; if (!parseRTSPURL(envir(), fBaseURL, username, password, destAddress, urlPortNum, &urlSuffix)) break; portNumBits destPortNum = fTunnelOverHTTPPortNum == 0 ? urlPortNum : fTunnelOverHTTPPortNum; if (username != NULL || password != NULL) { fCurrentAuthenticator.setUsernameAndPassword(username, password); delete[] username; delete[] password; } // We don't yet have a TCP socket (or we used to have one, but it got closed). Set it up now. fInputSocketNum = fOutputSocketNum = setupStreamSocket(envir(), 0); if (fInputSocketNum < 0) break; ignoreSigPipeOnSocket(fInputSocketNum); // so that servers on the same host that get killed don't also kill us // Connect to the remote endpoint: fServerAddress = *(netAddressBits*)(destAddress.data()); int connectResult = connectToServer(fInputSocketNum, destPortNum); if (connectResult < 0) break; else if (connectResult > 0) { // The connection succeeded. Arrange to handle responses to requests sent on it: envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION, (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this); } return connectResult; } while (0); resetTCPSockets(); return -1;}
首先解析parseRTSPURL,解析成功后将其fTunnelOverHTTPPortNum设置为解析端口或者默认端口554,然后创建stream socket,函数为setupStreamSocket,将其值赋予fInputSocketNum,fOutputSocketNum;在函数体内,设置socket为可重用,并且将socket与name绑定,最后设置socket为非阻塞模式。
int setupStreamSocket(UsageEnvironment& env, Port port, Boolean makeNonBlocking) { if (!initializeWinsockIfNecessary()) { socketErr(env, "Failed to initialize 'winsock': "); return -1; } int newSocket = createSocket(SOCK_STREAM); if (newSocket < 0) { socketErr(env, "unable to create stream socket: "); return newSocket; } int reuseFlag = groupsockPriv(env)->reuseFlag; reclaimGroupsockPriv(env); if (setsockopt(newSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseFlag, sizeof reuseFlag) < 0) { socketErr(env, "setsockopt(SO_REUSEADDR) error: "); closeSocket(newSocket); return -1; } // SO_REUSEPORT doesn't really make sense for TCP sockets, so we // normally don't set them. However, if you really want to do this // #define REUSE_FOR_TCP#ifdef REUSE_FOR_TCP#if defined(__WIN32__) || defined(_WIN32) // Windoze doesn't properly handle SO_REUSEPORT#else#ifdef SO_REUSEPORT if (setsockopt(newSocket, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuseFlag, sizeof reuseFlag) < 0) { socketErr(env, "setsockopt(SO_REUSEPORT) error: "); closeSocket(newSocket); return -1; }#endif#endif#endif // Note: Windoze requires binding, even if the port number is 0#if defined(__WIN32__) || defined(_WIN32)#else if (port.num() != 0 || ReceivingInterfaceAddr != INADDR_ANY) {#endif MAKE_SOCKADDR_IN(name, ReceivingInterfaceAddr, port.num()); if (bind(newSocket, (struct sockaddr*)&name, sizeof name) != 0) { char tmpBuffer[100]; sprintf(tmpBuffer, "bind() error (port number: %d): ", ntohs(port.num())); socketErr(env, tmpBuffer); closeSocket(newSocket); return -1; }#if defined(__WIN32__) || defined(_WIN32)#else }#endif if (makeNonBlocking) { if (!makeSocketNonBlocking(newSocket)) { socketErr(env, "failed to make non-blocking: "); closeSocket(newSocket); return -1; } } return newSocket;}
将socket设置为非阻塞模式,之后进入connectToServer函数,开始连接server,连接成功,调用setBackgroundHandling,设置为后台处理函数,采用回调函数incomingDataHandler来处理进入数据;而在connectToServer函数中,连接阻塞时调用setBackgroundHandling函数,设置连接句柄函数,采用connectionHandler函数来处理服务器连接,sendRequest函数其后根据不同的commandname进行相应的处理,然后再拼接打印字符串,并发送需要的命令信息。setBackgroundHandling函数如下:
void BasicTaskScheduler ::setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData) { if (socketNum < 0) return; FD_CLR((unsigned)socketNum, &fReadSet); FD_CLR((unsigned)socketNum, &fWriteSet); FD_CLR((unsigned)socketNum, &fExceptionSet); if (conditionSet == 0) { fHandlers->clearHandler(socketNum); if (socketNum+1 == fMaxNumSockets) { --fMaxNumSockets; } } else { fHandlers->assignHandler(socketNum, conditionSet, handlerProc, clientData); if (socketNum+1 > fMaxNumSockets) { fMaxNumSockets = socketNum+1; } if (conditionSet&SOCKET_READABLE) FD_SET((unsigned)socketNum, &fReadSet); if (conditionSet&SOCKET_WRITABLE) FD_SET((unsigned)socketNum, &fWriteSet); if (conditionSet&SOCKET_EXCEPTION) FD_SET((unsigned)socketNum, &fExceptionSet); }}
在这里将socketNum加入set集,设置read,write,exception,执行完这些代码之后,在这里需要注意的是fHandlers->assignHandler函数,其实现如下:
void HandlerSet::assignHandler(int socketNum, int conditionSet, TaskScheduler::BackgroundHandlerProc* handlerProc, void* clientData) { // First, see if there's already a handler for this socket: HandlerDescriptor* handler = lookupHandler(socketNum); if (handler == NULL) { // No existing handler, so create a new descr: handler = new HandlerDescriptor(fHandlers.fNextHandler); handler->socketNum = socketNum; } handler->conditionSet = conditionSet; handler->handlerProc = handlerProc; handler->clientData = clientData;}
在函数体中,lookupHandler函数通过socketNum在队列中查找是否存在当前的连接信息,如不存在则在链表中添加相关信息;执行完这些之后,进入main函数,执行,如下:
void BasicTaskScheduler0::doEventLoop(char* watchVariable) { // Repeatedly loop, handling readble sockets and timed events: while (1) { if (watchVariable != NULL && *watchVariable != 0) break; SingleStep(); }}
SingleStep函数如下:
void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) { fd_set readSet = fReadSet; // make a copy for this select() call fd_set writeSet = fWriteSet; // ditto fd_set exceptionSet = fExceptionSet; // ditto DelayInterval const& timeToDelay = fDelayQueue.timeToNextAlarm(); struct timeval tv_timeToDelay; tv_timeToDelay.tv_sec = timeToDelay.seconds(); tv_timeToDelay.tv_usec = timeToDelay.useconds(); // Very large "tv_sec" values cause select() to fail. // Don't make it any larger than 1 million seconds (11.5 days) const long MAX_TV_SEC = MILLION; if (tv_timeToDelay.tv_sec > MAX_TV_SEC) { tv_timeToDelay.tv_sec = MAX_TV_SEC; } // Also check our "maxDelayTime" parameter (if it's > 0): if (maxDelayTime > 0 && (tv_timeToDelay.tv_sec > (long)maxDelayTime/MILLION || (tv_timeToDelay.tv_sec == (long)maxDelayTime/MILLION &&tv_timeToDelay.tv_usec > (long)maxDelayTime%MILLION))) { tv_timeToDelay.tv_sec = maxDelayTime/MILLION; tv_timeToDelay.tv_usec = maxDelayTime%MILLION; } int selectResult = select(fMaxNumSockets, &readSet, &writeSet, &exceptionSet, &tv_timeToDelay); if (selectResult < 0) {#if defined(__WIN32__) || defined(_WIN32) int err = WSAGetLastError(); // For some unknown reason, select() in Windoze sometimes fails with WSAEINVAL if // it was called with no entries set in "readSet". If this happens, ignore it: if (err == WSAEINVAL && readSet.fd_count == 0) { err = EINTR; // To stop this from happening again, create a dummy socket: int dummySocketNum = socket(AF_INET, SOCK_DGRAM, 0); FD_SET((unsigned)dummySocketNum, &fReadSet); } if (err != EINTR) {#else if (errno != EINTR && errno != EAGAIN) {#endif// Unexpected error - treat this as fatal:#if !defined(_WIN32_WCE)perror("BasicTaskScheduler::SingleStep(): select() fails");// Because this failure is often "Bad file descriptor" - which is caused by an invalid socket number (i.e., a socket number// that had already been closed) being used in "select()" - we print out the sockets that were being used in "select()",// to assist in debugging:fprintf(stderr, "socket numbers used in the select() call:");for (int i = 0; i < 10000; ++i) { if (FD_ISSET(i, &fReadSet) || FD_ISSET(i, &fWriteSet) || FD_ISSET(i, &fExceptionSet)) { fprintf(stderr, " %d(", i); if (FD_ISSET(i, &fReadSet)) fprintf(stderr, "r"); if (FD_ISSET(i, &fWriteSet)) fprintf(stderr, "w"); if (FD_ISSET(i, &fExceptionSet)) fprintf(stderr, "e"); fprintf(stderr, ")"); }}fprintf(stderr, "\n");#endifinternalError(); } } // Call the handler function for one readable socket: HandlerIterator iter(*fHandlers); HandlerDescriptor* handler; // To ensure forward progress through the handlers, begin past the last // socket number that we handled: if (fLastHandledSocketNum >= 0) { while ((handler = iter.next()) != NULL) { if (handler->socketNum == fLastHandledSocketNum) break; } if (handler == NULL) { fLastHandledSocketNum = -1; iter.reset(); // start from the beginning instead } } while ((handler = iter.next()) != NULL) { int sock = handler->socketNum; // alias int resultConditionSet = 0; if (FD_ISSET(sock, &readSet) && FD_ISSET(sock, &fReadSet)/*sanity check*/) resultConditionSet |= SOCKET_READABLE; if (FD_ISSET(sock, &writeSet) && FD_ISSET(sock, &fWriteSet)/*sanity check*/) resultConditionSet |= SOCKET_WRITABLE; if (FD_ISSET(sock, &exceptionSet) && FD_ISSET(sock, &fExceptionSet)/*sanity check*/) resultConditionSet |= SOCKET_EXCEPTION; if ((resultConditionSet&handler->conditionSet) != 0 && handler->handlerProc != NULL) { fLastHandledSocketNum = sock; // Note: we set "fLastHandledSocketNum" before calling the handler, // in case the handler calls "doEventLoop()" reentrantly. (*handler->handlerProc)(handler->clientData, resultConditionSet); break; } } if (handler == NULL && fLastHandledSocketNum >= 0) { // We didn't call a handler, but we didn't get to check all of them, // so try again from the beginning: iter.reset(); while ((handler = iter.next()) != NULL) { int sock = handler->socketNum; // alias int resultConditionSet = 0; if (FD_ISSET(sock, &readSet) && FD_ISSET(sock, &fReadSet)/*sanity check*/) resultConditionSet |= SOCKET_READABLE; if (FD_ISSET(sock, &writeSet) && FD_ISSET(sock, &fWriteSet)/*sanity check*/) resultConditionSet |= SOCKET_WRITABLE; if (FD_ISSET(sock, &exceptionSet) && FD_ISSET(sock, &fExceptionSet)/*sanity check*/) resultConditionSet |= SOCKET_EXCEPTION; if ((resultConditionSet&handler->conditionSet) != 0 && handler->handlerProc != NULL) {fLastHandledSocketNum = sock; // Note: we set "fLastHandledSocketNum" before calling the handler, // in case the handler calls "doEventLoop()" reentrantly.(*handler->handlerProc)(handler->clientData, resultConditionSet);break; } } if (handler == NULL) fLastHandledSocketNum = -1;//because we didn't call a handler } // Also handle any newly-triggered event (Note that we do this *after* calling a socket handler, // in case the triggered event handler modifies The set of readable sockets.) if (fTriggersAwaitingHandling != 0) { if (fTriggersAwaitingHandling == fLastUsedTriggerMask) { // Common-case optimization for a single event trigger: fTriggersAwaitingHandling = 0; if (fTriggeredEventHandlers[fLastUsedTriggerNum] != NULL) {(*fTriggeredEventHandlers[fLastUsedTriggerNum])(fTriggeredEventClientDatas[fLastUsedTriggerNum]); } } else { // Look for an event trigger that needs handling (making sure that we make forward progress through all possible triggers): unsigned i = fLastUsedTriggerNum; EventTriggerId mask = fLastUsedTriggerMask; do {i = (i+1)%MAX_NUM_EVENT_TRIGGERS;mask >>= 1;if (mask == 0) mask = 0x80000000;if ((fTriggersAwaitingHandling&mask) != 0) { fTriggersAwaitingHandling &=~ mask; if (fTriggeredEventHandlers[i] != NULL) { (*fTriggeredEventHandlers[i])(fTriggeredEventClientDatas[i]); } fLastUsedTriggerMask = mask; fLastUsedTriggerNum = i; break;} } while (i != fLastUsedTriggerNum); } } // Also handle any delayed event that may have come due. fDelayQueue.handleAlarm();}
第一次发代码,出现错误请包涵啊。
- live555--testRTSPClient学习心得
- LIVE555再学习 -- testRTSPClient 实例
- 庖丁解牛-----Live555源码彻底解密(testRTSPClient流程图)
- live555 使用testRTSPClient 接收h264记录
- LIVE555再学习 -- testRTSPClient 源码分析
- Live555源码彻底解密(根据testRTSPClient讲解)
- live555学习心得
- 庖丁解牛-----Live555源码彻底解密(根据testRTSPClient讲解)
- live555 TestRtspClient -vs2008编译源码;
- 庖丁解牛-----Live555源码彻底解密(testRTSPClient --rtsp交互流程)
- Live555源码彻底解密(testRTSPClient --rtsp交互流程)
- 开源项目live555学习心得
- 开源项目live555学习心得
- 开源项目live555学习心得
- 如何将testRTSPClient从live555代码中分离出来,并建议第一个eclipse工程
- 开源项目live555学习心得(一)
- 开源项目live555学习心得(二)
- 开源项目live555学习心得(三)
- 提高Office2010等高版的启动速度文章链接收集-Office2010打开慢速度怎么办?
- sql server 重复记录相关 SQL
- 切分 Tomcat 的 catalina.out 文件,解决日志文件过大的问题
- 深入密码加salt原理的分析
- QueryPerformanceFrequency Window下的高精度计时器
- live555--testRTSPClient学习心得
- Keil C常量乘法的问题
- Matlab矩阵和架构
- C++ 使用delete删除指针
- 汇编学习笔记(四)
- MD5加密[JavaScript实现]
- 集深V5-定位与格集-层次坐标
- android wifi驱动开发日记(二)(三)
- Eclipse配置tomcat