关于Peercast源代码的分析

    技术2022-05-11  50

    关于Peercast源代码的分析  

    1. 以.h文件为单位,每个.h文件将建立一个分析文档。大致说明各个类完成的功能。

    2. 对于比较重要的部分将重点分析,例如Peercast中关于Gnutella协议的定义和实现

    3. 类图及各类间的调用关系图。由于比较复杂,将在后期推出

    4. Peercast执行流程

    5. 欢迎大家提意见以便改进。欢迎讨论和提问问题。

    期待共同进步,感谢大家的支持!

    Stream.h源文件分析

    Stream.h包括四个类,分别是Stream、MemoryStream、FileStream、IndirectStream。其中MemoryStream、FileStream、IndirectStream均继承自Stream类。

    流涉及三个基本操作:

    可以读取流。读取是从流到数据结构(如字节数组)的数据传输。 可以写入流。写入是从数据结构到流的数据传输。 流可以支持查找。查找是对流内的当前位置进行查询和修改。查找功能取决于流具有的后备存储区类型。例如,网络流没有当前位置的统一概念,因此一般不支持查找。 Stream 是所有流的抽象基类。流是字节序列的抽象概念,例如文件、输入/输出设备、内部进程通信管道或者 TCP/IP 套接字。Stream 类及其派生类提供这些不同类型的输入和输出的一般视图,使程序员不必了解操作系统和基础设备的具体细节。

    对实施者的说明:  在实现 Stream 的派生类时,必须提供 Read 和 Write 方法的实现。

    MemoryStream 类创建这样的流,该流以内存而不是磁盘或网络连接作为支持存储区。MemoryStream 封装以字符数组形式存储的数据,该数组在创建 MemoryStream 对象时被初始化,或者该数组可创建为空数组。可在内存中直接访问这些封装的数据。内存流可降低应用程序中对临时缓冲区和临时文件的需要。

    使用 FileStream 类对文件系统上的文件进行读取、写入、打开和关闭操作

    Peercast对于流的封装与.net framework对于Stream的封装类似,可参见http://msdn.microsoft.com/library/chs/default.asp?url=/library/CHS/cpref/html/frlrfSystemIOStreamClassTopic.asp

    common.h源文件分析

    GeneralException类:其中StreamException继承自GeneralException,而SockException、EOFException、CryptException、TimeoutException均从StreamException继承

    GnuID、GnuIDList、Host类

    GnuID是唯一标识GnuPacket的ID号。由16位组成

    主要方法有

    void generate(unsigned char = 0);   //通过随机数生成Brocast ID号void encode(class Host *, const char *,const char *,unsigned char); //通过IP地址和其他数据对ID进行重新编码

    GnuIDList维护一个GnuID的链表

    Host类用于对主机IP地址、端口号的处理

    IniFile.h源文件分析

    peercast.ini的格式如下

    [Server]serverPort = 7144autoServe = YesforceIP = isRoot = NomaxBitrateOut = 0maxRelays = 2maxDirect = 0

    IniFile类定义三个字符串变量,currLine,nameStr,valueStr

    currentLine对应INI文件中的一行,例如serverPort = 7144

    nameStr对应相应的变量名,例如serverPort

    valueStr对应相应的变量值,例如7144

    写入INI文件时根据写入变量值的不同提供几种写入方法:

     writeSection(const char *name)写入段,例如writeSection( "Server" )则写入[Server]

    writeIntValue(const char *name, int iv)写入整型变量,writeIntValue( serverPort, 7144 ),则写入

    serverPort = 7144,其他类似方法还有writeStrValue,writeBoolValue,writeLine等

    读取INI文件时,readNext()每次读取INI文件中的一行到currLine中,并把相应的变量名和变量值读取到nameStr和valueStr中

    getName()返回变量名,根据变量值类型的不同相应有getIntValue,getStrValue,getBoolValue

    这里用loadSettings的部分代码解释一下读取配置文件的过程

    void ServMgr::loadSettings(const char *fn){ IniFile iniFile;

     if (!iniFile.openReadOnly(fn))  saveSettings(fn);

     if (iniFile.openReadOnly(fn)) {  while (iniFile.readNext())  {   // server settings   if (iniFile.isName("serverPort"))    servMgr->serverHost.port = iniFile.getIntValue();   else if (iniFile.isName("autoServe"))    servMgr->autoServe = iniFile.getBoolValue();   else if (iniFile.isName("autoConnect"))    servMgr->autoConnect = iniFile.getBoolValue();   else if (iniFile.isName("icyPassword"))  // depreciated    strcpy(servMgr->password,iniFile.getStrValue());   else if (iniFile.isName("forceIP"))    servMgr->forceIP = iniFile.getStrValue();

    也就是用readNext()逐行读取并用IF ELSE语句判断是否是要读的变量,直至读到文件末尾为止

    Channel.h源代码分析

    ChanInfo类:保存频道信息

    ::String name; GnuID id,bcID; int  bitrate; TYPE contentType; PROTOCOL srcProtocol; unsigned int lastPlayStart,lastPlayEnd; unsigned int numSkips; unsigned int createdTime;

     STATUS  status;

     TrackInfo track; ::String desc,genre,url,comment;

    Channel类:管理具体的频道操作

    THREAD_PROC Channel::stream(ThreadInfo *thread)

    ChannelMgr类,它完成频道创建、管理、寻找、停止等操作

    Channel *ChanMgr::createChannel(ChanInfo &info, const char *mount)

    void ChanMgr::findAndPlayChannel(ChanInfo &info, bool keep)

    ChannelSource负责具体的流传输工作,其中定义一个虚方法stream由子类PeercastSource、ICYSource、URLSource实现

    virtual void stream(Channel *) = 0;

    ChannelHit:维持一份与你收听同一个频道的节点的信息

    void ChanHit::pickNearestIP(Host &h)

    void ChanHit::initLocal(int numl,int numr,int,int uptm,bool connected,unsigned int oldp,unsigned int newp)

    ChannelHitList:维持一份ChannelHit列表

    ChanHit *ChanHitList::addHit(ChanHit &h)

    http.h源文件分析

    定义HTTP消息:

    static const char *HTTP_SC_OK   = "HTTP/1.0 200 OK";static const char *HTTP_SC_NOTFOUND  = "HTTP/1.0 404 Not Found";static const char *HTTP_SC_UNAVAILABLE = "HTTP/1.0 503 Service Unavailable";

    static const char *HTTP_HS_SERVER  = "Server:";static const char *HTTP_HS_AGENT  = "User-Agent:"; static const char *HTTP_HS_CONTENT  = "Content-Type:"; static const char *HTTP_HS_HOST   = "Host:";static const char *HTTP_HS_ACCEPT  = "Accept:";static const char *HTTP_HS_LENGTH  = "Content-Length:";

    static const char *MIME_MP3   = "audio/mpeg";static const char *MIME_OGG   = "application/ogg";

    HTTP类

    class HTTP : public IndirectStream{public: HTTP(Stream &s) {  init(&s); }

     void initRequest(const char *r) {  strcpy(cmdLine,r); } void readRequest(); bool isRequest(const char *);

     int  readResponse(); bool checkResponse(int);

     bool nextHeader(); bool isHeader(const char *); char *getArgStr(); int  getArgInt();

     void getAuthUserPass(char *, char *);

     char cmdLine[8192],*arg;

    };

    wsocket.h源文件分析

    WSAClientSocket继承自ClientSocket,完成对基本WinSock函数的封装。

    ClientSocket只是提供一个接口,具体实现由其继承类WSAClientSocket(WINDOWS)和UClientSocket (UNIX)实现

    这里先介绍一下Host类:

    unsigned int ip; //主机IP

    unsigned short port; //主机端口号 unsigned int value;

    下面介绍一下WSAClientSocket的具体实现

    //初始化,每个Winsock应用都必须加载合适的WinSock DLL版本.加载库是通过调用WSAStartup函数实现的

    void WSAClientSocket::init(){ WORD wVersionRequested; WSADATA wsaData; int err;     wVersionRequested = MAKEWORD( 2, 0 ); err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 )  throw SockException("Unable to init sockets");

    }

    //建立套接字,通过调用socket函数来实现

    void WSAClientSocket::open(Host &rh){ sockNum = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

     if (sockNum == INVALID_SOCKET)  throw SockException("Can`t open socket");

     setBlocking(false);#ifdef DISABLE_NAGLE setNagle(false);#endif

     host = rh;

     memset(&remoteAddr,0,sizeof(remoteAddr));

     remoteAddr.sin_family = AF_INET; remoteAddr.sin_port = htons(host.port); remoteAddr.sin_addr.S_un.S_addr = htonl(host.ip);

    }

    //服务器绑定。一旦为某种协议创建了套接字,就必须将套接字绑定到一个已知地址上。使用bind函数

    void WSAClientSocket::bind(Host &h){ struct sockaddr_in localAddr;

     if ((sockNum = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)  throw SockException("Can`t open socket");

     setBlocking(false); setReuse(true);

     memset(&localAddr,0,sizeof(localAddr)); localAddr.sin_family = AF_INET; localAddr.sin_port = htons(h.port); localAddr.sin_addr.s_addr = INADDR_ANY;

     if( ::bind (sockNum, (sockaddr *)&localAddr, sizeof(localAddr)) == -1)  throw SockException("Can`t bind socket");

    //接下来要做的,是将套接字置入监听模式。bind函数的作用只是将套接字和指定的地址关联在一起。指示套接字等待连接传入的API是listen

     if (::listen(sockNum,SOMAXCONN))  throw SockException("Can`t listen",WSAGetLastError());

     host = h;}

     //现在我们已做好了接受客户机连接的准备,通过ACCEPT函数来完成

    ClientSocket *WSAClientSocket::accept(){

     int fromSize = sizeof(sockaddr_in); sockaddr_in from;

     int conSock = ::accept(sockNum,(sockaddr *)&from,&fromSize);

     if (conSock ==  INVALID_SOCKET)  return NULL;

        WSAClientSocket *cs = new WSAClientSocket(); cs->sockNum = conSock;

     cs->host.port = from.sin_port; cs->host.ip = from.sin_addr.S_un.S_un_b.s_b1<<24 |      from.sin_addr.S_un.S_un_b.s_b2<<16 |      from.sin_addr.S_un.S_un_b.s_b3<<8 |      from.sin_addr.S_un.S_un_b.s_b4;

     cs->setBlocking(false);#ifdef DISABLE_NAGLE cs->setNagle(false);#endif

     return cs;}

     //关闭套接字

    void WSAClientSocket::close(){ if (sockNum) {  shutdown(sockNum,SD_SEND);

      setReadTimeout(2000);  try  {   //char c;   //while (readUpto(&c,1)!=0);   //readUpto(&c,1);  }catch(StreamException &) {}

      if (closesocket(sockNum))   LOG_ERROR("closesocket() error");

      sockNum=0; }}

    //客户端连接

     void WSAClientSocket::connect(){ if (::connect(sockNum,(struct sockaddr *)&remoteAddr,sizeof(remoteAddr)) == SOCKET_ERROR)  checkTimeout(false,true);

    }

    //发送数据

    void WSAClientSocket::write(const void *p, int l){ while (l) {  int r = send(sockNum, (char *)p, l, 0);  if (r == SOCKET_ERROR)  {   checkTimeout(false,true);   }  else if (r == 0)  {   throw SockException("Closed on write");  }  else  if (r > 0)  {   stats.add(Stats::BYTESOUT,r);   if (host.localIP())    stats.add(Stats::LOCALBYTESOUT,r);

       updateTotals(0,r);   l -= r;   p = (char *)p+r;  } }}

    //接收数据

    int WSAClientSocket::read(void *p, int l){ int bytesRead=0; while (l) {  int r = recv(sockNum, (char *)p, l, 0);  if (r == SOCKET_ERROR)  {   // non-blocking sockets always fall through to here   checkTimeout(true,false);

      }else if (r == 0)  {   throw EOFException("Closed on read");

      }else  {   stats.add(Stats::BYTESIN,r);   if (host.localIP())    stats.add(Stats::LOCALBYTESIN,r);   updateTotals(r,0);   bytesRead += r;   l -= r;   p = (char *)p+r;  } } return bytesRead;}

    Sys.h源代码分析

    String类:完成字符串的一些定义和操作

    Random类:可调用next方法返回随机数

    Sys类:提供一些系统功能,如线程操作、返回随机数、返回时

    WEvent类:

    WLock类:对临界区操作的封装,用于线程同步

    ThreadInfo类:线程信息

     

    最新回复(0)