结合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网络编程学习笔记(五):socket的一些补充
Jun 09 Python
Python使用redis pool的一种单例实现方式
Apr 16 Python
使用Python读取大文件的方法
Feb 11 Python
Windows环境下python环境安装使用图文教程
Mar 13 Python
PyQt Qt Designer工具的布局管理详解
Aug 07 Python
python3爬取torrent种子链接实例
Jan 16 Python
Python3.x+pyqtgraph实现数据可视化教程
Mar 14 Python
Python使用20行代码实现微信聊天机器人
Jun 05 Python
Python编写memcached启动脚本代码实例
Aug 14 Python
Django Form常用功能及代码示例
Oct 13 Python
Python爬虫UA伪装爬取的实例讲解
Feb 19 Python
python图像处理基本操作总结(PIL库、Matplotlib及Numpy)
Jun 08 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函数http_build_query使用详解
2014/08/20 PHP
jquery简单体验
2007/01/10 Javascript
幻宇的层模拟窗口效果-提供演示和下载
2007/01/20 Javascript
Javascript 判断 object 的特定类转载
2007/02/01 Javascript
可以文本显示的公告栏的js代码
2007/03/11 Javascript
jquery插件开发方法(初学者)
2012/02/03 Javascript
js加强的经典分页实例
2013/03/15 Javascript
JS定义回车事件(实现代码)
2013/07/08 Javascript
jquery实现点击文字可编辑并修改保存至数据库
2014/04/15 Javascript
js类定义函数时用prototype与不用的区别示例介绍
2014/06/10 Javascript
JavaScript代码实现禁止右键、禁选择、禁粘贴、禁shift、禁ctrl、禁alt
2015/11/17 Javascript
input 禁止输入特殊字符的四种实现方式
2016/08/24 Javascript
JS实现颜色动态淡化效果
2017/03/06 Javascript
javascript中toFixed()四舍五入使用方法详解
2018/09/28 Javascript
利用Vue构造器创建Form组件的通用解决方法
2018/12/03 Javascript
详解微信小程序之一键复制到剪切板
2019/04/24 Javascript
解决layui轮播图有数据不显示的情况
2019/09/16 Javascript
JS中箭头函数与this的写法和理解
2021/01/14 Javascript
浅析Python中的join()方法的使用
2015/05/19 Python
实例解析Python的Twisted框架中Deferred对象的用法
2016/05/25 Python
Python中线程的MQ消息队列实现以及消息队列的优点解析
2016/06/29 Python
python提取log文件内容并画出图表
2019/07/08 Python
英国航空官网:British Airways
2016/09/11 全球购物
美国领先的礼品卡网站:GiftCards.com
2016/11/02 全球购物
真正的英国宝藏:Mappin & Webb
2019/05/05 全球购物
YII2 全局异常处理深入讲解
2021/03/24 PHP
应届大专毕业生个人自荐信
2013/09/22 职场文书
摄影专业毕业生求职信
2014/03/13 职场文书
一体化教学实施方案
2014/05/10 职场文书
校园学雷锋广播稿
2014/10/08 职场文书
工作保证书
2015/01/17 职场文书
大学生求职简历自我评价
2015/03/02 职场文书
2015年世界无车日活动总结
2015/03/23 职场文书
2016春节放假通知范文
2015/08/18 职场文书
导游词之黄果树瀑布
2019/09/20 职场文书
HTML 里 img 元素的 src 和 srcset 属性的区别详解
2023/05/21 HTML / CSS