数据链路层协议设计与实现(1)

数据链路层功能

数据链路层在TCP/IP协议模型里,位于第二层,它从网络层获取一个分组(packet),并将其打包成一帧(frame),然后发送给物理层。

当然数据链路层要做的不仅仅就只有这些,它要实现的功能还有许多,例如

  1. 向网路层提供一个定义良好的服务接口
  2. 处理传输过程中的错误
  3. 调节数据流,确保接收方不会被发送方的数据给淹没

下面逐步由简单的场景到复杂场景构建对应的协议

接口定义

后续实现的协议均实现了以下接口

public interface Protocol {

    int MAX_SEQ = 7;

    /**
     * 等待一个事件发生
     * @return
     */
    default EventType waitForEvent(){
        int i = ThreadLocalRandom.current().nextInt(EventType.values().length);
        return EventType.values()[i];
    }

    /**
     * 从网络层收到数据
     * @return
     */
    default Packet fromNetworkLayer(){
        return new Packet();
    }

    /**
     * 向网络层发送数据
     * @param packet 分组
     */
    default void toNetworkLayer(Packet packet){

    }

    /**
     * 从物理层收到一帧
     * @return
     */
    default Frame fromPhysicalLayer(){
        return new Frame();
    }

    /**
     * 发送一帧到物理层
     * @param frame
     */
    default void toPhysicalLayer(Frame frame){

    }

    /**
     * 启动定时器
     * @param seq 帧号
     */
    default void startTimer(int seq){

    }

    /**
     * 停止定时器
     * @param seq 帧号
     */
    default void stopTimer(int seq){

    }

    /**
     * 网络层允许接收数据
     */
    default void enableNetworkLayer(){

    }

    /**
     * 网络层不允许接收数据
     */
    default void disableNetworkLayer(){

    }

    /**
     * 帧号自增
     * @param seq 帧号
     * @return
     */
    default int inc(int seq){
        if (seq < MAX_SEQ){
            return seq + 1;
        }else{
            return 0;
        }
    }

    /**
     * 协议具体实现
     */
    void protocol();
}

无限制的单工协议

应用场景

  1. 数据只能单向传输
  2. 传输方和接收方的网路层总是处于就绪状态,处理时间可以被忽略
  3. 接收方的缓存空间无限大
  4. 信道传输的数据永远不会丢失或者损坏

协议实现

对于发送方来说,由于网路层处理数据的时间可以被忽略,同时数据不会损坏或者丢失,同时接收方的缓存空间无限大,无需考虑流控制,因此发送方只需不断的从网络层获取数据,然后发送给物理层即可

protected void sender(){
    while (true){
        //1. 从网络层(上层)获取数据
        Packet packet = fromNetworkLayer();

        //2. 生成一帧
        Frame frame = new Frame();
        frame.setPacket(packet);

        //3. 向物理层(下层)发送数据
        toPhysicalLayer(frame);
    }
}

对于接收方来说,由于缓存空间无限大,也无需考虑数据损坏丢失的情况,因此接收方只需要等待发送方发送的数据到来,然后发送给网络层即可

protected void receiver(){
    while (true){
        //1. 从物理层获取一帧
        Frame frame = fromPhysicalLayer();

        //2. 获取需要向网络层传输的数据
        Packet packet = frame.getPacket();

        //3. 想网络层发送数据
        toNetworkLayer(packet);
    }
} 

停等协议

应用场景

在实际应用场景中,接收方的缓存空间不可能无限大,用来存储所有进来的帧,并且按序发送给上层,因此我们需要考虑流控制问题

  1. 数据只能单向传输
  2. 接收方缓存空间有限,网络层处理数据需要时间
  3. 信道传输的数据永远不会丢失或者损坏

协议实现

停等协议主要解决的是如何避免发送方用超过接收方处理能力的大量数据来淹没接收方。解决的方式也比较简单,发送方根据接收方的反馈信息来发送数据。每当接收方收到一帧数据,并处理完该数据之后,会向发送方发送一个哑帧,用于告诉发送方可以发送下一帧了。

因此,对于发送方来说,在发送一帧之后,必须等到接收方发过来的确认,才可以发送下一帧

while (true){
    //1. 从网络层(上层)获取数据
    Packet packet = fromNetworkLayer();

    //2. 生成一帧
    Frame frame = new Frame();
    frame.setPacket(packet);

    //3. 向物理层(下层)发送数据
    toPhysicalLayer(frame);

    //4. 等待receiver发送帧已收到的事件
    waitForEvent();
}

对于接收方来说,在处理完一帧之后,需要向发送方发送一个确认,表示其可以继续发送下一帧

protected void receiver(){
    // 告知sender帧已经收到的信号
    Frame frameHasReceived = new Frame();
    while (true){
        //1. 等待sender发送数据
        waitForEvent();

        //2. 从物理层获取一帧
        Frame frame = fromPhysicalLayer();

        //3. 获取需要向网络层传输的数据
        Packet packet = frame.getPacket();

        //4. 想网络层发送数据
        toNetworkLayer(packet);

        //5. 告诉sender帧已经收到,可以发送下一帧
        toPhysicalLayer(frameHasReceived);
    }
}

自动重复请求协议

在实际生活中,信道传输的数据必然会出现丢失或者损坏的情况。数据链路层的职责就是将数据有序准确地发送给网络层。

那么如何解决这个问题呢?接收方在收到一帧之后,需要向发送方发送一个ACK,表明这一帧已经收到。同时 发送方需要设置一个定时器,如果在一定时间内没有收到ACK,则重复发送该帧。

由于接收方可能会收到一些重复的帧,因此接收方需要一种方式来区分某一帧是第一次收到的帧,还是重传的帧。解决这个问题的方式,就是在每一帧上加一个序列号。接收方通过检查帧号来区分新帧和重复帧。

序列号只需要一位就够了(0,1)。在任何时刻,接收方保存一个期望收到的序列号(exceptSeq)。如果收到的帧包含错误的序列号,则被认为是一个重复的帧而拒绝处理;如果包含正确的帧,则发送给网络层,同时期望收到的序列号模2增1(0 变成 1,1 变成 0)

(seq + 1) mod 2

协议实现

对于发送方来说,它需要考虑三种情形

  1. ACK完好无损地到达
  2. ACK被损坏
  3. 定时器过期

只有收到一个正确的ACK,发送方才会发送下一帧,否则则重复发送之前那一帧

protected void sender(){
    // 初始化,从网络层获取数据
    Packet packet = fromNetworkLayer();

    //初始化序列号
    int nextSendSeq = 0;
    Frame frame = new Frame();
    while (true){
        int seq = nextSendSeq;
        frame.setPacket(packet);
        frame.setSeq(seq);

        //启动定时器
        startTimer(seq);
        //向物理层发送数据
        toPhysicalLayer(frame);
        //等待事件(只有FRAME_ARRIVAL事件)到来
        EventType event = waitForEvent();
        //从receiver收到一帧
        if (event == FRAME_ARRIVAL){
            frame = fromPhysicalLayer();
            /* 如果ACK与之前发送的序列号相同,则表示receiver已正确收到该帧
            如果序列号不一致,则会重复发送之前的一帧
             */
            if (frame.getAck() == seq){
                //停止定时器
                stopTimer(seq);
                // 继续从网络层获取数据
                packet = fromNetworkLayer();
                // 序列号自增
                nextSendSeq = inc(seq);
            }
        }
    }
}

对于接收方来说,每收到一帧需要检查该帧的帧号是否与期望收到的帧号相同,若相同则处理,若不同则忽略

protected void receiver(){
    Frame frame = new Frame();
    // receiver期待收到的帧号
    int exceptSeq = 0;
    while (true){
        //等待收到一帧的事件
        EventType event = waitForEvent();
        if (event == FRAME_ARRIVAL){
            //从物理层获取数据
            frame = fromPhysicalLayer();
            // 如果收到的帧号是期望收到
            if (frame.getSeq() == exceptSeq){
                //将收到的数据发送到网路层
                toNetworkLayer(frame.getPacket());
                //期望收到的序列号自增
                exceptSeq = inc(exceptSeq);
            }
            //向sender发送ACK(无论是否收到正确的帧都需要发送ACK,sender只有收到ACK才知道该发哪一帧)
            frame.setKind(FrameKind.ACK);
            frame.setAck(1 - exceptSeq);
            toPhysicalLayer(frame);
        }
    }
}        

总结

  1. 数据链路层协议需要有错误控制以及流量控制的功能
  2. 通过接收方通过发送ACK来告知发送方数据已经收到,可以发送下一帧
  3. 接收方可以通过序列号来区分新帧和重复帧

Previous
数据链路层协议设计与实现(2) 数据链路层协议设计与实现(2)
捎带确认在上一篇数据链路层协议设计与实现(1)中,我们看到的几个协议,对于信道的利用率都不高,原因在于数据基本都是单向传输。对于停等协议和自动重传协议来说,其实数据已经是双向传输的,但是反向传输的都是ACK,这样利用率也不高。 针对上述的情
Next
OSI模型与TCP/IP模型 OSI模型与TCP/IP模型
OSI模型与TCP/IP模型OSI模型TCP/IP模型功能协议物理设备应用层应用层文件传输,电子邮件,虚拟终端TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet表示层数据格式化,数据加密无协议会话层解除或建立与别的节点的联