以前写了MYSEE 服务器端源码分析文档,主要是对其中的消息机制(sp,cp,ts,之间的交互)的分析。因为CU抽风,发表的文章无故给我删除了 ,所以这个不是完整的
Sp 数据结构分析(初稿)
一:准备工作
1 )熟悉 /opt/mysee/data/sp/ 目录下面有哪些目录( channel data playlist program )
2 )在目录 program 中有三个文件( 0 config keysample )
3 )知道一个 channel 到底是什么,里面包含什么东西?
这个问题困扰了我很久。
我现在的理解是这样的,在 program 中包含有很多目录,比如 /opt/mysee/data/sp/program/29bea7e94db8359982f1976e40cd780b , 都是 32 位的 16 进制数。 里面包含了 3 个文件( 0 config keysample )。 这个里面的文件是管理员上传的。在 sp 目录下面还有一个目录 channel 。说到这里,我说说一部电影是怎么传上去的(举例) 首先用格式转换工具将 .avi 等视频文件转换成为一个目录文件 29bea7e94db8359982f1976e40cd780b ,里面当然包含三个文件( 0 config keysample ) ,管理员将这个目录上传到 /opt/mysee/data/sp/program 中,这个是第一步。在系统初始化的时候,系统会打开 program 目录,然后依次检查下面的文件(比如 29bea7e94db8359982f1976e40cd780b )是否在 programhash 列表中,如果不在其中,我们就调用 callo 函数生成一个 channel 数据结构,赋值(没有对 channel 结构中的 media 指针赋值),然后添加到 programhash 列表。再对 channel 中的 media 指针赋值,添加到 channelhash 当中。
说说 media 是怎么赋值的,这个指针是指向 config 文件里面的一个数据字段 data 的
Config 文件内容如下:
BitRate=34.705500
BlockSize=16384
ChannelName=[2008.08.03] 军鸡 +shamo.2007
ResourceHash=29bea7e94db8359982f1976e40cd780b
DataLength=220
Data =vids^@^@^P^@<80>^@^@ª^@8<9b>qVP70^@^@^P^@<80>^@^@ª^@8<9b>q<80><9f>X^EVÃÃ^A^@ª^@UYZ^A^@^@^@Â^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@;]^F^@^@^@^@^@(^@^@^@^@^B^@^@<80>^A^@^@^A^@^L^@VP70^@^P^F^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@auds^@^@^P^@<80>^@^@ª^@8<9b>q1^@^@^@^@^@^P^<80>^@^@ª^@8<9b>q<81><9f>X^EVÃÎ^Q¿^A^@ª^@UYZA^@^@^@¡^@^@^@1^@^A^@D¬^@^@ý^@^@^B^@@^A
Programhash 和 channelhash 有什么区别? 为什么要分成两个 hash ,这两 hash 表又是怎么联系起来的?
用 hash 肯定是为了提高查询效率,我理解是在 channel 结构体中有一个 int ref 成员,它是一个 channel 的引用数, 当引用数 ref < 0 时,我们将改它从 programhash 表里面删除。
两个表的区别:
/opt/mysee/data/sp/program 目录中的文件是一直存在的,
/opt/mysee/data/sp/channel 目录中的文件是在打开节目是时候生成的,并且在改节目关闭后一定时间内是会删除的,这个在代码中是可以看见的。
上面这些我觉得就是要拥有两个 hash 表的原因,至于两者是怎么联系的。 (比如当在 channelhash 中删除一个 channel 时,对 programhash 中的相应的 channel 的应用数( ref )要减一)这个在下面具体数据结构中再分析
说了这么多废话其实是为了更好的理解下面的数据结构中成员的意思 :-D
二:具体数据结构分析
struct LiveChannelInfo
{
unsigned int maxID; // 这个 channel 全部文件中最大的 id 可以通过 newPListChannelFile 这个函数看出来
struct Session *dataSource; // source of data, a connection to SP or uplevel CP
unsigned int total;
struct SPUpdate s;
unsigned int max_queue; //pcinfo->max_queue = BLOCK_PER_FILE; 这个应该是每个文件所拥有的最大块的数目,要注意它和每一块多大的区别
float bitrate; // 码率
// struct BlockData *blocks;
int max_channel; // 有多少个频道
int cur_channel; // 当前 channel 数目
struct MediaData *media;
int isSave;//guess : issave = 1 represent save the channel (这个是在关闭 channel 的时候用的)
#ifdef __CP_SOURCE
char *bitflag; // for CP, it is pending status, for SP, it is keysample status
unsigned char *indisk;
unsigned int numofsp;
struct NormalAddress SPLIST[256];
#endif
#ifdef __SP_SOURCE
unsigned int startid;
struct MList *mlist; // 指向频道链表
/*
for the dir of /data/sp/program/ , there are three files . now we watch out the file 0,
input pointer point to this file . they may have more than just one file . but not beyond the numb of MAX_FILEINPUT .
in this project , we reality it just have only one file "0"
*/
FILE *input [MAX_FILEINPUT]; // 一个 channel 拥有的文件名列表
FILE *keyfile;// 这个指向我们每一个文件下面的那个 keysample 文件
unsigned int numinput; //the number of files one channel contains
unsigned int numblocks;
unsigned int userid;
float limitedBitRate;
unsigned int status; // 1 meaning this channel has been closed
time_t updated; // channel update time
#endif
};
注意:在这个数据结构中没有对 media 这个指针的操作
Input 指针也是重点要注意的
首先要注意的是 input 和 m_lists 的指针类型,一个是 file * , 一个是 channel *
两者都相当于二重指针, input 指向( 0 config keysample ) 0 这个文件 ,这里说说代码中为什么要用二重指针,这里只有一个文件( 0 这个文件),用一重的也可以呀! 代码实现中规定一个 channel 里面最多可以有 max_fileinput 这么多个文件,但是在我们的具体实现中只是生成了一个 文件 0 (你喜欢也可多生成一些,比如 1 , 2 文件等)。 这里的指针 m_lists 就是连接 channelhash 和 programhash 的指针。
----------------------------------------------------------------------------------------------------------------------
struct Channel
{
int type;
int ref ; // 某个节目的引用数 ( 也可以说频道的引用数 )
char fname[CHNLURL_LEN]; //filename 这个是通过 buildProgPath 这个函数生成的
char channel_name[CHNLURL_LEN];// 频道名
char channel_md5[MD5_LEN+1]; // 频道名 md5 码
int maxblocksize;// 从 config 文件中读取的 blocksize 大小 ( 每块的大小 ), 注意与 datalenght 的区别
int numclient; //
int numjob; // number of jobs
unsigned int ctime;
#ifdef __SP_SOURCE
int numofnp[MAX_TS];
#endif
#ifdef __CP_SOURCE
time_t last_nearpeer;
#endif
long long downsize; // recv from SP for CP, file size for SP;
long long upsize; // send to NP
FILE *db;
struct LiveChannelInfo *pcinfo;
struct Edge *PeerHead; //channel list head
struct Channel *next;// 具有相同 id 的 channel 的链接
struct Channel *lnext; // 全部 channel 的链接
};
注意:最后三个指针
struct Session
{
int type;
int socket;// 由 handle_new_connection 这个函数赋值
#ifdef __CP_SOURCE
unsigned char npcp; //TYPE_CP
unsigned char flag;
unsigned short first;
unsigned int sock_flag; /* saved for restore after connection succed */
float version; //np 的版本号,这个是从 message 的开头 的内存中读取出来的。
long long totalup;
struct PeerInfoWithAddr addr;
// struct CorePeerInfo *c;
// struct TransferInfo *t;
#endif
/*
at first ,we know that session present a client connect to sp .so the host and port is the value of the client (cp)
watch out the type of this two value
*/
unsigned int host;
unsigned int port;
unsigned int time_sec; //time of creation
unsigned int last_transferblock; //time of last send or recv a block
// 这里就很清楚了, off 其实就是 message 的长度
// buf+start 是 message 指针指向的地方
char *buf;
unsigned int start;
unsigned int off;
unsigned int numjob;
struct JobDes *head;
struct Channel *pc;// difficult point ( 在一个 session 中包含这个元素有什么作用 ?
struct Edge *header;
// 每个 session 通过 Edge 中的 enext 也串成一个链表。
// 这个链表的头是 session 结构中的 header 指针
struct Session *next;
};
struct Edge
{
struct Channel *head;
struct Session *me;
struct Edge *cnext;
struct Edge *enext;
};
最后说说 edge
Session: D,E
Channel A,B,C
Edge : P 等
对于 edge p ,对应 channel A 和 Session D 。举例分析
当我们要删除边 p 的时候
对与 channelA ,通过单向链表表头 PeerHead 来寻找所有连接到 A 上面的 edge ,如果表头第一个指向的边不是 p ,通过第一条边的 cnext 指针寻找下一条边。 直到找到或为 null 为止,然后删除改边
对于 Session D,通过单向链表表头 header 来寻找所以连接到改 Session 的所以边,如果表头第一个指向的边不是 p ,通过第一条边的 enext 指针寻找下一条边。 直到找到或为 null 为止然后删除改边
这是只是我的笔记,欢迎讨论 ^_^