侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

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

Python  /  管理员 发布于 7年前   230

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

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

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

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

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

server.py

import socketHOST = '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 socketHOST = 'localhost'PORT = 5000BUFFER_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连接会话。如果是要断开连接,大致过程是:

上图也很清晰的表明了三次握手的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="https:/article/%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, HTTPServerclass 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:8000hello 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 socket实现的简单通信功能示例
  • Python基于socket模块实现UDP通信功能示例
  • Python网络编程使用select实现socket全双工异步通信功能示例
  • php实现与python进行socket通信的方法示例
  • Python socket网络编程TCP/IP服务器与客户端通信
  • 利用Python中SocketServer 实现客户端与服务器间非阻塞通信
  • python实现简单socket通信的方法
  • Python通过websocket与js客户端通信示例分析
  • python socket通信编程实现文件上传代码实例


  • 上一条:
    总结网络IO模型与select模型的Python实例讲解
    下一条:
    Python的Tornado框架的异步任务与AsyncHTTPClient
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在python语言中Flask框架的学习及简单功能示例(0个评论)
    • 在Python语言中实现GUI全屏倒计时代码示例(0个评论)
    • Python + zipfile库实现zip文件解压自动化脚本示例(0个评论)
    • python爬虫BeautifulSoup快速抓取网站图片(1个评论)
    • vscode 配置 python3开发环境的方法(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2018-04
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2022-01
    • 2023-07
    • 2023-10
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客