Python 网络编程
目录
- 网络编程基础
- Socket 编程
- TCP 编程
- UDP 编程
- HTTP 协议基础
- urllib 模块
- Requests 库
- Web 服务器
- WebSocket
- FTP 编程
- 电子邮件
- SSH 连接
- 综合实战
网络编程基础
网络模型
OSI 七层模型:
- 物理层
- 数据链路层
- 网络层(IP)
- 传输层(TCP/UDP)
- 会话层
- 表示层
- 应用层(HTTP、FTP、SMTP 等)
IP 地址和端口
import socket
# 获取本机信息
hostname = socket.gethostname()
print(f"主机名: {hostname}")
ip_address = socket.gethostbyname(hostname)
print(f"IP 地址: {ip_address}")
# 常见端口
common_ports = {
"HTTP": 80,
"HTTPS": 443,
"FTP": 21,
"SSH": 22,
"SMTP": 25,
"DNS": 53,
"MySQL": 3306,
"Redis": 6379
}
for service, port in common_ports.items():
print(f"{service}: {port}")
Socket 编程
Socket 基础
Socket 是网络通信的端点,提供进程间通信的能力。
import socket
# 创建 Socket
# AF_INET: IPv4, AF_INET6: IPv6
# SOCK_STREAM: TCP, SOCK_DGRAM: UDP
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Socket 选项
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 设置超时
sock.settimeout(5) # 5秒超时
# 关闭 Socket
sock.close()
地址格式
# IPv4 地址格式: (host, port)
address_v4 = ("127.0.0.1", 8080)
# IPv6 地址格式: (host, port, flowinfo, scopeid)
address_v6 = ("::1", 8080, 0, 0)
# 域名解析
import socket
ip = socket.gethostbyname("www.example.com")
print(f"IP: {ip}")
TCP 编程
TCP 服务器
import socket
import threading
def handle_client(client_socket, address):
"""处理客户端连接"""
print(f"[新连接] {address}")
try:
while True:
# 接收数据
data = client_socket.recv(1024)
if not data:
break
message = data.decode('utf-8')
print(f"[{address}] 收到: {message}")
# 发送响应
response = f"服务器收到: {message}"
client_socket.send(response.encode('utf-8'))
except Exception as e:
print(f"[{address}] 错误: {e}")
finally:
client_socket.close()
print(f"[断开连接] {address}")
def start_tcp_server(host='127.0.0.1', port=8080):
"""启动 TCP 服务器"""
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((host, port))
server.listen(5)
print(f"[监听] {host}:{port}")
try:
while True:
client_socket, address = server.accept()
# 为每个客户端创建线程
thread = threading.Thread(
target=handle_client,
args=(client_socket, address)
)
thread.daemon = True
thread.start()
except KeyboardInterrupt:
print("\n[服务器关闭]")
finally:
server.close()
if __name__ == "__main__":
start_tcp_server()
TCP 客户端
import socket
def tcp_client(host='127.0.0.1', port=8080):
"""TCP 客户端"""
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
# 连接服务器
client.connect((host, port))
print(f"[已连接] {host}:{port}")
while True:
# 发送消息
message = input("输入消息 (quit 退出): ")
if message.lower() == 'quit':
break
client.send(message.encode('utf-8'))
# 接收响应
response = client.recv(1024)
print(f"服务器响应: {response.decode('utf-8')}")
except Exception as e:
print(f"错误: {e}")
finally:
client.close()
print("[连接关闭]")
if __name__ == "__main__":
tcp_client()
文件传输示例
import socket
import os
def send_file(client_socket, filepath):
"""发送文件"""
filename = os.path.basename(filepath)
filesize = os.path.getsize(filepath)
# 发送文件名和大小
header = f"{filename}|{filesize}"
client_socket.send(header.encode('utf-8'))
# 发送文件内容
with open(filepath, 'rb') as f:
while True:
bytes_read = f.read(4096)
if not bytes_read:
break
client_socket.sendall(bytes_read)
print(f"文件发送完成: {filename}")
def receive_file(client_socket, save_dir="."):
"""接收文件"""
# 接收文件头
header = client_socket.recv(1024).decode('utf-8')
filename, filesize = header.split('|')
filesize = int(filesize)
filepath = os.path.join(save_dir, filename)
# 接收文件内容
bytes_received = 0
with open(filepath, 'wb') as f:
while bytes_received < filesize:
bytes_read = client_socket.recv(4096)
if not bytes_read:
break
f.write(bytes_read)
bytes_received += len(bytes_read)
print(f"文件接收完成: {filename}")
UDP 编程
UDP 服务器
import socket
def udp_server(host='127.0.0.1', port=9090):
"""UDP 服务器"""
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind((host, port))
print(f"[UDP 监听] {host}:{port}")
try:
while True:
# 接收数据
data, address = server.recvfrom(1024)
message = data.decode('utf-8')
print(f"[{address}] 收到: {message}")
# 发送响应
response = f"服务器收到: {message}"
server.sendto(response.encode('utf-8'), address)
except KeyboardInterrupt:
print("\n[服务器关闭]")
finally:
server.close()
if __name__ == "__main__":
udp_server()
UDP 客户端
import socket
def udp_client(host='127.0.0.1', port=9090):
"""UDP 客户端"""
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
while True:
message = input("输入消息 (quit 退出): ")
if message.lower() == 'quit':
break
# 发送数据
client.sendto(message.encode('utf-8'), (host, port))
# 接收响应
response, server = client.recvfrom(1024)
print(f"服务器响应: {response.decode('utf-8')}")
except Exception as e:
print(f"错误: {e}")
finally:
client.close()
if __name__ == "__main__":
udp_client()
TCP vs UDP 对比
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠传输 | 不可靠 |
| 顺序 | 保证顺序 | 不保证顺序 |
| 速度 | 较慢 | 较快 |
| 适用场景 | 文件传输、网页 | 视频流、游戏 |
HTTP 协议基础
HTTP 请求结构
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
HTTP 响应结构
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234
<html>...</html>
常用 HTTP 方法
- GET: 获取资源
- POST: 创建资源
- PUT: 更新资源
- DELETE: 删除资源
- PATCH: 部分更新
urllib 模块
基本用法
from urllib.request import urlopen, Request
from urllib.parse import urlencode, quote
import json
# GET 请求
response = urlopen("https://httpbin.org/get")
print(response.status) # 200
print(response.headers)
data = response.read().decode('utf-8')
print(data)
# POST 请求
post_data = urlencode({"key": "value", "name": "测试"}).encode('utf-8')
request = Request("https://httpbin.org/post", data=post_data)
response = urlopen(request)
print(response.read().decode('utf-8'))
# 添加请求头
headers = {
"User-Agent": "Mozilla/5.0",
"Accept": "application/json"
}
request = Request("https://httpbin.org/headers", headers=headers)
response = urlopen(request)
URL 编码
from urllib.parse import urlencode, quote, unquote, urlparse
# 编码
params = {"name": "张三", "age": 25}
encoded = urlencode(params)
print(encoded) # name=%E5%BC%A0%E4%B8%89&age=25
# 解码
decoded = unquote(encoded)
print(decoded) # name=张三&age=25
# 解析 URL
url = "https://www.example.com/path?name=test&page=1"
parsed = urlparse(url)
print(f"协议: {parsed.scheme}")
print(f"域名: {parsed.netloc}")
print(f"路径: {parsed.path}")
print(f"参数: {parsed.query}")
下载文件
from urllib.request import urlretrieve
# 下载文件
url = "https://httpbin.org/image/jpeg"
filename = "downloaded.jpg"
urlretrieve(url, filename)
print(f"文件已保存到: {filename}")
Requests 库
安装
pip install requests
基本用法
import requests
# GET 请求
response = requests.get("https://httpbin.org/get")
print(response.status_code) # 200
print(response.text)
print(response.json())
# POST 请求
data = {"name": "张三", "age": 25}
response = requests.post("https://httpbin.org/post", json=data)
print(response.json())
# PUT 请求
response = requests.put("https://httpbin.org/put", json={"key": "value"})
# DELETE 请求
response = requests.delete("https://httpbin.org/delete")
请求参数
import requests
# URL 参数
params = {"page": 1, "limit": 10}
response = requests.get("https://httpbin.org/get", params=params)
print(response.url) # https://httpbin.org/get?page=1&limit=10
# 请求头
headers = {
"User-Agent": "MyApp/1.0",
"Authorization": "Bearer token123"
}
response = requests.get("https://httpbin.org/headers", headers=headers)
# Cookie
cookies = {"session_id": "abc123"}
response = requests.get("https://httpbin.org/cookies", cookies=cookies)
# 超时设置
try:
response = requests.get("https://httpbin.org/delay/5", timeout=3)
except requests.exceptions.Timeout:
print("请求超时")
文件上传
import requests
# 上传文件
files = {
"file": ("test.txt", open("test.txt", "rb"), "text/plain")
}
response = requests.post("https://httpbin.org/post", files=files)
print(response.json())
# 多个文件
files = [
("images", ("img1.jpg", open("img1.jpg", "rb"))),
("images", ("img2.jpg", open("img2.jpg", "rb")))
]
response = requests.post("https://httpbin.org/post", files=files)
Session 对象
import requests
# 创建会话
session = requests.Session()
# 设置默认请求头
session.headers.update({
"User-Agent": "MyApp/1.0"
})
# 设置 Cookie
session.cookies.set("session_id", "abc123")
# 多次请求共享状态
response1 = session.get("https://httpbin.org/cookies")
response2 = session.get("https://httpbin.org/headers")
# 关闭会话
session.close()
异常处理
import requests
from requests.exceptions import (
ConnectionError,
Timeout,
HTTPError,
RequestException
)
try:
response = requests.get("https://api.example.com/data", timeout=5)
response.raise_for_status() # 如果状态码不是 2xx,抛出异常
data = response.json()
print(data)
except ConnectionError:
print("网络连接错误")
except Timeout:
print("请求超时")
except HTTPError as e:
print(f"HTTP 错误: {e}")
except RequestException as e:
print(f"请求异常: {e}")
Web 服务器
简单 HTTP 服务器
from http.server import HTTPServer, SimpleHTTPRequestHandler
import webbrowser
import threading
def start_web_server(port=8000):
"""启动简单的 Web 服务器"""
server = HTTPServer(("127.0.0.1", port), SimpleHTTPRequestHandler)
print(f"服务器运行在 http://127.0.0.1:{port}")
# 自动打开浏览器
webbrowser.open(f"http://127.0.0.1:{port}")
try:
server.serve_forever()
except KeyboardInterrupt:
print("\n服务器关闭")
server.server_close()
if __name__ == "__main__":
start_web_server()
自定义 HTTP 服务器
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
class CustomHandler(BaseHTTPRequestHandler):
"""自定义请求处理器"""
def do_GET(self):
"""处理 GET 请求"""
if self.path == "/":
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
html = """
<html>
<head><title>Python Web Server</title></head>
<body>
<h1>欢迎使用 Python Web 服务器</h1>
<p><a href="/api/data">查看 API 数据</a></p>
</body>
</html>
"""
self.wfile.write(html.encode('utf-8'))
elif self.path == "/api/data":
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
data = {
"message": "Hello, World!",
"status": "success"
}
self.wfile.write(json.dumps(data, ensure_ascii=False).encode('utf-8'))
else:
self.send_error(404, "Not Found")
def do_POST(self):
"""处理 POST 请求"""
content_length = int(self.headers.get('Content-Length', 0))
post_data = self.rfile.read(content_length)
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
response = {
"received": post_data.decode('utf-8'),
"status": "success"
}
self.wfile.write(json.dumps(response).encode('utf-8'))
def log_message(self, format, *args):
"""自定义日志格式"""
print(f"[{self.client_address[0]}] {format % args}")
def run_server(host='127.0.0.1', port=8080):
"""运行服务器"""
server = HTTPServer((host, port), CustomHandler)
print(f"服务器运行在 http://{host}:{port}")
try:
server.serve_forever()
except KeyboardInterrupt:
print("\n服务器关闭")
server.server_close()
if __name__ == "__main__":
run_server()
WebSocket
安装
pip install websockets
WebSocket 服务器
import asyncio
import websockets
import json
connected_clients = set()
async def handler(websocket, path):
"""处理 WebSocket 连接"""
# 注册客户端
connected_clients.add(websocket)
print(f"[连接] 当前在线: {len(connected_clients)}")
try:
async for message in websocket:
print(f"[收到] {message}")
# 广播消息给所有客户端
broadcast_message = {
"type": "message",
"content": message,
"clients": len(connected_clients)
}
await broadcast(json.dumps(broadcast_message))
except websockets.exceptions.ConnectionClosed:
pass
finally:
# 注销客户端
connected_clients.discard(websocket)
print(f"[断开] 当前在线: {len(connected_clients)}")
async def broadcast(message):
"""广播消息"""
if connected_clients:
await asyncio.wait([
client.send(message)
for client in connected_clients
])
async def main():
"""启动 WebSocket 服务器"""
server = await websockets.serve(handler, "127.0.0.1", 8765)
print("WebSocket 服务器运行在 ws://127.0.0.1:8765")
await server.wait_closed()
if __name__ == "__main__":
asyncio.run(main())
WebSocket 客户端
import asyncio
import websockets
async def websocket_client():
"""WebSocket 客户端"""
uri = "ws://127.0.0.1:8765"
async with websockets.connect(uri) as websocket:
print("[已连接]")
# 启动接收消息的任务
receive_task = asyncio.create_task(receive_messages(websocket))
try:
while True:
message = input("输入消息: ")
await websocket.send(message)
except KeyboardInterrupt:
pass
finally:
receive_task.cancel()
async def receive_messages(websocket):
"""接收消息"""
try:
async for message in websocket:
print(f"[收到] {message}")
except asyncio.CancelledError:
pass
if __name__ == "__main__":
asyncio.run(websocket_client())
FTP 编程
FTP 客户端
from ftplib import FTP
import os
def ftp_example():
"""FTP 操作示例"""
# 连接 FTP 服务器
ftp = FTP("ftp.example.com")
ftp.login(user="username", passwd="password")
# 获取欢迎信息
print(ftp.getwelcome())
# 列出目录内容
ftp.dir()
# 切换目录
ftp.cwd("/public")
# 下载文件
with open("local_file.txt", "wb") as f:
ftp.retrbinary("RETR remote_file.txt", f.write)
# 上传文件
with open("local_file.txt", "rb") as f:
ftp.storbinary("STOR uploaded_file.txt", f)
# 创建目录
ftp.mkd("new_directory")
# 删除文件
ftp.delete("file_to_delete.txt")
# 删除目录
ftp.rmd("directory_to_delete")
# 关闭连接
ftp.quit()
# 匿名 FTP
def anonymous_ftp():
"""匿名 FTP 连接"""
ftp = FTP("ftp.debian.org")
ftp.login() # 匿名登录
ftp.cwd("/debian")
ftp.dir()
ftp.quit()
电子邮件
发送邮件(SMTP)
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
def send_email():
"""发送电子邮件"""
# 邮件配置
smtp_server = "smtp.gmail.com"
smtp_port = 587
sender_email = "your_email@gmail.com"
sender_password = "your_password"
receiver_email = "receiver@example.com"
# 创建邮件
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = "测试邮件"
# 邮件正文
body = "这是一封测试邮件"
message.attach(MIMEText(body, "plain"))
# 添加附件
filename = "document.pdf"
with open(filename, "rb") as attachment:
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header(
"Content-Disposition",
f"attachment; filename= {filename}"
)
message.attach(part)
# 发送邮件
try:
server = smtplib.SMTP(smtp_server, smtp_port)
server.ehlo()
server.starttls()
server.ehlo()
server.login(sender_email, sender_password)
server.sendmail(sender_email, receiver_email, message.as_string())
print("邮件发送成功")
except Exception as e:
print(f"邮件发送失败: {e}")
finally:
server.quit()
接收邮件(POP3/IMAP)
import poplib
import imaplib
import email
def receive_email_pop3():
"""使用 POP3 接收邮件"""
pop3_server = "pop.gmail.com"
username = "your_email@gmail.com"
password = "your_password"
# 连接服务器
server = poplib.POP3_SSL(pop3_server)
server.user(username)
server.pass_(password)
# 获取邮件数量
num_messages = len(server.list()[1])
print(f"共有 {num_messages} 封邮件")
# 获取最新邮件
if num_messages > 0:
response, lines, octets = server.retr(num_messages)
msg_content = b"\r\n".join(lines).decode("utf-8")
msg = email.message_from_string(msg_content)
print(f"主题: {msg['Subject']}")
print(f"发件人: {msg['From']}")
# 获取邮件正文
if msg.is_multipart():
for part in msg.walk():
if part.get_content_type() == "text/plain":
print(part.get_payload(decode=True).decode("utf-8"))
else:
print(msg.get_payload(decode=True).decode("utf-8"))
server.quit()
def receive_email_imap():
"""使用 IMAP 接收邮件"""
imap_server = "imap.gmail.com"
username = "your_email@gmail.com"
password = "your_password"
# 连接服务器
mail = imaplib.IMAP4_SSL(imap_server)
mail.login(username, password)
mail.select("inbox")
# 搜索邮件
status, messages = mail.search(None, "ALL")
email_ids = messages[0].split()
print(f"共有 {len(email_ids)} 封邮件")
# 获取最新邮件
if email_ids:
latest_email_id = email_ids[-1]
status, msg_data = mail.fetch(latest_email_id, "(RFC822)")
msg = email.message_from_bytes(msg_data[0][1])
print(f"主题: {msg['Subject']}")
print(f"发件人: {msg['From']}")
mail.close()
mail.logout()
SSH 连接
安装
pip install paramiko
SSH 客户端
import paramiko
def ssh_execute_command(hostname, username, password, command):
"""执行远程命令"""
# 创建 SSH 客户端
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
# 连接服务器
ssh.connect(hostname, username=username, password=password)
# 执行命令
stdin, stdout, stderr = ssh.exec_command(command)
# 获取输出
output = stdout.read().decode('utf-8')
error = stderr.read().decode('utf-8')
if output:
print(f"输出:\n{output}")
if error:
print(f"错误:\n{error}")
except Exception as e:
print(f"SSH 错误: {e}")
finally:
ssh.close()
def ssh_transfer_file(hostname, username, password, local_path, remote_path):
"""传输文件"""
transport = paramiko.Transport((hostname, 22))
transport.connect(username=username, password=password)
sftp = paramiko.SFTPClient.from_transport(transport)
try:
# 上传文件
sftp.put(local_path, remote_path)
print(f"文件已上传: {remote_path}")
# 下载文件
# sftp.get(remote_path, local_path)
except Exception as e:
print(f"SFTP 错误: {e}")
finally:
sftp.close()
transport.close()
# 使用密钥认证
def ssh_with_key(hostname, username, key_path, command):
"""使用密钥认证"""
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
private_key = paramiko.RSAKey.from_private_key_file(key_path)
ssh.connect(hostname, username=username, pkey=private_key)
stdin, stdout, stderr = ssh.exec_command(command)
print(stdout.read().decode('utf-8'))
ssh.close()
综合实战
实战1: RESTful API 客户端
"""
RESTful API 客户端
展示完整的 HTTP API 调用封装
"""
import requests
from typing import Dict, List, Optional, Any
import json
class APIClient:
"""RESTful API 客户端"""
def __init__(self, base_url: str, api_key: Optional[str] = None):
self.base_url = base_url.rstrip('/')
self.session = requests.Session()
# 设置默认请求头
self.session.headers.update({
"Content-Type": "application/json",
"Accept": "application/json"
})
# 添加 API Key
if api_key:
self.session.headers.update({
"Authorization": f"Bearer {api_key}"
})
def _build_url(self, endpoint: str) -> str:
"""构建完整 URL"""
return f"{self.base_url}/{endpoint.lstrip('/')}"
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
"""处理响应"""
try:
response.raise_for_status()
return {
"success": True,
"data": response.json() if response.content else None,
"status_code": response.status_code
}
except requests.exceptions.HTTPError as e:
return {
"success": False,
"error": str(e),
"status_code": response.status_code,
"data": response.text
}
def get(self, endpoint: str, params: Optional[Dict] = None) -> Dict[str, Any]:
"""GET 请求"""
url = self._build_url(endpoint)
response = self.session.get(url, params=params)
return self._handle_response(response)
def post(self, endpoint: str, data: Optional[Dict] = None) -> Dict[str, Any]:
"""POST 请求"""
url = self._build_url(endpoint)
response = self.session.post(url, json=data)
return self._handle_response(response)
def put(self, endpoint: str, data: Optional[Dict] = None) -> Dict[str, Any]:
"""PUT 请求"""
url = self._build_url(endpoint)
response = self.session.put(url, json=data)
return self._handle_response(response)
def delete(self, endpoint: str) -> Dict[str, Any]:
"""DELETE 请求"""
url = self._build_url(endpoint)
response = self.session.delete(url)
return self._handle_response(response)
def close(self):
"""关闭会话"""
self.session.close()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
# 使用示例
def main():
# 使用 JSONPlaceholder 测试 API
with APIClient("https://jsonplaceholder.typicode.com") as client:
# 获取帖子列表
result = client.get("/posts", params={"_limit": 5})
if result["success"]:
print(f"获取到 {len(result['data'])} 个帖子")
for post in result["data"][:2]:
print(f" - {post['title'][:50]}...")
# 获取单个帖子
result = client.get("/posts/1")
if result["success"]:
print(f"\n帖子标题: {result['data']['title']}")
# 创建新帖子
new_post = {
"title": "Test Post",
"body": "This is a test",
"userId": 1
}
result = client.post("/posts", data=new_post)
if result["success"]:
print(f"\n创建帖子 ID: {result['data']['id']}")
# 更新帖子
update_data = {"title": "Updated Title"}
result = client.put("/posts/1", data=update_data)
if result["success"]:
print(f"更新成功")
# 删除帖子
result = client.delete("/posts/1")
if result["success"]:
print(f"删除成功")
if __name__ == "__main__":
main()
实战2: 聊天室服务器
"""
聊天室服务器
展示 Socket 和线程的综合应用
"""
import socket
import threading
import json
from datetime import datetime
from typing import Set, Dict
class ChatRoom:
"""聊天室"""
def __init__(self):
self.clients: Dict[str, socket.socket] = {}
self.usernames: Dict[socket.socket, str] = {}
self.lock = threading.Lock()
def add_client(self, client_socket: socket.socket, username: str):
"""添加客户端"""
with self.lock:
self.clients[username] = client_socket
self.usernames[client_socket] = username
def remove_client(self, client_socket: socket.socket):
"""移除客户端"""
with self.lock:
if client_socket in self.usernames:
username = self.usernames.pop(client_socket)
self.clients.pop(username, None)
return username
return None
def broadcast(self, message: str, exclude: socket.socket = None):
"""广播消息"""
with self.lock:
for username, client_socket in self.clients.items():
if client_socket != exclude:
try:
client_socket.send(message.encode('utf-8'))
except:
pass
def get_online_users(self) -> list:
"""获取在线用户列表"""
with self.lock:
return list(self.clients.keys())
class ChatServer:
"""聊天服务器"""
def __init__(self, host='127.0.0.1', port=8888):
self.host = host
self.port = port
self.chat_room = ChatRoom()
self.server_socket = None
def start(self):
"""启动服务器"""
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.bind((self.host, self.port))
self.server_socket.listen(5)
print(f"[聊天服务器] 运行在 {self.host}:{self.port}")
try:
while True:
client_socket, address = self.server_socket.accept()
print(f"[新连接] {address}")
# 为每个客户端创建线程
thread = threading.Thread(
target=self.handle_client,
args=(client_socket, address)
)
thread.daemon = True
thread.start()
except KeyboardInterrupt:
print("\n[服务器关闭]")
finally:
self.server_socket.close()
def handle_client(self, client_socket: socket.socket, address):
"""处理客户端"""
username = None
try:
# 获取用户名
client_socket.send("请输入用户名: ".encode('utf-8'))
username = client_socket.recv(1024).decode('utf-8').strip()
if not username:
username = f"用户{address[1]}"
# 注册客户端
self.chat_room.add_client(client_socket, username)
# 通知所有人
welcome_msg = self._format_message("系统", f"{username} 加入了聊天室")
self.chat_room.broadcast(welcome_msg)
# 发送在线用户列表
online_users = self.chat_room.get_online_users()
user_list_msg = self._format_message("系统", f"在线用户: {', '.join(online_users)}")
client_socket.send(user_list_msg.encode('utf-8'))
# 处理消息
while True:
data = client_socket.recv(1024)
if not data:
break
message = data.decode('utf-8').strip()
if message.lower() == '/quit':
break
# 处理命令
if message.startswith('/'):
self._handle_command(client_socket, message)
else:
# 广播消息
formatted_msg = self._format_message(username, message)
self.chat_room.broadcast(formatted_msg)
except Exception as e:
print(f"[错误] {e}")
finally:
# 清理
if username:
removed_user = self.chat_room.remove_client(client_socket)
if removed_user:
leave_msg = self._format_message("系统", f"{removed_user} 离开了聊天室")
self.chat_room.broadcast(leave_msg)
client_socket.close()
print(f"[断开连接] {address}")
def _handle_command(self, client_socket: socket.socket, command: str):
"""处理命令"""
parts = command.split(' ', 1)
cmd = parts[0].lower()
if cmd == '/users':
users = self.chat_room.get_online_users()
msg = self._format_message("系统", f"在线用户: {', '.join(users)}")
client_socket.send(msg.encode('utf-8'))
elif cmd == '/help':
help_text = """
可用命令:
/users - 查看在线用户
/help - 显示帮助
/quit - 退出聊天室
"""
client_socket.send(help_text.encode('utf-8'))
else:
msg = self._format_message("系统", f"未知命令: {cmd}")
client_socket.send(msg.encode('utf-8'))
def _format_message(self, username: str, message: str) -> str:
"""格式化消息"""
timestamp = datetime.now().strftime("%H:%M:%S")
return json.dumps({
"timestamp": timestamp,
"username": username,
"message": message
}, ensure_ascii=False) + "\n"
# 聊天客户端
def chat_client(host='127.0.0.1', port=8888):
"""聊天客户端"""
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
client.connect((host, port))
print("[已连接到聊天服务器]")
# 启动接收消息的线程
receive_thread = threading.Thread(target=receive_messages, args=(client,))
receive_thread.daemon = True
receive_thread.start()
# 发送消息
while True:
message = input()
if message:
client.send(message.encode('utf-8'))
if message.lower() == '/quit':
break
except Exception as e:
print(f"[错误] {e}")
finally:
client.close()
print("[连接关闭]")
def receive_messages(client_socket: socket.socket):
"""接收消息"""
try:
while True:
data = client_socket.recv(1024)
if not data:
break
msg = json.loads(data.decode('utf-8'))
print(f"\n[{msg['timestamp']}] {msg['username']}: {msg['message']}")
print("> ", end='', flush=True)
except Exception:
pass
if __name__ == "__main__":
import sys
if len(sys.argv) > 1 and sys.argv[1] == "server":
server = ChatServer()
server.start()
else:
chat_client()
实战3: 网络扫描器
"""
网络扫描器
展示 Socket 编程的实际应用
"""
import socket
import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import List, Dict
class PortScanner:
"""端口扫描器"""
def __init__(self, target: str, timeout: float = 1.0):
self.target = target
self.timeout = timeout
self.open_ports: List[int] = []
self.lock = threading.Lock()
def scan_port(self, port: int) -> bool:
"""扫描单个端口"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(self.timeout)
result = sock.connect_ex((self.target, port))
sock.close()
if result == 0:
with self.lock:
self.open_ports.append(port)
return True
return False
except Exception:
return False
def scan_range(self, start_port: int, end_port: int, max_workers: int = 100) -> List[int]:
"""扫描端口范围"""
print(f"开始扫描 {self.target} 的端口 {start_port}-{end_port}...")
start_time = time.time()
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {
executor.submit(self.scan_port, port): port
for port in range(start_port, end_port + 1)
}
for future in as_completed(futures):
port = futures[future]
if future.result():
print(f" ✓ 端口 {port} 开放")
elapsed = time.time() - start_time
print(f"\n扫描完成! 耗时: {elapsed:.2f}秒")
print(f"开放的端口: {sorted(self.open_ports)}")
return sorted(self.open_ports)
def get_service_name(self, port: int) -> str:
"""获取服务名称"""
common_services = {
21: "FTP",
22: "SSH",
23: "Telnet",
25: "SMTP",
53: "DNS",
80: "HTTP",
110: "POP3",
143: "IMAP",
443: "HTTPS",
993: "IMAPS",
995: "POP3S",
3306: "MySQL",
5432: "PostgreSQL",
6379: "Redis",
8080: "HTTP-Proxy",
8443: "HTTPS-Alt"
}
return common_services.get(port, "Unknown")
def scan_with_services(self, start_port: int = 1, end_port: int = 1024) -> Dict[int, str]:
"""扫描端口并识别服务"""
open_ports = self.scan_range(start_port, end_port)
services = {}
for port in open_ports:
service = self.get_service_name(port)
services[port] = service
print(f" 端口 {port}: {service}")
return services
class NetworkInfo:
"""网络信息查询"""
@staticmethod
def get_local_ip() -> str:
"""获取本地 IP"""
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip
except Exception:
return "127.0.0.1"
@staticmethod
def get_hostname() -> str:
"""获取主机名"""
return socket.gethostname()
@staticmethod
def resolve_hostname(hostname: str) -> str:
"""解析主机名"""
try:
return socket.gethostbyname(hostname)
except socket.gaierror:
return None
@staticmethod
def reverse_lookup(ip: str) -> str:
"""反向查找主机名"""
try:
return socket.gethostbyaddr(ip)[0]
except socket.herror:
return None
# 使用示例
def main():
# 显示本地网络信息
info = NetworkInfo()
print("="*60)
print("网络信息")
print("="*60)
print(f"主机名: {info.get_hostname()}")
print(f"本地 IP: {info.get_local_ip()}")
# 扫描本地常见端口
target = "127.0.0.1"
print(f"\n{'='*60}")
print(f"扫描目标: {target}")
print(f"{'='*60}")
scanner = PortScanner(target, timeout=0.5)
services = scanner.scan_with_services(1, 1024)
if services:
print(f"\n发现 {len(services)} 个开放端口:")
for port, service in sorted(services.items()):
print(f" {port}/tcp - {service}")
else:
print("未发现开放端口")
if __name__ == "__main__":
main()
小结
| 协议/技术 | 说明 | 使用场景 |
|---|---|---|
| Socket | 底层网络通信 | 自定义协议 |
| TCP | 可靠传输 | 文件传输、聊天 |
| UDP | 快速传输 | 视频流、游戏 |
| HTTP | Web 协议 | API 调用、网页 |
| Requests | HTTP 库 | RESTful API |
| WebSocket | 双向通信 | 实时聊天、推送 |
| FTP | 文件传输 | 文件上传下载 |
| SMTP | 邮件发送 | 发送邮件 |
| SSH | 安全远程访问 | 远程管理 |
核心要点:
- 理解 TCP 和 UDP 的区别
- 掌握 Socket 编程基础
- 熟练使用 Requests 库
- 注意异常处理和超时设置
- 合理使用线程提高并发
- 遵循网络安全最佳实践
- 选择合适的协议和工具
掌握网络编程将帮助你构建各种网络应用!