设计要求:
这个是学校计网的项目,完成了部分项目要求,现在把要求放在下面,后面是项目源代码,可以直接在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函数会报错。