HTTP协议?
HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。HTTP是一个无状态的协议。
通常承载于TCP协议之上,有时也承载于TLS或SSL协议层之上,这个时候,就成了我们常说的HTTPS
默认HTTP的端口号为80,HTTPS的端口号为443。
what? 无状态什么鬼?
HTTP无状态协议是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,
则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快
由于web等客户端与服务器交互的应用程序出现后HTTP的无状态严重阻碍了这些应用的实现效率 说以就产生了cookie和Session
cookie:
当用户使用浏览器访问一个支持Cookie的网站的时候,用户会提供包括用户名在内的个人信息并且提交至服务器;
接着,服务器在向客户端回传相应的超文本的同时也会发回这些个人信息,当然这些信息并不是存放在HTTP响应体(Response Body)中的,
而是存放于HTTP响应头(Response Header);当客户端浏览器接收到来自服务器的响应之后,
浏览器会将这些信息存放在一个统一的位置,对于Windows操作系统而言,
我们可以从: [系统盘]:\Documents and Settings\[用户名]\Cookies目录中找到存储的Cookie;自此,客户端再向服务器发送请求的时候,
都会把相应的Cookie再次发回至服务器。而这次,Cookie信息则存放在HTTP请求头(Request Header)了。
Session :
所谓session就是指客户端与服务端之间的一种交互过程的状态信息(数据) 这个状态的定界以及生命期是应用本身的事情
当一个用户向服务器发送第一个请求时,服务器为其建立一个session 并且会给这个session创建一个标识号
这个用户随后的请求都应该包括这个标识好服务器会对这个标识判断请求属于哪一个session
这种机制不使用IP作为标识,因为很多机器是代理服务器上网,无法区分主机 可以用cookie和URL重写来实现session标识号(sessionID)
URL只是一个统称 实际上是URI包含URL和URN由于URN用的非常少 几乎说有的URI都是URL所以人们更喜欢叫URL
os.listdir(path)
获取文件
列表
os.path.isfile() :
判断一个 文件是否为
普通文件
os.path.isdir() :
判断一个文件是否为
目录
TFTP 文件服务器
项目功能 :
* 客户端有简单的页面命令提示
* 功能包含:
1. 查看服务器文件库中的文件列表(普通文件)
2. 可以下载其中的某个文件到本地
3. 可以上传客户端文件到服务器文件库
* 服务器需求 :
1. 允许多个客户端同时操作
2.每个客户端可能回连续发送命令
技术分析:
1. tcp套接字更适合文件传输
2. 并发方案 ---》 fork 多进程并发
3. 对文件的读写操作
4. 获取文件列表 ----》 os.listdir()
粘包的处理
整体结构设计
1. 服务器功能封装在类中(上传,下载,查看列表)
2. 创建套接字,流程函数调用 main()
3. 客户端负责发起请求,接受回复,展示
服务端负责接受请求,逻辑处理
编程实现
1. 搭建整体结构,创建网络连接
2. 创建多进程和类的结构
3. 每个功能模块的实现
服务器端:
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()
socket并发集成模块
python2 SocketServer
python3
socketserver
功能 :
通过模块提供的接口组合可以完成多进程/多线程 tcp/udp的 并发程序
StreamRequestHandler 处理tcp请求
DatagramRequestHandler 处理udp请求
ForkingMixIn 创建多进程
ThreadingMixIn 创建多线程
TCPServer 创建tcp server
UDPServer 创建udp server
ForkingTCPServer 等于 ForkingMixIn + TCPServer
ForkingUDPServer 等于 ForkingMixIn + UDPServer
ThreadingTCPServer 等于 ThreadingMixIn + TCPServer
ThreadingUDPServer 等于 ThreadingMixIn + UDPServer
示例:
#多进程 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()
基于多线程并发的HTTPServer
1. 接收浏览器http请求
2. 对请求进行一定的解析
3. 根据解析结果返回对应内容
4. 如果没有请求内容则返回404
5. 组织Response格式进行回发
升级:
* 使用多线程并发
* 增加了具体的请求解析和404情况
* 使用类进行代码封装
* 增加一定的数据获取功能
技术点 : threading并发
tcp socket 传输
HTTP请求和响应格式
相比上次升级了一点点
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()