结合Python的SimpleHTTPServer源码来解析socket通信


Posted in Python onJune 27, 2016

何谓socket
计算机,顾名思义即是用来做计算。因而也需要输入和输出,输入需要计算的条件,输出计算结果。这些输入输出可以抽象为I/O(input output)。

Unix的计算机处理IO是通过文件的抽象。计算机不同的进程之间也有输入输出,也就是通信。因此这这个通信也是通过文件的抽象文件描述符来进行。

在同一台计算机,进程之间可以这样通信,如果是不同的计算机呢?网络上不同的计算机,也可以通信,那么就得使用网络套接字(socket)。socket就是在不同计算机之间进行通信的一个抽象。他工作于TCP/IP协议中应用层和传输层之间的一个抽象。如下图:

结合Python的SimpleHTTPServer源码来解析socket通信

服务器通信
socket保证了不同计算机之间的通信,也就是网络通信。对于网站,通信模型是客户端服务器之间的通信。两个端都建立一个socket对象,然后通过socket对象对数据进行传输。通常服务器处于一个无线循环,等待客户端连接:

结合Python的SimpleHTTPServer源码来解析socket通信

socket 通信实例
socket接口是操作系统提供的,调用操作系统的接口。当然高级语言一般也封装了好用的函数接口,下面用python代码写一个简单的socket服务端例子:

server.py

import socket

HOST = 'localhost'   # 服务器主机地址
PORT = 5000       # 服务器监听端口
BUFFER_SIZE = 2048   # 读取数据大小

# 创建一个套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
# 绑定主机和端口
sock.bind((HOST, PORT))
# 开启socket监听
sock.listen(5)


print 'Server start, listening {}'.format(PORT)


while True:
  # 建立连接,连接为建立的时候阻塞
  conn, addr = sock.accept()
  while True:
    # 读取数据,数据还没到来阻塞
    data = conn.recv(BUFFER_SIZE)
    if len(data):
      print 'Server Recv Data: {}'.format(data)
      conn.send(data)
      print 'Server Send Data: {}'.format(data)
    else:
      print 'Server Recv Over'
      break
  conn.close()
sock.close()

client.py

import socket

HOST = 'localhost'
PORT = 5000
BUFFER_SIZE = 1024

# 创建客户端套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接到服务器
sock.connect((HOST, PORT))

try:
  message = "Hello"
  # 发起数据给服务器
  sock.sendall(message)
  amount_received = 0
  amount_expected = len(message)
  while amount_received < amount_expected:
    # 接收服务器返回的数据
    data = sock.recv(10)
    amount_received += len(data)
    print 'Client Received: {}'.format(data)

except socket.errno, e:
  print 'Socket error: {}'.format(e)
except Exception, e:
  print 'Other exception: %s'.format(e)
finally:
  print 'Closing connection to the server'
  sock.close()

TCP 三次握手
python代码写套接字很简单。传说的TCP三次握手又是如何体现的呢?什么是三次握手呢?

第一握:首先客户端发送一个syn,请求连接,
第二握:服务器收到之后确认,并发送一个 syn ack应答
第三握:客户端接收到服务器发来的应答之后再给服务器发送建立连接的确定。
用下面的比喻就是

C:约么?

S:约

C:好的

约会
这样就建立了一个TCP连接会话。如果是要断开连接,大致过程是:

结合Python的SimpleHTTPServer源码来解析socket通信

上图也很清晰的表明了三次握手的socket具体过程。

  • 客户端socket对象connect调用之后进行阻塞,此过程发送了一个syn。
  • 服务器socket对象调用accept函数之后阻塞,直到客户端发送来的syn,然后发送syn和ack应答
  • 客户端socket对象收到服务端发送的应答之后,再发送一个ack给服务器,并返回connect调用,建立连接。
  • 服务器socket对象接受客户端最后一次握手确定ack返回accept函数,建立连接。

至此,客户端和服务器的socket通信连接建立完成,剩下的就是两个端的连接对象收发数据,从而完成网络通信。

SimpleHTTPServer
构建一个简单的HTTP服务,需要继承HTTPServer,同时requesthandler也需要继承BaseHTTPRequestHandler。python已经实现了一个例子,那就是SimpleHTTPServer。因此分析SimpleHTTPServer来查看如何使用前面的一些类构建http服务。

曾经为了表示python的简洁优雅,经常会举这样的例子,python可以一行代码开启一个服务器。

$ python -m SimpleHTTPServer

这里的SimpleHTTPServer就是实现了HTTPServer的模块。

SimpleHTTPServer通过调用BaseHTTPServer模块的test方法做为入口。

def test(HandlerClass = SimpleHTTPRequestHandler,
     ServerClass = BaseHTTPServer.HTTPServer):
  BaseHTTPServer.test(HandlerClass, ServerClass)

test方法做了两件事,第一件就是使用HTTPServer接受一个监听地址和requestClass参数,创建了一个实例对象,调用server_forever方法开启服务。

1.SimpleHTTPRequestHandler
根据之前的分析,使用httpserver的服务,我们只需要继续BaseHTTPRequestHandler,并提供自省的method方法即可。

class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
  server_version = "SimpleHTTP/" + __version__

  def do_GET(self):
    f = self.send_head()
    if f:
      self.copyfile(f, self.wfile)
      f.close()

  def do_HEAD(self):
    f = self.send_head()
    if f:
      f.close()

do_GET 和 do_HEAD 分别实现了http的get请求和head请求的处理。他们调用send_head方法:

   

def send_head(self):

    path = self.translate_path(self.path)
    f = None
    if os.path.isdir(path):
      if not self.path.endswith('/'):
        self.send_response(301)
        self.send_header("Location", self.path + "/")
        self.end_headers()
        return None
      for index in "index.html", "index.htm":
        index = os.path.join(path, index)
        if os.path.exists(index):
          path = index
          break
      else:
        return self.list_directory(path)
    ctype = self.guess_type(path)
    try:
      f = open(path, 'rb')
    except IOError:
      self.send_error(404, "File not found")
      return None
    self.send_response(200)
    self.send_header("Content-type", ctype)
    fs = os.fstat(f.fileno())
    self.send_header("Content-Length", str(fs[6]))
    self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
    self.end_headers()
    return f

send_head 方法通过uri的path分析得到客户请求的网路路径。构造head的mime元信息并发送到客户端,然后返回一个打开path的文件句柄。

2.copyfile
do_GET的下一步就是通过 copyfile方法,将客户请求的path的文件数据写入到缓冲可写文件中,发送给客户端。

3.list_directory
SimpleHTTPServer模块还提供了list_directory方法,用于响应path是一个目录,而不是文件的情况。

def list_directory(self, path):
  try:
    list = os.listdir(path)
  except os.error:
    self.send_error(404, "No permission to list directory")
    return None
  list.sort(key=lambda a: a.lower())
  f = StringIO()
  displaypath = cgi.escape(urllib.unquote(self.path))
  f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
  f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
  f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
  f.write("<hr>\n<ul>\n")
  for name in list:
    fullname = os.path.join(path, name)
    displayname = linkname = name
    # Append / for directories or @ for symbolic links
    if os.path.isdir(fullname):
      displayname = name + "/"
      linkname = name + "/"
    if os.path.islink(fullname):
      displayname = name + "@"
      # Note: a link to a directory displays with @ and links with /
    f.write('<li><a href="%s">%s</a>\n'
        % (urllib.quote(linkname), cgi.escape(displayname)))
  f.write("</ul>\n<hr>\n</body>\n</html>\n")
  length = f.tell()
  f.seek(0)
  self.send_response(200)
  encoding = sys.getfilesystemencoding()
  self.send_header("Content-type", "text/html; charset=%s" % encoding)
  self.send_header("Content-Length", str(length))
  self.end_headers()
  return f

由此可见,处理客户端的请求,只需要使用 send_reponse, send_header 和 end_headers ,就能向客户端发送reponse。

4.自定义http服务
定义一个CustomHTTPRequestHadnler继承自BaseHTTPRequestHandler。在其内实现do_GET 方法来处理get请求。

然后再定义一个CustomHTTPServer继承自HTTPServer,它接受CustomHTTPRequestHadnler作为自己的handler。简单的代码如下:

# -*- coding: utf-8 -*-

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer


class CustomHTTPRequestHandler(BaseHTTPRequestHandler):
  def do_GET(self):
    self.send_response(200)
    self.send_header('Content-type', 'text/html')
    self.end_headers()
    self.wfile.write("hello world\r\n")


class CustomHTTPServer(HTTPServer):
  def __init__(self, host, port):
    HTTPServer.__init__(self, (host, port), CustomHTTPRequestHandler)


def main():
  server = CustomHTTPServer('127.0.0.1', 8000)
  server.serve_forever()


if __name__ == '__main__':
  main()

使用curl访问可以得到

➜ ~ curl http://127.0.0.1:8000
hello world
➜ ~

控制台会打出访问的log。

127.0.0.1 - - [01/Jun/2015 11:42:33] "GET / HTTP/1.1" 200 -

从socket的建立,select的IO模式,再到Server和Handler的组合构建服务。我们已经熟悉了python的基本网络编程。python的web开发中,更多是使用WSGI协议。实现该协议的还有 uWSGI和gunicorn等库。相比那些库,python内部提供了一个wsgiref模块,实现了一个简单wsgi服务--simple_server。

接下来将会通过分析simple_server,更好的掌握WSGI协议。

Python 相关文章推荐
Python中time模块与datetime模块在使用中的不同之处
Nov 24 Python
详解Swift中属性的声明与作用
Jun 30 Python
使用python实现ANN
Dec 20 Python
PyCharm 常用快捷键和设置方法
Dec 20 Python
python表格存取的方法
Mar 07 Python
pycharm 在windows上编辑代码用linux执行配置的方法
Oct 27 Python
Python设计模式之代理模式实例详解
Jan 19 Python
python处理excel绘制雷达图
Oct 18 Python
python 基于dlib库的人脸检测的实现
Nov 08 Python
Python任务自动化工具tox使用教程
Mar 17 Python
使用OpenCV对车道进行实时检测的实现示例代码
Jun 19 Python
使用pandas读取表格数据并进行单行数据拼接的详细教程
Mar 03 Python
Python的Tornado框架的异步任务与AsyncHTTPClient
Jun 27 #Python
深入解析Python中的descriptor描述器的作用及用法
Jun 27 #Python
Python中的字符串查找操作方法总结
Jun 27 #Python
解析Python中的__getitem__专有方法
Jun 27 #Python
详解Python中的__getitem__方法与slice对象的切片操作
Jun 27 #Python
Python使用smtplib模块发送电子邮件的流程详解
Jun 27 #Python
Python教程之全局变量用法
Jun 27 #Python
You might like
php中引用符号(&amp;)的使用详解
2013/11/13 PHP
php多维数组去掉重复值示例分享
2014/03/02 PHP
PHP中如何防止外部恶意提交调用ajax接口
2016/04/11 PHP
php写app接口并返回json数据的实例(分享)
2017/05/20 PHP
jquery加载页面的方法(页面加载完成就执行)
2011/06/21 Javascript
在网站上应该用的30个jQuery插件整理
2011/11/03 Javascript
解决遍历时Array.indexOf产生的性能问题
2012/07/03 Javascript
JavaScript中json使用自己总结
2013/08/13 Javascript
js实现幻灯片效果(基于jquery插件)
2013/11/05 Javascript
通过pjax实现无刷新翻页(兼容新版jquery)
2014/01/31 Javascript
js 获取浏览器版本以此来调整CSS的样式
2014/06/03 Javascript
node.js中的fs.fchmod方法使用说明
2014/12/16 Javascript
JavaScript 异常处理 详解
2015/02/06 Javascript
JS实现的倒计时效果实例(2则实例)
2015/12/23 Javascript
js实现有过渡渐变效果的图片轮播相册(兼容IE,ff)
2016/01/19 Javascript
JS中常用的输出方式(五种)
2016/06/12 Javascript
JavaScript中的对象和原型(一)
2016/08/12 Javascript
完美的js div拖拽实例代码
2016/09/24 Javascript
jQuery使用bind函数实现绑定多个事件的方法
2017/10/11 jQuery
VuePress 静态网站生成方法步骤
2019/02/14 Javascript
[02:37]2018DOTA2亚洲邀请赛赛前采访 VP.no[o]ne心中最强SOLO是谁
2018/04/04 DOTA
python备份文件以及mysql数据库的脚本代码
2013/06/10 Python
python中实现指定时间调用函数示例代码
2017/09/08 Python
浅谈Python NLP入门教程
2017/12/25 Python
python调用百度地图WEB服务API获取地点对应坐标值
2019/01/16 Python
Python一行代码解决矩阵旋转的问题
2019/11/30 Python
python实现FTP循环上传文件
2020/03/20 Python
在 Python 中使用 7zip 备份文件的操作
2020/12/11 Python
竞选宣传委员演讲稿
2014/05/24 职场文书
个人承诺书格式
2014/06/03 职场文书
师德承诺书
2015/01/20 职场文书
监察建议书
2015/02/04 职场文书
销售内勤岗位职责
2015/02/10 职场文书
2015年行政部工作总结
2015/04/28 职场文书
《曾国藩家书》读后感——读家书,立家风
2019/08/21 职场文书
Python深度学习之实现卷积神经网络
2021/06/05 Python