本文将从零开始,通过一个简单的Python HTTP服务器示例,带你走进网络编程的世界。
网络编程简单来说就是让不同计算机上的程序能够相互通信。就像人与人之间通过语言交流一样,计算机之间也有自己的"语言"——网络协议。
HTTP(HyperText Transfer Protocol)是万维网的基础协议,我们每天浏览网页时都在使用它。它采用"请求-响应"模式:
客户端(如浏览器)发送请求
服务器接收并处理请求
服务器返回响应
Socket是网络通信的基本操作单元,可以理解为网络通信的"端点"。就像打电话需要两部电话机一样,网络通信需要两个Socket。
让我们来看这段代码,我将分部分详细解释每一行:
import socket
import threading
import os
socket
:Python的标准库,提供网络通信功能
threading
:用于多线程编程,让服务器能同时处理多个请求
os
:操作系统接口,这里用于检查文件是否存在
def server(): tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
2.2 创建Socket对象
这里创建了一个TCP Socket:
socket.AF_INET
:表示使用IPv4地址族
socket.SOCK_STREAM
:表示使用面向连接的TCP协议
TCP协议的特点:
可靠传输:数据不会丢失
有序传输:数据按发送顺序到达
面向连接:通信前需要建立连接
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
SO_REUSEADDR
选项允许重用本地地址,这在服务器重启时特别有用,可以避免"地址已在使用中"的错误。
tcp_server.bind(('', 8000)) # 默认8000
''
:表示绑定到所有可用网络接口
8000
:端口号,HTTP默认是80,这里使用8000避免权限问题
端口号小知识:
0-1023:知名端口,需要管理员权限
1024-49151:注册端口
49152-65535:动态/私有端口
tcp_server.listen(128)
128
是backlog参数,表示操作系统可以挂起的最大连接数。当服务器忙时,新连接可以排队等待。
print("Server,Start!")
while True:
new_server, ip_port = tcp_server.accept()
accept()
方法会阻塞程序,直到有客户端连接。它返回:
一个新的Socket对象(new_server
):用于与这个特定客户端通信
客户端地址和端口(ip_port
)
recv_data = new_server.recv(4096).decode()
recv(4096)
:从Socket接收最多4096字节数据
decode()
:将字节数据解码为字符串
HTTP请求示例:
GET /about HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0
...
new_path = recv_data.split(" ",2)[1].replace("/","")
split(" ",2)
:按空格分割请求行,最多分割2次
[1]
:取第二部分,即请求路径(如/about)
replace("/","")
:去掉路径前的斜杠
例如:GET /about HTTP/1.1
→ about
if new_path == "" or new_path == "favicon.ico":
new_path = "index"
空路径(如GET / HTTP/1.1
)→ 返回index页面
浏览器会自动请求favicon.ico
,我们也返回index页面
if not os.path.exists(f"./templates/{new_path}.html"):
new_path = "/error"
如果请求的页面不存在,则返回错误页面。注意这里/error
会被前面的代码处理为error
。
http_header = "HTTP/1.1 200 OK\r\n"
http_body = "Server: PWS/1.0\r\n"
HTTP响应格式:
状态行:HTTP/1.1 200 OK
响应头:Server: PWS/1.0
空行:\r\n
响应体:HTML内容
with open(f"./templates/{new_path}.html", "r",encoding="utf-8") as f:
data = f.read()
使用with
语句安全地打开文件,并指定UTF-8编码读取内容。
python
total_path = (http_header + http_body + "\r\n"+data).encode() new_server.send(total_path) new_server.close()
将响应头和内容合并
encode()
:转换为字节数据
send()
:发送给客户端
close()
:关闭连接(HTTP/1.0是短连接)
def start():
main_thread = threading.Thread(target=server)
main_thread.start()
Thread
创建新线程运行server
函数,这样主线程可以继续执行其他任务。
if __name__== "__main__" :
start()
__name__ == "__main__"
确保代码只在直接运行时执行,而不是被导入时执行。
一个完整的HTTP请求包括:
GET /about HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Accept: text/html
\r\n
[请求体]
我们的服务器生成的响应:
HTTP/1.1 200 OK
Server: PWS/1.0
...
常见状态码:
200 OK:请求成功
404 Not Found:资源不存在
500 Internal Server Error:服务器内部错误
web_server/
├── server.py
└── templates/
├── index.html
├── about.html
└── error.html
index.html示例:
Home Page
Welcome to My Server
About Us
python server.py
访问http://localhost:8000
查看效果
目前只处理GET请求,可以扩展支持POST、PUT等。
使用字典或装饰器实现更灵活的路由。
添加对CSS、JavaScript和图片的支持。
对于CPU密集型任务,多进程比多线程更有效。
8000是一个常用开发端口,避免使用需要root权限的端口(如80)。
当前使用多线程,也可以考虑异步IO(asyncio)。
绑定到0.0.0.0
而不仅仅是localhost
。
通过这个简单的HTTP服务器项目,我们学习了:
Socket网络编程基础
HTTP协议的基本结构
Python多线程应用
文件操作和异常处理
网络编程看似复杂,但拆解后其实很容易理解。希望这篇教程能帮你迈出网络编程的第一步!
import socket
import threading
import os
def server():
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcp_server.bind(('', 8000)) # 默认8000
tcp_server.listen(128)
print("Server,Start!")
while True:
new_server, ip_port = tcp_server.accept()
recv_data = new_server.recv(4096).decode()
new_path = recv_data.split(" ",2)[1].replace("/","")
if new_path == "" or new_path == "favicon.ico":
new_path = "index"
if not os.path.exists(f"./templates/{new_path}.html"):
new_path = "/error"
http_header = "HTTP/1.1 200 OK\r\n"
http_body = "Server: PWS/1.0\r\n"
with open(f"./templates/{new_path}.html", "r",encoding="utf-8") as f:
data = f.read()
total_path = (http_header + http_body + "\r\n"+data).encode()
new_server.send(total_path)
new_server.close()
def start():
main_thread = threading.Thread(target=server)
main_thread.start()
if __name__== "__main__" :
start()