Python网络编程(socketserver、TFTP云盘、HTTPServer服务器模型)
HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。HTTP是一个无状态的协议。
通常承载于TCP协议之上,有时也承载于TLS或SSL协议层之上,这个时候,就成了我们常说的HTTPS
默认HTTP的端口号为80,HTTPS的端口号为443。
from socket import * import os import signal import sys import time # 文件库 FILE_PATH = "/home/tarena/" # 实现功能模块 class TftpServer(object): def __init__(self,connfd): self.connfd = connfd # 查询 def do_list(self): # 获取列表 file_list = os.listdir(FILE_PATH) if not file_list: self.connfd.send("文件库为空".encode()) # 服务器目录无文件 return else: # 有文件 self.connfd.send(b'OK') time.sleep(0.1) files = "" for file in file_list: # 发送所有普通文件的文件名并且不是隐藏文件 if os.path.isfile(FILE_PATH+file) and file[0] != '.': # 文件名间隔符 用于客户端解析 files = files + file + '#' # 一次全部发送 简单粗暴 self.connfd.send(files.encode()) # 下载 def do_get(self,filename): # 判断文件是否纯在 try: fd = open(FILE_PATH + filename,'rb') except: self.connfd.send("文件不存在".encode()) return self.connfd.send(b'OK') time.sleep(0.1) # 发送文件 try: while True: data = fd.read(1024) if not data: break self.connfd.send(data) except Exception as e: print(e) time.sleep(0.1) self.connfd.send(b'##') # 表示文件发送完成 print("文件发送完毕") # 上传 def do_put(self,filename): # 限制文件命重复导致覆盖源文件 try: fd = open(FILE_PATH+filename,'xb') except: self.connfd.send("无法上传".encode()) return except FileExistsError: self.connfd.send("文件已存在".encode()) return self.connfd.send(b'OK') # 上传文件 while True: data = self.connfd.recv(1024) if data == b'##': break fd.write(data) fd.close() print("文件上传完毕") # 流程控制,创建套接字,创建并发,方法调用 def main(): HOST = '0.0.0.0' PORT = 8888 ADDR = (HOST,PORT) # 创建套接字 sockfd = socket() sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sockfd.bind(ADDR) sockfd.listen(5) # 忽略子进程退出 signal.signal(signal.SIGCHLD, signal.SIG_IGN) # 循环等待客户端链接 while True: try: connfd, addr = sockfd.accept() except KeyboardInterrupt: sockfd.close() sys.exit("服务器退出") except Exception as e: print(e) continue print("客户端登录:",addr) # 创建父子进程 pid = os.fork() # 进入子进程 if pid == 0: # 关闭子进程内无用套接字 sockfd.close() tftp = TftpServer(connfd) # __init__传参 while True: data = connfd.recv(1024).decode() # 断开连接 if (not data) or data[0] == 'Q': print("客户端退出") sys.exit(0) elif data[0] == "L": # 申请查询 tftp.do_list() elif data[0] == 'G': # 解析文件名 filename = data.split(' ')[-1] # 申请下载 tftp.do_get(filename) elif data[0] == 'P': filename = data.split(' ')[-1] # 申请上传 tftp.do_put(filename) else: print("客户端发送错误指令") else: # 关闭父进程内无用套接字 connfd.close() # 父进程只用来做客户端链接 continue if __name__ == "__main__": main()
from socket import * import sys import time # 实现各种功能请求 class TftpClient(object): def __init__(self,sockfd): self.sockfd = sockfd def do_list(self): self.sockfd.send(b'L') # 发送请求类型 # 接收服务器回应 data = self.sockfd.recv(1024).decode() if data == "OK": data = self.sockfd.recv(4096).decode() files = data.split('#') for file in files: print(file) print("文件展示完毕") else: # 请求失败原因 print(data) # 下载指定文件 def do_get(self,filename): self.sockfd.send(('G ' + filename).encode()) data = self.sockfd.recv(1024).decode() # 请求成功 if data == 'OK': fd = open(filename,'wb') while True: data = self.sockfd.recv(1024) # 结束符 if data == b'##': break fd.write(data) fd.close() print("%s 下载完成\n"%filename) else: # 请求失败原因 print(data) def do_put(self,filename): # 判断本地是否有要上传的文件 try: fd = open(filename,'rb') except: print("上传文件不存在") return self.sockfd.send(("P "+filename).encode()) data = self.sockfd.recv(1024).decode() # 请求成功 if data == 'OK': while True: data = fd.read(1024) if not data: break self.sockfd.send(data) fd.close() # 发送结束符并防止粘包 time.sleep(0.1) self.sockfd.send(b'##') print("%s 上传完毕"%filename) else: # 请求失败原因 print(data) # 创建套接字并建立连接 def main(): # 终端输入地址 if len(sys.argv) < 3: print("argv is error") return HOST = sys.argv[1] PORT = int(sys.argv[2]) ADDR = (HOST,PORT) sockfd = socket() sockfd.connect(ADDR) # 创建对象 tftp = TftpClient(sockfd) while True: print("") print("==========命令选项===========") print("********** list *********") print("********** get file ******") print("********** put file ******") print("********** quit *********") print("=============================") cmd = input("输入命令>>") # 去除空格判断命令 if cmd.strip() == "list": # 查询 tftp.do_list() # 获取文件上传或下载命令 elif cmd[:3] == "get": # 拆分命令获取文件名 filename = cmd.split(' ')[-1] # 下载 tftp.do_get(filename) elif cmd[:3] == "put": filename = cmd.split(' ')[-1] # 上传 tftp.do_put(filename) # 退出 elif cmd.strip() == "quit": sockfd.send(b'Q') sockfd.close() sys.exit("欢迎使用") else: print("请输入正确命令!") if __name__ == "__main__": main() 多线程并发 threading模块完成多线程并发 对比多进程并发 优势 : 资源消耗少 缺点 : 需要注意对共享资源的操作 实现步骤: 1. 创建套接字,绑定,监听 2. 接收客户端连接请求,创建新的线程 3. 主线程继续等待其他客户端连接,分支线程执行客户端具体请求 4. 处理完客户端请求后分支线程自然退出,关闭客户端套接字 示例: from socket import * import os,sys from threading import * HOST = "0.0.0.0" PORT = 8888 ADDR = (HOST,PORT) #客户端处理函数 def handler(connfd): print("Connect from",connfd.getpeername()) while True: data = connfd.recv(1024).decode() if not data: break print(data) connfd.send(b'Receive your msg') connfd.close() def main(ADDR): s = socket() s.bind(ADDR) s.listen(5) while True: try: connfd,addr = s.accept() # 处理 Ctrl + C except KeyboardInterrupt: s.close() sys.exit("服务器退出") # 其他异常 except Exception as e: print(e) continue # 创建子线程用于处理客户端请求 t = Thread(target=handler,args= (connfd,)) t.setDaemon(True) t.start() if __name__ == __main__: main()
#多进程 tcp server from socketserver import * #创建server类 # class Server(ForkingMixIn,TCPServer): # class Server(ForkingTCPServer): # pass #多线程tcp并发 class Server(ThreadingTCPServer): pass #具体的请求处理类 class Handler(StreamRequestHandler): def handle(self): # self.request ==> accept返回的套接字 print("Connect from",self.request.getpeername()) while True: data = self.request.recv(1024).decode() if not data: break print(data) self.request.send(b'Receive') if __name__ == __main__: #创建server对象 server = Server(("0.0.0.0",8888),Handler) #启动服务器 server.serve_forever()
from socket import * from threading import Thread import time # 存放静态页面的目录 STATIC_DIR = "./static" ADDR = ('0.0.0.0', 8000) # HTTPServer类,封装具体功能 class HTTPServer(object): def __init__(self, address): # 创建套接字 self.sockfd = socket() # 设置端口重用 self.sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) self.sockfd.bind(address) self.sockfd.listen(5) # 为对象增加属性变量 self.name = "HTTPServer" self.port = address[1] self.address = address # 启动服务器 def serve_forever(self): print("Listen the port %d"%self.port) while True: # 循环接收客户端请求并创建新的套接字 connfd, addr = self.sockfd.accept() # 创建线程并运行处理具体请求 clientThread = Thread(target = self.handleRequest,args = (connfd,)) # 主线程结束时结束线程 clientThread.setDaemon(True) clientThread.start() def handleRequest(self, connfd): # 接收客户端请求 request = connfd.recv(4096) # 按行切割 字符串 requestHeadlers = request.splitlines() # 获取请求行 print(connfd.getpeername(), ":" , requestHeadlers[0]) # 获取请求内容并解析 getRequest = str(requestHeadlers[0]).split(' ')[1] # 并判断请求类型 if getRequest == '/' or getRequest[-5:] == '.html': # 请求行为网页请求 data = self.get_html(getRequest) else: # 请求指定数据内容 data = self.get_data(getRequest) # 响应请求并返还内容 connfd.send(data.encode()) connfd.close() # 用于处理网页请求 def get_html(self,page): # 判断是否为主页请求 if page == "/": filename = STATIC_DIR + "/index.html" else: filename = STATIC_DIR + page try: f = open(filename) except Exception: # 没有找到页面 responseHeadlers = "HTTP/1.1 404 Not Found\r\n" responseHeadlers += "Content-Type: text/html\r\n" responseHeadlers += '\r\n' responseBody = "<h1>Sorry,not found the page</h1>" else: responseHeadlers = "HTTP/1.1 200 OK\r\n" responseHeadlers += "Content-Type: text/html\r\n" responseHeadlers += '\r\n' for i in f: responseBody += i # 页面存不存在否响应 finally: return responseHeadlers + responseBody # 用于处理数据内容请求 def get_data(self,data): responseHeadlers = "HTTP/1.1 200 OK\r\n" responseHeadlers += "\r\n" if data == "/time": responseBody = time.ctime() elif data == "/ParisGabriel": responseBody = "Welcome to ParisGabriel" else: responseBody = "The data not found" return responseHeadlers + responseBody if __name__ == "__main__": # 生成服务器对象 httpd = HTTPServer(ADDR) # 启动服务器 httpd.serve_forever()