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类:线程信息