前言
这个实验已经做了一个多月了,本来打算把程序功能完善一下再分享出来,无奈最近太忙了,又怕改来改去又改崩了就迟迟未改。最近终于把想学了好久的Git学了,就把这个代码传到了Github上。后续如果不出意外的话会继续完善,借此学习版本控制。
备注:我的Github上有HNU计网Lab2完整代码,欢迎参考 链接
解决问题
- 用户名重复提示重新输入
- 选择服务类型
- 用X.txt代替接收窗口,使得收发分离
- 使用服务器作为中转站实现消息、文件转发(C/S模式)
Server
from concurrent.futures import ThreadPoolExecutor
from socket import *
import time
# 发送文件
def file_send(filename, clientSocket):
# 异常处理 文件不存在则返回错误信息
try:
file = open(filename, encoding='utf-8')
except FileNotFoundError:
print("没找到文件,请检查你的输入")
sentences = []
content = file.read()
# clientSocket.send(("即将传输文件:"+filename).encode('utf-8'))
time.sleep(0.1)
# 将content切割成长度为1024的一段段文字 最后一段<=1024
while len(content) > 0:
sentences.append(content[:min(len(content), 1024)])
content = content[min(len(content), 1024):] # 割了就扔掉
# 循环发送切割后的文字段
for sentence in sentences:
# 将字符串类型转换成字节类型后发送
clientSocket.send(sentence.encode('utf-8'))
time.sleep(0.1)
clientSocket.send("EOF".encode()) # 最后自动发送EOF表示消息传输完毕
time.sleep(0.1)
return
# 接收文件
def file_recv(filename, clientSocket):
file = open(filename, "w", encoding='utf-8')
sentence = clientSocket.recv(1024).decode('utf-8')
while sentence != "EOF":
# 将接收的文字写入文件
file.write(sentence)
sentence = clientSocket.recv(1024).decode('utf-8')
print("服务器收到文件!可在当前文件夹下查看")
return
# 发送信息
def msg_send(content, clientSocket):
# clientSocket.send(("当前为消息的传输").encode('utf-8')) # 首先告知对方当前进行的是消息的传输
time.sleep(0.1) # 防止粘包
sentences = []
# 将content切割成长度为1024的一段段字符串 最后一段<=1024
while len(content) > 0:
sentences.append(content[:min(len(content), 1024)])
content = content[min(len(content), 1024):] # 割了就扔掉
# 循环发送切割后的消息段
for sentence in sentences:
# 将字符串类型转换成字节类型后发送
clientSocket.send(sentence.encode('utf-8'))
time.sleep(0.1)
clientSocket.send("EOF".encode()) # 最后自动发送EOF表示消息传输完毕
time.sleep(0.1)
# 接收消息
# 接收消息
def msg_recv(clientSocket):
sentences = ""
sentence = clientSocket.recv(1024).decode('utf-8')
while sentence != "EOF": # 消息传输未完毕
sentences += sentence # 拼接字符串
sentence = clientSocket.recv(1024).decode('utf-8') # 继续接收
# print(sentences) # 打印接收到的消息
return sentences
# 服务器收取来自客户端的消息或文件
def recv_proc(connectionSocket):
link = connectionSocket.recv(1024).decode('utf-8')
link = link.split(":") # 以 : 进行分隔成两个字符串
from_link = link[0]
to_link = link[1]
global conn_pool # 使用维护的连接 若想在函数内部对函数外的变量进行操作,就需要在函数内部声明其为global
# 如果接收方不存在且不是服务器Server
if not conn_pool.get(to_link) and to_link != "Server": # get()函数返回指定键的value
users = ""
for key, value in conn_pool.items(): # items() 函数作用:以列表返回可遍历的(键, 值) 元组数组。
users += key+' '
connectionSocket.send("Message From Server".encode('utf-8'))
time.sleep(0.1)
connectionSocket.send(("NotExist%s" % users).endcode('utf-8'))
return 2, "", "", ""
sentences = ""
parse = connectionSocket.recv(1024).decode('utf-8')
print(parse) # 测试测试测试
# 判断是文件、退出标志还是信息
if parse[:4] == "file":
sentences = parse
file_recv(parse[5:], connectionSocket)
elif parse == ":q": # 退出连接
sentences = parse
return 1, sentences, from_link, to_link
elif parse == "msg":
sentences = msg_recv(connectionSocket)
return 0, sentences, from_link, to_link
# 服务端发
def send_proc(parse, from_link, to_link):
global conn_pool
if parse == ":q":
conn_pool[from_link].send(("Message From Server").encode('utf-8'))
time.sleep(0.1)
conn_pool[from_link].send(parse.encode('utf-8'))
return 1
connectionSocket = conn_pool[to_link]
print("用户 %s 向用户 %s 发送消息" % (from_link, to_link))
if to_link != "Server": # 不是和服务器聊天,而是和其他用户
connectionSocket.send(("Message From %s" % from_link).encode())
connectionSocket.send(parse.encode('utf-8'))
else:
print("Receive from %s:" % name, end='')
if parse[0:4] == "file":
if to_link != "Server":
file_send(parse[5:], connectionSocket)
print("文件转发成功")
else:
if to_link != "Server":
msg_send(parse, connectionSocket)
else:
print(parse)
return 0
# 服务器作为一个中转站,先接收,再发送
def main_proc(connectionSocket, name):
global conn_pool
while 1:
fg, parse, from_link, to_link = recv_proc(connectionSocket)
if fg == 2: # 如果接收方不存在且不是服务器Server
continue
fg = send_proc(parse, from_link, to_link)
if fg == 1: # 退出连接
break
del conn_pool[name] # 连接关闭,删除映射关系
print("用户 %s 已下线(断开连接)" % name)
connectionSocket.close()
serverPort = 2121
# 创建套接字
serverSocker = socket(AF_INET, SOCK_STREAM)
serverSocker.bind(('', serverPort))
serverSocker.listen(1)
global conn_pool # 维护的连接
conn_pool = dict() # 用户名:打开的tcp连接
conn_pool["Server"] = serverPort
print("我已经准备好提供服务啦")
# 创建线程池 最多为5个用户建立“群聊”
pool = ThreadPoolExecutor(max_workers=5)
while 1:
connectionSocket, addr = serverSocker.accept()
name = connectionSocket.recv(1024).decode('utf-8') # 接收线程名保存在name中
while conn_pool.get(name):
connectionSocket.send("用户名已存在,请重新输入!".encode('utf-8'))
name = connectionSocket.recv(1024).decode('utf-8') # 重新接收
connectionSocket.send("加入聊天成功!".encode('utf-8'))
conn_pool[name] = connectionSocket
print("与来自 %s 的用户 %s 建立连接" % (str(addr), name))
# connectionSocket,name是函数main_proc的参数
pool.submit(main_proc, connectionSocket, name)
pool.shutdown(wait=True) # wait=True表示关闭线程池之前需要等待所有工作线程结束
Client
from socket import *
from threading import Thread
import time
# 发送信息
def msg_send(content, clientSocket):
clientSocket.send(("当前为消息的传输:").encode('utf-8')) # 首先告知对方当前进行的是消息的传输
time.sleep(0.1) # 防止粘包
sentences = []
# 将content切割成长度为1024的一段段字符串 最后一段<=1024
while len(content) > 0:
sentences.append(content[:min(len(content), 1024)])
content = content[min(len(content), 1024):] # 割了就扔掉
# 循环发送切割后的消息段
for sentence in sentences:
# 将字符串类型转换成字节类型后发送
clientSocket.send(sentence.encode('utf-8'))
time.sleep(0.1)
clientSocket.send("EOF".encode()) # 最后自动发送EOF表示消息传输完毕
time.sleep(0.1)
# 接收消息
def msg_recv(clientSocket):
sentences = ""
sentence = clientSocket.recv(1024).decode('utf-8')
while sentence != "EOF": # 消息传输未完毕
sentences += sentence # 拼接字符串
sentence = clientSocket.recv(1024).decode('utf-8') # 继续接收
print(sentences, file=fw) # 打印接收到的消息
fw.flush()
return
# 发送文件
def file_send(filename, clientSocket):
# 异常处理 文件不存在则返回错误信息
try:
file = open(filename, encoding='utf-8')
except FileNotFoundError:
print("没找到文件,请检查你的输入")
sentences = []
content = file.read()
# clientSocket.send(("即将传输文件:"+filename).encode('utf-8'))
time.sleep(0.1)
# 将content切割成长度为1024的一段段文字 最后一段<=1024
while len(content) > 0:
sentences.append(content[:min(len(content), 1024)])
content = content[min(len(content), 1024):] # 割了就扔掉
# 循环发送切割后的文字段
for sentence in sentences:
# 将字符串类型转换成字节类型后发送
clientSocket.send(sentence.encode('utf-8'))
time.sleep(0.1)
clientSocket.send("EOF".encode()) # 最后自动发送EOF表示消息传输完毕
time.sleep(0.1)
return
# 接收文件
def file_recv(filename, clientSocket):
fil = open(filename, "w", encoding='utf-8')
sentence = clientSocket.recv(1024).decode()
while sentence != "EOF":
# 将接收的文字写入文件
fil.write(sentence)
sentence = clientSocket.recv(1024).decode()
# print("已成功接收文件!可在当前文件夹下查看", file=fil) # 打印接收到的消息
fil.flush()
print("文件接收成功!可在当前文件夹下查看", file=fw)
fw.flush()
return
# 发送线程
def send_proc(clientSocket, name):
fg = True # 信号
while fg:
to_name = input("请输入你想要聊天的对象:")
clientSocket.send((name+":"+to_name).encode('utf-8')) # 告知服务器收发双方名字
time.sleep(0.5) # 阻塞 等待收线程完成
global exist
if exist:
exist = False # ????
continue
parse = input("请选择服务:\nfile:文件名\nmsg\n:q\n")
clientSocket.send(parse.encode('utf-8'))
if parse == "msg":
content = input("请输入消息:")
# msg_send(content,clientSocket)
clientSocket.send(content.encode('utf-8'))
clientSocket.send(("EOF").encode('utf-8'))
elif parse[:4] == "file":
file_send(parse[5:], clientSocket)
elif parse == ":q":
print(("您已下线"))
clientSocket.close()
# 收线程
def recv_proc(clientSocket):
fg = True
while fg:
from_whom = clientSocket.recv(1024).decode('utf-8') # 来自谁
parse = clientSocket.recv(1024).decode('utf-8') # 内容是
if parse[:4] == "file":
print('\n%s(文件)' % from_whom, end=":", file=fw)
fw.flush()
file_recv(parse[5:], clientSocket)
elif parse == ":q":
fg = False # 退出循环
elif parse[:8] == "NotExist":
print('%s' % from_whom, end=':') # 输出Message From Server
print("User Not Found")
print("User list: %s" % parse[8:])
global exist
exist = True # 没找到接收方
else:
print('\n%s' % from_whom, end=':', file=fw)
fw.flush()
msg_recv(clientSocket)
return
severName = '127.0.0.1'
severPort = 2121
clientSocket = socket(AF_INET, SOCK_STREAM)
clientSocket.connect((severName, severPort))
name = input("请输入你用于交流的用户名:")
clientSocket.send(name.encode('utf-8')) # 发送给服务器校验(是否重复)并注册(加入字典)
reply = clientSocket.recv(1024).decode('utf-8') # 接收服务器的校验结果
while reply == "用户名已存在":
print(reply)
name = input("请输入你用于交流的用户名:")
clientSocket.send(name.encode('utf-8'))
reply = clientSocket.recv(1024).decode('utf-8')
print(reply)
fw = open(name+'.output', "w", encoding='utf-8') # 开一个文件当做用户的聊天框
fw.write("这里是%s的聊天框" % name)
fw.flush() # 将缓冲区中的数据立刻写入文件
global exist # 用于确定接收方是否存在的信号量
exist = False
# 开启收、发的线程
send_task = Thread(target=send_proc, args=(clientSocket, name))
recv_task = Thread(target=recv_proc, args=(clientSocket,))
send_task.start()
recv_task.start()
send_task.join() # 使用join函数,主线程将被阻塞,一直等待被使用了join方法的线程运行完成
recv_task.join()