验证BCL可靠性协议C version1.0[Incomplete]

本文介绍了一种基于集合点通信的BCL可靠性协议实现方案,该方案允许数据包丢包并实现了队列自动重发功能。此外,还加入了传输介质模拟,能够模拟丢包、包失序及延迟阻塞等情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/*
* PROMELA Validation Model
* 协议层验证
*
* 验证BCL可靠性协议C version1.0
*
* Yuhuang
* 2007-7-29
*/





/*
* 本版本特点:采用集合点来通信,允许CHK、RTT、ACK、DATA丢包,实现了队列自动重发。
*    加入了对传输介质的模拟。介质可以丢包,导致包失序。
*    加入了对传输介质中存在延迟阻塞的模拟。包可以失序。
*    加入对接收方向发送方的失序模拟。 
*     CHK的语义实现
*
*/






/*
* 测试记录: 
* WIN=5 QSIZE=2 跑了大约_秒钟
* WIN=7 QSIZE=3 跑了大约_分钟
* WIN=9 QSIZE=4 ???
*/





/************** 数据结构定义 *************/


/* 序号范围定义 */
#define WIN 7
/*
 发送队列大小定义 */
#define QSIZE 3
/*
Note:上面是比较保险的做法: WIN = QSIZE * 2 + 1*/

/*
#define inc(x) (x=(x+1)%WIN)
*/

/* 消息类型定义 */
mtype 
= { ACK,DATA,RRT,CHK }


/* 
 * { ACK|RRT,res|version, seqno }
 
*/
chan sevts 
= [0] of { mtype,byte,byte };
chan revts 
= [0] of { mtype,byte,byte };


/*
 * { DATA|CHK, res|version, seqno} 
 * 数据队列 
 
*/
chan sdata 
= [0] of { mtype,byte,byte };
chan rdata 
= [0] of { mtype,byte,byte };

/*
 * 为实现阻塞模拟,需要定义复合类型数组
 
*/
typedef Msg{
    mtype type;
    byte version;
    byte seqno;
};


/* 发送记录队列 */
/* 当ACK发生的时候,ACK(含)之前的内容都被清除 */
byte head_seq;
byte tail_seq;
byte cur_seq;

/* 发送队列元素状态标志 */
bool sflag[WIN];    
/* = 0,已确认;=1,未确认  */
/* 发送窗口的范围是由head_seq和QSIZE一起决定的 */
/* Avaliable Range: head_seq ~ head_seq+QSIZE-1 */


/* 接收方期望系列号 */
byte expected_seqno;
/* 接收方窗口 */
/* min , max */
byte min_seq;
byte max_seq;
byte rseqno;    
/* recv seqno */
/* 未收到消息记录链 */
bool rflag[WIN];    
/* =1表示未收到, =0 表示已经收到*/


/* 进程说明:
 * 1. sender2media    发送方到接收方的介质模拟进程
 * 2. receiver2media     接收方到发送方的介质模拟进程
 * 3. sender        发送方进程
 * 4. receiver        接收方进程
 
*/

proctype sender2media()
{
    byte seq;
    byte v;
    mtype type;
    
    
/* 阻塞队列 */
    Msg msg[QSIZE];
    
/* flag语义:
     *     flag[i] = 0的时候,表示msg[i] 为空
     *     flag[i]  = m (m>0) 的时候,表示从msg[i]被阻塞以来,没有被阻塞的包的个数为m
     * flag用法:
     *     (1)每次收到一个消息,将不为零的flag[i]逐个加一
     *    (2)发现某个flag[i] 的值大于等于QSIZE时必须将其从阻塞队列中取出,进行发送或丢弃,转4
     *    (3)随机选择一个不为空的阻塞Msg,将其从阻塞队列中取出,进行发送或丢弃
     *    (4)flag子序列结束
    
*/
    byte flag[QSIZE];    
    byte i;    
/* 记录随机数下标用 */
    byte j;    
/* 产生随机数用 */
    
    
    
do
    
::sdata?type,v,seq ->
        
/* CHK语义实现 */
        
        
/*CHK的完整语义为:
        检查CHK携带的seqno之前(含)的数据包是否发送到位.通过硬件相关技术,
        它可以严格保证在CHK发出之前的所有数据包都已经丢失或者到达,
        而不会存在某个数据包因为延迟而后于CHK包到达。
        
*/
        i
=0;
        j
=0;
        
if
        
::(type==CHK) ->
            
do
            
:: i < QSIZE ->
                
if
                
::flag[i] != 0 ->    /*当前Msg为一个不为空的阻塞Msg*/
                    
if    /* 随机选择是发送还是丢弃 */
                    
::rdata!msg[i].type,msg[i].version,msg[i].seqno    /* 发送 */
                    
::skip    /*丢弃 */
                    fi;
                    flag[i] 
= 0;    /* 标记当前消息槽可用 */
                
::else 
                fi;
                i 
= i + 1;
            
:: else break
            od;            
        
::else ->skip
        fi;
/*======================================================*/    
        
/* 阻塞语义实现 */
        
        
/* 不为空者逐个加一 */
        i 
= 0;
        j 
= 0;    
        
do
        
:: i < QSIZE ->
            
if
            
::flag[i] != 0 ->
                flag[i] 
= flag[i] + 1
            
::else -> skip
            fi;
            i 
= i + 1
        
:: else break
        od;
        
/*寻找需要立即处理的Msg */
        i 
= 0;
        j 
= 0;
        
do
        
:: i < QSIZE ->
            
if
            
::(flag[i] == QSIZE-1->
                
if
                
::rdata!msg[i].type,msg[i].version,msg[i].seqno->    /* 立即发送,否则模拟的网络存在重大错误 */
                    flag[i] 
= 0
                fi
            
::else -> skip
            fi;
            i 
= i + 1;
        
:: else break
        od;    
        
/* 随机选择一个不为空的阻塞Msg,将其从阻塞队列中取出,进行发送或丢弃*/
        i 
= 0;
        j 
= 0;
        
do
        
:: i < QSIZE ->
            
if
            
::flag[i] != 0 ->    /*选择到一个不为空的阻塞Msg*/
                
if    /* 随机选择是发送还是丢弃 */
                
::rdata!msg[i].type,msg[i].version,msg[i].seqno    /* 发送 */
                
::skip    /*丢弃 */
                fi;                
                flag[i] 
= 0;    /* 标记当前消息槽可用 */
                
break;
            
::skip
            fi;
            i 
= i + 1
        
:: else break
        od;

/*======================================================*/

        
/* 阻塞模块的任务已经完成, 现在处理当前消息 -- 阻塞它或者传递它! */
        
        
/* 阻塞或传递当前的数据包 */
        i
=0;
        j
=0;
        
if
        
::skip ->    /* 阻塞这个数据包*/
            
/* 随机选择一个空位 */
            
do
            
::j>=0 ->
                j 
= (j + 1% QSIZE
            
::skip ->
                i 
= j;        /*获得随机数 */
                
break    
            od;

            
if
            
::flag[i]==0 ->    /*Msg[i]为空,可以放下一条阻塞消息*/
                msg[i]
.type = type;
                msg[i]
.version = v;
                msg[i]
.seqno = seq;
                flag[i] 
= 1;    /*标记Msg[i] 中已经存有消息*/
                goto endofsend1    
/* 信息已经阻塞,无须发送 */
                
            
::else->    /* Msg[i]中已有一条消息,且不阻塞这条消息了 */
                
if
                
::rdata!type,v,seq
                
::skip
                fi
            fi;
endofsend1
:        skip
        
::skip ->    /* 直接传递或丢弃数据包 */        
            
if
            
::rdata!type,v,seq
            
::skip
            fi
        fi
    od
}


proctype receiver2media()
{
    byte seq;
    byte v;
    mtype type;
    
    
/* 阻塞队列 */
    Msg msg[QSIZE];
    
/* flag语义:
     *     flag[i] = 0的时候,表示msg[i] 为空
     *     flag[i]  = m (m>0) 的时候,表示从msg[i]被阻塞以来,没有被阻塞的包的个数为m
     * flag用法:
     *     (1)每次收到一个消息,将不为零的flag[i]逐个加一
     *    (2)发现某个flag[i] 的值大于等于QSIZE时必须将其从阻塞队列中取出,进行发送或丢弃,转4
     *    (3)随机选择一个不为空的阻塞Msg,将其从阻塞队列中取出,进行发送或丢弃
     *    (4)flag子序列结束
    
*/
    byte flag[QSIZE];    
    byte i;    
/* 记录随机数下标用 */
    byte j;    
/* 产生随机数用 */
    
    
    
do
    
::revts?type,v,seq ->    /* 从receiver方接到消息 */

        
/* 不为空者逐个加一 */
        i 
= 0;
        j 
= 0;    
        
do
        
:: i < QSIZE ->
            
if
            
::flag[i] != 0 ->
                flag[i] 
= flag[i] + 1
            
::else -> skip
            fi;
            i 
= i + 1
        
:: else break
        od;
        
/*寻找需要立即处理的Msg */
        i 
= 0;
        j 
= 0;
        
do
        
:: i < QSIZE ->
            
if
            
::(flag[i] == QSIZE-1->
                
if
                
::sevts!msg[i].type,msg[i].version,msg[i].seqno->    /* 立即发送,否则模拟的网络存在重大错误 */
                    flag[i] 
= 0
                fi
            
::else -> skip
            fi;
            i 
= i + 1;
        
:: else break
        od;    
        
/* 随机选择一个不为空的阻塞Msg,将其从阻塞队列中取出,进行发送或丢弃*/
        i 
= 0;
        j 
= 0;
        
do
        
:: i < QSIZE ->
            
if
            
::flag[i] != 0 ->    /*选择到一个不为空的阻塞Msg*/
                
if    /* 随机选择是发送还是丢弃 */
                
::sevts!msg[i].type,msg[i].version,msg[i].seqno    /* 发送 */
                
::skip    /*丢弃 */
                fi;
                flag[i] 
= 0;    /* 标记当前消息槽可用 */
                
break;
            
::skip
            fi;
            i 
= i + 1
        
:: else break
        od;


        
/* 阻塞模块的任务已经完成, 现在处理当前消息 -- 阻塞它或者传递它! */
        
        
/* 阻塞或传递当前的数据包 */
        i
=0;
        j
=0;
        
if
        
::skip ->    /* 阻塞这个数据包*/
            
/* 随机选择一个空位 */
            
do
            
::j>=0 ->
                j 
= (j + 1% QSIZE
            
::skip ->
                i 
= j;        /*获得随机数 */
                
break    
            od;

            
if
            
::flag[i]==0 ->    /*Msg[i]为空,可以放下一条阻塞消息*/
                msg[i]
.type = type;
                msg[i]
.version = v;
                msg[i]
.seqno = seq;
                flag[i] 
= 1;    /*标记Msg[i] 中已经存有消息*/
                goto endofsend2    
/* 信息已经阻塞,无须发送 */
                
            
::else->    /* Msg[i]中已有一条消息,且不阻塞这条消息了 */
                
if
                
::sevts!type,v,seq
                
::skip
                fi
            fi;
endofsend2
:        skip
        
::skip ->    /* 直接传递或丢弃数据包 */        
            
if
            
::sevts!type,v,seq
            
::skip
            fi
        fi
    od

}



/*
* 发送方
* 发送两类消息: DATA(数据) 、CHK( 缓冲区可用性检查) 

* 接受两类消息: ACK( 确认) 、RRT(请求重传) 
*/
proctype sender()
{

    byte s;
    byte version;
    int rrt_version;

    byte i;
    byte j;
    
    
/* 发送队列*/
    Msg sbuf[WIN];    
/* 为了方便,不使用QSIZE。实际上,某个时刻起作用的个数仍然为QSIZE个 */

    
    
/* 假设第0个包已经发出 */
    head_seq 
= 0;
    tail_seq 
= 0;
    sflag[
0= 1;
    
    cur_seq 
= 0;    /*定位发送点 */
    
    rrt_version 
= 0/* init rrt version number */
    
    
    
do
    
::(tail_seq+WIN-head_seq+1)%WIN < QSIZE ->    /** 队列不为满 (没有超出窗口范围),允许加入新的待发消息 **/
        
/*加入一个元素到发送队列中 */
progress_2
:    tail_seq = (tail_seq + 1)%WIN;/* 更新tail,  head保持不变 */
        sflag[tail_seq] 
= 1;
        
/* 立即发送之 */
        sdata
!DATA,0,tail_seq;    /* try to send now! */

        
    
::sevts?ACK,version,->            /** 接收到ACK **/
        
/* 无须进行ACK失序处理 */
        
assert(s<WIN);
        
/* ACK的时候需要检查是否可以向前滑动窗口(这里头变尾不变) */
        i
=s;
        sflag[i] 
= 0;    /* 标志sflag[s] 已经确认 */
        
if
        
::i==head_seq ->    /*可以滑动,至少一格,多则数格 */
            
do
            
::sflag[i]==0 ->    /* 此元素已经确认 */
                i 
= (i + 1)%WIN;
            
::else ->
                
/* 将队列前面连续的都确认了的消息从窗口范围内删除。*/
                head_seq 
= i; /* got new head,此时do的第一个分支又可以工作了 */
                
break;
            od
        
::else
        fi
        
        
        
    
::sevts?RRT,version,->            /** 接收到RRT **/
        
if
        
::(version == rrt_version) ->    /* 预期rrt*/
            
/*重发s,rrt_version加1  */
            sdata
!DATA,0,i;
            rrt_version 
= (rrt_version + 1% WIN;    /* inc(rrt_version); */
            
        
:: else ->    /* 接受到非预期rrt */
            skip    
/* 简单丢弃之 */
        fi
    
    
::timeout ->    /* 超时 */
        
/* 扫描发送队列,找到第一个可以发送的消息,对其发出CHK */
        i 
= head_seq;
        j 
= tail_seq;
        
do
        
:: i <= tail_seq ->
            
if
            
::(sflag[i] == 1->
                sdata
!CHK,rrt_version,i;
                
break;
            
::else -> skip
            fi;
            i 
= (i + 1)%WIN;
        
:: else ->        
            
break;
        od;
    od
}



/*
* 接收方
* 发送两类消息: ACK( 确认) 、RRT(请求重传) 

* 接受两类消息: DATA(数据) 、CHK( 缓冲区可用性检查) 
*/
proctype receiver()
{


    byte v;

    
    
/* 假设0号消息已经收到 */
    max_seq 
= 0;
    min_seq 
= 0;
    rflag[
0= 0;
    
    
do
    
::rdata?CHK,v,rseqno ->    /** 收到CHK消息 **/
        
if
        
::((min_seq+WIN-rseqno-1)%WIN < WIN/2->        /* seqno < min_seqno */
            revts
!ACK,v,rseqno;
        
::((rseqno-max_seq+WIN-1)%WIN < WIN/2->        /* Seqno > max_seqno */
            revts
!RRT,v,rseqno;
        
::else                        /*  min_seqno < seqno < max_seqno */
            
/*检查s是否已经接收到*/
            
if
            
::rflag[rseqno]==1 ->    /*未收到*/
                revts
!RRT,v,rseqno;
            
::else ->    /*已收到*/
                revts
!ACK,v,rseqno;
            fi
        fi



    
::rdata?DATA,v,rseqno ->    /** 收到DATA消息,s为消息序列号 **/
        
if
        
::((rseqno-min_seq+WIN-1)%WIN < WIN/2 && (max_seq-rseqno+WIN-1)%WIN < WIN/2->    /* s在二者之间 */
            rflag[rseqno] 
= 0;    /* 标志为已经收到*/
            revts
!ACK,v,rseqno;
            
/* 尝试更新 min_seq */
            rseqno
=min_seq;
            
do
            
::rflag[rseqno]==0 ->
                
if
                
/*::error  (max_seq-rseqno+WIN-1)%WIN < WIN/2 ->*/
                
::(max_seq-rseqno+WIN)%WIN <= WIN/2 ->
                    rseqno 
= (rseqno + 1)%WIN;
                
::else -> break;
                fi
            
::else ->
                
break;
            od;
            
            min_seq 
= rseqno-1;
            
        
::((rseqno-max_seq+WIN-1)%WIN <= WIN/2->        /* seqno > max_seqno */
            
/* 将max_seq到s之间的数据全部标记为未收到 */
            
do
            
::max_seq < rseqno ->
                max_seq 
= (max_seq + 1)%WIN;
                rflag[max_seq] 
= 1;    /*标记未收到*/
            
::else ->
                
break;
            od;
            rflag[max_seq]  
= 0;    /* max_seq已经更新成了s,标记为已经收到 */
        
::else ->    /* seqno = max_seqno || seqno <= min_seqno */
            
assert(false && 1==0);    /* deadly fault *//*收到重复消息*/
        fi        
    od
}


/* Promela入口程序 */
init
{
    atomic{
    run sender();
    run sender2media();
    run receiver();
    run receiver2media();
    }

}
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值