Part.1 简单的socket单次数据传输
服务端:
1 #服务器端 2 import socket 3 4 server = socket.socket() # 声明socket类型,同时生成socket连接对象 5 server.bind(('localhost',9999)) # 绑定地址端口,#特别注意,这里面是一个元组,第一次写可能会漏掉一个括号导致报错 6 server.listen() # 监听 7 8 conn,addr = server.accept() # conn是客户端连接过来在服务器端为其生成的一个连接实例(阻塞) 9 data = conn.recv(1024) # 接收数据,最大1024(阻塞)10 print("recv:",data)11 conn.send(data.upper()) # 将接收的数据变成大写,发送回去12 13 server.close() # 关闭连接
客户端:
1 #客户端 2 import socket 3 4 client = socket.socket() # 声明socket类型,同时生成socket连接对象 5 client.connect(('localhost',9999)) # 特别注意,这里面是一个元组,第一次写可能会漏掉一个括号导致报错 6 7 client.send(b"hello world") # 发送数据 8 data = client.recv(1024) # 接收数据,最大1024(阻塞) 9 print("recv:",data)10 11 client.close() # 关闭连接
Part.2 socket数据多次发送
服务端:
1 #实现一个连接的连续往返通话 2 import socket 3 4 server = socket.socket() 5 server.bind(('localhost',9999)) 6 server.listen() 7 print("等待消息......") 8 9 conn,addr = server.accept() #等待新的客户端过来10 while True: # 循环是实现同一个连接内,可以发送多次消息,而不是发送一次就退出了11 data = conn.recv(1024)12 print("recv:",data.decode())13 # 在windows上面断开客户端,服务端也会终止服务,而在Linux或者mac上断开客户端,这里就接收空数据,并不会断开,一直死循环接收空14 # 所以下面判断传来的数据是否为空15 if not data:16 print(client has lost...)17 break18 conn.send(data.upper())19 20 server.close()
客户端:
1 import socket 2 3 client = socket.socket() 4 client.connect(('localhost',9999)) 5 6 while True: 7 msg = input(">>:").strip() # strip方法是去掉两头的空格或者指定字符串 8 if len(msg) == 0:continue # 这里不能send空(输入时直接回车),不然客户端就会卡住,所以要加一个判断 9 client.send(msg.encode()) # encode转换为byte,如果是单纯的字符串,前面加b10 data = client.recv(1024)11 print('recv:',data.decode()) # decode与encode相反12 13 client.close()
Part.3 socket服务端执行返回客户端的输入命令
服务端:
1 import socket 2 import os 3 4 server = socket.socket() 5 server.bind(('localhost',9999)) 6 server.listen() 7 8 conn,addr = server.accept() 9 while True:10 data = conn.recv(1024)11 print("recv:",data)12 if not data:13 print("client had closed......")14 break15 res = os.popen(data.decode()).read() # 或者decode("utf-8")16 # res = os.popen(data).read() 如果执行命令后得到的结果有中文,这样写会报错17 conn.send(res.encode()) # 或者encode("utf-8")18 # conn.send(res) 如果执行命令后得到的结果有中文,这样写会报错19 20 server.close()
客户端:
1 import socket 2 3 client = socket.socket() 4 client.connect(('localhost',9999)) 5 6 while True: 7 cmd = input(">>>:").strip() 8 if len(cmd) == 0:continue 9 client.send(cmd.encode())10 res = client.recv(1024)11 print(res.decode())12 13 client.close()
Part.4 客户端断开连接,服务端也能等待下一个连接
服务端:
1 import socket 2 3 server = socket.socket() 4 server.bind(('localhost',9999)) 5 server.listen() 6 7 while True: # 此循环实现,当目前连接的一个客户端断开连接时,服务端能重新等待第二个客户端的连接,而不是关闭客户端就导致服务端也关闭了 8 conn,addr = server.accept() 9 print("建立连接,等待消息。。。")10 while True:11 data = conn.recv(1024)12 if not data:break13 print("recv:",data.decode())14 conn.send(data.upper())15 16 server.close()
Part.5 socket处理一次客户请求返回大于1024字节的数据
服务端:
1 import socket 2 import os 3 4 server = socket.socket() 5 server.bind(('localhost',9999)) 6 server.listen() 7 8 while True: 9 conn,addr = server.accept()10 print("new conn:",addr)11 while True:12 data = conn.recv(1024)13 if not data:14 print("client has lost...")15 break16 print("excution cmd:",data)17 res = os.popen(data.decode()).read()18 if len(res) == 0: # res长度为0,说明传过来的命令是错误的,不能返回正确的结果,所以这里做判断19 res = data.decode() + ": command not found"20 conn.send(str(len(res.encode())).encode()) # 发送命令结果大小21 a = conn.recv(1024) # 上下两个send在一块时,会发生粘包,缓冲区将两次发送的数据粘在一块发送,所以这里就设置一个接收消息来分割两次发送22 # conn.send(str(len(res)).encode()) # 这里的res是命令返回结果,是字符串,这里len针对中文和byte的长度返回值是不同的,所以要转换为byte再len23 conn.send(res.encode())24 25 server.close()
客户端:
1 import socket 2 3 client = socket.socket() 4 client.connect(('localhost',9999)) 5 6 while True: 7 cmd = input(">>>:").strip() 8 if len(cmd) == 0: continue 9 client.send(cmd.encode())10 res_size_byte = client.recv(1024)11 client.send(b'1') # 防止粘包而设置的发送12 res_size = int(res_size_byte.decode()) # 这是接收到的服务端发来的命令结果总大小13 recvd_size = 0 #recvd_size是已经接收了的大小值14 while recvd_size != res_size: #判断目前接收了命令结果的大小是否与服务端发来的结果总大小相同,相同代表接收完了15 data = client.recv(1024)16 recvd_size += len(data) # 每次收到的有可能小于1024,所以必须用len判断17 print(data.decode())18 19 client.close()
Part.6 socket实现简单的文件下载功能
服务端:
1 import socket 2 import os 3 # import hashlib 4 5 server = socket.socket() 6 server.bind(('localhost',9999)) 7 server.listen() 8 9 while True:10 conn,addr = server.accept()11 print("new conn:",addr)12 while True:13 cmd_file = conn.recv(1024)14 if not cmd_file: continue15 cmd,filename = cmd_file.decode().split() # 客户端下载文件命令一般是 命令+文件,所以使用split分割出命令和文件16 if os.path.isfile(filename): # 判断这个是否是文件,可能不存在,也可能是目录17 f = open(filename,"rb")18 # m = hashlib.md5()19 file_size = os.stat(filename).st_size # os.stat是可以获取文件的详细信息,其中st_size表示文件的大小20 conn.send(str(file_size).encode()) # 将文件大小发送给客户端21 conn.recv(1024) # 等待客户端确认,防止粘包22 for line in f:23 # m.update(line)24 conn.send(line)25 f.close()26 27 server.close()
客户端:
1 import socket 2 3 client = socket.socket() 4 client.connect(('localhost',9999)) 5 6 while True: 7 cmd = input(">>>:").strip() 8 if len(cmd) == 0: continue 9 if cmd.startswith("get"):10 filename = cmd.split()[1] # 获取文件名11 client.send(cmd.encode())12 file_size_byte = client.recv(1024) # 接收文件大小13 client.send(b"ready") # 防止粘包14 file_size = int(file_size_byte.decode())15 recvd_size = 0 # 传输接收到了的文件大小16 f = open(filename,"wb")17 while recvd_size < file_size: # 传输文件,当传输到最后一次的时候数据大小不一定是1024,所以会发生粘包,因此下面会判断最后一次剩余大小做处理18 if file_size - recvd_size > 1024:19 size = 102420 else:21 size = file_size -recvd_size22 data = client.recv(size) # 如果传输的时候不是最后一次,那么size每次都是1024,如果是最后一次,size就是最后剩余大小23 recvd_size += len(data)24 f.write(data)25 else:26 print("file transfer finished")27 f.close()28 29 client.close()
Part.7 socketserver对socket的再次封装
服务端:
1 import socketserver 2 3 class MyTCPHandler(socketserver.BaseRequestHandler): 4 5 def handle(self): # 服务端跟客户端所有的交互都写在这个handle函数里 6 while True: # 可以多次接收客户端的数据进行处理,而不是进行一次就完了 7 try: 8 self.data = self.request.recv(1024).strip() # 接收数据 9 # if not self.data: break10 print("{} wrote:".format(self.client_address[0]))11 print(self.data)12 self.request.send(self.data.upper())13 except ConnectionResetError as e: # 出现这个错误表示客户端已断开,上面的判断数据是否为空 没用14 print("err:",e)15 break16 17 if __name__ == "__main__":18 HOST,POST = "localhost",999919 # server = socketserver.TCPServer((HOST,POST),MyTCPHandler) # 一个服务端处理一个客户端,单线程20 server = socketserver.ThreadingTCPServer((HOST, POST), MyTCPHandler) # 一个服务端处理多个客户端,多线程21 server.serve_forever()
eg:实现一个简单的ftp
1.用户登录和注册,用户登录后才能使用
2.每个用户有自己的家目录
3.可以查看当前家目录下的文件
4.用户上传文件到家目录
5.用户从家目录下载文件
6.在上传和下载文件的时候,显示进度条
7.不断开客户端情况下,退出当前用户,可以登录其他用户
#服务端ftp - main - server.py #代码程序文件 - conf - user #存储用户名和MD5加密密码的文件 -home - dev #dev用户的家目录 - admin - user1 - user2
# 100 防止粘包# 404 文件未找到# 101 登录成功# 102 密码错误# 103 用户不存在# 104 注册成功# 105 用户已存在# 130 目录下没有文件
dev$e77989ed21758e78331b20e477fc5582#用户名和加密的密码之间用$分隔开
import socketserverimport jsonimport osclass MyTCPHandler(socketserver.BaseRequestHandler): def put(self,*args): '''接收客户端文件''' file_dic = args[0] filename = file_dic["filename"] filesize = file_dic["filesize"] fileuser = file_dic["fileuser"] file_path = "../home/" + fileuser + "/" + filename if os.path.isfile(file_path): # 判断文件是否存在 f = open(file_path + ".new","wb") else: f = open(file_path,"wb") self.request.send(b"100") # 防止粘包 recvd_size = 0 while recvd_size < filesize: # 判断文件已经接收的大小和文件总大小 data = self.request.recv(1024) f.write(data) recvd_size += len(data) else: f.close() print("file has uploaded") def get(self,*args): """发送文件到客户端""" file_dic = args[0] filename = file_dic["filename"] fileuser = file_dic["fileuser"] file_path = "../home/" + fileuser + "/" + filename if os.path.isfile(file_path): # 判断文件是否存在 file_size = os.stat(file_path).st_size # 获取文件的大小 self.request.send(str(file_size).encode()) # 发送文件大小给客户端 self.request.recv(1024) f = open(file_path,"rb") for line in f: self.request.send(line) else: f.close() print("file download finish") else: self.request.send(b"404 none") # 文件不存在 def login(self,*args): """用户登录,用接收到的用户名和密码去和user文件遍历验证""" user_dic = args[0] username = user_dic["username"] password = user_dic["password"] f = open(r"../conf/user","r") for line in f: line = line.strip() line_list = line.split("$") # print(line_list[0],username) # print(line_list[1],password) if line_list[0] == username and line_list[1] == password: self.request.send(b"101") # 101 登录成功 return elif line_list[0] == username and line_list[1] != password: self.request.send(b"102") # 102 密码错误 f.close() return else: self.request.send(b"103") # 103 用户不存在 f.close() def regist(self,*args): """注册用户,主要是接收到传来的用户名和密码,将其写入到user文件中""" user_dic = args[0] username = user_dic["username"] password = user_dic["password"] f = open(r"../conf/user","r+") # r+ 读写,可以先检查是否存在 for line in f: line = line.strip() line_list = line.split("$") if line_list[0] == username: f.close() self.request.send(b"105") return temp = "\n" + username + "$" + password # 加上 \n 换行符,不然所有的用户都在一行 f.write(temp) f.close() os.mkdir("../home/{}".format(username)) # 在注册的同时就创建好相应的家目录 self.request.send(b"104") def ls(self,*args): """查看指定目录下文件,并返回相应的文件列表""" username = args[0]["username"] data = os.listdir("../home/" + username) # 列出指定目录下的所有文件 if len(data) == 0: self.request.send(b"130 none") else: self.request.send(json.dumps(data).encode()) def handle(self): print("{} wrote:".format(self.client_address[0])) while True: try: self.data = self.request.recv(1024).strip() file_dic = json.loads(self.data.decode()) # loads将字符串类型转化为字典类型(数据传输只能是byte类型,字典必须事先用json转) action = file_dic["action"] if hasattr(self,action): # 这里使用反射,对客户端传来的数据action,告诉服务端该操作什么 func = getattr(self,action) func(file_dic) except ConnectionResetError as e: print("err:",e) breakif __name__ == "__main__": HOST,POST = "localhost",9998 server = socketserver.ThreadingTCPServer((HOST, POST), MyTCPHandler) # 一个服务端处理多个客户端,多线程 server.serve_forever()
import socketimport osimport jsonimport hashlibuser = "none" # 当用户成功登录后,就会改变这个全局变量为当前用户名class FtpClient(object): def __init__(self): self.client = socket.socket() def connection(self,ip,port): """ 客户端去连接服务端 :param ip: 服务端IP地址 :param port: 服务端端口号 """ self.client.connect((ip,port)) def login(self): """ 用户登录验证 :return:登录成功返回True,登录失败返回False """ m = hashlib.md5() # 设定加密类型 username = input("[login]:").strip() passw = input("[login] :").strip() m.update(bytes(passw,encoding="utf-8")) # 对密码进行加密 password = m.hexdigest() # 返回十六进制的加密后的密码,加密时必须对bytes类型数据加密,使用bytes时必须指定encoding user_dic = { "username":username, "password":password, # 这里的密码已经是加密了的 "action":"login" } user_dic_byte = str(json.dumps(user_dic)).encode() # 将这个字典转化为bytes类型,传给服务端 self.client.send(user_dic_byte) login_res_byte = self.client.recv(1024) # 返回用户验证后的状态码(用户名或者密码对不对) login_res = login_res_byte.decode() if login_res == "101": # 用户名和密码正确 global user user = username print("Login successed") return True elif login_res == "102": # 用户存在,密码错误 print("password error") return False elif login_res == "103": # 用户不存在 print("user {} does not exist".format(username)) return False def regist(self): """用户注册""" m = hashlib.md5() username = input("[regist] :").strip() passw = input("[regist] :").strip() m.update(bytes(passw,encoding="utf-8")) # 加密时必须对bytes类型数据加密,使用bytes时必须指定encoding password = m.hexdigest() # 返回十六进制的加密后的密码 user_dic = { "username": username, "password": password, # 这里的密码已经是加密了的 "action": "regist" } user_dic_byte = str(json.dumps(user_dic)).encode() self.client.send(user_dic_byte) regist_res_byte = self.client.recv(1024) regist_res = regist_res_byte.decode() if regist_res == "104": # 用户注册成功 print("Regist successed") return False elif regist_res == "105": # 用户已经存在 print("user {} exist".format(username)) return False def userlogin(self): """实现用户登录或注册的交互""" while True: option = input(" (L/l or G/g):").strip() if option.upper() == "L" and self.login(): break elif option.upper() == "G": self.regist() else: continue self.interact() def interact(self): """ 实行客户端与服务端的交互 """ while True: opt = input("[{}@ftpserver ~]# ".format(user)).strip() if len(opt) == 0: continue cmd = opt.split()[0] # 取出命令 if hasattr(self,cmd): # 通过反射,执行相应的函数(一个命令对应一个函数,服务端也是如此) func = getattr(self,cmd) func(opt) else: self.help() def help(self): """ 命令使用帮助信息 """ msg = """命令使用帮助: ls 查看家目录下的文件目录 put file 上传文件 get file 下载文件 exit 退出当前用户 """ print(msg) def progress(self,passed,all): """ 进度条显示 :param passed: 已经进行过的长度 :param all: 进度条总长度 """ s = passed * 100 // all a = s * "#" b = (100 - s) * "-" print('\r %d%% [%s%s]' % (s, a, b), end="") # 光标回到行首,两个百分号表示输出一个百分号,end默认为\n,意为换行,改为空格就不换行 def put(self,*args): """ 客户端上传文件 """ cmd_list = args[0].split() # 将命令和文件名分开 if len(cmd_list) > 1: # 判断输入的命令是否正确,列表里命令加上文件名至少是两个元素 filename = cmd_list[1] if os.path.isfile(filename): # 判断文件是否存在 filesize = os.stat(filename).st_size # 获取文件的大小 file_dic = { "filename":filename, "filesize":filesize, "action":"put", # action是为了告诉服务端该执行什么操作 "fileuser":user # 传这个值是因为要告诉服务端把文件放到哪个用户的家目录 } self.client.send(json.dumps(file_dic).encode()) responce = self.client.recv(1024) # 防止粘包,后续可用作验证返回 f = open(filename,"rb") recvd_size = 0 for line in f: recvd_size += len(line) self.progress(recvd_size,filesize) # 进度条函数,传入已传文件大小和总文件大小 self.client.send(line) else: print("\nfile upload finish") f.close() else: print("'" + filename + "': No such file") def get(self,*args): """ 客户端下载文件 """ cmd_list = args[0].split() if len(cmd_list) > 1: filename = cmd_list[1] file_dic = { "filename":filename, "action":"get", "fileuser": user } self.client.send(json.dumps(file_dic).encode()) file_size_byte = self.client.recv(1024) # 传回的要么是文件大小,要么是状态码,下面就做判断 if file_size_byte.decode() != "404 none": file_size = int(file_size_byte.decode()) self.client.send(b"100") recvd_size = 0 if os.path.isfile(filename): f = open(filename + ".new","wb") else: f = open(filename,"wb") while recvd_size < file_size: # 判断已接收的文件大小和文件总大小 data = self.client.recv(1024) f.write(data) recvd_size += len(data) self.progress(recvd_size,file_size) # 进度条 else: f.close() print("\nfile has download") else: print("file no exist") def exit(self,*args): # 退出当前用户 self.userlogin() def ls(self,*args): """查看用户家目录下的所有文件""" ls_dic = { "username":user, "action":"ls", } self.client.send(json.dumps(ls_dic).encode()) data = self.client.recv(1024) if data.decode() != "130 none": # 要么返回列表,要么返回状态码 ls_list = json.loads(data.decode()) for i in ls_list: print(i)ftp = FtpClient()ftp.connection("localhost",9998)ftp.userlogin()