live555 源码分析:MediaSever
来源:互联网 发布:易语言人工智能机器人 编辑:程序博客网 时间:2024/05/16 11:12
位于 live555 项目 mediaServer 目录下的是 “LIVE555 Media Server”,它是一个完整的 RTSP 服务器应用程序。它可以把多种媒体文件转为流,提供给请求者。
这里来看一下 “LIVE555 Media Server” 的实现。抛开其中向终端输出应用程序信息的代码, “LIVE555 Media Server” 主程序的代码像下面这样:
#include <BasicUsageEnvironment.hh>#include "DynamicRTSPServer.hh"#include "version.hh"int main(int argc, char** argv) { // Begin by setting up our usage environment: TaskScheduler* scheduler = BasicTaskScheduler::createNew(); UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler); UserAuthenticationDatabase* authDB = NULL;#ifdef ACCESS_CONTROL // To implement client access control to the RTSP server, do the following: authDB = new UserAuthenticationDatabase; authDB->addUserRecord("username1", "password1"); // replace these with real strings // Repeat the above with each <username>, <password> that you wish to allow // access to the server.#endif // Create the RTSP server. Try first with the default port number (554), // and then with the alternative port number (8554): RTSPServer* rtspServer; portNumBits rtspServerPortNum = 554; rtspServer = DynamicRTSPServer::createNew(*env, rtspServerPortNum, authDB); if (rtspServer == NULL) { rtspServerPortNum = 8554; rtspServer = DynamicRTSPServer::createNew(*env, rtspServerPortNum, authDB); } if (rtspServer == NULL) { *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n"; exit(1); } . . . . . . if (rtspServer->setUpTunnelingOverHTTP(80) || rtspServer->setUpTunnelingOverHTTP(8000) || rtspServer->setUpTunnelingOverHTTP(8080)) { *env << "(We use port " << rtspServer->httpServerPortNum() << " for optional RTSP-over-HTTP tunneling, or for HTTP live streaming (for indexed Transport Stream files only).)\n"; } else { *env << "(RTSP-over-HTTP tunneling is not available.)\n"; } env->taskScheduler().doEventLoop(); // does not return return 0; // only to prevent compiler warning}
“LIVE555 Media Server” 的主程序非常简单,做的事情仅仅如下面这样:
1. 创建 TaskScheduler
用于执行任务调度。关于 TaskScheduler
的更详细信息可以参考 live555 源码分析:基础设施。
2. 创建 UsageEnvironment
用于日志输出等 I/O 操作。关于 UsageEnvironment
的更详细信息可以参考 live555 源码分析:基础设施。
3. 创建 RTSPServer
用于接受连接、处理请求等。这里会首先尝试使用 554 端口,如果失败,就尝试使用 8554 端口,如果两个端口都没法用,就失败退出程序。
4. 为 RTSPServer
设置 HTTP 隧道端口。这里会依次尝试使用 80、8000 和 8080 端口。
5. 执行事件循环。
TaskScheduler
用于执行任务调度,但要监听的 socket,以及 socket 上的 I/O 事件的处理程序,则需要 RTSPServer
,也就是 DynamicRTSPServer
提供。DynamicRTSPServer
是 “LIVE555 Media Server” 应用程序的核心,其定义如下:
#ifndef _RTSP_SERVER_SUPPORTING_HTTP_STREAMING_HH#include "RTSPServerSupportingHTTPStreaming.hh"#endifclass DynamicRTSPServer: public RTSPServerSupportingHTTPStreaming {public: static DynamicRTSPServer* createNew(UsageEnvironment& env, Port ourPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds = 65);protected: DynamicRTSPServer(UsageEnvironment& env, int ourSocket, Port ourPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds); // called only by createNew(); virtual ~DynamicRTSPServer();protected: // redefined virtual functions virtual ServerMediaSession* lookupServerMediaSession(char const* streamName, Boolean isFirstLookupInSession);};
DynamicRTSPServer
的类层次结构如下图:
要监听的 socket 以及 socket 上的 I/O 事件的处理程序是在 DynamicRTSPServer
创建的过程中注册给 TaskScheduler
的。DynamicRTSPServer
需要通过其静态函数 createNew()
创建,该函数定义如下:
DynamicRTSPServer*DynamicRTSPServer::createNew(UsageEnvironment& env, Port ourPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds) { int ourSocket = setUpOurSocket(env, ourPort); if (ourSocket == -1) return NULL; return new DynamicRTSPServer(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds);}DynamicRTSPServer::DynamicRTSPServer(UsageEnvironment& env, int ourSocket, Port ourPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds) : RTSPServerSupportingHTTPStreaming(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds) {}
在 DynamicRTSPServer
的 createNew()
中,首先创建一个 socket,然后用这个 socket 创建 DynamicRTSPServer
对象。构造函数顺着类继承层次一级级执行上去。
RTSPServerSupportingHTTPStreaming
构造函数定义如下:
RTSPServerSupportingHTTPStreaming::RTSPServerSupportingHTTPStreaming(UsageEnvironment& env, int ourSocket, Port rtspPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds) : RTSPServer(env, ourSocket, rtspPort, authDatabase, reclamationTestSeconds) {}
RTSPServer
构造函数定义如下:
RTSPServer::RTSPServer(UsageEnvironment& env, int ourSocket, Port ourPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationSeconds) : GenericMediaServer(env, ourSocket, ourPort, reclamationSeconds), fHTTPServerSocket(-1), fHTTPServerPort(0), fClientConnectionsForHTTPTunneling(NULL), // will get created if needed fTCPStreamingDatabase(HashTable::create(ONE_WORD_HASH_KEYS)), fPendingRegisterOrDeregisterRequests(HashTable::create(ONE_WORD_HASH_KEYS)), fRegisterOrDeregisterRequestCounter(0), fAuthDB(authDatabase), fAllowStreamingRTPOverTCP(True) {}
GenericMediaServer
构造函数定义如下:
GenericMediaServer::GenericMediaServer(UsageEnvironment& env, int ourSocket, Port ourPort, unsigned reclamationSeconds) : Medium(env), fServerSocket(ourSocket), fServerPort(ourPort), fReclamationSeconds(reclamationSeconds), fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)), fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)), fClientSessions(HashTable::create(STRING_HASH_KEYS)) { ignoreSigPipeOnSocket(fServerSocket); // so that clients on the same host that are killed don't also kill us // Arrange to handle connections from others: env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket, incomingConnectionHandler, this);}
在 GenericMediaServer
的构造函数中,要监听的 Server socket 及该 socket 上的 I/O 事件处理程序,被注册给任务调度器。事件循环中检测到 socket 上出现 I/O 事件时,该处理程序会被调用到。注册的事件处理程序为 GenericMediaServer::incomingConnectionHandler()
。
Medium
构造函数定义如下:
Medium::Medium(UsageEnvironment& env) : fEnviron(env), fNextTask(NULL) { // First generate a name for the new medium: MediaLookupTable::ourMedia(env)->generateNewName(fMediumName, mediumNameMaxLen); env.setResultMsg(fMediumName); // Then add it to our table: MediaLookupTable::ourMedia(env)->addNew(this, fMediumName);}
在 Medium
中,主要通过 MediaLookupTable
维护一个 medium name 到 Medium
对象的映射表。MediaLookupTable
可以看作是 BasicHashTable
对值类型为 Medium
对象指针的特化,该类定义如下:
class MediaLookupTable {public: static MediaLookupTable* ourMedia(UsageEnvironment& env); HashTable const& getTable() { return *fTable; }protected: MediaLookupTable(UsageEnvironment& env); virtual ~MediaLookupTable();private: friend class Medium; Medium* lookup(char const* name) const; // Returns NULL if none already exists void addNew(Medium* medium, char* mediumName); void remove(char const* name); void generateNewName(char* mediumName, unsigned maxLen);private: UsageEnvironment& fEnv; HashTable* fTable; unsigned fNameGenerator;};
MediaLookupTable
的引用实际由 UsageEnvironment
持有:
_Tables* _Tables::getOurTables(UsageEnvironment& env, Boolean createIfNotPresent) { if (env.liveMediaPriv == NULL && createIfNotPresent) { env.liveMediaPriv = new _Tables(env); } return (_Tables*)(env.liveMediaPriv);}. . . . . . MediaLookupTable* MediaLookupTable::ourMedia(UsageEnvironment& env) { _Tables* ourTables = _Tables::getOurTables(env); if (ourTables->mediaTable == NULL) { // Create a new table to record the media that are to be created in // this environment: ourTables->mediaTable = new MediaLookupTable(env); } return ourTables->mediaTable;}. . . . . . void MediaLookupTable::generateNewName(char* mediumName, unsigned /*maxLen*/) { // We should really use snprintf() here, but not all systems have it sprintf(mediumName, "liveMedia%d", fNameGenerator++);}MediaLookupTable::MediaLookupTable(UsageEnvironment& env) : fEnv(env), fTable(HashTable::create(STRING_HASH_KEYS)), fNameGenerator(0) {}
medium name 在 Medium
构造过程中分配,创建的 Medium
也在此时被加入 MediaLookupTable
中:
void MediaLookupTable::addNew(Medium* medium, char* mediumName) { fTable->Add(mediumName, (void*)medium);}. . . . . . void MediaLookupTable::generateNewName(char* mediumName, unsigned /*maxLen*/) { // We should really use snprintf() here, but not all systems have it sprintf(mediumName, "liveMedia%d", fNameGenerator++);}
Server socket 的创建及连接建立
在 DynamicRTSPServer
的 createNew()
,创建 DynamicRTSPServer
对象之前,会先通过 setUpOurSocket()
创建一个 socket,setUpOurSocket()
是 GenericMediaServer
的一个静态函数,其定义为:
int GenericMediaServer::setUpOurSocket(UsageEnvironment& env, Port& ourPort) { int ourSocket = -1; do { // The following statement is enabled by default. // Don't disable it (by defining ALLOW_SERVER_PORT_REUSE) unless you know what you're doing.#if !defined(ALLOW_SERVER_PORT_REUSE) && !defined(ALLOW_RTSP_SERVER_PORT_REUSE) // ALLOW_RTSP_SERVER_PORT_REUSE is for backwards-compatibility ##### NoReuse dummy(env); // Don't use this socket if there's already a local server using it#endif ourSocket = setupStreamSocket(env, ourPort); if (ourSocket < 0) break; // Make sure we have a big send buffer: if (!increaseSendBufferTo(env, ourSocket, 50*1024)) break; // Allow multiple simultaneous connections: if (listen(ourSocket, LISTEN_BACKLOG_SIZE) < 0) { env.setResultErrMsg("listen() failed: "); break; } if (ourPort.num() == 0) { // bind() will have chosen a port for us; return it also: if (!getSourcePort(env, ourSocket, ourPort)) break; } return ourSocket; } while (0); if (ourSocket != -1) ::closeSocket(ourSocket); return -1;}
GenericMediaServer::setUpOurSocket()
主要即是,通过 setupStreamSocket()
函数创建一个 TCP socket,增大该 socket 的发送缓冲区,并 listen 该 socket。
setupStreamSocket()
函数在 groupsock 模块中定义:
_groupsockPriv* groupsockPriv(UsageEnvironment& env) { if (env.groupsockPriv == NULL) { // We need to create it _groupsockPriv* result = new _groupsockPriv; result->socketTable = NULL; result->reuseFlag = 1; // default value => allow reuse of socket numbers env.groupsockPriv = result; } return (_groupsockPriv*)(env.groupsockPriv);}void reclaimGroupsockPriv(UsageEnvironment& env) { _groupsockPriv* priv = (_groupsockPriv*)(env.groupsockPriv); if (priv->socketTable == NULL && priv->reuseFlag == 1/*default value*/) { // We can delete the structure (to save space); it will get created again, if needed: delete priv; env.groupsockPriv = NULL; }}static int createSocket(int type) { // Call "socket()" to create a (IPv4) socket of the specified type. // But also set it to have the 'close on exec' property (if we can) int sock;#ifdef SOCK_CLOEXEC sock = socket(AF_INET, type|SOCK_CLOEXEC, 0); if (sock != -1 || errno != EINVAL) return sock; // An "errno" of EINVAL likely means that the system wasn't happy with the SOCK_CLOEXEC; fall through and try again without it:#endif sock = socket(AF_INET, type, 0);#ifdef FD_CLOEXEC if (sock != -1) fcntl(sock, F_SETFD, FD_CLOEXEC);#endif return sock;}. . . . . .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;}
在这里基本上就是创建 TCP socket,为 socket 设置 RESUE 选项,将 socket 绑定到目标端口,并根据需要设置 socket 为非阻塞的。
接着来看,在 live555 中是如何启动监听 server socket 上的 I/O 事件,并处理这些事件的。
我们看到,在 GenericMediaServer
的构造函数中,用于接收客户端发起的连接的 server socket,及该 socket 上的 I/O 事件处理程序,被注册给任务调度器。当有客户端发起了到 “LIVE555 Media Server” 的连接时,该处理程序会被调用到。事件处理程序为 GenericMediaServer::incomingConnectionHandler()
,相关的几个函数声明为:
class GenericMediaServer: public Medium {. . . . . .protected:. . . . . . static void incomingConnectionHandler(void*, int /*mask*/); void incomingConnectionHandler(); void incomingConnectionHandlerOnSocket(int serverSocket);. . . . . .protected: virtual ClientConnection* createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr) = 0;
GenericMediaServer::incomingConnectionHandler()
为静态函数,incomingConnectionHandler()
及 incomingConnectionHandlerOnSocket(int serverSocket)
为非虚函数,它们的定义为:
void GenericMediaServer::incomingConnectionHandler(void* instance, int /*mask*/) { GenericMediaServer* server = (GenericMediaServer*)instance; server->incomingConnectionHandler();}void GenericMediaServer::incomingConnectionHandler() { incomingConnectionHandlerOnSocket(fServerSocket);}void GenericMediaServer::incomingConnectionHandlerOnSocket(int serverSocket) { struct sockaddr_in clientAddr; SOCKLEN_T clientAddrLen = sizeof clientAddr; int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen); if (clientSocket < 0) { int err = envir().getErrno(); if (err != EWOULDBLOCK) { envir().setResultErrMsg("accept() failed: "); } return; } ignoreSigPipeOnSocket(clientSocket); // so that clients on the same host that are killed don't also kill us makeSocketNonBlocking(clientSocket); increaseSendBufferTo(envir(), clientSocket, 50*1024);#ifdef DEBUG envir() << "accept()ed connection from " << AddressString(clientAddr).val() << "\n";#endif // Create a new object for handling this connection: (void)createNewClientConnection(clientSocket, clientAddr);}
可以看到,当监听的 server socket 上发现有客户端发起的连接请求时,处理过程大体为:
1. 通过 accept()
获得新建立的连接的 socket。
2. 设置新 socket 的选项,使它称为非阻塞 socket,并增大该 socket 的发送缓冲区。可以看一下在 live555 中设置 socket 选项的这些函数,它们在 groupsock 模块中定义:
Boolean makeSocketNonBlocking(int sock) {. . . . . . int curFlags = fcntl(sock, F_GETFL, 0); return fcntl(sock, F_SETFL, curFlags|O_NONBLOCK) >= 0;#endif}. . . . . .void ignoreSigPipeOnSocket(int socketNum) {. . . . . . signal(SIGPIPE, SIG_IGN); #endif #endif}static unsigned getBufferSize(UsageEnvironment& env, int bufOptName, int socket) { unsigned curSize; SOCKLEN_T sizeSize = sizeof curSize; if (getsockopt(socket, SOL_SOCKET, bufOptName, (char*)&curSize, &sizeSize) < 0) { socketErr(env, "getBufferSize() error: "); return 0; } return curSize;}. . . . . .static unsigned increaseBufferTo(UsageEnvironment& env, int bufOptName, int socket, unsigned requestedSize) { // First, get the current buffer size. If it's already at least // as big as what we're requesting, do nothing. unsigned curSize = getBufferSize(env, bufOptName, socket); // Next, try to increase the buffer to the requested size, // or to some smaller size, if that's not possible: while (requestedSize > curSize) { SOCKLEN_T sizeSize = sizeof requestedSize; if (setsockopt(socket, SOL_SOCKET, bufOptName, (char*)&requestedSize, sizeSize) >= 0) { // success return requestedSize; } requestedSize = (requestedSize+curSize)/2; } return getBufferSize(env, bufOptName, socket);}unsigned increaseSendBufferTo(UsageEnvironment& env, int socket, unsigned requestedSize) { return increaseBufferTo(env, SO_SNDBUF, socket, requestedSize);}
- 通过
createNewClientConnection()
,创建一个新的客户端连接ClientConnection
。createNewClientConnection()
是一个纯虚函数,它的实现将在类继承层次中实现了该函数的类中层次最低的那个类里,对于DynamicRTSPServer
->RTSPServerSupportingHTTPStreaming
->RTSPServer
->GenericMediaServer
这个继承层次,从DynamicRTSPServer
类开始,逐级向上找,可以发现createNewClientConnection()
函数的实现在RTSPServerSupportingHTTPStreaming
类中。
RTSPServerSupportingHTTPStreaming
类的
createNewClientConnection()
函数定义如下:
GenericMediaServer::ClientConnection*RTSPServerSupportingHTTPStreaming::createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr) { return new RTSPClientConnectionSupportingHTTPStreaming(*this, clientSocket, clientAddr);}RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStreaming(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr) : RTSPClientConnection(ourServer, clientSocket, clientAddr), fClientSessionId(0), fStreamSource(NULL), fPlaylistSource(NULL), fTCPSink(NULL) {}
这里简单地创建一个 RTSPClientConnectionSupportingHTTPStreaming
类对象。在 live555 中,用 GenericMediaServer
的内部类 GenericMediaServer::ClientConnection
表示一个客户端连接。对于 “LIVE555 Media Server” 而言,这个类的继承层次体系结构如下图所示:
RTSPClientConnectionSupportingHTTPStreaming
类对象构造函数调用其父类 RTSPServer::RTSPClientConnection
的构造函数,后者的实现为:
RTSPServer::RTSPClientConnection::RTSPClientConnection(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr) : GenericMediaServer::ClientConnection(ourServer, clientSocket, clientAddr), fOurRTSPServer(ourServer), fClientInputSocket(fOurSocket), fClientOutputSocket(fOurSocket), fIsActive(True), fRecursionCount(0), fOurSessionCookie(NULL) { resetRequestBuffer();}
RTSPServer::RTSPClientConnection
的构造函数继续调用其父类 GenericMediaServer::ClientConnection
的构造函数:
GenericMediaServer::ClientConnection::ClientConnection(GenericMediaServer& ourServer, int clientSocket, struct sockaddr_in clientAddr) : fOurServer(ourServer), fOurSocket(clientSocket), fClientAddr(clientAddr) { // Add ourself to our 'client connections' table: fOurServer.fClientConnections->Add((char const*)this, this); // Arrange to handle incoming requests: resetRequestBuffer(); envir().taskScheduler() .setBackgroundHandling(fOurSocket, SOCKET_READABLE|SOCKET_EXCEPTION, incomingRequestHandler, this);}
在 live555 中,GenericMediaServer
用于执行通用的媒体服务器相关的管理,包括管理所有的客户端连接。GenericMediaServer::ClientConnection
的构造函数中会将其自身加入 GenericMediaServer
的哈希表 fClientConnections
中。更重要的是,将客户端连接的 socket 及该 socket 上的 I/O 事件处理程序注册给了任务调度器,其中 I/O 事件处理程序为 GenericMediaServer::ClientConnection::incomingRequestHandler()
函数。
也就是说,”LIVE555 Media Server” 中与客户端的交互将由 GenericMediaServer::ClientConnection::incomingRequestHandler()
函数完成,这个函数定义如下:
void GenericMediaServer::ClientConnection::incomingRequestHandler(void* instance, int /*mask*/) { ClientConnection* connection = (ClientConnection*)instance; connection->incomingRequestHandler();}void GenericMediaServer::ClientConnection::incomingRequestHandler() { struct sockaddr_in dummy; // 'from' address, meaningless in this case int bytesRead = readSocket(envir(), fOurSocket, &fRequestBuffer[fRequestBytesAlreadySeen], fRequestBufferBytesLeft, dummy); handleRequestBytes(bytesRead);}
在 GenericMediaServer::ClientConnection
的 incomingRequestHandler()
中,会从客户端连接的 socket 中读取数据,然后调用 handleRequestBytes()
函数做进一步的处理。
读取数据的动作由 groupsock 模块中的 readSocket()
完成:
int readSocket(UsageEnvironment& env, int socket, unsigned char* buffer, unsigned bufferSize, struct sockaddr_in& fromAddress) { SOCKLEN_T addressSize = sizeof fromAddress; int bytesRead = recvfrom(socket, (char*)buffer, bufferSize, 0, (struct sockaddr*)&fromAddress, &addressSize); if (bytesRead < 0) { //##### HACK to work around bugs in Linux and Windows: int err = env.getErrno(); if (err == 111 /*ECONNREFUSED (Linux)*/. . . . . . || err == EAGAIN#endif || err == 113 /*EHOSTUNREACH (Linux)*/) { // Why does Linux return this for datagram sock? fromAddress.sin_addr.s_addr = 0; return 0; } //##### END HACK socketErr(env, "recvfrom() error: "); } else if (bytesRead == 0) { // "recvfrom()" on a stream socket can return 0 if the remote end has closed the connection. Treat this as an error: return -1; } return bytesRead;}
这里通过 recvfrom()
函数从 socket 中读取数据。
GenericMediaServer::ClientConnection
中,处理客户端请求的这几个函数声明如下:
static void incomingRequestHandler(void*, int /*mask*/); void incomingRequestHandler(); virtual void handleRequestBytes(int newBytesRead) = 0;
handleRequestBytes()
为纯虚函数,需要由子类实现。对于 RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStreaming
-> RTSPServer::RTSPClientConnection
-> GenericMediaServer::ClientConnection
这个继承层次体系,该函数的实现实际上位于 RTSPServer::RTSPClientConnection
中。
从客户端接收到的请求,将由 RTSPServer::RTSPClientConnection::handleRequestBytes()
处理。
live555 源码分析系列文章
live555 源码分析:简介
live555 源码分析:基础设施
live555 源码分析:MediaSever
- live555 源码分析:MediaSever
- live555 源码分析
- live555 源码架构分析
- Live555源码分析:generateSDPDescription
- live555 源码分析:简介
- live555 源码分析:基础设施
- live555 源码分析:RTSPServer
- live555 源码分析:ServerMediaSession
- Live555源码分析
- live555 源码分析:简介
- live555源码分析-live555大致流程
- live555源码分析-live555大致流程
- live555 服务端源码分析一
- Live555源码分析之1
- live555 源码分析:播放启动
- live555搭建流媒体服务器2--live555源码分析
- live555搭建流媒体服务器2--live555源码分析2
- live555源码分析---- DESCRIBE命令处理
- jmeter正则表达式提取器使用实例
- 面试
- Delphi 快速初始化功能树(cxTreeView)
- Android 文字绘制(DrawText)技术总结
- 哈希表入门--详解
- live555 源码分析:MediaSever
- ubuntu安装显卡驱动+cuda+cudnn+vsftp
- 静态内部类单例缓存地区
- HTML表单数据传输
- 关于std::bind绑定栈变量对象的思考
- 关于 ioctl 详细信息
- 使用 poi 根据 word 模板生成 word 文件
- Angular4.x 引入第三方 JS
- 适用于STL泛型的归并(合并排序)排序算法设计