前言
这个实验已经做了一个多月了,本来打算把程序功能完善一下再分享出来,无奈最近太忙了,又怕改来改去又改崩了就迟迟未改。最近终于把想学了好久的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的一段段文字 最后一段<=1024while 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的一段段字符串 最后一段<=1024while 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# 如果接收方不存在且不是服务器Serverif not conn_pool.get(to_link) and to_link != "Server": # get()函数返回指定键的valueusers = ""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 = parsefile_recv(parse[5:], connectionSocket)elif parse == ":q": # 退出连接sentences = parsereturn 1, sentences, from_link, to_linkelif parse == "msg":sentences = msg_recv(connectionSocket)return 0, sentences, from_link, to_link
# 服务端发def send_proc(parse, from_link, to_link):global conn_poolif 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 1connectionSocket = 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_poolwhile 1:fg, parse, from_link, to_link = recv_proc(connectionSocket)if fg == 2: # 如果接收方不存在且不是服务器Servercontinuefg = send_proc(parse, from_link, to_link)if fg == 1: # 退出连接breakdel 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] = connectionSocketprint("与来自 %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的一段段字符串 最后一段<=1024while 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的一段段文字 最后一段<=1024while 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 existif exist:exist = False # ????continueparse = 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 = Truewhile 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 Serverprint("User Not Found")print("User list: %s" % parse[8:])global existexist = True # 没找到接收方else:print('\n%s' % from_whom, end=':', file=fw)fw.flush()msg_recv(clientSocket)returnseverName = '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()