# 例程9:TCP文件传输程序——服务器端,与例程10构成一对
# 客户端申请下载finecomp.exe二进制文件,服务器端读出该文件的前300字节内容,发送给客户端
count = 0 # 记录在线人数
os = OS() # 定义操作系统对象
time = TIME() # 定义TIME对象time
def serverFunc(list) # 定义线程函数
{
count = count + 1
print("在线人数:%\n",count)
clientnet = list[0] # 拆包线程参数,列表第一项是客户端的NET对象clientnet
clientaddr = list[1] # 列表第二项是客户端的IP地址
# 服务器监听到客户端接入后,给客户端发送信息响应
buf = "请发送文件名,包含扩展名!" # 响应信息内容
sendnet = clientnet.sendNet() # 定制单次发送的NET对象
sendnet.send(buf,len(buf)) # 基于定制NET对象,启动发送(发送长度不允许超过1024字节,如果超出,需要切片发送)
while sendnet.sendOK() != -1 {PowerDown(3)} # 等待发送函数退出(-1表示退出),退出原因:有超时退出和发送完成退出
xsend = sendnet.sendRead() # 读出发送结果
if xsend == -1 # 超时
{
count = count - 1 # 在线人数计数器 -1
print("在线人数:%\n",count)
clientnet.close() # 关闭clientnet
return # 结束文件服务线程
}
# 服务器发送完应答信息后,转而等待客户端发送过来的文件名(包含扩展名),
# 该等待时间不确定,如果10分钟内没有收到文件名,则,结束线程。
timescount = 0 # 计数器用于控制服务器超时退出伺服。
while True
{
recvnet = clientnet.recvNet() # 定制本次接收的NET对象
recvnet.recv() # 基于定制NET对象,启动接收
while recvnet.recvOK() != -1 {PowerDown(3)} # 等待接收函数退出(退出原因:超时、货收到信息),释放资源。
#接收结果是一个列表,第一项是-1表示接收超时,否则表示收到的数据包字节数,第二项是收到的数据(最大不超过1024字节)
xrecv = recvnet.recvRead("string") # 读取接收结果
if xrecv[0] == -1 # 在初始化时我们设置的tiemout = 3000毫秒,系统自动重新连接5次,每次超时时间大约15秒
{
timescount = timescount + 1 # 每超时一次计数器(timescount)+1
if timescount > 40 # 40*15 = 600秒,即10分钟,如果如果10分钟,结束对当前客户端的伺服服务。
{
count = count - 1 # 在线人数计数器 -1
print("在线人数:%\n",count)
clientnet.close() # 关闭clientnet
return # 结束文件服务线程
}
continue # 没有超过设置时间,继续接收
}
recvbuf = xrecv[1] # 获取接收到的数据
break
}
# 解析收到的信息
if recvbuf != "exit" # 如果收到的不是"exit",应该是文件名
{
fp = FILEOPEN(recvbuf,"rb") # 收到文件名后,服务器尝试打开文件
if fp == False # 如果文件打不开,通知客户端,然后结束线程
{
fp.close()
buf = "打开文件失败,请检查文件名、或路径是否错误!"
print("%\n",buf) # 报错
sendnet = clientnet.sendNet() # 定制发送NET对象
sendnet.send(buf,len(buf)) # 启动发送,返回资源ID号
while sendnet.sendOK() != -1 {PowerDown(3)} # 等待发送函数退出(-1表示退出),释放资源
xsend = sendnet.sendRead() # 读取发送结果
if xsend == -1 # 超时,网络有问题
{
count = count - 1 # 在线人数计数器 -1
print("在线人数:%\n",count)
clientnet.close() # 关闭clientnet
return # 结束文件服务线程
}
}
else # 正确打开文件,准备发送文件
{
data = fp.read(200) # 读出300个字节的二进制文件
fp.close() # 关闭文件
# 先发送通知:客户端收到该通知后,切换到二进制数据接收模式
sendnet = clientnet.sendNet() # 定制发送NET对象
sendnet.send("binary",6) # 转向二进制接收的通知
while sendnet.sendOK() != -1 {PowerDown(3)} # 检测发送函数直到退出,释放资源
xsend = sendnet.sendRead() # 读取发送结果
if xsend == -1 # 超时,结束服务。
{
count = count - 1 # 在线人数计数器 -1
print("在线人数:%\n",count)
clientnet.close() # 关闭clientnet
return # 结束文件服务线程
}
time.sleep(500)
# 发送二进制发送数据
sendnet = clientnet.sendNet() # 定制发送NET对象
sendnet.send(data,200) # 基于定制NET对象,启动发送
while sendnet.sendOK() != -1 {PowerDown(3)} # 检测发送函数直到退出,释放资源
xsend = sendnet.sendRead() # 读取发送结果
if xsend == -1 # 超时,结束服务。
{
count = count - 1 # 在线人数计数器 -1
print("在线人数:%\n",count)
clientnet.close() # 关闭clientnet
return # 结束文件服务线程
}
time.sleep(500)
# 至此,文件发送结束,再发送"exit",通知客户端,文件全部发送完毕。
sendnet = clientnet.sendNet() # 定制发送NET对象
sendnet.send("exit",4) # 基于定制对象,启动发送
while sendnet.sendOK() != -1 {PowerDown(3)} # 检测发送函数直到退出,释放资源
xsend = sendnet.sendRead() # 读取发送结果
if xsend == -1 # 超时,结束本次连接
{
count = count - 1 # 在线人数计数器 -1
print("在线人数:%\n",count)
clientnet.close() # 关闭clientnet
return # 结束文件服务线程
}
}
}
# 至此,要么是客户端主动下线了,要么是申请的文件全部发送结束了
count = count - 1 # 在线人数计数器 -1
print("在线人数:%\n",count)
clientnet.close() # 关闭clientnet,定制的NET对象,系统会自动回收
# 线程执行完毕,自动结束线程
}
server_addr = "192.168.111.195" # 服务器地址
port = 5500 # 服务端口号
net = NET() # 定义NET对象net
net = net.socket(net.IPv4,net.STREAM,net.TCP) # 创建socket,并赋值net(IPv4、STREAM数据流、TCP协议)
net.bind(net.IPv4,server_addr,port) # 将服务器:地址簇、地址和端口号与net绑定
net.timeout(3000) # 设置超时时间3000毫秒
net.listen(10) # 将net设置为监听状态,参数10是最大并发客户端数量
while True
{
print("服务器伺服接收!\n")
net.accept() # 开始监听,net.timeout()设置对accept有效
while net.acceptOK() != -1 {PowerDown(3)} # 等待退出监听函数(返回-1表示退出accept函数)
#读出的监测结果是一个列表,列表第一项是-1表明超时,如果不是-1,表明监测到客户接入,
#列表第二项是客户端的net,第三项是客户端地址
xaccept = net.acceptRead() # 读出结果是一个列表
if xaccept[0] == -1 {continue} # 超时,继续监测
list = [xaccept[1],xaccept[2]] # 读出监听到的客户端的net和客户端地址,打包成一个列表
thread = THREAD() # 定义线程对象
thread = thread.CreateThread(serverFunc,list) # 创建子线程处理该连接,列表作为线程参数
thread.start() # 启动子线程
}
net.clear() #清除网络资源
# 例程10:TCP文件传输程序——客户端,与例程9构成一对
gui = GUI("fine") # 定义FIne窗口的GUI对象,用于图形界面显示
time = TIME()
#gui.HideConsoleWindow() # 隐藏控制台窗口
serveraddr = "192.168.111.195" # 服务器地址
port = 5500 # 该服务的端口号
net = NET() # 定义一个NET对象
net = net.socket(net.IPv4,net.STREAM,net.TCP) # 地址族:IPv4、数据流格式:STREAM、通讯协议:TCP
net.timeout(3000) # 设置net的接收和发送超时时间,connect()不受net.timeout()超时设置影响。
# 连接服务器
count = 0
box = GUI("box") # 创建MessageBox窗口对象
box.MessageBox("开始连接……","确定")
while box.MessageBoxClosed() != -1
{
net.connect(net.IPv4,serveraddr,port) # 参数是服务器端的:地址族、网址、端口号
while net.connectOK() != -1
{
PowerDown(3)
count = count + 1
box.SendMessageBox("连接中:" + itoc(count)) # 显示连接中提示
}
# 至此,表明以退出连接状态
box.CloseMessageBox() # 关闭box窗口
x = net.connectRead() # 读取连接的结果
if x == -1 # 如果连接不成功,创建嵌套MessageBox提示。
{
box1 = GUI("box") # 创建MessageBox嵌套(二级MessageBox窗口)
box1.MessageBox("未能连接到服务器,将退出程序!","确定")
while box1.MessageBoxClosed() != -1 {PowerDown(3)}
return # 退出主线程(退出程序)
}
}
# 连接到服务器后,等待服务器应答
count = 0 # 尝试接收次数
while True # 进入接收循环
{
recvnet = net.recvNet() # 定制单次接收NET对象
recvnet.recv() # 基于定制对象启动接收
while recvnet.recvOK() != -1 {PowerDown(3)} # 等待接收函数退出
xrecv =recvnet.recvRead("string") # 读取结果,释放资源
if xrecv[0] == -1 # 超时(大约18秒)
{
box = GUI("box") # 创建MessageBox窗口
box.MessageBox("超时!未接收到服务器应答,将退出程序!","确定")
while box.MessageBoxClosed() != -1 {PowerDown(3)}
return # 退出主线程(退出程序)
}
recvbuf = xrecv[1] # 接收到服务器应答,读取数据,并释放接收资源
break # 退出接收循环
}
# 设计Fine窗口,输入要下载的文件名
title = "TCP文件传输——客户端" # 窗口标题
size = [30,5,80,20] # 窗口位置、尺寸
text = ["服务器应答","text",12,3,62,1] # 显示区域
edit = ["文件名","edit","s",12,7,62,1] # 录入文件名控件
button = [" 确定 ","button",55,11,12,1] # “确定”按钮
list = [title,size,text,edit,button] # 打包窗口参数
gui.Fine(list) # 创建Fine窗口,并返回资源ID号
gui.SendText([recvbuf]) # 窗口创建后,稍微延迟一会再显示,否则显示不出来
filename = "" # 初始化文件名为空
while gui.FineClosed() != -1 # 检测文件录入窗口是否关闭
{
PowerDown(3) # 降低数据录入期间的功耗
# 等待输入待下载文件名
if gui.FineReady() == -1 {continue} # 检测是否有数据录入(0有数据,-1无数据)
x = gui.FineRead() # 读取数据,第一项是指令(button-0),第二项是文件名(对应edit控件)
filename = x[1] # 拆包,把文件名取出
if filename == ""
{
# 弹窗提示错误
box = GUI("box") # 创建MessageBox对象
box.MessageBox("文件名不能为空!","确定") # 创建MessageBox窗口
if box.MessageBoxClosed() != -1 {PowerDown(3)} # 等待关闭窗口
continue # 跳转,等待下次数据录入
}
gui.CloseFine() # 获取文件名后,关闭文件名录入窗口
}
# 没有文件名,将退出程序。但是,由于已经建立了连接,退出程序前,需要通知服务器,以便服务器及时关闭对应的服务
if filename == ""
{
buf = "exit" # 客户端退出指令
sendnet = net.sendNet() # 定制单次发送的NET对象
sendnet.send(buf,len(buf)) # 启动发送,并返回发送资源
while sendnet.sendOK() != -1 {PowerDown(3)} # 等待发送函数退出(返回-1表示发送函数退出),退出后,立即释放资源。
net.close() #清除网络资源
return # 退出程序
}
# 文件名录入成功后,向服务器发送文件名
sendnet = net.sendNet() # 定制单次发送NET对象
sendnet.send(filename,len(filename)) # 启动发送,并返回发送资源
while sendnet.sendOK() != -1 {PowerDown(3)} # 等待发送函数退出(返回-1表示发送函数退出)
xsend = sendnet.sendRead() # 读发送结果,同时释放资源
if xsend == -1 # 发送字节数为0,表明发送错误
{
box = GUI("box") # 创建MessageBox窗口对象
box.MessageBox("发送文件名超时,将退出程序!","确定") # 弹出提示信息
while box.MessageBoxClosed() != -1 {PowerDown(3)} # 等待关闭MessageBox窗口
net.close() # 清除网络资源
return # 程序返回,退出程序
}
recv_finished = 0 # 文件接收完毕标志(0未接收完,1接收完毕)
# 设计Fine窗口,动态显示收到的数据
title = "服务器下载文件接"
size = [10,1,120,40]
menu = ["文件","menu","保存"]
editbox = ["","editbox",0,0,116,36]
list = [title,size,menu,editbox]
gui = GUI("fine") # 创建Fine窗口对象(每次创建窗口都需要先创建窗口对象,一个窗口对象对应一个窗口)
gui.Fine(list)
while gui.FineClosed() != -1
{
PowerDown(3)
while not recv_finished # 如果文件未收完,持续循环直到文件接收完毕
{
# 冗余处理,检测窗口状态,如果用户中途关闭接收窗口,终止接收,关闭net,退出程序
if gui.FineClosed() == -1
{
net.close() # 清理网络资源
box = GUI("box") # 创建MessageBox窗口对象,弹窗提示。
box.MessageBox("客户端退出接收程序!","确定") # 弹出提示信息
while box.MessageBoxClosed() != -1 {PowerDown(3)} # 等待关闭MessageBox窗口
return
}
# 循环接收服务器端发送的数据包
recvnet = net.recvNet() # 每次接收数据包之前,需要定制一个接收Net对象,基于此对象操作
recvnet.recv() # 启动接收
while recvnet.recvOK() != -1 {PowerDown(3)} # 等待接收过程结束,并释放资源
xrecv = recvnet.recvRead("string") # 读取结果
if xrecv[0] == -1 # 超时次数超限
{
box = GUI("box") # 弹窗提示错误
box.MessageBox("接收文件超时,将退出程序!","确定") # 弹出提示信息
while box.MessageBoxClosed() != -1 {PowerDown(3)} # 等待关闭MessageBox窗口
gui.CloseFine() # 关闭fine窗口
net.close() # 关闭net,清理网络资源
return # 程序返回,退出程序
}
recvbuf = xrecv[1] # 如果接收正确,获取数据
if recvbuf == "exit" # 收到的数据包为“exit”时,表明文件发送完毕、服务器已退出
{
recv_finished = 1 # 置位数据接收完毕标志
box = GUI("box") # 弹窗提示
box.MessageBox("文件接收完毕!","确定") # 弹出提示信息
while box.MessageBoxClosed() != -1 {PowerDown(3)} # 等待关闭MessageBox窗口
break # 退出发送循环
}
elif recvbuf == "binary" # 转向二进制接受模式
{
# 使用二进制接收模式,循环接收,直到收到"exit"发送结束标志,置recv_finished = 1,退出程序
while True
{
recvnet = net.recvNet() # 每次接收数据包之前,需要定制一个接收Net对象,基于此对象操作
recvnet.recv() # 启动接收
while recvnet.recvOK() != -1 {PowerDown(3)} # 等待接收过程结束,并释放资源
xrecv = recvnet.recvRead("binary") # 读取结果
if xrecv[0] == -1 # 超时次数超限
{
box = GUI("box") # 弹窗提示错误
box.MessageBox("接收文件超时,将退出程序!","确定") # 弹出提示信息
while box.MessageBoxClosed() != -1 {PowerDown(3)} # 等待关闭MessageBox窗口
gui.CloseFine() # 关闭fine窗口
net.close() # 关闭net,清理网络资源
return # 程序返回,退出程序
}
recvbuf = xrecv[1] # 如果接收正确,获取数据
if recvbuf == "exit" # 收到的数据包为“exit”时,表明文件发送完毕、服务器已退出
{
recv_finished = 1 # 置位数据接收完毕标志
box = GUI("box") # 弹窗提示
box.MessageBox("文件接收完毕!","确定") # 弹出提示信息
while box.MessageBoxClosed() != -1 {PowerDown(3)} # 等待关闭MessageBox窗口
break # 退出发送循环
}
else # 收到数据包
{
str = sprint(recvbuf)
gui.SendEditbox([str]) # 将收到的数据显示在显示区
}
}
}
else # 收到数据包
{
str = sprint(recvbuf)
gui.SendEditbox([str]) # 将收到的数据显示在显示区
}
}
# 数据接收完毕之前,程序无法流转到此处,按钮消息被阻滞(不响应按钮和菜单),之后才响应菜单或按钮
if gui.FineReady() == 0 # 有界面数据输出
{
x = gui.FineRead() # x[0]是指令(menu-0-0来自menu),x[1]是文件内容(来自editbox)
# 检查同名文件是否存在
fp = FILEOPEN(filename,"r") # 尝试打开文件
if fp == False # 打开失败,表明文件不存在
{
is_writefile = 1 # 置位写文件标志,可以写入文件标志
}
else # 如果能打开文件,说明文件存在,询问是否同意覆盖原文
{
fp.close() # 关闭文件
box = GUI("box") # 创建弹窗GUI对象
box.MessageBox(filename+"已经存在,是否要覆盖原文件?","确定|取消")
while box.MessageBoxClosed() != -1 {PowerDown(3)} # 等待做出选择
select = gui.MessageBoxRead(box) # 读取选择结果
if select == 0 # 0表明是确定
{
is_writefile = 1 # 置位写文件标志,将写入文件
}
else # 表明选择的是取消、或直接关闭弹窗
{
is_writefile = 0 # 清0写文件标志,将不会写文件
}
}
if is_writefile == 1 # 如果写文件标志是1,进行写文件操作(将覆盖原文件)
{
fp = FILEOPEN(filename,"w") # 只写方式打开文件,
fp.write(x[1]) # 将从editbox控件上读出的内容,写入文件
fp.close() # 关闭文件
}
}
}
net.close() #清除网络资源