PythonServer
服务器模型
硬件服务器
- 主机、集群
- 厂商:IBM、HP、联想、浪潮
软件服务器:编写的服务端应用程序,在硬件服务器上运行,一般依托于操作系统,给用户提供一套完整的服务
- httpserver:处理http请求
- webserver:网站的后端应用服务器程序
- 邮箱服务器:邮件处理
- ftp文件服务器:文件的上传下载
功能:网络连接、逻辑处理、数据交互、数据传输、协议的实现
结构:
- c/s 客户端服务器模型
- b/s 浏览器服务器模型
服务器目标:处理速度更快、并发量更高、安全性更强
- 硬件要求:
- 更高的配置
- 更好的集成分布技术
- 更好的网络优化
- 网络安全技术
- 软件要求:
- 占用资源更少
- 运行更稳定
- 算法更优良
- 安全性更好
- 并发性更高
- 更容易扩展
- 硬件要求:
循环模型
- 循环接收客户端请求,处理请求。同一时刻只能处理一个请求,处理完毕后再处理下一个
- 优点:实现简单,占用资源少
- 缺点:无法同时处理多个客户端任务
- 适用情况:处理的任务可以短时间完成,不需要建立并发,更适合udp使用
并发模型
- 能够同时处理多个客户端请求
IO并发:IO多路复用(例如:select)
- 优点:资源消耗少,IO处理速度快
- 缺点:不能适用cpu密集型程序
多进程/多线程并发
- 为每个客户端创建单独的进程线程,执行请求
- 优点:
- 每个客户端可以长期占有服务器运行程序
- 能够使用多核资源,可以处理IO或者cpu运算
- 缺点:消耗系统资源高
多进程并发模型--使用fork实现多进程并发(例如之后的ftp服务器)
- 创建套接字,绑定,监听
- 等待接收客户端请求
- 创建新的进程处理客户端请求
- 原有进程继续等待接收新的客户端连接
- 如果客户端退出则关闭子进程
在父进程中忽略子进程状态改变,子进程退出自动由系统处理
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
# fork_server.py
from socket import *
import os
import sys
import signal
# 客户端处理函数
def client_handler(c):
print("处理子进程的请求:", c.getpeername())
try:
while True:
data = c.recv(1024)
if not data:
break
print(data)
c.send("收到客户端的请求".encode())
except (KeyboardInterrupt, SystemError):
sys.exit()
except Exception as e:
print(e)
c.close()
sys.exit(0)
# 创建套接字
HOST = ""
PORT = 9999
ADDR = (HOST, PORT)
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(ADDR)
s.listen(5)
print("进程%d等待客户端连接" % os.getpid())
# 在父进程中忽略子进程状态改变,子进程退出自动由系统处理
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
while True:
try:
c,addr = s.accept()
except KeyboardInterrupt:
sys.exit("服务器推出")
except Exception as e:
print("Error:", e)
continue
# 为客户端创建新的进程,处理请求
pid = os.fork()
# 子进程处理请求
if pid == 0:
client_handler(c)
# 父进程或者创建失败,都会继续等待下个客户端连接
else:
continue
进程6254等待客户端连接
处理子进程的请求: ('127.0.0.1', 63820)
b'hello'
处理子进程的请求: ('127.0.0.1', 63828)
b'world'
An exception has occurred, use %tb to see the full traceback.
SystemExit: 服务器推出
An exception has occurred, use %tb to see the full traceback.
SystemExit: 服务器推出
An exception has occurred, use %tb to see the full traceback.
SystemExit: 服务器推出
/Users/haoen110/miniconda3/lib/python3.7/site-packages/IPython/core/interactiveshell.py:3275: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D.
warn("To exit: use 'exit', 'quit', or Ctrl-D.", stacklevel=1)
/Users/haoen110/miniconda3/lib/python3.7/site-packages/IPython/core/interactiveshell.py:3275: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D.
warn("To exit: use 'exit', 'quit', or Ctrl-D.", stacklevel=1)
/Users/haoen110/miniconda3/lib/python3.7/site-packages/IPython/core/interactiveshell.py:3275: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D.
warn("To exit: use 'exit', 'quit', or Ctrl-D.", stacklevel=1)
#tcp_client.py
from socket import *
#创建套接字
sockfd = socket(AF_INET,SOCK_STREAM)
#发起连接
server_addr = ('127.0.0.1',9999)
sockfd.connect(server_addr)
while True:
#消息发送接收
data = input("发送>>")
if not data:
break
sockfd.send(data.encode())
data = sockfd.recv(1024)
print("接受到:",data.decode())
#关闭套接字
sockfd.close()
ftp文件服务器
- 项目功能
- 服务端和客户端两部分,要求启动一个服务端,可以同时处理多个客户端请求
- 功能
- 可以查看服务端文件库中所有的普通文件
- 从客户端可以下载文件库的文件到本地
- 可以将本地文件上传的服务端文件库
- 退出
客户端使用print在终端打印简单的命令提示,通过命令提示发起请求
技术分析(fork、tcp并发)
- 每个功能要单独封装,整体功能写在一个类中
- 如何搭建整体架构,完成网络通讯
功能分析
- 获取文件列表
- 客户端:
- 发送请求
- 得到回复判断能否获取列表
- 接收文件名称列表打印
- 服务端:
- 接收请求
- 判断请求类型
- 判断能否满足请求,回复信息确认
- 执行请求发送文件列表
os.listdir(path)
获取目录中文件列表
os.path.isfile()
判断是否为普通文件
os.path.isdir()
判断是否为目录
- 客户端:
文件下载
- 客户端:
- 发送请求 (文件名)
- 得到回复判断能否下载
- 下载文件
- 服务端:
- 接收请求
- 判断请求类型
- 判断能否满足请求,回复信息确认
- 执行请求发送文件
'''
ftp 文件服务器
'''
from socket import *
import os
import sys
import time
import signal
#文件库路径
FILE_PATH = "~/Documents/ftpFile/"
HOST = '0.0.0.0'
PORT = 8000
ADDR = (HOST,PORT)
#将文件服务器功能写在类中
class FtpServer(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 file[0] != '.' and \
os.path.isfile(FILE_PATH + file):
files = files + file + '#'
self.connfd.sendall(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)
#发送文件
while True:
data = fd.read(1024)
if not data:
time.sleep(0.1)
self.connfd.send(b'##')
break
self.connfd.send(data)
print("文件发送完毕")
def do_put(self,filename):
try:
fd = open(FILE_PATH + filename,'wb')
except:
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():
sockfd = socket()
sockfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
sockfd.bind(ADDR)
sockfd.listen(5)
#处理子进程退出
signal.signal(signal.SIGCHLD,signal.SIG_IGN)
print("Listen the port 8000...")
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()
ftp = FtpServer(connfd)
#判断客户端请求
while True:
data = connfd.recv(1024).decode()
if not data or data[0] == 'Q':
connfd.close()
sys.exit("客户端退出")
elif data[0] == 'L':
ftp.do_list()
elif data[0] == 'G':
filename = data.split(' ')[-1]
ftp.do_get(filename)
elif data[0] == 'P':
filename = data.split(' ')[-1]
ftp.do_put(filename)
else:
connfd.close()
continue
if __name__ == "__main__":
main()
'''
ftp 客户端
'''
from socket import *
import sys
import time
#基本文件操作功能
class FtpClient(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("文件列表展示完毕\n")
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:
f = open(filename,'rb')
except:
print("没有找到文件")
return
self.sockfd.send(('P ' + filename).encode())
data = self.sockfd.recv(1024).decode()
if data == 'OK':
while True:
data = f.read(1024)
if not data:
time.sleep(0.1)
self.sockfd.send(b'##')
break
self.sockfd.send(data)
f.close()
print("%s 上传完成"%filename)
else:
print(data)
def do_quit(self):
self.sockfd.send(b'Q')
#网络连接
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()
try:
sockfd.connect(ADDR)
except:
print("连接服务器失败")
return
ftp = FtpClient(sockfd) #功能类对象
while True:
print("========== 命令选项 ===========")
print(">>list")
print(">>get file")
print(">>put file")
print(">>quit")
print("===============================")
cmd = input("请输入命令>>")
if cmd.strip() == 'list':
ftp.do_list()
elif cmd[:3] == 'get':
filename = cmd.split(' ')[-1]
ftp.do_get(filename)
elif cmd[:3] == 'put':
filename = cmd.split(' ')[-1]
ftp.do_put(filename)
elif cmd.strip() == "quit":
ftp.do_quit()
sockfd.close()
sys.exit("谢谢使用")
else:
print("请输入正确命令!!!")
continue
if __name__ == "__main__":
main()
threading的多线程并发
对比多进程并发:
* 消耗资源较少
* 线程应该更注意共享资源的操作
* 在python中应该注意GIL问题,网络延迟较高,线程并发也是一种可行的办法
- 创建套接字,绑定监听
- 接收客户端请求,创建新的线程
- 主线程继续接收其他客户端连接
- 分支线程启动对应的函数处理客户端请求
- 当客户端断开,则分支线程结束
import traceback
traceback.print_exc()
功能:更详细的打印异常信息
from socket import *
import os,sys
from threading import *
import traceback
HOST = '0.0.0.0'
PORT = 8888
ADDR = (HOST,PORT)
#客户端处理函数
def handler(connfd):
print("Connect from",connfd.getpeername())
while True:
data = connfd.recv(1024)
if not data:
break
print(data.decode())
connfd.send(b'Receive request')
connfd.close()
#创建套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(ADDR)
s.listen(5)
#等待客户端请求
while True:
try:
connfd,addr = s.accept()
except KeyboardInterrupt:
s.close()
sys.exit("服务器退出")
except Exception:
traceback.print_exc()
continue
t = Thread(target = handler,args = (connfd,))
t.setDaemon(True)
t.start()
集成模块的使用
python2:SocketServer
python3:socketserver
- 功能:通过模块的不同类的组合完成多进程/多线程的tcp/udp的并发
StreamRequestHandler
处理tcp套接字请求
DatagramRequestHandler
处理udp套接字请求
TCPServer
创建tcp server
UDPServer
创建udp server
ForkingMixIn
创建多进程
ForkingTCPServer
--> ForkingMinIn + TCPServer
ForkingUDPServer
--> ForkingMinIn + UDPServer
ThreadingMixIn
创建多线程
ThreadingTCPServer
--> ThreadingMinIn + TCPServer
ThreadingUDPServer
--> ThreadingMinIn + UDPServer
# TCP
from socketserver import *
#创建服务器类
# class Server(ForkingMixIn,TCPServer):
# class Server(ForkingTCPServer):
class Server(ThreadingMixIn,TCPServer):
pass
class Handler(StreamRequestHandler):
def handle(self):
#self.request ==> accept 返回的套接字
print("Connect from",\
self.request.getpeername())
while True:
data = self.request.recv(1024)
if not data:
break
print(data.decode())
self.request.send(b'Receive')
if __name__ == "__main__":
server_addr = ('0.0.0.0',8888)
#创建服务器对象
server = Server(server_addr,Handler)
#启动服务器
server.serve_forever()
# UDP
from socketserver import *
#创建服务器类
class Server(ThreadingMixIn,UDPServer):
pass
class Handler(DatagramRequestHandler):
def handle(self):
while True:
data = self.rfile.readline()
print(self.client_address)
if not data:
break
print(data.decode())
self.wfile.write(b"Receive")
if __name__ == "__main__":
server_addr = ('0.0.0.0',8888)
#创建服务器对象
server = Server(server_addr,Handler)
#启动服务器
server.serve_forever()
HTTPServer V2.0
作用
- 接收客户端请求
- 解析客户端请求
- 组织数据,形成HTTP response
- 将数据发送给客户端
升级
- 采用多线程并发接收多个客户端请求
- 基本的请求解析,根据请求返回相应的内容
- 除了可以请求静态网页,也可以请求简单的数据
- 将功能封装在一个类中
技术点
- socket tcp 套接字
- http协议的请求响应格式
- 线程并发的创建方法
- 类的基本使用
#coding=utf-8
'''
http server v2.0
1.多线程并发
2.可以请求简单数据
3.能进行简单请求解析
4.结构使用类进行封装
'''
from socket import *
from threading import Thread
import sys
import traceback
#httpserver类 封装具体的服务器功能
class HTTPServer(object):
def __init__(self,server_addr,static_dir):
#增添服务器对象属性
self.server_address = server_addr
self.static_dir = static_dir
self.ip = server_addr[0]
self.port = server_addr[1]
#创建套接字
self.create_socket()
def create_socket(self):
self.sockfd = socket()
self.sockfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
self.sockfd.bind(self.server_address)
#设置监听等待客户端连接
def serve_forever(self):
self.sockfd.listen(5)
print("Listen the port %d"%self.port)
while True:
try:
connfd,addr = self.sockfd.accept()
except KeyboardInterrupt:
self.sockfd.close()
sys.exit("服务器退出")
except Exception:
traceback.print_exc()
continue
#创建新的线程处理请求
clientThread = Thread\
(target = self.handleRequest,args=(connfd,))
clientThread.setDaemon(True)
clientThread.start()
#客户端请求函数
def handleRequest(self,connfd):
#接收客户端请求
request = connfd.recv(4096)
#解析请求内容
requestHeaders = request.splitlines()
print(connfd.getpeername(),":",requestHeaders[0])
#获取具体请求内容
getRequest = str(requestHeaders[0]).split(' ')[1]
if getRequest == '/' or getRequest[-5:] == '.html':
self.get_html(connfd,getRequest)
else:
self.get_data(connfd,getRequest)
connfd.close()
def get_html(self,connfd,getRequest):
if getRequest == '/':
filename = self.static_dir + "/index.html"
else:
filename = self.static_dir + getRequest
try:
f = open(filename)
except Exception:
#没有找到页面
responseHeaders = "HTTP/1.1 404 NOT FOUND\r\n"
responseHeaders += '\r\n'
responseBody = "Sorry,not found the page"
else:
responseHeaders = "HTTP/1.1 200 OK\r\n"
responseHeaders += '\r\n'
responseBody = f.read()
finally:
response = responseHeaders + responseBody
connfd.send(response.encode())
def get_data(self,connfd,getRequest):
urls = ['/time','/tedu','/python']
if getRequest in urls:
responseHeaders = "HTTP/1.1 200 OK\r\n"
responseHeaders += '\r\n'
if getRequest == "/time":
import time
responseBody = time.ctime()
elif getRequest == '/tedu':
responseBody = "Welcome to tedu"
elif getRequest == '/python':
responseBody = "人生苦短我用Python"
else:
responseHeaders = "HTTP/1.1 404 NOT FOUND\r\n"
responseHeaders += '\r\n'
responseBody = "Sorry,not found the data"
response = responseHeaders + responseBody
connfd.send(response.encode())
if __name__ == "__main__":
#服务器IP
server_addr = ('0.0.0.0',8000)
#我的静态页面存储目录
static_dir = './static'
#生成对象
httpd = HTTPServer(server_addr,static_dir)
#启动服务器
httpd.serve_forever()
协程基础
定义:纤程,微线程。协程的本质是一个单线程程序,所以协程不能够使用计算机多核资源。
作用:能够高效的完成并发任务,占用较少的资源。因此协程的并发量较高。
原理:通过记录应用层的上下文栈区,实现在运行中进行上下文跳转,达到可以选择性地运行想要运行的部分,以此提高程序的运行效率。
优点:
- 消耗资源少
- 无需切换开销
- 无需同步互斥
- IO并发性好
缺点:无法利用计算机多核
yield ---> 协程实现的基本关键字
greenlet
- `greenlet.greenlet()`生成协程对象
- `gr.switch()`选择要执行的协程事件
from greenlet import greenlet
def test1():
print(12)
gr2.switch()
print(34)
gr2.switch()
def test2():
print(56)
gr1.switch() # 会接着上一次的继续打印
print(78)
#协程对象
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
12
56
34
78
gevent
- 作用:
- 将协程事件封装为函数
- 生成协程对象
gevent.spawn(func,argv)
- 功能:生成协程对象
- 参数:
- func 协程函数
- argv 给协程函数传参
- 返回值:返回协程对象
gevent.joinall()
- 功能:回收协程
- 参数:列表,将要回收的协程放入列表
gevent.sleep(n)
- 功能:设置协程阻塞,让协程跳转
- 参数:n 阻塞时间
from gevent import monkey
monkey.patch_all()
- 功能:修改套接字的IO阻塞行为
- 必须在socket导入之前使用
import gevent
from time import sleep
def foo(a,b):
print("a = %d,b = %d"%(a,b))
gevent.sleep(2)
print("Running foo again")
def bar():
print("Running int bar")
gevent.sleep(3)
print("Running bar again")
#生成协程
f = gevent.spawn(foo,1,2)
g = gevent.spawn(bar)
# sleep(3)
print("===============")
gevent.joinall([f,g])
print("%%%%%%%%%%%%%%")
===============
a = 1,b = 2
Running int bar
Running foo again
Running bar again
%%%%%%%%%%%%%%
import gevent
from gevent import monkey
monkey.patch_all()
from socket import *
from time import ctime
def server(port):
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',port))
s.listen(3)
while True:
c,addr = s.accept()
print("Connect from ",addr)
# handler(c) #循环服务器
gevent.spawn(handler,c) #协程服务器
def handler(c):
while True:
data = c.recv(1024)
if not data:
break
print(data.decode())
c.send(ctime().encode())
c.close()
if __name__ == "__main__":
server(8888)
PythonServer的更多相关文章
- thrift笔记
Thrift tutorial 演示 python服务端与客户端本文的开发环境是windows 7 + python2.7.3Thrift官方主页:http://thrift.apache.org/先 ...
- anible包模块管理
ansible使用包管理模块 一般使用的包管理模块的RPM,和YUM 参数 必填 默认 选择 说明 Conf_file No YUM的配置文件 Disable_dbg_check No No Yes/ ...
- ansible命令执行模块使用
ansible命令执行模块使用 1.命令执行模块-command 在远程节点上运行命令. 命令模块使用命令名称,接上空格-的分割符作为参数使用,但是不支持管道符和变量等,如果要使用这些,那么可以使用s ...
- ansible文件模块使用
1. 文件组装模块-assemble assemble主要是将多份配置文件组装为一份配置文件. 参数 必填 默认 选项 说明 Backup 否 No Yes/no 是否创建备份文件,使用时间戳 Del ...
- ansible定时任务模块和用户组模块使用
接上篇,还是一些基础模块的使用,这里主要介绍的是系统模块的使用. 下面例子都进行过相关的实践,从而可以直接进行使用相关的命令. 3.用户模块的使用 用户模块主要用来管理用户账号和用户的属性(对远程主机 ...
- ansible服务模块和组模块使用
本篇文章主要是介绍ansible服务模块和组模块的使用. 主要模块为ansible service module和ansible group moudle,下面的内容均是通过实践得到,可以直接运行相关 ...
- C++和python使用struct传输二进制数据结构来实现
网络编程问题往往涉及二进制数据的传输.在C++经常使用的传输是文本字符串和分组结构. 假设该数据可以预先送入连续的内存区域,然后让send函数来获得的第一个地址,这一块连续的内存区就能完成传输数据.文 ...
- Python学习笔记20:server先进
我们不依赖于一个框架,CGI如果是,只能使用socket介面.他完成了一个可以处理HTTP要求Pythonserver. 基于,不管是什么的计算机的操作系统(推荐Linux)和Python该计算机可被 ...
- elk集成安装配置
三台虚拟机 193,194,195 本机 78 流程 pythonserver -> nginx -> logstash_shipper->kafka->logstash_in ...
随机推荐
- JS的解析与执行过程—全局预处理阶段之全局词法环境对象
问题:有如下代码 var a = 1; function pop() { alert(a); var a = 5; } pop();//执行结果,弹出undefined 这段代码的执行结果为undef ...
- 参考《机器学习实战》高清中文PDF+高清英文PDF+源代码
机器学习是人工智能研究领域中一个极其重要的研究方向,在现今的大数据时代背景下,捕获数据并从中萃取有价值的信息或模式,成为各行业求生存.谋发展的决定性手段,这使得这一过去为分析师和数学家所专属的研究领域 ...
- 紫书 习题 10-44 UVa 11246 ( 容斥原理)
把k的倍数的删去(k, 2k, 3k--),但是k^2不应该删去,因为k已经删去,所以不存在某个数乘上k之后为k^2 所以k^2可以留下,然后因为有k^2,所以k^3就是k^2的k倍,所以k^3要删去 ...
- Centos6.5 安装lamp环境
转载自:http://www.jb51.net/article/37987.htm (转载请注明出处,谢谢) 准备篇: 1.配置防火墙,开启80端口.3306端口vi /etc/sysconfig/i ...
- 【Codeforces Round #425 (Div. 2) B】Petya and Exam
[Link]:http://codeforces.com/contest/832/problem/B [Description] *能代替一个字符串(由坏字母组成); ?能代替单个字符(由好字母组成) ...
- WPF通用框架ZFS《项目结构介绍01》_模块介绍
首页介绍: 下图为项目运行首页图片, 大的结构分为三块: 1.Header首部模块(存放通知组件[全局通知.消息管理 ].扩展模块[皮肤.系统设置.关于作者.退出系统]) 2.Left左侧菜单模块(存 ...
- struts2的acton标签中的ignoreContextParams属性和param子元素的冲突
<s:action ignoreContextParams="true" executeResult="true" name="login&qu ...
- [Python] Manipulate Data with Dictionaries in Python
Dictionaries may be familiar to you as hash maps. In this lesson, you will learn how to create them, ...
- OpenCASCADE 3 Planes Intersection
OpenCASCADE 3 Planes Intersection eryar@163.com Abstract. OpenCASCADE provides the algorithm to sear ...
- 【面试加分项】java自己定义注解之申明注解
之前的博客http://blog.csdn.net/u010590685/article/details/47029447介绍了java的注解的基本知识今天我们学习怎样使用自己定义注解. 首先我们要声 ...