网工干货知识

超全学习笔记
当前位置:首页 > 干货知识

使用CRC实现的Python – Stop & Wait机制

更新时间:2026年03月27日   作者:spoto   标签(Tag):

“停止并等待”协议是一种错误控制协议。在这种协议中,发送方会依次发送数据包,并等待接收方的确认回复。如果收到确认回复,那么发送方会继续发送下一个数据包;如果没有收到确认回复,那么发送方会重新发送之前的数据包,直到收到确认回复为止。

注意:如需了解更多关于“停止并等待协议”的信息,请参考《Stop and Wait ARQ》这篇文章。

CRC,也就是……循环冗余校验这是一种错误检测机制,其运作流程如下:

发送方

  • 请选择由发送方和接收方共同确定的生成多项式。设 k 为该多项式所生成的密钥中的比特数。
  • 在实际的二进制数据之后,添加 k-1 个 0。
  • 将步骤2中得到的数值除以该密钥,然后保留余数。
  • 将剩余的位数附加到实际的二进制数据中,然后发送该数据包。

接收端

  • 将接收到的数据位除以密钥的值。
  • 如果余数不为零,那么数据就已被损坏了。

在本文中,我们将实现“停止并等待”算法,以便…发送方使用CRC算法来计算错误检测码。并且将其发送出去。通过套接字将数据以及错误检测代码一起发送给接收方。在确认帧无误之后,接收方会保存这些数据。

程序的具体步骤如下:

在发送方

  • 从文件中读取数据,每次读取 n 个字符。
  • 将其转换为二进制形式
  • 生成其CRC值。
  • 将数据以及CRC比特发送给接收方。但在发送之前,需要随机引入一些错误。

在接收端:

  • 接收数据包。
  • 确认该文件是否没有错误。如果不存在错误,则提取数据位,将其转换为字符形式,然后将其保存到输出文件中。最后发送确认消息。OK如果不需要的话,请发送。NAK.

在发送方

  • 如果收到了“OK”信号,那么继续处理接下来的n个字符。否则,如果收到了“NAK”信号,那么需要再次将数据加上CRC校验位后发送给接收方,同时随机引入一些错误。

最终结果:输入文件和输出文件应该保持一致。日志文件中应显示有多少帧出现了错误,以及针对每一帧进行了多少次重试操作。

错误生成

错误产生的过程如下:

  • 生成一个随机数字,假设这个数字是 r1。然后执行 r1 % 2 的操作。如果结果为 0,则不需要引入任何错误,直接发送原始的二进制位。如果结果为 1,那么就需要引入一个错误。
  • 为了确定哪一位出现了错误,再生成一个随机数字,比如 r2。计算 r2 % (接收到的帧的大小)。假设你得到的答案是 i。那么,将该位翻转即可。 i-TH位。现在将其发送给接收方吧。

请将此文件保存为……error_gen.py。

Python3
进口 随机的类/类别 err_gen() :    def 引发错误/导致问题(自我, in_str) :        chk = (整数)(随机的.随机的() * 1000) % 2                if  chk :            返回 in_str        索引号 = (整数)(随机的.随机的() * 1000) % 长度(in_str)        f_bit = '*'        if in_str[索引号] == ‘0’:            f_bit = 1        否则 :            f_bit = ‘0’        out_str = in_str[ : 索引号] + f_bit + in_str[索引号 + 1 : ]        返回 out_strif __name__ == __main__:    数据 = 1001010    打印(初始状态:, 数据)    打印(决赛:, err_gen().引发错误/导致失败(数据))

输出结果:

Initial :  1001010Final :  0001010

除以2的余数

为了计算CRC值,需要使用“模2除法”的方法。不过,详细解释这种算法的原理超出了本文的范围。有兴趣了解的人可以参考相关文献或资料来了解“模2除法”的原理。

请将此文件保存为……calc.py

Python3
def 异或(a, b):    结果 = []    为了 i in 范围/幅度(1, 长度(b)):        if a[i] == b[i]:            结果.添加/附加(‘0’)        否则:            结果/成果.附加(1)    返回 ''.加入(结果/成果)def mod2div(股息, 除数):    选择/挑选 = 长度(除数)    临时文件 = 股息[0: 选择]    当…的时候 选择/挑选 < 长度(股息):        if 临时文件[0] == 1:            临时文件 = 异或(除数, 临时文件) + 股息[选择]        否则:            临时文件 = 异或(‘0’*选择, 临时文件) + 股息[选择/挑选]        选择 += 1    if 临时文件[0] == 1:        临时文件 = 异或(除数, 临时文件)    否则:        临时文件 = 异或(‘0’*选择, 临时文件)    返回 临时文件if __name__ == __main__:    股息 = 10010101    除数 = 011010    打印(股息 + “%” + 除数 + = “ + mod2div(股息, 除数))

输出结果:

10010101 % 011010 = 01001

配置数据

这些是接收者和发送者都共同使用的常数。请将此文件保存为……configuration.py。

Python3
# 需要阅读的字符数量# 立刻。帧大小 = 10# TCP套接字的缓冲区大小缓冲区大小 = 1024 * 10# 用于确定终点的常数文件数量与交易次数。文件结束 = ##**##**##**##**### CRC生成器密钥CRC生成器 = 10110100110101110011010101110100000101# 接受与拒绝确认信息# 从接收者到发送者。拒绝 = NAK接受 = “好的”

客户/用户

该客户端类将包含五个方法。

  • 构造函数要使用给定的IP地址和端口号,通过套接字连接到服务器。
  • asciiToBin将ASCII字符串转换为二进制字符串。
  • 追加零字符在二进制数据的末尾添加 k-1 个 0。
  • 编码在实际数据位末尾生成并添加CRC码。
  • 发送文件
    • 该方法每次从输入文件中读取 n 个字符。
    • 通过调用编码方法来创建需要发送的数据包。
    • 调用 induces_error方法,以在数据包中随机引入错误。
    • 发送数据包,然后等待确认回复。
    • 如果收到的确认是积极的,那么继续处理接下来的n个比特位。
    • 否则,就重新发送当前的数据包。
    • 当文件被完全读取后,需要发送一个标志来通知接收方停止等待下一帧数据。
    • 终止该会话。

请将此文件保存为……CLIENT.py。

Python3
从…开始  进口 mod2div从…开始 error_gen 进口 err_gen从…开始 配置 进口 *进口 插座类/类别 客户/用户:    def __init__(自我, ipadd, 波特尼):        自我.插座/插槽 = 插座.插座(插座.AF_INET, 插座.SOCK_STREAM)        自我.插座/孔位.连接((ipadd, 波特恩))    def 将 ASCII 转换为二进制格式(自我, 数据):              # 将ASCII字符转换为二进制形式。        返回 箱/柜(整数.从字节中读取数据(数据.编码(), “大”))    def 追加零字符(自我, 信息/消息):              # 在末尾添加 n-1 个 0。        信息/消息 = (消息.简洁明了(长度(CRC生成器) - 1 + 长度(消息), ‘0’))        返回 信息/消息    def 编码(自我, 数据):        # 将ASCII字符转换为二进制形式        消息/信息 = 自我.将 ASCII 字符串转换为二进制格式(数据)        股息 = 自我.追加零字符(消息)        # 生成CRC值并将其添加到结果中        CRC = mod2div(股息, CRC生成器)        curr_frame = (消息/信息 + CRC)        返回 curr_frame    def 发送文件(自我, 文件名=‘file.txt’):        f = 开放的(文件名)        数据 = f.阅读(帧大小)        当…的时候 长度(数据) > 0:            # 对数据进行编码处理            curr_frame = 自我.编码(数据)            # 引入错误/产生误差            curr_frame = err_gen().引发错误/导致问题(curr_frame)            # 发送帧            自我.插座/插槽.发送(curr_frame.编码()))            # 收到确认消息            if 自我.插座/插槽.接收(缓冲区大小).解码() == “好的”:                数据 = f.阅读(帧大小)        # 终止会话        自我.插座/孔位.发送(文件结束.编码()))        自我.插座/插槽.关闭()        f.关闭()        打印(“文件已发送”)新客户 = 客户/用户(ipadd=127.0.0.1, 波特恩=3241)新客户.发送文件(文件名=“file.txt”)

服务器

该服务器类将包含六种方法。

  • 构造函数负责监听指定IP地址和端口上的客户端请求。
  • iszero为了确定一个字符串是否表示数字0。
  • 已损坏/失效为了确定接收到的数据是否因与CRC生成器进行模2除法运算而受到损坏,需要进行相应的处理。
  • 解码从接收到的数据包中提取数据位,并将其转换为对应的ASCII值。
  • 日志/记录需要记录每一帧的进入情况,以便将其添加到日志文件中。
  • 接收文件
    • 该方法会从发送方接收数据包。
    • 通过调用 isCurrupted 函数来验证其有效性。
    • 如果数据包是有效的,那么就会对其进行解码,并将解码后的数据复制到服务器的某个文件中。同时,还会向发送方发送一个确认信号,同时将数据包的详细信息记录到日志文件中。
    • 否则,它会向发送方发送一个否定确认。
    • 如果接收到的数据包表示文件的结束,那么就会终止所有连接,并返回结果。

请将此文件保存为……SERVER.py

Python3
进口 插座  进口 mod2div从…开始 配置 进口 * 服务器:    def __init__(自我, ip地址, 波特恩):        自我.插座/孔位 = 插座.插座(插座.AF_INET, 插座.SOCK_STREAM)        自我.插座/孔位.绑定((ip地址, 波特恩))        自我.插座/插槽.(5)    def iszero(自我, 数据):        为了 x in 数据:            if x != ‘0’:                返回 错误的/不正确的        返回 确实如此    def 已损坏/中断(自我, 信息/消息):        返回  自我.iszero(mod2div(信息/消息, CRC生成器))    def 解码(自我, 消息/信息):        信息/消息 = 信息/消息[: 1 - 长度(CRC生成器)]        n = 整数(消息/信息, 2)        返回 n.到字节数((n.字节长度() + 7) // 8, “大”).解码()    def 日志/记录(自我, loghandle, itr, 已接收的帧, 检索次数):        loghandle.写作/书写(帧号: + str(itr) + "\n")        loghandle.书写/写作(帧内容:\"" +                        自我.解码(已接收的帧) + "“\n”")        loghandle.写作/书写(重试次数: + str(检索次数) + "无需额外解释。")    def 接收文件(自我, 文件路径, logpath):        已接收的套接字, 地址 = 自我.插座/孔位.接受()        f = 开放的(文件路径, ‘w’)        l = 开放的(logpath, ‘w’)        itr = 1        检索次数 = 0        当…的时候 1:            已接收的帧 = 已接收的套接字.接收(缓冲区大小).解码()            if 已接收的帧 == 文件结束:                f.关闭()                l.关闭()                自我.插座/孔位.关闭()                打印(“文件已收到”)                返回            if 自我.
              马上抢免费试听资格
意向课程:*必选
姓名:*必填
联系方式:*必填
QQ:
思博SPOTO在线咨询

相关资讯

即刻预约

免费试听-咨询课程-获取免费资料