qsort源码

/***
*qsort.c - quicksort algorithm; qsort() library function for sorting arrays
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
*       To implement the qsort() routine for sorting arrays.
*
*****************************************************************************

**/

#include <cruntime.h>
#include <stdlib.h>
#include <search.h>
#include <internal.h>

/* 加快运行速度的优化选项 */
#pragma optimize("t", on)

/* 函数原型*/
static void __cdecl shortsort(char *lo, char *hi, size_t width,
                int (__cdecl *comp)(const void *, const void *));
static void __cdecl swap(char *p, char *q, size_t width);

/* this parameter defines the cutoff between using quick sort and
   insertion sort for arrays; arrays with lengths shorter or equal to the
   below value use insertion sort */

/* 这个参数定义的作用是,当快速排序的循环中遇到大小小于CUTOFF的数组时,就使用插入
排序来进行排序,这样就避免了对小数组继续拆分而带来的额外开销。这里的取值8,是
经过测试以后能够时快速排序算法达到最快的CUTOFF的值。*/

#define CUTOFF 8            /* testing shows that this is good value */

 
/* 源代码中这里是qsort的代码,但是我觉得先解释了qsort要调用的函数的功能比较

好。

    shortsort函数:

    这个函数的作用,上面已经有提到。就是当对快速排序递归调用的时候,如果遇到
大小小于CUTOFF的数组,就调用这个函数来进行排序,而不是继续拆分数组进入下一层
递归。因为虽然这里用的是基本排序方法,它的运行时间和O(n^2)成比例,但是如果是
只有8个元素,它的速度比需要递归的快速排序要快得多。另外,在源代码的注释中,说
这是一个插入排序(insertion sort),但是我觉得这个应该是一个选择排序才对
(selection sort)。至于为什么用选择排序而不用插入排序,应该是和选择排序的元素
交换次数少有关系,只需要N-1次交换,而插入排序平均需要(N^2)/2次。之所以要选择
交换次数少的算法,是因为有可能数组里面的单个元素的大小很大,使得交换成为最主
要的性能瓶颈。

参数说明:

char *lo;    指向要排序的子数组的第一个元素的指针
char *hi;    指向要排序的子数组的最后一个元素的指针
size_t width;  数组中单个元素的大小
int (__cdecl *comp)(const void *,const void *);   用来比较两个元素大
小的函数指针,这个函数是你在调用qsort的时候传入的参数,当前一个指针指向的元素
小于后一个时,返回负数;当相等时,返回0;当大于时,返回正数。*/

//选择排序 
static void __cdecl shortsort (
    char *lo,
    char *hi,
    size_t width,
    int (__cdecl *comp)(const void *, const void *)
    )
{
    char *p, *max;
    /* Note: in assertions below, i and j are alway inside original bound of array to sort. */
    while (hi > lo) {
        max = lo;
      /*下面这个for循环作用是从lo到hi的元素中,选出最大的一个,max指针指向这个最大项*/
        for (p = lo+width; p <= hi; p += width) {
            if (comp(p, max) > 0) {
                max = p;
            }
        }
      /*这里把最大项和hi指向的项向交换*/
        swap(max, hi, width);
       /*hi向前移动一个指针。经过这一步,在hi后面的是已经排好序的比未排序部分所有的数要大的数。*/
        hi -= width;
    }
}

 

/*下面分析swap函数:
这个函数比较简单,就是交换两个项的操作,不过是用指针来实现的。
*/

static void __cdecl swap (
    char *a,
    char *b,
    size_t width
    )
{
    char tmp;
    if ( a != b )
        /* Do the swap one character at a time to avoid potential alignment
           problems. */
        while ( width-- ) {
            tmp = *a;
            *a++ = *b;
            *b++ = tmp;
        }
}

 

/*下面是最重要的部分,qsort函数:*/
/*使用的是非递归方式,所以这里有一个自定义的栈式结构,下面这个定义是栈的大小
*/

#define STKSIZ (8*sizeof(void*) - 2)

void __cdecl qsort (
    void *base,
    size_t num,
    size_t width,
    int (__cdecl *comp)(const void *, const void *)
    )
{
   /*由于使用了某些技巧(下面会讲到),使得栈大小的需求不会大于1+log2(num),因此30的栈大小应该是足够了。为什么说是30呢?
   其实在上面STKSIZ的定义中可以计算出sizeof(void*)=4,所以8*4-2=30*/
    char *lo, *hi;              /* ends of sub-array currently sorting   数组的两端项指针,用来指明数组的上界和下界*/
    char *mid;                  /* points to middle of subarray  数组的中间项指针*/
    char *loguy, *higuy;        /* traveling pointers for partition step  循环中的游动指针*/
    size_t size;                /* size of the sub-array  数组的大小*/
    char *lostk[STKSIZ], *histk[STKSIZ];
    int stkptr;                 /* stack for saving sub-array to be processed  栈顶指针*/

/*如果只有一个或以下的元素,则退出*/
    if (num < 2 || width == 0)
        return;                 /* nothing to do */
        
    stkptr = 0;                 /* initialize stack */

    lo = base;
    hi = (char *)base + width * (num-1);        /* initialize limits */

    /*这个标签是伪递归的开始*/
recurse:

    size = (hi - lo) / width + 1;        /* number of el's to sort */
   /*当size小于CUTOFF时,使用选择排序算法更快*/
    if (size <= CUTOFF) {
        shortsort(lo, hi, width, comp);
    }
    else {
      /*首先我们要选择一个分区项。算法的高效性要求我们找到一个近似数组中间值
的项,但我们要保证能够很快找到它。我们选择数组的第一项、中间项和最后一项的中
间值,来避免最坏情况下的低效率。测试表明,选择三个数的中间值,比单纯选择数组
的中间项的效率要高。

       我们解释一下为什么要避免最坏情况和怎样避免。在最坏情况下,快速排序算法
的运行时间复杂度是O(n^2)。这种情况的一个例子是已经排序的文件。如果我们选择最
后一个项作为划分项,也就是已排序数组中的最大项,我们分区的结果是分成了一个大
小为N-1的数组和一个大小为1的数组,这样的话,我们需要的比较次数是N + N-1 + N-2 
+ N-3 +...+2+1=(N+1)N/2=O(n^2)。而如果选择前 中 后三个数的中间值,这种最坏情况的
数组也能够得到很好的处理。*/

        mid = lo + (size / 2) * width;      /* find middle element */
       /*第一项 中间项 和最后项三个元素排序*/

        /* Sort the first, middle, last elements into order */
        if (comp(lo, mid) > 0) {
            swap(lo, mid, width);
        }
        if (comp(lo, hi) > 0) {
            swap(lo, hi, width);
        }
        if (comp(mid, hi) > 0) {
            swap(mid, hi, width);
        }

 /*下面要把数组分区成三块,一块是小于分区项的,一块是等于分区项的,而另一块是大于分区项的。*/
/*这里初始化的loguy 和 higuy两个指针,是在循环中用于移动来指示需要交换的两个元素的。
higuy递减,loguy递增,所以下面的for循环总是可以终止。*/

        loguy = lo; /* traveling pointers for partition step  循环中的游动指针*/
        higuy = hi; /* traveling pointers for partition step  循环中的游动指针*/

        /* Note that higuy decreases and loguy increases on every iteration,
           so loop must terminate. */
        for (;;) {
           /*开始移动loguy指针,直到A[loguy]>A[mid]*/
            if (mid > loguy) {
                do  {
                    loguy += width;
                } while (loguy < mid && comp(loguy, mid) <= 0);
            }

            /*如果移动到loguy>=mid的时候,就继续向后移动,使得A[loguy]>a[mid]。
            这一步实际上作用就是使得移动完loguy之后,loguy指针之前的元素都是不大于划分值的元素。*/
            if (mid <= loguy) {
                do  {
                    loguy += width;
                } while (loguy <= hi && comp(loguy, mid) <= 0);
            }

           /*执行到这里的时候,loguy指针之前的项都比A[mid]要小或者等于它*/
           
            /*下面移动higuy指针,直到A[higuy]<=A[mid]*/
            do  {
                higuy -= width;
            } while (higuy > mid && comp(higuy, mid) > 0);

           /*如果两个指针交叉了,则退出循环。*/

            if (higuy < loguy)
                break;

            /* 此时A[loguy]>A[mid],A[higuy]<=A[mid],loguy<=hi,higuy>lo。*/
           /*交换两个指针指向的元素*/
            swap(loguy, higuy, width);

            /* If the partition element was moved, follow it.  Only need
               to check for mid == higuy, since before the swap,
               A[loguy] > A[mid] implies loguy != mid. */

           /*如果划分元素的位置移动了,我们要跟踪它。

              因为在前面对loguy处理的两个循环中的第二个循环已经保证了loguy>mid,

             即loguy指针不和mid指针相等。

             所以我们只需要看一下higuy指针是否等于mid指针,

            如果原来是mid==higuy成立了,那么经过刚才的交换,中间值项已经到了

loguy指向的位置(注意:刚才是值交换了,但是并没有交换指针。当higuy和mid相等,交换higuy和loguy指向的内容,higuy依然等于mid),所以让mid=loguy,重新跟踪中间值。*/

            if (mid == higuy)
                mid = loguy;

            /* A[loguy] <= A[mid], A[higuy] > A[mid]; so condition at top
               of loop is re-established */

            /*这个循环一直进行到两个指针交叉为止*/
        }

        /*     A[i] <= A[mid] for lo <= i < loguy,
               A[i] > A[mid] for higuy < i < hi,
               A[hi] >= A[mid]
               higuy < loguy
           implying:
               higuy == loguy-1
               or higuy == hi - 1, loguy == hi + 1, A[hi] == A[mid] */

       /*上一个循环结束之后,因为还没有执行loguy指针和higuy指针内容的交换,所以loguy指针的前面的数组元素都不大于划分值,而higuy指针之后的数组元素都大于划分值,所以此时有两种情况:

       1)  higuy=loguy-1

       2)  higuy=hi-1,loguy=hi+1

       其中第二种情况发生在一开始选择三个元素的时候,hi指向的元素和mid指向的元素值相等,而hi前面的元素全部都不大于划分值,使得移动loguy指针的时候,一直移动到了hi+1才停止,再移动higuy指针的时候,higuy指针移动一步就停止了,停在hi-1处。

       */

        /* Find adjacent elements equal to the partition element.  The
           doubled loop is to avoid calling comp(mid,mid), since some
           existing comparison funcs don't work when passed the same value
           for both pointers. */

        higuy += width;
        if (mid < higuy) {
            do  {
                higuy -= width;
            } while (higuy > mid && comp(higuy, mid) == 0);
        }
        if (mid >= higuy) {
            do  {
                higuy -= width;
            } while (higuy > lo && comp(higuy, mid) == 0);
        }

        /* OK, now we have the following:
              higuy < loguy
              lo <= higuy <= hi
              A[i]  <= A[mid] for lo <= i <= higuy
              A[i]  == A[mid] for higuy < i < loguy
              A[i]  >  A[mid] for loguy <= i < hi
              A[hi] >= A[mid] */

   /* We've finished the partition, now we want to sort the subarrays
           [lo, higuy] and [loguy, hi].
           We do the smaller one first to minimize stack usage.
           We only sort arrays of length 2 or more.*/

       /*
         我们可以想像一下,对于一个已经排序的数组,如果每次分成N-1和1的数组,
        而我们又每次都先处理N-1那一半,那么我们的递归深度就是和N成比例,这样对于大N,栈空间的开销是很大的。
        如果先处理1的那一半,栈里面最多只有2项。当划分元素刚好在数组中间时,栈的长度是logN。
         对于栈的操作,就是先把大的数组信息入栈。
       */

        if ( higuy - lo >= hi - loguy ) {
            if (lo < higuy) {
                lostk[stkptr] = lo;
                histk[stkptr] = higuy;
                ++stkptr;
            }                           /* save big recursion for later */

            if (loguy < hi) {
                lo = loguy;
                goto recurse;           /* do small recursion */
            }
        }
        else {
            if (loguy < hi) {
                lostk[stkptr] = loguy;
                histk[stkptr] = hi;
                ++stkptr;               /* save big recursion for later */
            }

            if (lo < higuy) {
                hi = higuy;
                goto recurse;           /* do small recursion */
            }
        }
    }

    /* We have sorted the array, except for any pending sorts on the stack.
       Check if there are any, and do them. */

   /*出栈操作,直到栈为空,退出循环*/

    --stkptr;
    if (stkptr >= 0) {
        lo = lostk[stkptr];
        hi = histk[stkptr];
        goto recurse;           /* pop subarray from stack */
    }
    else
        return;                 /* all subarrays done */
}

转载自:http://blog.csdn.net/jeiwt/article/details/5460966


  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1. 架构说明 目前的协议有如下一些特点: 1) 客户向服务器发送请求, 每个请求的长度不定. 请求的长度在第一个INT中指定. 2) 每个服务器通常会向多种客户提供服务, 例如, TS要同时向CP, NP提供服务, CP要向NP和其他CP提供服务, 同时还是其他CP, TS, SP的客户. 3) 每个服务器为客户服务时, 通常是长期的, 会涉及多次请求-应答的来回. 这样的结构, 主要是为了能够支持大量并发客户连接而设计的. 在具有大量并发客户 连接时, 无论采用线程还是进程, 都无法进行有效的服务, 因此必须采用select 轮询方式. 2. 基本数据结构说明 对于每个客户端, 需要保存该客户端相应的一些信息. 目前的CPnew.c, SPnew.c 和TSnew.c的核心数据结构基本相同, 都由Session, SessionCluster (TSnew.c中) 或者 ServerDesc (CPnew.c和SPnew.c)构成. 其中, Session是每个客户端相关的数据, SessionCluster(或者是ServerDesc)是 有关每种服务的信息, 其中有一个指向该服务相关的各个Session的指针. Session 这一数据结构不是在有客户请求时动态分配的, 而是在最开始初始化时就已经分配 好的, 当有新客户请求到来时, 服务器搜索这一预先分配好的这些Session, 发现其中 有空闲则使用, 如果没有空闲就报告错误. 对于TS和CP(SP)来说, 最大的区别是TS使用UDP协议, 而CP和SP则使用TCP协议, 二者的 不同在于: 1) 对于TCP协议的客户端, 由于每个客户端都使用不同的socket, 因此select之后 只需要看各个客户端的fd_set是否置位就可以了, 而对于UDP客户端, 找到相应的 客户端需要进行一次查找过程. TS使用了一些措施来减轻查找所带来的开销. 2) TCP协议中, 发来得数据是流形式的, 因此需要进行消息分块, 有可能两个消息 在一次read中读完, 也有可能一个消息需要读很多次, 这两种情况都需要考虑, 因此 每个Session中都有一个buf, rstart, rlen, 用来存储读来但还没有处理的消息, 同样, 写的过程中也需要考虑写的时候有可能没有一次写完, 因此也需要每个Session中 保留wbuf, wstart, wlen三项. UDP中则不同, 在协议实现中假设每个UDP数据包中 所包含的消息都是完整的, 因此没有这几项. SessionCluster(或者是ServerDesc)来说, 描述了一个服务, 这个服务由这样几个 主要的部分构成 1) sock: 描述所所使用的socket 2) cur: 当前客户端的个数 3) max: 最多容纳客户端的个数 4) head: Session的头, head[0]为第一个Session, head[max-1]为最后一个session 5) init: 这一服务中每个Session需要执行的初始化操作. (函数指针) 6) process: 这一服务中消息的处理函数 7) closure: 这一服务中需要的析构函数 3. 主要结构说明 process_child: 主要函数, 这一函数主要用来 设置socks和wsocks, 对于SP和CP, 只有Session的wlen>0的时候才设置wsocks; select; 对于每个ServerDesc(或者SessionCluster), 进行process_type 在SP和CP中, 为了支持PUSHLIST操作, 在每一次循环前先要进行processJob 在CP中, 还周期进行periodCheck, 用来将过期的连结清除 在TS中, 周期进行periodLog, 用来将过期的客户连接清除 process_type: 对于每个Session, 检查是否可读. 如果可读, 检查是否有完整的消息, *(unsigned int *)(rbuf+rstart) 0, 则进行写 4. 其他重要的模块 1) 配置模块 配置模块主要由struct NamVal, read_config, free_config组成, NamVal结构中, Name是在cfg文件中的名字, ptr是指向存放的指针, type是数据的类型, 目前支持这样 几种类型 'd': 整数类型, ptr是一个整数指针 's': 字符串类型, ptr是一个指向指针的指针, (char **) 'b': 字符串buffer类型, ptr是一个char *, 使用这种类型时应当注意, 对于's'类型, read_config将为该val分配内存(malloc), 但是对于'b' 类型, ptr所指向的必须是已经 分配好的内存 两个重要的函数分别为: read_config, 参数为文件名, 一个struct NamVal *, 以及该struct NamVal的项数 free_config, 参数为和read_config相同的struct NamVal *以及项数 2) mysql 模块 mysql模块主要有MYSQL *local_mysql以及三个函数构成, 这三个函数是 init_mysql, 初始化mysql, 返回一个MYSQL *, 一般用来初始化local_mysql query_mysql, 执行一个mysql语句, 格式为query_mysql (local_mysql, "mysql语句, 其中格式和printf的格式相同, 例如delete from %s等", 所需要的值) query_mysql_select, 执行一个mysql的select语句, 与上面不同的是, 它返回一个 MYSQL_RES *. 3) network排序模块 这一模块主要由networks结构, readNETBLOCK函数, getnetwork函数, compareNet函数 构成, 其中, readNETBLOCK用来读入network配置文件, 初始化全局变量NETBLOCKS, NETBLOCKS是一个 networks结构数组, 有MAX_NET项. getnetowrk用来查找和一个IP地址最接近的netblock compareNet是在qsort中用到的一个函数, 对找到的NPPeer进行排序, 让同一个网络 中的NPPeer排在前面. 4) 图管理 在目前的CP, SP, NP中, CP可以同时加入多个频道, 而NP也可以有多个资源, 为了描述 这种结构, 引入了图的概念. 每个边(Edge)存储了指向NP的指针, 指向Channel的指针, 在TS中还需要存储这一Session在这一Channel中的各个Interval. 每个Channel通过Edge 中的cnext串成一个链表, 这个链表的头是Channel结构中的PeerHead, 而每个Session 通过Edge中的enext也串成一个链表, 这个链表的头是Session结构中的header. 相关的函数有: newEdge: 新添一个边, 参数为Channel *, Session *, 对于TS还需要一个ChannelInfo来 初始化Edge中的信息 delEdge: 删除一个边, 参数为Edge * 5) Channel模块 Channel模块的功能主要是: TS中用来处理NEED_PEERS, SP中还需要保存和查找频道数据, 频道都使用图结构进行管理. 频道的搜索为了效率方面的因素, 采用了Hash进行搜索, ChannelHash中使用的是字符串 hash, 如hash_str所示. TS中的Channel相对较为简单, SP和CP中Channel还需要管理Channel相关的数据. 这些 数据以文件的形式存在硬盘上/var/tmp/目录下, 文件名随机生成, 对于每一块的相关信息, 由BlockData来保存, BlockData中的firstsampl, message_size, message_id, offset分别 存储了firstsample信息, 快的长度, 块的id, 以及在文件中的offset. SP和CP的处理有所不同, 对于CP, 块是以hash的方式来存放的, 例如, 块的ID为1000, 而 max_queue为100, 则存储位置为1000%100=0. 对于SP, 如果资源是一个CS发来的频道, 则是一个循环队列, 每一块按照次序分别存放在相应位置, 如果到了队列尾部, 就再从 队列头开始. 如果资源是文件, 就不保存BlockData信息, 直接根据blockID到原文件定位. 涉及Channel的函数有很多, 如locate_by_id, locate_order_by_id, newChannel, freeChannel, saveBlock等. 6) Berkeley DB模块 这只在SP中涉及, 主要是打开DB文件, 查询某个md5的位置. 主要涉及到DB* MediaDB, openDB, openMedia这两个函数 openDB: 参数为DB文件的名 openMedia: 参数为md5和一个整数指针, 返回FILE *以及该文件的长度, 在整数指针中 7) Job模块 Job模块用在CP和SP中, 用来处理PUSHLIST, PUSHLIST消息可以重新设置Job的列表, 也可以添加Job或者是删除Job. 涉及到job.c中的函数和JobDes结构. JobDes结构 中一个Session *, 一个Channel *用于标识该Job所属的Session和Channel, num表示 所需要下载的BlockID数, job是一个指向整数的指针, mask也是一个指向整数的指针, job 是需要下载的BlockID, 如果mask为0,则需要进行下载, 如果为1, 则不需要. addJob: 添加job的时候, 不检查该Job是否已经在列表中, 直接生成一个Job然后 添加到链表中. deleteJob: 删除Job时, 检查所有Job列表中的具有相同Session和Channel的Job, 然后将需要删除的blockID的相应mask设置为1. processJob: 对于每个job, 从cur开始, 利用process_P2P_REQUEST_real来传输 第一个mask为0的块, 如果都为1, 就删除这个job. freeJob: 删除某个JobDes. freeJobList: 删除某个Session的所有JobDes, 通常用于该Session退出时使用. 8) Interval模块 Interval模块用在TS中, 用来表示NP上面所有的快区间, 目前块区间由一个开始 字段和一个长度字段来标识. 对于Interval的主要操作是merge和delete, merge 是将原有的Interval和新的Interval列表合在一齐, 而delete则是从原有的当中 去掉新的. merge: 算法如下, 使用了缓冲Interval列表tmp. if (old < new[j]) tmp[k] = old; else tmp[k] = new[j]; 然后再看old和new中哪些能够可以和tmp[k]合并 delete: 较为复杂一些, 考虑下面几种情况 old的开始比new[j]的结束大 old的结束在new[j]的开始前 old和new[j]有共同部分, 而且 old含在new[j] 中 new[j]含在old中 互不包含, new[j] 在前 互不包含, old 在前 5. 一些快速算法 1) 在使用UDP的TS中, 在客户初次登录时, 需要查找空闲的Session, 此外, 客户有可能 会重复发送LOGIN消息, 这时需要检查这一客户端是否已经在Session列表中, 第三, 当 客户端发送消息时, 需要找到相应的Session. 为了避免这些查询, 分别使用了如下方法. 首先, 建立一个Hash表, 开始的时候所有空闲Session都串到Hash[0]处, 每当来一个 新的客户端时,从Hash[0]中取出Session, 链到相应的hashid上. 为此, hash所得的值 不能为0, 如果为0, 就返回最大的可能hashid. 根据来源端口和IP地址查询Session也使用这一Hash表. 客户端发送消息时, 使用了用于验证的7个字节中的前3字节, 用这3字节来标识Session 的下标, 这样就避免了查询开销. 2) 使用maxid来减少搜索次数. 在TCP中没有使用Hash, 使用了maxid这一项, 用来记录Session中最大的id, 由于在Session 初始化的时候, 是查找ID最小的空闲Session, 因此可以认为Session是比较紧凑的, 由于SP和CP支持的客户端要比TS少得多, 因此这样的处理是可以接受的. 在客户退出的时候, 有可能需要更新maxid, 这一更新是由Clientclosure来完成的, Clientclosure更新maxid, 然后再调用相应的析构函数. 3) 长期idle的连接的超时处理. 由于超时处理需要遍历整个列表, 为了节约系统资源, IDLE时间比较长, 此外, 一般还需要定期报告系统统计数字, 因此需要及时性. 为此, 一般periodLog或者periodCheck都判断是执行这两者中的哪一种操作. 4) 查询CPPeer时, 考虑到目前只支持GCP, 因此直接采用了GCPCHOICE,设置为当前 负载最小的GCP, 在GCP报告或者是GCP登录, 退出的时候更新. 6. 消息处理 1) TS消息处理 NP2TS_LOGIN: NP向TS登录, 按照来源IP地址和所报告的npport进行hash, 如果距离上次 发送NP2TS_LOGIN消息的时间小于SILENCE_TIME, 则直接返回, 否则发送WELCOME消息. NP2TS_REPORT: 报告Interval信息, 如果refresh为true, 则重置, 否则则先增加后删除. NP2TS_NEED_PEERS: 查询Peer信息, 使用findCPPeer寻找合适的CP, 使用findNPPeers 寻找合适的NP. NP寻找时, 找到结果后按照networks来排序, 保证在同一个网络中的 排在前面. NP2TS_LOGOUT: 退出 NP2TS_RES_LIST:发送当前NP的所有RESOURCE, 使用addSession来进行处理, 如果还没有这 条边, 就添加 NP2TS_REQ_RES: 添加RES, 并返回Peers NP2TS_DEL_RES: 删除RES CP2TS_REGISTER: 登录, CP向TS登录, 按照来源IP地址和所报告的npport进行hash, 如果距离上次发送CP2TS_REGISTER⒌氖奔湫∮赟ILENCE_TIME, 则直接返回, 否则发送 WELCOME消息. CP2TS_UPDATE: 报告CP负载 CP2TS_NEED_PEERS: ECP查询用, 目前尚未使用 2) SP消息处理 P2P_HELLO: 加入某个频道, 如果频道存在 如果是个Media文件: 返回SPUPDATE, 表明这一频道的最小最大blockID 否则: 如果这一频道已经结束, 返回结束信息 如果频道不存在 如果是个Media文件: 返回SPUPDATE, 表明这一频道的最小最大blockID, 建立频道 否则: 返回一个SPUPDATE指示错误 P2P_PUSHLIST: 重置或者是增加删除任务列表. 重置时, 先删除所有的相关任务, 然后 再增加或删除. CS2SP_REGISTER: 建立频道 CS2SP_UPDATE: 更新频道信息 CS2SP_BLOCK: 发送数据块 3) CP消息处理 P2P_HELLO: 加入某个频道, 根据提供的SP地址来建立相应连接 P2P_PUSHLIST: 重置或者是增加删除任务列表 P2P_SPUPDATE: SP发来的SPUPDATE, 如果是Media文件, 则不转发给NP P2P_RESPONSE: SP发来的数据块. 此外CP还需要向TS注册. 目前只有GCP一种类型在使用.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值