最近因为公司项目的缘故,开始学习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 connection
transportTypeStr = "/TCP;unicast";
portTypeStr = ";interleaved";
rtpNumber = fTCPStreamIdCount++;
rtcpNumber = fTCPStreamIdCount++;
} else { // normal RTP streaming
unsigned 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 operation
cmdURL = (char*)sessionURL(*request->session());
sessionId = fLastSessionId;
originalScale = request->session()->scale();
} else {
// Media-level operation
char 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");
#endif
internalError();
}
}
// 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();
}
第一次发代码,出现错误请包涵啊。