python 多线程网络编程 ( 二 )
背景
我在[第一篇文章中]已经介绍了如何实现一个多线程的todo
应用,接下来我将会研究如何使这个服务器完成下面这几个功能。
- 1.使用正则表达式解析用户发送的请求数据;
- 2.使用
ThreadLocal
技术; - 3.让浏览器也能够访问我们的服务器;
客户端数据解析
对于当前客户端数据解析是存在问题的,当一条命令包含了GET
就进行GET
操作是不正确的,例如当用户输入一条下面的命令。
AttackeGET/task_01/ok
服务器同样会给出查询结果,这个问题我们可以通过使用正则表达式来解决并且对比GET
字符命令是否匹配,例如下面的方式实现。
if method == 'GET':
pass
解析用户请求,与发送用户请求应该是两部分工作,是可以分离的。所以可以新建一个文件parser.py
并且编写一个解析类如下。
import re
class ClientRequestParser:
def __init__(self, data, db):
"""
The `data` is string from client side!
"""
try:
pattern = re.compile(r'(?P<method>.*)/(?P<command>.*)/(?P<status>.*)')
m = pattern.match(data)
self.request_data=m.groupdict()
self.request_method = self.request_data.get('method','No Key Found')
self.db=db
except :
print 'client command error'
self.request_method = 'no method'
def get(self, db, task_id):
response = db.get(task_id,'Key No Found')
return response
def post(self,db, command):
pattern = re.compile(r'(?P<key>.*)=(?P<value>.*)')
m = pattern.match(command)
post_data = m.groupdict()
key=post_data.get('key','Key No Found')
value=post_data.get('value','Key No Found')
db[key]=value
response='submit success'
return response
def response(self):
response=''
if self.request_method == 'GET':
print 'Get method'
task_id=self.request_data.get('command','No Key Found')
response=self.get(self.db, task_id)
elif self.request_method == 'POST':
command=self.request_data.get('command','No Key Found')
response=self.post(self.db, command)
else:
response = 'client request error'
response=response+'\r\n'
return response
通过把解析用户请求的数据分离后,在回复用户请求时的逻辑就非常简单了。
import socket
import threading
from parser import ClientRequestParser
class ThreadSocket(object):
"""
"""
todo_list = {
'task_01':'see someone',
'task_02':'read book',
'task_03':'play basketball'
}
def __init__(self, host, port):
self.host = host
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind((self.host, self.port))
def listen(self):
self.sock.listen(5)
while True:
client, address = self.sock.accept()
client.settimeout(60)
threading.Thread(target=self.handleClientRequest, args=(client, address)).start()
def handleClientRequest(self, client, address):
while True:
try:
data = client.recv(1024)
if data:
response=ClientRequestParser(data=data,db=self.todo_list).response()
client.send(response)
else:
raise error("Client has disconnected")
except:
client.close()
if __name__ == '__main__':
server=ThreadSocket('',9000)
server.listen()
所有代码可以通过git checkout v0.7
获得,运行测试结果如下。
可以发现我们的设计目标同样实现了,那么到目前为止我们都没有测试多个客户端发起请求时会出现什么情况,下面测试两个终端模拟客户发起请求的运行截图。
使用Thread Local技术
从上一节的运行截图的结果可以发现,第二个客户端访问服务器时也能看到你一个用户所POST
的数据,这就会导致数据的不安全。那么如何才能让每一个线程又拥有自己独立的空间呢?这个就需要使用到ThreadLocal
技术了,下面一起来看看代码如何实现。
首先在ThreadSocket
类之前定义下面这个local
实例,如下代码所示。
local = threading.local()
然后在ThreadSOcket
类中listen
方法中传递local
参数。
threading.Thread(target=self.handleClientRequest, args=(client, address, local)).start()
并且把handleClientRequest
也修改为接收local
参数的方法。
def handleClientRequest(self, client, address,local):
local.todo_list = {}
最后修改ClientRequestParser
的传人参数。
response=ClientRequestParser(data=data,db=local.todo_list).response()
当前的代码可以通过git checkout v0.8
获得,运行结果如下:
可以发现,第一个客户端所POST
的数据与第二个客户端是完全分开的,他们的空间是彼此独立的。
修复BUG
到目前为止我一直都没有谈到程序中存在的几个bug
原因很简单,我想把主要思路先理清楚。然而随着项目的扩大bug
一直存在难免会照成很多不可预知的后果,所以现在让我先把他们列出来。
- 1.客户端没有任何提醒就断开连接 (套接字设置了超时或者是服务器端出现错误,但是异常捕获后没有提醒只是简单的关闭连接);
- 2.当客户端超时后断开连接了,子线程无法退出。(因为
client.close()
执行完成后,没有调用break
语句,从而导致子线程不断的循环读取已经释放的套接字); - 3.在异常处理阶段,没有实现
raise error("Client has disconnected")
中的error方法; - 4.使用
CTRL+C
无法中断服务器(主要原因是由于第二点列出的原因造成的,只要增加break
就可以解决);
修复后的代码可以通过git checkout v0.8a
获得。
支持浏览器访问
要让浏览器去访问服务器,就必须要实现http
协议了。什么是http
协议呢?我不打算用一大串文字去叙述,显然那是在重复造轮子而且造的轮子还不一定标准,相信你会找到适合自己理解的文档去理解什么是http
协议的,下面首先让你体验一下浏览器发送请求给服务器的时候都会发送什么数据。打开一个终端使用下面的命令运行一段程序。
nc -l 0.0.0.0 9090
运行命令后服务器处于监听状态,可以使用你的浏览器访问下面这个地址。
http://127.0.0.1:9090
可以看到,你所打开的终端出现下面一下客户请求数据。
这个时候,你如果想了解每一行输出代表什么意思,可以查看http
相关文档了。注意到我们的浏览器状态是一直阻塞等待着服务器回复的,运行结果如下。
现在要思考的问题就是我们怎么去回复这个浏览器呢?要想知道服务器返回什么给客户端,可以继续使用nc
命令。下面使用nc
这把网络瑞士军刀防伪百度主页。
nc www.baidu.com 80
当你输入完这个命令后会要求你输入一些数据而这些数据就是一个浏览器应该发起http
请求的数据,下面我们可以新建一个文件为request.txt
文件。
GET / HTTP/1.1
Host: www.baidu.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us
Connection: keep-alive
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/601.6.17(KHTML, like Gecko) Version/9.1.1 Safari/601.6.17
注意到我把主机Host
由原来的127.0.0.1 9000
改为了指向www.baidu.com
这个应该很容易理解的。回车两次后就可以得到如下的输出结果。下面首先把输出截图附上。
上面的截图是百度的服务器反馈截图,我们不能完全照搬别人的服务器数据我们需要修改的项目如下。
- 第一行的
HTTP/1.1 302 Moved Temporarily
修改为HTTP/1.1 OK
;
- 第一行的
- 删除那些
cookie
设置项等;
- 删除那些
下面是我们的服务器应该反馈的输出文本文件以便你复制粘贴,当然当前的所有代码文件你都可以在我的 [github] 上获得。
HTTP/1.1 OK
Date: Mon, 18 Jul 2016 01:43:23 GMT
Content-Type: text/html
Content-Length: 215
Connection: Keep-Alive
Location: https://127.0.0.1:9000/
Server: BWS/1.1
X-UA-Compatible: IE=Edge,chrome=1
<html>
<head><title>302 Found</title></head>
<body bgcolor="white">
<center><h1>302 Found</h1></center>
<hr><center>pr-nginx_1-0-284_BRANCH Branch
Time : Wed Jul 6 10:57:30 CST 2016</center>
</body>
</html>
现在我们知道服务器需要符合这样的格式这样一些数据浏览器才能够解析识别,如果你不理解什么是http
协议的话可能对response.txt
文件的前面部分会比较陌生,但是当你看到后半部分的时候,你会发现那是你熟悉的html
协议。很多人经常把两者搞混,甚至有些人认为html
不就是http
协议吗?这些都是基础不扎实。那么我们回到服务器设计上来,我们需要服务器应答那些必要的数据。
首先,我们需要将文件response.txt
加载称为一个字符串以备发送,在shell
运行下面的命令可以实现这个读取功能。
好啦,知道怎么实现这个功能后,咱们可以设计一个简单的make_response
方法,下面让我们修改一下paser.py
文件。
def response(self):
response=''
if self.request_method == 'GET':
print 'Get method'
task_id=self.request_data.get('command','No Key Found')
response=self.get(self.db, task_id)
elif self.request_method == 'POST':
command=self.request_data.get('command','No Key Found')
response=self.post(self.db, command)
else:
response = 'client request error'
response=response+'\r\n'
#return response
return self.make_response(response)
def make_response(self,response):
response=''
with open('response.txt') as p:
response=p.read()
return response
你可以发现我们多增加了一个方法,并且修改了response
方法的最后一行代码。当前版本的代码可以通过git checkout v0.9
获得,下面可以测试一下运行效果。
为了进行下一步,我觉得对于http
我们有必要了解下面这一行的意义。
Content-Length: 215
它指代的是html
文件资源的大小,浏览器需要服务器提供这个参数以便解析展现到页面。那么下面我们如何分离这个html
出来呢?有几点工作需要我们去做。
1.分离http
头部与html
分离;
2.实现html
资源大小计算功能,并且把计算值填充到http
的相关域中。
下面使用shell
演示我们需要的效果。
注意在使用谷歌浏览器Chrome
访问服务器时,如果服务器提供的html
不完整会自动补全,那么这个过程可能会影响对http
头部Content-Length
域的理解。
总结
这篇本来早就写好了,一直没有来得及发布。主要是这段时间找工作哈!
[github]:https://github.com/landpack/python-socket-with-thread
python 多线程网络编程 ( 二 )的更多相关文章
- python多线程网络编程
背景 使用过flask框架后,我对request这个全局实例非常感兴趣.它在客户端发起请求后会保存着所有的客户端数据,例如用户上传的表单或者文件等.那么在很多客户端发起请求时,服务器是怎么去区分不同的 ...
- python之网络编程
本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类: 消息传递(管道.FIFO.消息队列) 同步(互斥量.条件变量.读写锁.文件和写记录锁.信号量) 共享内存(匿名的和具名的) 远程过程调用 ...
- Python高级网络编程系列之第一篇
在上一篇中我们简单的说了一下Python中网络编程的基础知识(相关API就不解释了),其中还有什么细节的知识点没有进行说明,如什么是TCP/IP协议有几种状态,什么是TCP三次握手,什么是TCP四次握 ...
- python基础网络编程--转
python之网络编程 本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类: 消息传递(管道.FIFO.消息队列) 同步(互斥量.条件变量.读写锁.文件和写记录锁.信号量) 共享内存(匿名的 ...
- python异步IO编程(二)
python异步IO编程(二) 目录 开门见山 Async IO设计模式 事件循环 asyncio 中的其他顶层函数 开门见山 下面我们用两个简单的例子来让你对异步IO有所了解 import asyn ...
- Linux网络编程(二)
Linux网络编程(二) 使用多进程实现服务器并发访问. 采用多进程的方式实现服务器的并发访问的经典范例. 程序实现功能: 1.客户端从标准输入读入一行文字,发送到服务器. 2.服务器接收到客户端发来 ...
- Python的网络编程--思维导图
Python的网络编程--思维导图
- python 基础网络编程2
python 基础网络编程2 前一篇讲了socketserver.py中BaseServer类, 下面介绍下TCPServer和UDPServer class TCPServer(BaseServer ...
- python 基础网络编程1
python 基础网络编程1 Source code: Lib/socketserver.py lib的主目录下有一个sockserver.py文件, 里面是python基本的网络编程模型 共有一个b ...
随机推荐
- Qt 5.7设置调试器
mingw版本下自带的,这个我就不在赘述. 现在来说一下msvc版本下调试器,cdb,这个需要到ms去下载. thunder://QUFodHRwOi8vZG93bmxvYWQubWljcm9zb2Z ...
- mac OS X 配置Python+Web.py+MySQLdb环境
MAC默认支持Python 2.7所以不用安装. 1.安装pip sudo easy_install pip 2.安装Web.py sudo pip install Web.py 3.安装MySQLd ...
- VMware 克隆 Linux 系统后找不到 eth0 网卡问题(转)
[问题描述] 使用 VMware 虚拟机的克隆功能,快速复制已安装好的 Linux 系统. 克隆完成之后,发现没有 eth0 网卡. [解决方法] 1. 编辑 /etc/udev/rules.d/70 ...
- 十分钟能学会的框架,MVC+20个常用函数
LazyPHP(以下简称LP)是一个轻框架. 之所以开发这么一个框架,是因为其他框架给的太多.在高压力的情况下,ORM和盘根错节的对象树反而将简单的页面请求处理复杂化,在调试和性能上带来反面效果. L ...
- CSS伪类选择器
一.CSS伪类选择器用于给某些选择器添加效果语法规则:选择器:伪选择器例:a:link {color: #FF0000} 未访问的链接 a:visited {color: #00FF00} 已访问的链 ...
- Jetty源码分析(一)
一.目的 1.了解jetty组成架构: 2.学习jetty启动过程: 3.学习请求访问过程: 4.学习jetty内各模块作用,学习各模块内部代码: 二.jetty版本 本文所学习的jetty版本为:9 ...
- 'MAMapKit/MAMapKit.h' file not found
1.应该是derived data没清导致的.在Window -> Organizer -> Projects,找到你这个项目,然后点下右边derived data后边的delete按钮. ...
- hdu 5945 Fxx and game
青年理论计算机科学家Fxx给的学生设计了一款数字游戏. 一开始你将会得到一个数X,每次游戏将给定两个参数x,k,t, 任意时刻你可以对你的数执行下面两个步骤之一: .X=X−i(<=i<= ...
- AndroidStudio学习笔记-第一个安卓程序
要带一个本科生做一部分跟安卓有点关系的项目,于是趁着机会学习一下编写安卓程序. 第一篇材料来自谷歌官方,传送门:https://developer.android.com/training/basic ...
- BI先特技软件 Analyzer安装时的部分问题
废话不多,先看第一个问题,我先运行了 Analyzer.3.0.2357b.64.exe 然后就是傻瓜式地下一步 好的,现在问题来了,当我运行安装完毕的Analyzer时,页面给了我这样的提示“ HT ...