实验目的:
运用各种编程语言实现基于 Go-Back-N 或 SR 的可靠数据传输软件。
环境配置:macos14.4 python3.11.7
设计思路:
发送端:
初始化和设置套接字连接:
首先,程序通过socket模块创建一个套接字,并绑定到主机和指定的端口上。这一步骤使得程序可以接收来自其他计算机的连接。
通过调用listen()方法监听传入的连接请求,并使用accept()方法接受连接,建立与客户端的连接。
交换用户名称:
在连接建立之后,双方首先交换他们的名字。这是通过发送和接收编码后的字符串完成的。
消息传输:
用户输入的消息首先被编码为二进制形式,然后通过套接字发送。
程序引入了一个简化版的滑动窗口协议来控制消息的发送。这种协议通常用于确保数据传输的可靠性,通过动态调整窗口大小来控制传输速率和顺序,以适应网络条件。
二进制编码:
消息在发送之前被转换为二进制编码。这不仅展示了数据转换的概念,也为消息传输提供了一个基础的加工过程。
滑动窗口协议的简化实现:
在发送二进制消息时,程序采用了滑动窗口机制。用户可以指定窗口的大小,即一次可以发送多少位数据。数据被分段发送,每次发送一小部分,根据接收方的确认(ACK)来决定是继续发送下一部分还是重发当前部分。
这个过程通过循环实现,模拟了在实际网络通信中使用滑动窗口协议来处理确认丢失(例如,“ACK Lost”)的情况,从而确保所有数据最终都能正确传输。
退出机制:
用户可以通过输入特定的指令(例如"[e]")来退出聊天室。在这之后,程序会发送一条离开聊天室的消息给对方,然后关闭连接。
接收端:
初始化和连接到服务器:
代码开始时,首先通过socket库创建一个套接字,然后请求用户输入服务器的地址和要使用的端口号(在这个示例中,端口号被硬编码为1234),以及用户的名字。
使用用户提供的信息,程序尝试连接到服务器。连接成功后,它会发送用户的名字到服务器,并接收服务器端用户的名字。
消息接收循环:
客户端进入一个无限循环,等待从服务器接收消息。首先,它接收表示即将到来的消息长度和窗口大小的信息。消息长度在这里似乎未被直接使用,而窗口大小用于确定在需要接收ACK之前可以接收多少个消息片段。
通过循环接收每个消息片段,客户端模拟了ACK确认可能会丢失的情况。这是通过随机决定是否发送一个表示ACK丢失的消息来实现的。如果模拟表示ACK已经收到(即没有丢失),它就会将接收到的消息片段拼接到最终消息中。
模拟ACK丢失:
对于每个接收到的消息片段,客户端随机决定是模拟ACK确认的丢失还是成功接收。这通过随机生成0或1来实现,其中0表示ACK丢失,1表示ACK成功接收。
如果模拟了ACK的丢失,客户端会通知服务器ACK丢失,服务器根据设计可能会重发相同的消息片段。如果ACK成功接收,客户端则向服务器发送确认,表示可以发送下一个消息片段。
问题和潜在改进:
代码在打印收到的消息长度(m)时存在逻辑上的小误解,实际上应该是打印完整的、拼接后的消息(a)。
此外,对于消息长度的接收和处理似乎并没有在代码中得到充分利用,可能是一个遗漏的细节。
结束通信:
代码没有显示如何结束通信循环或处理用户输入[e]来退出聊天的情况,这可能是留给读者作为练习的部分。
此时,双方已经完成链接的建立,在发送方输入信息,并且设置好窗口的长度,进行信息的传递:
发送方:在完成信息设定之后就会开始进行数据的传输,因为考虑到这次的实验是本机传递,不可能丢包,所以在程序自动设定了50%概率丢包判定。
原代码:
发送端
# 导入必要的模块
import time, socket, sys
# 将十进制转换为二进制
def decimalToBinary(n):
return n.replace("0b", "")
# 将字符串转换为二进制编码
def binarycode(s):
a_byte_array = bytearray(s, "utf8")
byte_list = []
# 将每个字符转换为二进制表示并添加到列表中
for byte in a_byte_array:
binary_representation = bin(byte)
byte_list.append(decimalToBinary(binary_representation))
# 合并二进制字符串
a=""
for i in byte_list:
a=a+i
return a
# 打印欢迎信息和初始化信息
print("\n欢迎来到聊天室\n")
print("初始化....\n")
time.sleep(1)
# 创建套接字并绑定主机和端口
s = socket.socket()
host = socket.gethostname()
ip = socket.gethostbyname(host)
port = 1234
s.bind((host, port))
print(host, "(", ip, ")\n")
name = input(str("请输入你的名字: "))
# 开始监听传入连接
s.listen(1)
print("\n等待传入连接...\n")
conn, addr = s.accept()
print("来自", addr[0], "的连接 (端口:", addr[1], ")\n")
# 接收对方的名字并发送自己的名字
s_name = conn.recv(1024)
s_name = s_name.decode()
print(s_name, "已连接到聊天室\n输入 [e] 退出聊天室\n")
conn.send(name.encode())
while True:
# 输入消息
message = input(str("我 : "))
conn.send(message.encode())
# 如果消息是退出指令,发送退出消息并退出循环
if message == "[e]":
message = "离开聊天室!"
conn.send(message.encode())
print("\n")
break
# 将消息转换为二进制编码
message=binarycode(message)
f=str(len(message))
conn.send(f.encode())
# 设置窗口大小
i=0
j=0
j=int(input("输入窗口大小 -> "))
# 初始化窗口边界
b=""
j=j-1
f=int(f)
k=j
# 循环发送消息直到发送完毕
while i!=f:
while(i!=(f-j)):
conn.send(message[i].encode())
b=conn.recv(1024)
b=b.decode()
print(b)
if(b!="ACK Lost"):
time.sleep(1)
print("接收到确认信息!滑动窗口范围为 "+(str(i+1))+" 到 "+str(k+1)+",现在发送下一个数据包")
i=i+1
k=k+1
time.sleep(1)
else:
time.sleep(1)
print("数据位的确认信息丢失!滑动窗口仍在范围 "+(str(i+1))+" 到 "+str(k+1)+",现在重新发送相同的数据包")
time.sleep(1)
while(i!=f):
conn.send(message[i].encode())
b=conn.recv(1024)
b=b.decode()
print(b)
if(b!="ACK Lost"):
time.sleep(1)
print("接收到确认信息!滑动窗口范围为 "+(str(i+1))+" 到 "+str(k)+",现在发送下一个数据包")
i=i+1
time.sleep(1)
else:
time.sleep(1)
print("数据位的确认信息丢失!滑动窗口仍在范围 "+(str(i+1))+" 到 "+str(k)+",现在重新发送相同的数据包")
time.sleep(1)
接收端
# 导入必要的模块
import time, socket, sys
import random
# 打印欢迎信息和初始化信息
print("\n欢迎来到聊天室\n")
print("初始化....\n")
time.sleep(1)
# 创建套接字
s = socket.socket()
shost = socket.gethostname()
ip = socket.gethostbyname(shost)
print(shost, "(", ip, ")\n")
# 输入服务器地址、名字和端口号
host = input(str("输入服务器地址: "))
name = input(str("\n输入你的名字: "))
port = 1234
print("\n正在尝试连接 ", host, "(", port, ")\n")
time.sleep(1)
# 连接到服务器
s.connect((host, port))
print("已连接...\n")
# 发送本地名字并接收对方名字
s.send(name.encode())
s_name = s.recv(1024)
s_name = s_name.decode()
print(s_name, "已加入聊天室\n输入 [e] 退出聊天室\n")
while True:
# 接收消息长度
m=s.recv(1024)
m=m.decode()
# 接收窗口大小
k=s.recv(1024)
k=k.decode()
k=int(k)
i=0
a=""
b=""
# 模拟 ACK 丢失
f=random.randint(0,1)
message=""
# 循环接收消息直到接收完毕
while i!=k:
# 模拟 ACK 丢失
f=random.randint(0,1)
if(f==0):
b="ACK Lost"
message = s.recv(1024)
message = message.decode()
s.send(b.encode())
elif(f==1):
b="ACK "+str(i)
message = s.recv(1024)
message = message.decode()
s.send(b.encode())
a=a+message
i=i+1
print("收到的消息为 :", m)
实验结果:
实验总结:
本次实验通过创建一个简单的聊天室应用,探讨了在Python中使用套接字(Sockets)进行基本网络通信的概念。实验分为服务器端和客户端两部分代码,展示了如何建立连接、交换信息,并模拟了网络通信中的一个关键挑战——确认消息(ACK)的丢失。
理解网络基础:通过动手实践,深入理解了网络通信的基础,尤其是数据如何在网络上发送和接收,以及如何通过编程实现基本的网络通信协议。
协议的实现和挑战:实验介绍了滑动窗口协议的基本概念和实现,展示了在数据传输过程中如何处理错误和数据丢失。
编程技能提升:加深了对Python编程和套接字编程的理解,特别是在网络编程方面。