原始实现VS框架实现
“掌握一个类似于框架的高级工具是有用的,但是基础的东西可以让你永远不被淘汰。不要被工具限制了自己的发展。 ”在当今Python服务器框架 (framework, 比如Django, Twisted, web.py等等) 横行的时代,从底层的socket开始写服务器似乎是一个出力不讨好的笨方法。
框架的意义在于掩盖底层的细节,提供一套对于开发人员更加友好的API,并处理诸如MVC的布局问题。框架允许我们快速的构建一个成型而且成熟的Python服务器。然而,框架本身也是依赖于底层(比如socket)。对于底层socket的了解,不仅可以帮助我们更好的使用框架,更可以让我们明白框架是如何设计的。
更进一步, ”如果拥有良好的底层socket编程知识和其他系统编程知识,你完全可以设计并开发一款自己的框架。如果你可以从底层socket开始,实现一个完整的Python服务器,支持用户层的协议,并处理好诸如MVC(Model-View-Control)、多线程(threading)等问题,并整理出一套清晰的函数或者类,作为接口(API)呈现给用户,你就相当于设计了一个框架。“
我们已经看到:
许多成功的网站都是利用动态语言(比如Python, Ruby或者PHP,比如twitter和facebook)快速开发,在网站成功之后,将代码转换成诸如C和JAVA这样一些效率比较高的语言,从而让服务器能更有效率的面对每天亿万次的请求。在这样一些时间,底层的重要性,就远远超过了框架。
预备知识:TCP/IP和socket
我们需要对网络传输,特别是TCP/IP协议和socket有一定的了解。socket是进程间通信的一种方法 (参考Linux进程间通信),它是基于网络传输协议的上层接口。socket有许多种类型,比如基于TCP协议或者UDP协议(两种网络传输协议)。其中又以TCP socket最为常用。
socket接口是实际上是操作系统提供的系统调用。 TCP socket与双向管道(duplex PIPE)有些类似,一个进程向socket的一端写入或读取文本流,而另一个进程可以从socket的另一端读取或写入,比较特别是,这两个建立socket通信的进程可以分别属于两台不同的计算机。
所谓的TCP协议,就是规定了一些通信的守则,以便在网络环境下能够有效实现上述进程间通信过程。双向管道(duplex PIPE)存活于同一台电脑中,所以不必区分两个进程的所在计算机的地址,而socket必须包含有地址信息,以便实现网络通信。
一个socket包含四个地址信息: 两台计算机的IP地址和两个进程所使用的端口(port)。IP地址用于定位计算机,而port用于定位进程 (一台计算机上可以有多个进程分别使用不同的端口)。
TCP socket
在互联网上,我们可以让某台计算机作为服务器。服务器开放自己的端口,被动等待其他计算机连接。当其他计算机作为客户,主动使用socket连接到服务器的时候,服务器就开始为客户提供服务。 请求新浪网页
# 导入socket模块
import socket
# 创建一个客户端socket
# 参数1: 使用 socket.AF_INET 表示使用的ip协议 版本,socket.AF_INET 是ipv4, socket.AF_INET6 表示的是ipv6
# 参数2: SOCK_STREAM 表示数据以流的形式传输
clientSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 连接服务器--打招呼
# 参数address是服务器地址, 该地址包括ip地址,端口号 ,参数是元组的形式,第一个是IP地址. 第二是端口号
# http://www.sina.com.cn/ 域名 ------> DNS域名服务器 ---->ip地址
clientSocket.connect(("www.baidu.com.cn",80))
# 发起请求,请求新浪
# 需要字节形式的数据
clientSocket.send(b"GET / HTTP/1.1\r\nHost:www.baidu.com.cn\r\n\r\n")
# 创建一个list来接收所有的数据
listData = []
# 将数据写入到文件中去
# wf = open("sina.html","wb")
# 接收数据,参数表示接受的数据大小
while True:
content = clientSocket.recv(1024)
if len(content) == 0: # 接收完毕
break
listData.append(content)
# wf.write(content)
# wf.flush()
# 字节转换成字符串
strData = b"".join(listData).decode("utf-8")
# print(strData)
# wf.close()
# 关闭连接
clientSocket.close()
# 将响应头 与 html内容分开, 参数2表示只分割一次
head,htmlbody = strData.split("\r\n\r\n",1)
# print(head)
print(htmlbody)
复制代码
首先是服务器端: 我们使用bind()方法来赋予socket以固定的地址和端口,并使用listen()方法来被动的监听该端口。当有客户尝试用connect()方法连接的时候,服务器使用accept()接受连接,从而建立一个连接的socket:
import socket
# Address
HOST = ''
PORT = 8000
reply = 'Yes'
# 创建套接字,绑定套接字到本地IP与端口
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
# passively wait, 3: maximum number of connections in the queue
s.listen(3)
# accept and establish connectionconn,
addr = s.accept()
# receive message
request = conn.recv(1024)
print 'request is: ',request
print 'Connected by', addr
# send message
conn.sendall(reply)
# close connection
conn.close()
复制代码
将以上保存为文件org_ser.py
其次,客户端:我们主动使用connect()方法来搜索服务器端的IP地址和端口,以便客户可以找到服务器,并建立连接。
import socket
# Address
HOST = '172.20.202.155'
PORT = 8000
request = 'can you hear me?'
# configure socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
# send message
s.sendall(request)
# receive message
reply = s.recv(1024)
print 'reply is: ',reply
# close connection
s.close()
复制代码
调试运行:
左上角是用浏览器对服务器进行访问的结果,服务器回传了“Yes”,对应服务器中的代码:reply = 'Yes'。中间图是服务器运行图,最下面是客户端运行图,可以看出客户端向服务器请求的内容为:request = 'can you hear me?',而客户端得到的反馈是“Yes”。
程序运行正常
作者:rebirth_2017 链接:www.jianshu.com/p/a324616b8… 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。