Python实现简易TCP与HTTP服务器详解
本文深入解析Python中Socket编程原理,从底层TCP连接建立到高层HTTP协议实现,逐步演示如何编写简易服务器。内容涵盖Socket基础、TCP/IP通信机制、HTTP请求响应格式解析,以及基于Python标准库实现静态资源服务的具体代码示例,适合希望理解网络通信底层机制的开发者。
Socket接口本质上是操作系统提供的系统调用。使用Socket并不局限于Python,开发者可以使用C或Java编写功能相同的Socket服务器,且各语言调用Socket的方式高度相似(例如Apache服务器便是由C语言实现)。
Web框架可视为预置好的服务器组件,其优势在于处理底层细节以加速开发,但框架通常无法跨语言通用,且受限于Python自身的性能瓶颈。许多知名网站初期采用Python、Ruby或PHP等动态语言快速迭代,待业务成功后,常将核心逻辑迁移至C或Java等高性能语言,以应对每日亿级请求的高并发压力。
TCP/IP协议栈与Socket技术基础
Socket是进程间通信(IPC)的一种机制,位于网络传输协议之上。Socket类型多样,包括基于TCP协议和UDP协议的实现,其中TCP Socket最为常用。TCP Socket类似于双向管道(duplex PIPE),允许一个进程向Socket一端写入或读取文本流,另一端进程则进行对应操作。其独特之处在于,建立通信的两个进程可位于不同的计算机上,仅需通过网络链路和通信协议即可连接。
TCP协议定义了网络环境下实现上述进程间通信的具体规则。与仅存在于单台计算机内的双向管道不同,Socket通信必须包含地址信息,以确保数据能在网络中准确路由至目标计算机。
一个Socket连接包含四个关键地址信息:两台计算机的IP地址以及两个进程所使用的端口(port)。IP地址用于定位具体的计算机设备,而端口用于区分同一台计算机上的不同进程(一台计算机可运行多个进程,各自占用不同端口)。
TCP Socket通信机制
我们可以指定某台计算机作为服务器,该服务器开放特定端口,被动等待其他计算机连接该端口。当其他计算机作为客户端,主动使用Socket发起连接时,服务器便开始提供服务,双方由此建立双向通信通道。
在Python中,我们使用标准库中的socket模块进行底层的Socket编程。
服务器端实现首先通过bind()方法为Socket绑定固定的地址和端口,并使用listen()方法被动监听该端口。当客户端尝试通过connect()方法连接时,服务器使用accept()接受连接,从而建立一个独立的连接Socket。
服务器端代码示例(server.py)
import socket
# Address
HOST = '127.0.0.1'
PORT = 8000
# Configure socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
#socket.socket()创建一个socket对象,并说明socket使用的是IPv4(AF_INET,IP version 4)
#和TCP协议(SOCK_STREAM)。
# passively wait, 3: maximum number of connections in the queue
s.listen(3)
# accept and establish connection
conn, addr = s.accept()
# receive message
request = conn.recv(1024)
print ('request is: ',request)
print ('Connected by', addr)
# send message
reply = 'Yes'
"""
好像3.* socket.send 传递的数据必须是bytes。不能是str。发送和接收数据时做下编码转换就可以了。修改如下:s.sendall(request.encode())
"""
conn.sendall(reply.encode())
# close connection
conn.close()
客户端代码示例(client.py)
import socket # Address HOST = '127.0.0.1' PORT = 8000 request = 'can you hear me?' # configure socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) """
注意:在Python 3.*版本中,socket.send()传递的数据必须是bytes类型,不能直接传递str。在发送和接收数据时,需进行编码转换,例如修改为s.sendall(request.encode())。
"""
# send message
s.sendall(request.encode())
# receive message
reply = s.recv(1024)
print ('reply is: ',reply)
# close connection
s.close()
我们可以对Socket的两端调用recv()方法接收信息,调用sendall()方法发送信息,从而实现分处两台计算机的两个进程间的通信。通信结束后,使用close()方法关闭Socket连接。
基于TCP Socket的HTTP服务器实现
利用TCP Socket建立两台远程计算机的连接即可实现通信。然而,Socket传输自由度极高,容易引发安全和兼容性问题。因此,我们通常利用应用层协议(如HTTP协议)来规范Socket的使用规则及传输信息的格式。
HTTP协议通过请求-响应(request-response)模式来使用TCP Socket。
客户端向服务器发送一段文本作为request,服务器端在接收到request后,向客户端发送一段文本作为response。
完成一次request-response交互后,该TCP Socket连接即被废弃。后续的request将建立新的Socket连接。
request和response本质上是文本,但HTTP协议对这两类文本均有特定的格式要求。
HTTP服务器代码示例(Httpserver.py)
import socket
# Address
HOST = ''
PORT = 8000
# Prepare HTTP response
text_content = '''HTTP/1.x 200 OK
Content-Type: text/html
<head>
<title>WOW</title>
</head>
<html>
<p>Wow, Python Server</p>
<IMG src="test.jpg"/>
</html>
'''
# Read picture, put into HTTP format
f = open('test.jpg','rb')
pic_content = '''
HTTP/1.x 200 OK
Content-Type: image/jpg
'''
pic_content = pic_content.bytes()+ f.read()
f.close()
# Configure socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
# infinite loop, server forever
while True:
# 3: maximum number of requests waiting
s.listen(3)
conn, addr = s.accept()
request = conn.recv(1024)
method = request.split(' ')[0]
src = request.split(' ')[1]
# deal with GET method
if method == 'GET':
# ULR
if src == '/test.jpg':
content = pic_content
else: content = text_content
print ('Connected by', addr)
print ('Request is:', request)
conn.sendall(content.bytes())
# close connection
conn.close()
代码解读
服务器会根据request的内容,向客户传输text_content或pic_content中的一条,作为response文本返回。
整个response结构分为三部分:起始行(start line)、头信息(head)和主体(body)。
起始行为第一行:HTTP/1.x 200 OK。它由空格分为三个片段:HTTP/1.x表示使用的HTTP版本;200为状态码(status code),是HTTP协议规定的表示服务器正常接收并处理请求的代码;OK是供人类阅读的状态码描述。
头信息紧随起始行,与主体之间由一个空行分隔。此处text_content或pic_content仅包含一行头信息,例如text_content使用Content-Type: text/html表示主体信息类型为HTML文本。
主体信息为HTML文件或JPG文件的内容。注意,对于JPG文件,我们使用'rb'模式打开,这是为了与Windows系统兼容。因为在Windows下,JPG被视为二进制(binary)文件,而在UNIX系统中,通常不区分文本文件和二进制文件。
HTTP客户端代码示例(HttpClient.py)
客户端程序可由开发者自行编写,但通常直接使用浏览器作为客户端。
request由客户端程序发送给服务器。尽管request也可像response那样分为三部分,但其格式与response不同。request由客户发送给服务器,例如以下是一个典型的request示例:
GET /test.jpg HTTP/1.x Accept: text/*
request的起始行分为三部分:第一部分为请求方法(request method),第二部分是URL,第三部分为HTTP版本。常见的request method包括GET、PUT、POST、DELETE、HEAD,其中最常用的是GET和POST。GET用于请求服务器发送资源给客户,POST用于请求服务器接收客户送来的数据。打开网页时通常使用GET方法,填写并提交表单时通常使用POST方法。第二部分为URL,通常指向一个资源(服务器上的资源或其他地方的资源),如当前指向服务器当前目录下的test.jpg。
根据HTTP协议规定,服务器需根据请求执行相应操作。正如服务器程序中所示,Python程序首先检查request的方法,随后根据URL的不同生成不同的response(text_content或pic_content),最后将该response发送回客户端。
测试环节
在终端运行上述Httpserver程序作为服务器端,同时打开一个浏览器作为客户端(若有时间,也可用Python编写客户端,原理与上述TCP Socket客户端程序类似)。在浏览器地址栏输入:127.0.0.1:8000。
使用浏览器的调试功能(F12)
GET / HTTP/1.1 Host: 127.0.0.1:8000 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip, deflate Connection: keep-alive
浏览器接收到text_content后,发现正文HTML中包含<img>标签,得知需要获取text.jpg文件以补充图片,于是立即发出第二个请求:
GET /test.jpg HTTP/1.1 Host: 127.0.0.1:8000 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1 Accept: image/png,image/*;q=0.8,*/*;q=0.5 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip, deflate Connection: keep-alive Referer: http://127.0.0.1:8000/
小结
1) 在上述服务器程序中,我们使用while循环使服务器持续运行。实际上,可以利用多线程知识,将while循环中的内容改为多进程或多线程工作模式。
2) 当前服务器程序尚不完善,可让Python程序调用其他功能以实现更复杂的服务。例如制作时间服务器,向客户返回日期和时间;或使用Python内置数据库,构建完整的LAMP服务器。
3) socket模块属于底层包。Python标准库中还提供高层包,如SocketServer、SimpleHTTPServer、CGIHTTPServer、cgi等。这些包旨在帮助用户更轻松地使用Socket,理解起来较为直观。利用这些高层包,你可以编写出相当成熟的服务器。
4) 在经历所有底层开发的繁琐后,会发现框架的使用极为便捷,因此建议转向使用框架。当然,你也可以选择参与框架的开发工作。
总结
以上介绍了使用Python实现简单服务器功能的方法,希望对大家有所帮助。如有任何疑问,请留言,小编会及时回复。感谢大家对本站的支持!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













