计网 TCP滑动窗口模拟 Python

本文介绍了如何使用Python实现一个滑动窗口协议的模拟项目,涉及多线程、数据包发送与接收、重传机制、窗口管理以及TCP流量控制。项目要求包括在两台计算机间传输文件,保证有序接收、滑动窗口操作和数据丢失处理等功能。
摘要由CSDN通过智能技术生成

设计要求:

这个是学校计网的项目,完成了部分项目要求,现在把要求放在下面,后面是项目源代码,可以直接在Windows上跑。代码运行结果没有GUI界面,只有收发双发发送的信息。注释比较多,很容易看懂。
1.Windows 环境下运行,程序应在1-2台PC上运行;
2.演示在两台计算机间传输文件。允许在同一台机器中用两个独立线程来模拟;
3.功能:
   1)由一台PC(线程)向另一台PC(线程)发送数据包,界面应显示出双方帧个数变化,帧序号,发送和接受速度,界面中必须动态显示数据帧的发送情况和接受情况,包括在相应窗口详细显示相应的ACK和其他收发数据帧后发出的消息,以表明模拟协议的正确运作过程。
   2)接收方及发送方应具有按序收发帧的能力;
   3)接受方应有固定大小的滑动窗口,并对收到信息缓存。当发送方速度过快或帧丢失(超时),接受方应发送消息,要求暂停或重传;
   4)发送方发送速度应可以调节,并可以暂停或重发;
   5)发送方重传时可仅重传帧(丢失帧);


项目介绍:


 滑动窗口协议是TCP使用的一种流量控制方法。该协议允许发送方在停止并等待确认前可以连续发送多个分组。由于发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据的传输。本课题通过设计软件,实现对滑动窗口协议的模拟。
1.运行时启动两个线程client 和server。其中sever 初始应该为监听状态;
2.可指定滑动窗口数目m(m=2n,n为大于1的整数)和要发送的帧总数,停等的超时时间间隔以及发送类型(正常发送,即没有缺帧和错序发送帧的现象),发送速率等参数;
4.client端发出帧,帧的内容可同序号或者为“111”“222”…“aaa”“bbb”…或者是一段文章中的部分内容;
5.选择发送类型为“缺帧”,模拟因网络拥塞造成丢帧的情况,开始发送情况同“正常发送”的情况。不同的是在帧x发送前用户可以选择丢失,则客户端继续接收帧x+1,x+2…并且对帧x+1,x+2…发出确认并缓存该帧;
6.服务器端等待一段相当长的时间(超时),重发帧x;
7.选择发送类型为“错序发送”。将一组待发送的帧按照指定(错序)顺序发送。具体实现同“缺帧”情况;
 

其他还有部分设计要求没有完成,就没放上去,下面是代码

代码:

client.py

import socket
import threading
import sys
import time
import random


global mis_send_rate
global window_start
global window_end
finish=False #数据是否发送完毕
data_size=100 #数据总长度
mis_send_rate=0.1#发送丢失率为0.1
host = 'localhost'
port = 1234
data_index=0 #数据发送的序号
sliding_window_size=10 #窗口大小
window_start=0 #窗口的起始编号,是数组的下标
window_end=window_start+sliding_window_size-1 #窗口的末尾编号,是数组可以发送的下标,所以要减一
resend_time=2 #重发计时
send_speed=0.5 #发送速度



class data():
    def __init__(self,value):
        self.message=value
        self.time=0
        self.start=0
        self.end=0
        self.received=False
        self.index=value #此处表示该数据的序号,为了赋值方便,这里序号的值和数据的值相同,实际情况数据的值应当改变
    def start_time(self):
        self.start=time.time()
    def end_time(self):
        self.end=time.time()
    def count_time(self):
        self.time=self.end-self.start


# 创建数据
message=[]
for i in range(data_size):
    message.append(data(i+1))

def judge_finish(message):
    for i in range(len(message)):
        if message[i].received==False:
            return False
    return True



def get_message(data_index,message):
    rate = random.random() #获取0-1的随机数来模拟数据的丢失
    if rate>mis_send_rate:
        return message[data_index].message,message[data_index].index,True
    else :return message[data_index].message,message[data_index].index,False

#用于窗口向前滑动
def check_window_size(message):
    global window_start
    global window_end
    while not finish:
        sliding_size = 0
        # 这里window_start和window_end都可以取到,因此需要加一。
        # 当窗口滑动到末端时,window_end可能会滑出数据范围,这个时候会出现下标超出的现象,加入min,当超出时上限改为data_size
        for i in range(window_start,min(window_end+1,data_size)):
            if message[i].received==True: #已确认的数据必须要连着,并且第一个数据一定要收到确认帧,才会向前滑动
                sliding_size+=1
            else:break

        #窗口向前滑动
        if sliding_size>0:
            print("窗口向前滑动",sliding_size,"个单位")
            window_start+=sliding_size
            window_end+=sliding_size
    print("-----------------------------确认帧已全部接受,接受确认帧线程结束-------------------------------")



# 接受数据
def receive(socket,signal,message):
    #另启动一个线程进行窗口大小的检查
    check_window_size_thread=threading.Thread(target=check_window_size,args = (message,))
    check_window_size_thread.start()
    while signal:
        try:
            # 数据从这里进行接受
            data = socket.recv(32)
            data=int(data.decode("utf-8")) #序号从1开始
            message[data-1].received=True #数组引用从0开始,所以减一。   只有这里可以将received变成true
        except:
            print("You have been disconnected from the server")
            signal = False
            break


# 建立连接
try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((host, port))
except:
    print("Could not make a connection to the server")
    input("Press enter to quit")
    sys.exit(0)
receiveThread = threading.Thread(target = receive, args = (sock,True,message))
receiveThread.start()


#发送数据
while not finish: #index是下标,size是数据的长度,size要比index大1
    #检查各个信息的时间是否超时
    finish=judge_finish(message)
    for i in range(data_index):
        #end 和 count组合可以计算时间

        message[i].end_time()
        message[i].count_time()

        if message[i].time>=2 and message[i].received==False:# 如果判断超时,则重发数据
            message_send,message_index,flag=get_message(i,message) # 此时重发数据仍有可能发送失败,flag用于判断是否发送成功
            message[i].start_time()  # 看作数据已发送,开始计时
            if flag:  # 等于-1说明发送过程中丢失,但是窗口还是发出了数据,所以序号加一
                sock.sendall(str.encode(str([message_index,message_send])))
                print(message_index, '重新发送成功')
                time.sleep(send_speed)
            else:
                print(message_index, '重新发送失败')
                time.sleep(send_speed)


    #data_index应表示即将发送的数据下标,从0开始
    if data_index<=min(window_end,data_size-1):
        message_send,message_index,flag = get_message(data_index, message)
        message[data_index].start_time()#看作数据已发送,开始计时
        if flag: #等于-1说明发送过程中丢失,但是窗口还是发出了数据,所以序号加一
            sock.sendall(str.encode(str([message_index,message_send])))
            print(message_index,'发送成功')
            time.sleep(send_speed)
        else:
            print(message_index,'发送失败')
            time.sleep(send_speed)
        data_index+=1

print("--------------------数据已全部发送,client发送数据线程结束----------------------")

server.py

import socket
import threading
import random
import time


data_size=100
mis_send_rate=0.5
connections = []
total_connections=0
send_speed=0.5 #发送速度

# 变成确认帧
class ack():
    def __init__(self,index):
        self.index=index
        self.sended=False

def get_data(data):
    rate = random.random()  # 获取0-1的随机数来模拟数据的丢失
    if rate > mis_send_rate:
        return data.index,True
    else:
        return data.index,False

def ack_resend(data):
    client=connections[0]
    rate = random.random()
    if rate > mis_send_rate:
        client.socket.sendall(str.encode(str(data)))
        time.sleep(send_speed)
        print("第",data, "个确认帧重新发送成功")
    else:
        time.sleep(send_speed)
        print("第", data, "个确认帧重新发送失败")

#从这里开始发送ack线程
def ack_send(shared_list):
    client=connections[0] #这里默认只有一个client,否则不能为0
    while True:
        for i in range(len(shared_list)):
            if shared_list[i]!=None: #如果这个元素不为空,说明该位置已收到元素,应当发送确认帧
                if shared_list[i].sended==False:
                    # shared_list[i]=ack(shared_list[i]) #变成一个ACK类,要用sended属性
                    ack_readyto_send,flag=get_data(shared_list[i]) #判断是否发送成功
                    shared_list[i].sended=True
                    if flag:
                        client.socket.sendall(str.encode(str(ack_readyto_send)))  # 返回第i个确认帧,格式是字符串
                        time.sleep(send_speed)
                        print("第",ack_readyto_send, "个确认帧发送成功")

                    else:
                        time.sleep(send_speed)
                        print("第", ack_readyto_send, "个确认帧发送失败")

class Client(threading.Thread):
    def __init__(self, socket, address, id, name, signal):
        threading.Thread.__init__(self)
        self.socket = socket
        self.address = address
        self.id = id
        self.name = name
        self.signal = signal

    def __str__(self):
        return str(self.id) + " " + str(self.address)

    def run(self):
        data_list=[None]*data_size #用于存放收到的数据
        shared_list=[None]*data_size #用于存放收到的序号
        ack_send_thread=threading.Thread(target=ack_send,args = (shared_list,))
        ack_send_thread.start()

        while self.signal:
            try:
                data = self.socket.recv(32) #这里是数据的接受
            except: #如果接受不到数据就显示断开链接并退出循环
                print("Client " + str(self.address) + " has disconnected")
                self.signal = False
                connections.remove(self)
                break

            data=eval(data.decode("utf-8"))#data下标从1开始
            if shared_list[data[0]-1]==None: #表示第一次收到这个序号的数据
                shared_list[data[0]-1]=ack(data[0])#将对应序号的数据传入列表,并封装成ack,
                data_list[data[0]-1]=data[1]
                print("收到第",data[0],"个数据,数据为:",data[1])
            else:#第二次收到这个数据,说明ack发送失败,再次发送ack
                ack_resend(data[0]) #表示这个数据的序号

def newConnections(socket):
    while True:
        sock, address = socket.accept()
        global total_connections
        connections.append(Client(sock, address, total_connections, "Name", True))
        connections[len(connections) - 1].start() #这步后面执行这个client类的run方法
        total_connections+=1
def main():
    host = 'localhost'
    port = 1234
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind((host, port))
    sock.listen(5)
    newConnectionsThread = threading.Thread(target = newConnections, args = (sock,))
    newConnectionsThread.start()

main()



运行方式:

先运行server.py,就会处于监听状态,再运行client.py,就连接上去了。发送速率不能设为0,否则eval函数会报错。

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值