Peercast简介、分析及常见问题处理 (一)

Peercast收听电台的源代码流程分析 以收听JOKV-FM(TEST)为例,在YP上点击Play,则其URL地址为peercast://pls/25838B9F1EAE27079B793C9FBA0E4156?tip=222.148.187.176:7144case WM_COPYDATA:   {    COPYDATASTRUCT *pc = (COPYDATASTRUCT *)lParam;    LOG_DEBUG("UR 阅读详情

 一。简介 
    PeerCast.org成立于2002年四月,它是一个非盈利性的站点,提供免费的P2P电台软件。这个项目的目标是创建一个容易使用、简单的和可靠的软件客户端,从而使任何人都可以广播流媒体,而不必耗费昂贵的服务器或者带宽。

    PeerCast为广播者提供相当可观的节省,因为他们不必提供带宽给所有的接听者。一个单独的56K调制解调器即可用来为整个网络广播一个电台。

    PeerCast是一个健壮的网络,因为不存在中心服务器,每个用户可以是客户,也可是服务器,也可以是流的广播者。它为广播者提供了匿名服务,因为要追踪到源文件流并不是件简单的事。甚至可能的是,可以让一个不同国家的客户收听广播,而这个客户可作为整个网络的源。

    PeerCast是一个把音频/视频服务器和客户端集合在一起的软件。你可以通过PeerCast来收听众多的网络电台,也可以自己广播。PeerCast头等的特性是你不需要一个有庞大带宽的服务器来为众多听众提供广播服务,你所需要的只是PeerCast和一个外部的广播工具。

   Peercast和其他P2P文件共享软件的工作方式大部分相同,除了一点,用户下载的不是文件而是流。然后这些流实时地与其他用户进行交换。对于任何连接到网络的机器来说,没有任何数据会被存储到本地机上。

   公司可以让一个PeerCast客户端为整个本地网提供音频流。或者你也可以和你的朋友们在因特网上建立一个私人网络来收听音乐。是否直接连接到PeerCast网络,这取决于你自己的选择。

二。源代码文件结构分析 

Peercast分为两个目录:
    core目录主要完成核心的操作
    ui目录实现界面

其中ui下面又可分为html目录和win32目录
    html目录主要是一些网页,用来完成Peercast的相关配置,其中en为英文版本。
    以下就en下的一些文件做一些介绍:

    win32目录下的文件:
      Simple.cpp 负责窗口界面的生成及一些相关启动管理
      gui.cpp 完成peercast主窗口(GUI)的一些操作

core目录:
    common目录:主要完成通用的核心代码(主要代码均在此目录下)
    unix目录:与UNIX相关的一些类
    win32目录:与win32相关的一些类
      Wsys.cpp:继承自Sys,提供基本的win32功能例如开启线程
      Wsocket.cpp:ClientSocket的WINDOWS版本.处理实际的读写TCP操作

下面着重介绍一下common目录,这是整个peercast的核心

    Common.h 定义了一些异常处理类和GnuID、GnuIdList和Host类
    Channel.cpp: 频道流类。它们进行客户之间的实际媒体流传输
    Gnutella.cpp: GnuPacket是一个Guntella协议,GnuStream是一个流,用来读取/写入GunPackets
    Html.cpp: Html协议的一些处理
    Http.cpp: Http协议的一些处理
    Icy.cpp:
    IniFile.cpp 配置信息的读取和修改
    Jis\mms\mp3\nsv\ogg:处理不同的流格式,包括读取HEADER信息等
    Pcp.cpp:Peercast的网络协议
    Peercast.cpp: 整个Peercast流程的管理,包括启动频道管理器、服务管理器,关闭,登录日志等,设置服务器密码等
    RTSP.cpp:只是个实验,并没有实际的东西
    Servent.cpp:是客户端之间真正的连接。他们处理handShaking,数据转输和GnuPackets的处理。每个servent在连接时有一个socket分配给它,它用这个socket来传输所有的数据
    Servhs.cpp:处理一些HANDSHAKE相关的东西
    ServMgr.cpp: 处理服务器信息的读取和修改。一个管理类,用来处理多样的severnt连接
    Socket.cpp : ClientSocket是一个通用的socket接口,与OS/HW无关
    Stat.cpp: 相关数据的统计
    Stream.cpp: 一些流文件的读取和写入处理
    Sys.cpp:一个对所有系统的基础类,例如开启线程,创建socket
    url.cpp: 关于URL的一些处理操作
    XML.cpp:进行电台信息的一些XML存取操作

三。如何用Peercast发布和收听电台 

如何实时广播MP3:
所需软件:Peercast + Winamp + ShoutcastDSP

1.安装Peercast(下载地址http://www.peercast.org/download.php
2.安装Winamp
3.安装ShoutCastDSP
4.打开Peercast
5.配置ShoutcastDSP。在Winamp中点击选项->参数,在参数面版左边选择插件中的DSP音效,可以在右边看见Nullsoft SHOUTcast Source DSP,双击弹出SHOUTcast窗口。在ouput中的Address填上localhost,Port填7144(与Peercast相同),Password一栏填上在Peercast中设置的密码(初始为空)。在Yellowpages选项中可自己设置自己电台的相关信息。
6.使用Winamp播放你想播放的Mp3文件,在SHOUTcast中点击Connect
7.若连接成功,Peercast在Relay窗口中显示你广播的电台
8.yp.peercast.org会自动收录你的电台地址。

如何收听:
1.安装Peercast
2.点击yp.peercast.org
3.选择你想收听的电台,或者也可以在右上角通过关键字进行搜索
4.点击play,peercast会自动调用默认的播放器进行播放

(相关讨论)
Q:不知版主有没有研究过peercast的root模式?我真的不知道root模式有什么用.因为我不用root模式,而用normal,它同样可以发挥yellow page的作用,只要把另一个peercast上的yp的IP设成这个root下的peercast IP,这个root下的peercast channel信息上,就有另一个peercast广播频道源上的信息.所以,root不root,并不重要.只要看YP是谁,就成.YP的作用,还是很明显,就是提供发现所以以此为YP的peercast上的频道源的集成,若是有一个CGI程序对这个集成xml作一个解析,得到channel ID后,就可以做成一个像yp.peercast.org那样的网站了... 
不知版主对上有何高见?欢迎讨论...我对源码研究不多,正在看C++语言呢,不久就要深入源码分析了,请多指教!
A:按Peercast开发者的解释,作为YP的网站确实是要设在root模式下的。 
对于如何做一个yp.peercast.org的网站,你的说法是完全正确的。 
至于只作为normal模式能否接收到广播频道源的信息,我这两天做测试看看。 
关于源码的分析,你可以看看我博客上发表的源码分析文章,希望对你有所帮助并就我的错误和改进之处多提意见。 
希望我们合作愉快!

Q:请问在PeerCast中的一个问题我最近在研究PeerCast, 
我这里遇到一个问题,我没有弄明白! 
即当我点击一个网页里面的"Play"按钮,为什么PeerCast会收到一个WM_COPYDATA这个消息 
这个消息是谁发送的!我自已如何才能做到这一点呢???? 
A:你可以看看Peercast对收到WM_COPYDATA的处理,也就是播放相应的频道,换句话说就是实现Play功能 
case WM_COPYDATA: 

COPYDATASTRUCT *pc = (COPYDATASTRUCT *)lParam; 
LOG_DEBUG("URL request: %s",pc->lpData); 
if (pc->dwData == WM_PLAYCHANNEL) 

ChanInfo info; 
servMgr->procConnectArgs((char *)pc->lpData,info); 
chanMgr->findAndPlayChannel(info,false); 

//sys->callLocalURL((const char *)pc->lpData,servMgr->serverHost.port); 

break; 

再参考Peercast如何发送WM_COPYDATA消息 
if (chanURL) 

COPYDATASTRUCT copy; 
copy.dwData = WM_PLAYCHANNEL; 
copy.cbData = strlen(chanURL)+1; // plus null term 
copy.lpData = chanURL; 
SendMessage(oldWin,WM_COPYDATA,NULL,(LPARAM)&copy); 

相信你会找到自己的解决方案的

Q:再请教一个问题,peercast的启动参数有-kill, -multi...但-url是什么意义,我看了一下源码,不能确定,版主有没有知道啊?我觉得好像是用-urlmyform://这种形式,用myform://来取代peercast://的,不知对不对?
A:-url方式是这样使用的,假设你知道JOKV-FM(TEST)这个电台的URL地址为: 
peercast://pls/6618AD6E10CD60AF27307AFFF0401345?tip=220.157.200.33:7144peercast -url 
那么使用以下命令peercast -url peercast://pls/6618AD6E10CD60AF27307AFFF0401345?tip=220.157.200.33:7144 
也可以直接访问到这个电台

四。局域网中的Peercast网络架设 

前面介绍的Peercast发布和收听是基于公网的,如果你是在一个小型局域网内,想在局域网内架设一个广播电台网络,那么该如何设置呢?

1.选择一台机器作为服务器端,安装Peercast并将其设置工作在root模式下(在setting中修改)。编写一个动态网页读取XML信息,显示现有的网络电台,设置其网页访问地址例如yp.cuc.edu.cn。
2.其他机器均作为客户端,安装Peercast,默认设置于normal模式下,并将其中的YP address项改为服务器端的IP地址。
3.按上文介绍的方式发布网络电台
4.收听时访问服务器网页地址,点击Play即可播放

(yp.peercast.org就是这样的一个例子 
在另外一台机器上输入http://ip/admin?cmd=viewxml可以读取XML,这意味着你可以通过发送HTTP的GET请求来获取XML信息)

五。Peercast中关于Gnutella包源码实现分析 

一个Gnutella客户机通过与另一个当前在网络中的客户机建立连接来使自己与网络相连。
一旦网络上的另一个客户机的地址被获取,一个与该客户机的TCP/IP连接将被创建,以下的Gnutella连接请求字符串(ASCII编码)将被发送:

GNUTELLA CONNECT/<protocol version string>

Peercast定义:static const char *GNU_CONNECT   = "GNUTELLA CONNECT/0.6";  

一个客户机愿意接受连接的话必须回应

GNUTELLA OK

Peercast定义:static const char *GNU_OK    = "GNUTELLA/0.6 200 OK";

一旦一个客户机成功连接到网络上,他与其它客户机通讯通过发送和接收Gnutella协议描述字。每一个描述符前都有一个以下字节结构的描述头,如下所示:
Descriptor Header

DescriptorID网络描述符:16个字节的字符串唯一标示网络的描述符号。

Payload Descriptor负载描述符:

0x00 = Ping
0x01 = Pong
0x40 = Push
0x80 = Query
0x81 = QueryHit 

Peercast定义:
static const int GNU_FUNC_PING = 0;
static const int GNU_FUNC_PONG = 1;
static const int GNU_FUNC_QUERY = 128;
static const int GNU_FUNC_HIT = 129;
static const int GNU_FUNC_PUSH = 64;

TTL生存期:描述字符在删除前在Gnutella网络中向前传递的次数。每个客户端在将包向前传递前将TTL减一。当TTL等于0,描述符将不再被向前传递。

Hops描述符被向前传递的次数:作为一个描述符向前传递,头部的TTL和Hops字必须满足以下条件:

TTL(0) = TTL(i) + Hops(i)

Payload Length负载长度:表示紧接着头部后面的描述符部分的长度。下一个描述符头后的从头部算起的Payload Length字节数,也就是没有间隔或保留字在Gnutella的数据流中。

TTL是网络中唯一的描述过期的机制。客户机应该仔细检查收到的描述符的TTL区并必要时减少它的值。滥用TTL区将会导致没有必要的网络阻塞和差劲的网络性能。

Payload Length区是客户机查找输入流中下一个描述符的唯一可靠方式。Gnutella协议不提供一个“监视”字符串或任何其它的描述符同步的方式。因此,客户机应该严格保证每一个收到的描述符的Payload Length区的有效性(至少为固定长度的描述符)。如果一个客户机不能和输入的流同步,它应该断掉与这个输入流有关的来自发送方的客户机,不管是产生这个流还是向前传递这个流的无效的客户机。紧接着描述头的是一个有效装载包含以下之一的描述符:

Peercast定义一个GnuPacket类来保存包的信息。

class GnuPacket
{
public:
 unsigned char func; //描述符类型,包括Ping\Pong\Query\Hit\Push
 unsigned char ttl;  //生存周期
 unsigned char hops; //记录描述符被传送的次数
 unsigned int len; //数据长度
 GnuID id;  //描述符ID:由16字节组成,用于唯一标识一个网络描述符

 char data[MAX_DATA];  //实际数据
};

Ping (0x00)

Ping描述符没有相关的有效装载和数据长度为0。一个Ping只是简单地有一个描述头表述,它的有效装载区是0x00和装载长度区为0x00000000。

一个客户机用Ping描述符

Peercast实现:void GnuPacket::initPing(int t)

      只需简单设置ping描述头表述和TTL值(初始化为t)及hop值(初始化为0),并生成校验ID

Pong (0x01)

Port

Port:同意接收响应的客户机的端口

IP Address:响应的客户机的地址(此数据区高位字节在后)

Number of Files Shared:本机共享文件的数量

Number of Kilobytes Shared:本机所有共享文件的空间大小,以K为单位

Peercast实现:void GnuPacket::initPong(Host &h, bool ownPong, GnuPacket &ping)

 data.writeShort(h.port);  // 写入响应的客户机端口
 data.writeLong(SWAP4(h.ip)); // 响应的客户机的地址
 data.writeLong(chanMgr->numChannels()); //本机频道的数量
 data.writeLong(servMgr->totalOutput(false)); // 本机总输出数据量大小,以K为单位

Query (0x80)

字节偏移0 1 2 …

Minimum Speed :最小响应速度,响应的客户机的速度必须在此速度之上( 以K/秒为单位)

Search criteria:查询关键字,一个零结尾的字符串。这个字符串的最大长度由描述头的Payload Length负载长度规定。

Peercast实现:void GnuPacket::initFind(const char *str, XML *xml, int maxTTL)

 mem.writeShort(0);  // 最小响应速度为0
 mem.write((void *)str,slen+1); // 写入要搜索的字符串

QueryHit (0x81)

Number of Hits:符合搜索条件的结果数

Port:能接受连接的客户机的端口

IP Address :响应客户机的地址(此数据区高位字节在后)

Speed :响应客户机的连线速度(以K/秒为单位)

Result Set :响应查询的结果集。其中包含一个Number_of_Hits的部分,其中每个都包含以下结构

  File Index:一个数字,由响应的客户机指定,用来唯一标示响应的文件结果

  File Size:与File index相符的文件的大小

  File Name:已双零结尾的与File index相符的文件的名字

Result Set的长度由描述头的Payload Length负载长度规定。

Servent Identifier:一个16位的字符串用来唯一标示网络上的客户机。功能上用来标示客户机的网络地址。用在Push指令上。

QueryHit指令只有在收到一个Query指令后响应才发出。一个客户机只有在它严格符合查询关键字时才对一个Query指令进行响应。

 Peercast定义:bool GnuPacket::initHit(Host &h, Channel *ch, GnuPacket *query, bool push, bool busy, bool stable, bool tracker, int maxttl)
       mem.writeChar(1);   // 能接受连接的客户机的端口
       mem.writeShort(h.port);  // 能接受连接的客户机的端口
       mem.writeLong(SWAP4(h.ip)); // 响应客户机的地址(此数据区高位字节在后)

       mem.writeLong(0);    // index
       mem.writeShort(ch->getBitrate()); // 响应客户机的连线速度(以K/秒为单位)
       mem.writeShort(ch->localListeners());  // 听众数目

Push (0x40)

Servent Identifier:一个16位的字符串用来唯一标示网络上的客户机,该客户机请求下载带有File_Index的文件。

File Index:下载目标客户机的文件的唯一标识,初始化的客户机应该根据返回的QueryHit指令的File_Index中的标识设置。

IP Address:下载带有File_Index的文件的客户机的地址(此数据区高位字节在后)

Port:下载带有File_Index的文件的客户机的端口

Peercast定义:void GnuPacket::initPush(ChanHit &ch, Host &sh)
     data.write(ch.packetID.id,16); //一个16位的字符串用来唯一标示网络上的客户机,该客户机请求下载带有Channel_Index的文件。
    data.writeLong(ch.index);    //下载目标客户机的频道的唯一标识
    data.writeLong(SWAP4(sh.ip)); // 下载带有Channel_Index的频道的客户机的地址(此数据区高位字节在后)
    data.writeShort(sh.port);  // 下载带有Channel_Index的频道的客户机的端口

六。Peercast的PUSH实现方式 

防火墙后的客户机   

并非总是在初始化一个文件下载后都可以与Gnutella客户机建立直接连接。客户机可能在防火墙后并不允许通过它的Gnutella端口进入的连接。如果一个直接连接不能建立,客户机若想下载文件可能会请求共享文件的客户机采用“推送”方式来代替。一个客户机可以通过发送一个Push文件推送请求到发送QueryHit请求的客户机处来实现。作为Push请求目标的客户机(在客户机标志区标示一个Push的描述符)应该接收Push描述符,尝试建立一个新的TCP/IP连接到请求客户机(在Push描述符中标示有IP地址和端口)。如果直接连接不能建立,那么可能发起Push请求的客户机自己也在防火墙后。这种情况,文件传输将不能进行。

 Peercast实现:if (hit.firewalled) strcat(flstr,"Push,");   

如果一个直接连接可以从防火墙后的客户机建立到发起Push请求的客户机,防火墙后的客户机应该立刻发送以下的:

 GIV :/\n\n 这里的:和是Push请求头中的的文件索引和客户机标示,是本地文件表中文件索引为的文件。客户机收到GIV请求头(Push请求者)应该从头中取出和并构造一个如下的HTTP GET请求: GET /get/// HTTP/1.0\r\n Connection: Keep-Alive\r\n Range: bytes=0-\r\n User-Agent: Gnutella\r\n3 \r\n 余下的下载过程和上面所述的“文件下载”内容一致。 可允许的用户-代理字符串由HTTP标准定义。客户机开发者不能对这里使用的值做自己的假定。其中的值“Gnutella”只是用来演示举例而已。

 Peercast实现:s->initGIV(h,c->info.id);

七。如何用peercast实现转播 

如果你想转播一个在网上已经存在的音频或视频流,那么这个操作非常简单。

首先在peercast菜单中选择Advanced->Broadcast

在弹出的页面中填上相关的信息,最重要的URL项填上已经存在的音视频流地址,例如http://ccd.zjonline.com.cn/mp3/agtbw.mp3,再填上其他的相关信息

点击Create Relay,创建成功后你可以在GUI窗口中看到你正在转播的频道

八。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=/l ... 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 = 7144
autoServe = Yes
forceIP = 
isRoot = No
maxBitrateOut = 0
maxRelays = 2
maxDirect = 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语句判断是否是要读的变量,直至读到文件末尾为止

十一。Peercast接收到GnuPacket的处理过程 

对于包是丢弃、广播或者是继续路由是通过设置R_TYPE类型来判断的。

这是在GnuStream类中定义的,其中GnuStream完成收包、发包、处理包等操作

enum R_TYPE
 {
  R_PROCESS,
  R_DEAD,
  R_DISCARD,  //丢弃
  R_ACCEPTED, //接受
  R_BROADCAST, //广播
  R_ROUTE, //路由
  R_DUPLICATE, /复制
  R_BADVERSION,
  R_DROP
 };

通过GnuStream::R_TYPE processPacket(GnuPacket &, Servent *, GnuID &)这个函数来执行处理进程,下面我们就这个函数进行具体的分析:

接收到包时,首先把TTL值递减,HOP值递增

 in.ttl--;
 in.hops++;

通过读取包中的func值来判断命令的类型(Ping\Pong\Query\QueryHit\Push),再执行相应处理

switch(in.func)
 {
  case GNU_FUNC_PING: 
  case GNU_FUNC_PONG:

收到Ping消息应返回一个Pong消息,并将消息设置为广播方式

     GnuPacket pong;
     pong.initPong(sh,true,in);
     serv->outputPacket(pong,true)) //发出消息
     ret = R_BROADCAST;

收到Pong消息应进行判断,若是对本机发出的Ping消息的回复,则表明与远端主机建立连接;若否,则路由返回

if (servMgr->isReplyID(in.id))
      {
       servMgr->addHost(h,ServHost::T_SERVENT,sys->getTime());  //建立连接
       ret = R_ACCEPTED;
      }else
       ret = R_ROUTE;


收到Query消息,首先应把消息广播出去.然后若本机存在Query要寻找的频道,则返回一个QueryHit消息

注意Gnutella中的文件在Peercast中相对应的是一个频道,因此文件名相对应的是频道的ID

ret = R_BROADCAST;

numHits = chanMgr->findChannels(info,hits,16); //info是一个频道信息结构,此时保存着Query消息中寻找的频道信息

for(int i=0; i<numHits; i++)
    {
     GnuPacket hit;
     if (hit.initHit(sh,hits,&in,push,busy,stable,tracker,in.hops))
      serv->outputPacket(hit,true);
    }

收到QueryHit消息,则表示频道已找到,加入收听.若位于防火墙后面,则加上PUSH消息

if (hit.firewalled) strcat(flstr,"Push,");

readHit(data,hit,in.hops,in.id)

其中readHit()中加入收听的执行语句为

if (info.id.isSet())
  {
   if (!chanMgr->findHitList(info))
    chanMgr->addHitList(info);

}

收到Push消息,则新分配servent类,按GIV方式进行传送

Servent *s = servMgr->allocServent();
       if (s)
        s->initGIV(h,c->info.id);

十二。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)


十三。通过日志文件分析Peercast频道的创建过程 

由ChanMgr创建新频道并加入到当前的频道列表中

[CHAN] New channel created

Channel *ChanMgr::createChannel(ChanInfo &info, const char *mount)
{
 Channel *nc=NULL;

 nc = new Channel();

 nc->info = info;

 nc->next = channel;
 channel = nc;

 LOG_CHANNEL("New channel created");
 return nc;

}

由Channel类启动频道,并加入到ChangMgr的Hit列表中
[CHAN] Channel started

THREAD_PROC Channel::stream(ThreadInfo *thread)
{
// thread->lock();

 Channel *ch = (Channel *)thread->data;

 while (thread->active && !peercastInst->isQuitting)
 {
  LOG_CHANNEL("Channel started");


  ChanHitList *chl = chanMgr->findHitList(ch->info);
  if (!chl)
   chanMgr->addHitList(ch->info);

  ch->sourceData->stream(ch);

由Channel根据频道信息,创建相应的源
[CHAN] Channel is MP3 - meta: 0

ChannelStream *Channel::createSource()
{

 ChannelStream *source=NULL;

switch(info.contentType)
  {
   case ChanInfo::T_MP3:
    LOG_CHANNEL("Channel is MP3 - meta: %d",icyMetaInterval);
    source = new MP3Stream();
    break;

}

return source;

}

十四。通过日志文件分析Peercast的HANDSHAKE过程 

[DBUG] ShoutCast client

void Servent::handshakeHTTP(HTTP &http, bool isHTTP)
{
 LOG_DEBUG("ShoutCast client");
 handshakeICY(Channel::SRC_SHOUTCAST,isHTTP);
}

void Servent::handshakeICY(Channel::SRC_TYPE type, bool isHTTP)
{
 servMgr->checkFirewall();
 //Channel ID用IP地址和速率来编码以避免重复
 info.id = chanMgr->broadcastID;
 info.id.encode(NULL,info.name.cstr(),loginMount,info.bitrate);
 LOG_DEBUG("Incoming source: %s : %s",info.name.cstr(),ChanInfo::getTypeStr(info.contentType));
 c = chanMgr->createChannel(info,loginMount); //创建频道
}

[DBUG] ICY icy-name:Sonic's radio
[DBUG] ICY icy-genre:Pop
[DBUG] ICY icy-url:http://www.sonic.com
[DBUG] ICY icy-irc:#shoutcast
[DBUG] ICY icy-icq:0
[DBUG] ICY icy-aim:N/A
[DBUG] ICY icy-pub:1
[DBUG] ICY icy-br:48


bool Servent::handshakeStream(ChanInfo &chanInfo)
{

 HTTP http(*sock);

if (chanInfo.contentType != ChanInfo::T_MP3)
   addMetadata=false;

  if (addMetadata && (outputProtocol == ChanInfo::SP_HTTP))  // winamp mp3 metadata check
  {

   sock->writeLine(ICY_OK);

   sock->writeLineF("%s %s",HTTP_HS_SERVER,PCX_AGENT);
   sock->writeLineF("icy-name:%s",chanInfo.name.cstr());
   sock->writeLineF("icy-br:%d",chanInfo.bitrate);
   sock->writeLineF("icy-genre:%s",chanInfo.genre.cstr());
   sock->writeLineF("icy-url:%s",chanInfo.url.cstr());
   sock->writeLineF("icy-metaint:%d",chanMgr->icyMetaInterval);
   sock->writeLineF("%s %s",PCX_HS_CHANNELID,idStr);

   sock->writeLineF("%s %s",HTTP_HS_CONTENT,MIME_MP3);

  }

}

在开始前我们需要一个有效的IP地址

[DBUG] Checking firewall..

void ServMgr::checkFirewall()
{
   Servent::handshakeOutgoingPCP(atom,sock->host,remoteID,agent,true);
}

[DBUG] PCP outgoing waiting for OLEH..
[DBUG] Got new ip: 218.249.186.209:0
[DBUG] Firewall is set to ON
[DBUG] PCP Outgoing handshake complete.


void Servent::handshakeOutgoingPCP(AtomStream &atom, Host &rhost, GnuID &rid, String &agent, bool isTrusted)
{
   LOG_DEBUG("PCP outgoing waiting for OLEH..");

   LOG_DEBUG("Got new ip: %s",ipstr);
   servMgr->serverHost.ip = thisHost.ip;
   
   LOG_DEBUG("PCP Outgoing handshake complete.");
}

十五。用WM_COPYDATA实现进程间通信 

进程间通讯的方式有很多,常用的有共享内存、命名管道和匿名管道、发送消息等几种方法来直接完成,另外还可以通过socket口、配置文件和注册表等来间接实现进程间数据通讯任务。以上这几种方法各有优缺点,具体到在进程间进行大数据量数据的快速交换问题上,则可以排除使用配置文件和注册表的方法;另外,由于管道和socket套接字的使用需要有网卡的支持,因此也可以不予考虑。这样,可供选择的通讯方式只剩下共享内存和发送消息两种。由于数据量比较大,这样在使用消息进行通讯时就无法通过消息参数将数据直接携带到接收方,只能以地址传送的方式进行。当一个应用程序向另一个应用程序发送数据时将会发出WM_COPYDATA系统消息,因此可以考虑通过向消息队列插入WM_COPYDATA消息的方法来实现数据在进程间的拷贝。

  在使用WM_COPYDATA消息时,由第一个消息参数指定发送窗口的句柄,第二个消息参数则为一同数据相关的数据结构COPYDATASTRUCT的指针,此结构原形声明如下: 
typedef struct tagCOPYDATASTRUCT {
DWORD dwData; 
DWORD cbData; 
PVOID lpData; 
} COPYDATASTRUCT;

  其中,只需将待发送数据的首地址赋予lpData、并由cbData指明数据块长度即可。消息发出后,接收方程序在WM_COPYDATA消息的响应函数中通过随消息传递进来的第二个参数完成对数据块的接收。但是在使用WM_COPYDATA消息时,只能用SendMessage()函数发送而不能使用PostMessage(),这两个函数虽然功能非常相似都是负责向指定的窗口发送消息,但是SendMessage()函数发出消息后不是马上返回,而是在接收方的消息响应函数处理完之后才能返回,并能够得到返回结果。在此期间发送方程序将被阻塞,SendMessage()后面的语句不能被继续执行。而PostMessage()函数在发出消息后马上返回,其后语句能够被立即执行,但是无法获取消息的执行结果。可见,在交换数据量较大的情况下实现数据频繁而又快速的交换用发送WM_COPYDATA消息的方法也是不合适的,当数据传输过于频繁时将有可能导致数据的丢失。

  比之以上几种进程间通讯方法,共享内存有着明显的优势。共享内存是通过直接操作内存映射文件来进行的,而内存映射文件又是进行单机数据共享的最低层机制,前面几种数据交换方式在低层都是通过内存映射文件来进行的。因此使用共享内存可以以较小的开销获取较高的性能,是进行大数据量数据快速交换的最佳方案。

十六。Peercast的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;

};

十七。CGI的基本编程要素 

CGI的工作流程:

    服务器根据客户端发送的请求方法(GET\POST\HEAD),将信息发送给CGI脚本。CGI脚本进行信息处理并将结果返回给服务器。服务器再对返回结果进行分析,然后发送给客户端。

CGI脚本的解析流程:

1.判断方法类型,根据不同类型做相应处理(GET\POST\HEAD)

2.若方法为GET,则只需往页面中写入相应的HTML代码

3.若方法为POST,先分析POST信息,再分离NAME和VALUE。然后返回相应值。

十八。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);

}

8.Python基础语法(五)续【光说不练假把式,上题】 Python基础语法(二) 有关于Python循环语句的几道实操,让大家理解知识点更深更透彻~ 阅读详情

相关推荐

小龙虾 OpenClaw 权限全攻略:轻松激活 elevated (最新版 OpenClaw 自动化任务降智、无权限、无法执行sudo等系统命令、提升 elevated 权限的解决方案)

升级小龙虾 OpenClaw 后权限不足?elevated 报错?📛 本文教你如何正确配置 OpenClaw 权限💻,让自动化任务顺利执行 🚀

XiaoqiangClub的博客 697

文看懂P2P网络:从原理到应用与挑战

P2P网络种去中心化的分布式网络架构,节点间平等共享资源。本文介绍了P2P网络的定义、特点(去中心化、可扩展性、健壮性等)、四种拓扑结构分类(中心化、全分布式非结构化、全分布式结构化和半分布式)及其典型应用场景(文件共享、在线协作、物联网等)。同时分析了P2P网络面临的技术风险、信用风险等挑战,并展望了其与AI、区块链等新技术融合的未来发展趋势。P2P网络凭借独特优势将继续在互联网发展中发挥重要作用。

大雨淅淅的博客 4809

P2P网络技术核心原理与应用实战

在信息技术飞速发展的今天,P2P(Peer-to-Peer,点对点)网络通信模式作为种去中心化的网络技术,已经成为研究和应用的热点。本章将为大家介绍P2P网络的基本概念、主要特点以及其在信息技术领域的基本应用和潜在影响。

weixin_35749796的博客 1714

全面解析P2P网络

P2P(Peer-to-Peer)网络种分布式网络架构,其中每个节点(或称为“对等体”)都是网络的参与者,节点之间直接进行数据交换与通信。不同于传统的客户端-服务器架构,P2P网络没有集中化的服务器或管理节点,所有节点具有对等的功能,既可以作为数据的请求者(客户端),也可以作为数据的提供者(服务器)。这使得P2P网络能够在去中心化的环境中实现资源共享、信息传播以及计算任务分配。P2P网络个显著特点是每个节点可以独立地发现其他节点,并与之建立连接,从而形成个动态变化的网络结构。

hello.reader 8927

P2P 网络(Peer-to-Peer Network)

P2P 网络是区块链的基石,它确保数据可以去中心化、高效、安全地传播。P2P 网络不仅在区块链中发挥重要作用,还广泛应用于文件共享、去中心化存储、分布式计算、去中心化社交网络等多个领域。在区块链系统中,P2P 网络是数据传播的基础,确保交易和区块信息能够高效、安全地在全网传播。P2P 网络可以用来构建去中心化的计算平台,提供算力共享、去中心化 AI 计算等服务。未来,P2P 网络将在区块链、Web3 和去中心化存储等领域发挥更大作用。P2P 网络的未来将更加去中心化,推动全球互联网基础设施的变革。

小宝哥Code的专栏 4271

区块链-P2P()

区块链-P2P() P2P 打洞DEMO

yunteng521的博客 3500

论P2P的实现(如何穿透NAT)

以前写的老文章,转过csdn这边来.         最近对p2p(peer to peer)技术十分感兴趣,以前用VB的时候曾尝试过学习穿透NAT方面的知识,很可惜那时候并没有成功(由于我当时的兴趣并不大),现在大学开始教 C++,突然间对p2p技术产生兴趣,这有很多方面的原因         现在基本上很多网络应用的软件都涉及p2p技术(QQ,PPS,对战平台...),p2p技术旨在于摒弃

NekoCode 的记事本 3185

Peercast简介分析常见问题处理(二)

//服务器绑定。旦为某种协议创建了套接字,就必须将套接字绑定到个已知地址上。使用bind函数 void WSAClientSocket::bind(Host &h) {  struct sockaddr_in localAddr;  if ((sockNum = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)   thro

1164

PeerCast分析要点PeerCast和媒体播放器的通信原理)

PeerCast和媒体播放器的通信原理 这里分析的仅以MP3为例,PeerCast和播放器的数据通信是通过HTTP协议来实现 首先播放器通过向本地PeerCast发送http请求(注意媒体数据是从PeerCast本地的缓冲区内读取到播放器的,通过以下的请求地址实现)通过向Pe

星宇环点 1298

关于Peercast源代码的分析

关于Peercast源代码的分析   1. 以.h文件为单位,每个.

iPub的专栏 1434

P2P网络基础(学习笔记)

P2P网络基础(学习笔记)基本概述P2P与C/S架构P2P网络的特点P2P网络的主要功能P2P网络的分类集中式全分布式非结构化全分布式结构化混合式比特币网络P2P网络应用 基本概述 P2P(peer-to-peer)网络又称为对等式网络,或者点对点网络。这是种无中心的服务器、完全由用户群进行交换信息的互联网体系,P2P网络的每个用户即是个客户端,同时也具备服务器的功能。 它的定义是:网络的参与者共享他们所拥有的部分硬件资源(处理能力、存储能力、网络连接能力、打印机等),这些共享资源通过网络提供服务

yeaqqqq的博客 8459

计算机网络——p2p

通过请求稀缺的块,可以让更多的节点参与到这些块的共享中,提高电影的可用性。例如,在个拥有数百万个节点的非结构化 P2P 网络中,节点 A 需要查找资源 X,可能需要向大量的邻居节点发送查询请求,并且这些请求可能会在网络中转发多次,才能找到资源 X。如果所有节点都只请求常见的块,那么拥有稀缺块的节点可能会被闲置,而拥有常见块的节点可能会因为过多的请求而负担过重。例如,在个 P2P 文档共享网络中,如果大家都只请求些热门文档的部分块,可能会导致这些块的传输路径过于拥挤,降低网络吞吐量。

qq_42183367的博客 2336

P2P协议概述

从结构上来说,它使用了Gnutella的全分布式的结构,这样可以使系统更好地扩展,因为它无需中央索引服务器存储文件名,它是自动地把性能好的机器分为SupserNode,SupserNode存储着离它最近的叶子节点的文件信息,这些SuperNode再联通起来形成个Overlay Network。目前互联网中广泛使用集中式、层次式等拓扑结构,Internet本身是世界上最大的非集中式的互联网络,但是九十年代所建立的网络应用系统却是完全的集中式的系统、很多Web应用都是运行在集中式的服务器系统上。

flyingbike的博客 1479

P2P搜神器:高效搜索电影歌曲种子的指南

本文还有配套的精品资源,点击获取 简介:P2P技术是种去中心化的网络通信模式,它允许网络中的每个节点既是服务的消费者也是服务的提供者。在P2P搜神器中,P2P技术被用来高效地搜索和分享电影歌曲的种子。本文将详细介绍P2P技术的核心特点、P2P搜神器的原理,以及使用P2P技术时需要注意的挑战和风险。 1. P2P技术简介 P2P(Peer-to-Peer)技...

weixin_30653091的博客 1977

P2P知识(2)--P2P详解

p2p详解1、什么是p2pP2P是种技术,但更多的是种思想,有着改变整个互联网基础的潜能的思想。(1)p2p的概念P2P是peer-to-peer的缩写,peer在英语里有“(地位、能力等)同等者”、“同事”和“伙伴”等意义。这样来,P2P也就可以理解为“伙伴对伙伴”的意思,或称为对等联网。目前人们认为其在加强网络上人的交流、文件交换、分布计算等方面大有前途。简 单的说,P2

DoYou?'s csdn 5389

P2P:不只是网络借贷,更是金融变革

P2P:不只是网络借贷,更是金融变革。

JiYan_cyan的博客 1183

经典p2p下载工具:比特彗星(BitComet)

比特彗星(BitComet)是个运用了P2P下载技术的工具,BitComet比特彗星现在经过多年发展,已经充分发挥了下载工具的的大部分功能,并且BitComet比特彗星现在主要功能完全免费,更加符合大多数用户的需求。

onlinedown_net的博客 5149

区块链:P2P技术是什么

摘要:包括比特币、以太坊等在内的去中心化的区块链平台,其底层网络都是采用的P2P技术实现,每个节点都是对等的。而本文,则先通过介绍P2P技术的特点和发展历史,让大家对P2P这个技术的来龙去脉有个初步的认识和了解。然后在下篇文章中,我会详细介绍第三代P2P技术(DHT)—Kademlia算法的实现原理。 1.p2p的定义 P2P是peer-to-peer的简称,又称为点对点技...

跨链技术践行者 3万+

详解P2P技术

P2P = Peer to Peer 现在P2P也有很多不同架构,以下是常见的些P2P架构 纯P2P架构 没有总是在线的服务器 任意端系统之间直接通信 对等方之间可以间断连接并可 以改变IP地址 例子: 文件分发 流媒体 VoIP 复杂应用纯P2P无法实现 P2P: 集中式目录 Napster公司首先设计,由中央集中服务器管理 当对等方启动时,它通知目录 服务器以下信息 IP地址 可供共享的对象名称 Alice查询文件“Hey Jude” 3) Al.

3899

网络协议十二之P2P协议

“兄弟,有种子吗?”     “什么种子?小麦种吗?”     “......,来,哥今天带你认识下什么是种子”。     大家说起种子,应该都知道是用来下载资源的。那么资源下载都有哪些方式?种子下载又有什么优势呢? 下载电影的两种方式#     第种是通过 HTTP 进行下载。这种方式,有过经历的人应该体会到,当下载文件稍大点,下载速度简直能把人急死。     第二种方式就是是通过 FTP(文件传输协议)。FTP 采用两个 TCP 连接来传输个文件。 控制连接。服务器以被动的方式,打开众所周

flyingbike的博客 717

docker-toolbox-portable-win64-20.10.17.zip

最新版本的docker client for windows, 方便VSCODE使用,通过配置DOCK TCP HOST啥的连接docker 实例。docker --versionDocker version 20.10.17, build 100c701 docker-compose.exe --versiondocker-compose version 1.29.2, build 5becea4c

上一篇: const int*和int const*
下一篇: Peercast简介、分析及常见问题处理(二)
Canvas
博客等级 码龄16年 4粉丝 1原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值