手把手教你用Python和Go实现HTTP网络编程附完整代码示例从零搭建Web请求客户端与本地服务器实战演练
先搞懂HTTP到底是个啥
想象一下,你给好朋友写信,信上要写清楚:你是谁、你要寄给谁、你写了啥内容、怎么寄过去。HTTP协议其实就是这样一套”写信规则”。
当你在浏览器输入一个网址时,浏览器会发一个请求给服务器,服务器收到后回一个响应。这个过程就像你去餐厅点菜——你是客户端,厨师是服务器,菜单就是HTTP协议。
一个简单的HTTP请求长这样:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
第一行告诉你用了什么方法(GET)、要什么资源(/index.html)、啥版本(HTTP/1.1)。后面那些是”头信息”,相当于信里的附加说明。服务器回应的也差不多:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234
<html>...</html>
200 OK就是”成功啦”的意思,后面跟着响应头和内容。
Python版:用最简单的方式玩起来
Python有个内置模块叫http.server,不用装任何东西就能用。
先搞个超简单的本地服务器
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
class SimpleHandler(BaseHTTPRequestHandler):
def do_GET(self):
# 不同的路径返回不同的内容
if self.path == '/':
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(b'<h1>欢迎来到我的Python服务器!</h1>')
elif self.path == '/api/user':
# 返回JSON数据
user_data = {
"name": "小明",
"age": 12,
"hobby": "编程"
}
self.send_response(200)
self.send_header('Content-Type', 'application/json; charset=utf-8')
self.end_headers()
self.wfile.write(json.dumps(user_data, ensure_ascii=False).encode('utf-8'))
elif self.path == '/hello':
# 支持从URL获取名字
# 比如访问 /hello?name=小红
name = self.path.split('name=')[1] if 'name=' in self.path else '朋友'
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(f'<h1>你好, {name}!</h1>'.encode('utf-8'))
else:
# 404错误
self.send_response(404)
self.send_header('Content-Type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(b'<h1>404 - 找不到页面</h1>')
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', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(f'<h1>收到了POST请求!数据: {post_data.decode("utf-8")}</h1>'.encode('utf-8'))
# 让日志更简洁,不输出那些烦人的访问记录
def log_message(self, format, *args):
pass
# 启动服务器
server = HTTPServer(('127.0.0.1', 8080), SimpleHandler)
print("🎉 服务器已启动!在浏览器访问 http://127.0.0.1:8080 试试")
print("试试这些地址:")
print(" http://127.0.0.1:8080/")
print(" http://127.0.0.1:8080/api/user")
print(" http://127.0.0.1:8080/hello?name=小红")
print("按 Ctrl+C 停止服务器")
server.serve_forever()
把这段代码存为server.py,然后运行python server.py,打开浏览器访问http://127.0.0.1:8080就能看到效果了。
用Python发HTTP请求
import urllib.request
import urllib.parse
import json
print("=== Python发HTTP请求实战 ===\n")
# 1. 最简单的GET请求
print("【1】简单GET请求")
response = urllib.request.urlopen('http://httpbin.org/get')
print(f"状态码: {response.status}")
print(f"响应内容: {response.read().decode('utf-8')[:200]}...\n")
# 2. 发POST请求
print("【2】POST请求")
data = {'name': '小红', 'age': 12}
# 把数据编码成URL格式
encoded_data = urllib.parse.urlencode(data).encode('utf-8')
req = urllib.request.Request(
'http://httpbin.org/post',
data=encoded_data,
method='POST'
)
response = urllib.request.urlopen(req)
result = json.loads(response.read().decode('utf-8'))
print(f"服务器收到的数据: {result['json']}\n")
# 3. 带自定义头部的请求
print("【3】带自定义Header的请求")
headers = {
'User-Agent': 'MyPythonClient/1.0',
'Authorization': 'Bearer your-token-here'
}
req = urllib.request.Request(
'http://httpbin.org/headers',
headers=headers
)
response = urllib.request.urlopen(req)
result = json.loads(response.read().decode('utf-8'))
print(f"发送的headers: {result['headers']}\n")
# 4. 处理错误
print("【4】处理错误请求")
try:
response = urllib.request.urlopen('http://httpbin.org/status/404')
except urllib.error.HTTPError as e:
print(f"捕获到HTTP错误: 状态码 {e.code}")
print(f"错误内容: {e.read().decode('utf-8')}\n")
# 5. 用Python的requests库(更简单)
print("【5】用requests库(需要先pip install requests)")
try:
import requests
# 超级简洁的GET
resp = requests.get('http://httpbin.org/get')
print(f"GET请求状态: {resp.status_code}")
# 简洁的POST
resp = requests.post('http://httpbin.org/post', json={'name': '小明'})
print(f"POST请求状态: {resp.status_code}")
print(f"返回的JSON: {resp.json()}\n")
except ImportError:
print("requests库未安装,运行 pip install requests 即可使用\n")
Go版:更快更简洁
Go语言天生适合写网络程序,标准库里的net/http包非常强大,而且不需要安装第三方依赖。
搭建Go本地服务器
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
// 用户结构体,用于返回JSON
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Hobby string `json:"hobby"`
}
// 请求处理函数 - 返回用户信息
func handleGetUser(w http.ResponseWriter, r *http.Request) {
user := User{
Name: "小明",
Age: 12,
Hobby: "编程",
}
// 设置响应头为JSON
w.Header().Set("Content-Type", "application/json; charset=utf-8")
// 写入状态码和JSON数据
json.NewEncoder(w).Encode(user)
}
// 处理欢迎页面
func handleHello(w http.ResponseWriter, r *http.Request) {
// 从URL路径中提取名字
// 比如访问 /hello/小红
name := "朋友"
if len(r.URL.Path) > 6 {
name = r.URL.Path[6:]
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, "<h1>你好, %s!</h1>", name)
}
// 处理根路径
func handleHome(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, `<h1>欢迎来到我的Go服务器!</h1>
<p>试试这些地址:</p>
<ul>
<li><a href="/api/user">/api/user</a> - 获取用户信息</li>
<li><a href="/hello/小明">/hello/名字</a> - 打招呼</li>
</ul>`)
}
// 处理POST请求
func handlePost(w http.ResponseWriter, r *http.Request) {
// 读取请求体
buf := make([]byte, 1024)
n, _ := r.Body.Read(buf)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, "<h1>收到了POST请求!数据: %s</h1>", string(buf[:n]))
}
func main() {
// 注册路由
http.HandleFunc("/", handleHome)
http.HandleFunc("/api/user", handleGetUser)
http.HandleFunc("/hello/", handleHello)
http.HandleFunc("/submit", handlePost)
// 启动服务器,监听8080端口
fmt.Println("🎉 Go服务器已启动!在浏览器访问 http://127.0.0.1:8080")
fmt.Println("按 Ctrl+C 停止服务器")
log.Fatal(http.ListenAndServe(":8080", nil))
}
把这段代码存为server.go,然后运行:
go run server.go
打开浏览器访问http://127.0.0.1:8080就能看到效果了。
用Go发HTTP请求
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
fmt.Println("=== Go发HTTP请求实战 ===\n")
// 1. 简单GET请求
fmt.Println("【1】简单GET请求")
resp, err := http.Get("http://httpbin.org/get")
if err != nil {
fmt.Println("请求失败:", err)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Printf("状态码: %d\n", resp.StatusCode)
fmt.Printf("响应内容: %s...\n\n", string(body[:min(200, len(body))]))
// 2. POST请求(发送JSON数据)
fmt.Println("【2】POST请求(发送JSON)")
userData := map[string]interface{}{
"name": "小红",
"age": 12,
}
jsonData, _ := json.Marshal(userData)
resp, err = http.Post(
"http://httpbin.org/post",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
fmt.Println("请求失败:", err)
return
}
defer resp.Body.Close()
body, _ = ioutil.ReadAll(resp.Body)
fmt.Printf("状态码: %d\n", resp.StatusCode)
fmt.Printf("响应内容: %s\n\n", string(body[:min(200, len(body))]))
// 3. 带自定义Header的请求
fmt.Println("【3】带自定义Header的请求")
req, _ := http.NewRequest("GET", "http://httpbin.org/headers", nil)
req.Header.Set("User-Agent", "MyGoClient/1.0")
req.Header.Set("Authorization", "Bearer your-token-here")
client := &http.Client{}
resp, err = client.Do(req)
if err != nil {
fmt.Println("请求失败:", err)
return
}
defer resp.Body.Close()
body, _ = ioutil.ReadAll(resp.Body)
fmt.Printf("状态码: %d\n", resp.StatusCode)
fmt.Printf("响应内容: %s\n\n", string(body[:min(200, len(body))]))
// 4. 处理超时和错误
fmt.Println("【4】带超时的请求")
client := &http.Client{
Timeout: 5000, // 5秒超时
}
resp, err = client.Get("http://httpbin.org/delay/1")
if err != nil {
fmt.Println("请求失败:", err)
} else {
defer resp.Body.Close()
fmt.Printf("状态码: %d\n\n", resp.StatusCode)
}
// 5. 用Go的官方JSON处理
fmt.Println("【5】解析JSON响应")
resp, err = http.Get("http://httpbin.org/get")
if err != nil {
fmt.Println("请求失败:", err)
return
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("解析后的数据: %+v\n", result)
}
// 辅助函数:取最小值
func min(a, b int) int {
if a < b {
return a
}
return b
}
进阶:做一个简单的留言板系统
光会发请求和搭服务器还不够,咱们来做个有趣的项目——一个多人留言板!
Python版留言板
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
from urllib.parse import urlparse, parse_qs
import os
# 用文件存储留言
DATA_FILE = 'messages.json'
def load_messages():
if os.path.exists(DATA_FILE):
with open(DATA_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
return []
def save_messages(messages):
with open(DATA_FILE, 'w', encoding='utf-8') as f:
json.dump(messages, f, ensure_ascii=False, indent=2)
class MessageBoardHandler(BaseHTTPRequestHandler):
def do_GET(self):
parsed = urlparse(self.path)
if parsed.path == '/':
# 返回留言页面
messages = load_messages()
messages_json = json.dumps(messages, ensure_ascii=False)
html = f'''<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>我的留言板</title>
<style>
body {{ font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }}
.message {{ border: 1px solid #ddd; padding: 15px; margin: 10px 0; border-radius: 8px; }}
.message .author {{ font-weight: bold; color: #333; }}
.message .time {{ color: #999; font-size: 12px; }}
.form {{ background: #f5f5f5; padding: 20px; border-radius: 8px; margin-bottom: 20px; }}
input, textarea {{ width: 100%; padding: 10px; margin: 5px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }}
button {{ background: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }}
button:hover {{ background: #45a049; }}
</style>
</head>
<body>
<h1>📝 留言板</h1>
<div class="form">
<h3>发一条新留言</h3>
<form id="messageForm">
<input type="text" id="name" placeholder="你的名字" required>
<textarea id="content" placeholder="写点什么吧..." rows="3" required></textarea>
<button type="submit">提交留言</button>
</form>
</div>
<div id="messages">
<h3>所有留言</h3>
</div>
<script>
var messages = {messages_json};
function renderMessages() {{
var container = document.getElementById('messages');
var html = '<h3>所有留言 ({0}条)</h3>'.format(messages.length);
messages.slice().reverse().forEach(function(msg) {{
html += '<div class="message">';
html += '<span class="author">' + msg.name + '</span> ';
html += '<span class="time">' + msg.time + '</span>';
html += '<p>' + msg.content + '</p>';
html += '</div>';
}});
container.innerHTML = html;
}}
document.getElementById('messageForm').addEventListener('submit', function(e) {{
e.preventDefault();
var name = document.getElementById('name').value;
var content = document.getElementById('content').value;
fetch('/api/messages', {{
method: 'POST',
headers: {{'Content-Type': 'application/json'}},
body: JSON.stringify({{name: name, content: content}})
}}).then(function() {{
location.reload();
}});
}});
renderMessages();
</script>
</body>
</html>'''
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(html.encode('utf-8'))
elif parsed.path == '/api/messages':
# 获取所有留言
messages = load_messages()
self.send_response(200)
self.send_header('Content-Type', 'application/json; charset=utf-8')
self.end_headers()
self.wfile.write(json.dumps(messages, ensure_ascii=False).encode('utf-8'))
else:
self.send_response(404)
self.end_headers()
def do_POST(self):
parsed = urlparse(self.path)
if parsed.path == '/api/messages':
content_length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(content_length)
data = json.loads(body.decode('utf-8'))
messages = load_messages()
messages.append({
'name': data['name'],
'content': data['content'],
'time': '刚刚'
})
save_messages(messages)
self.send_response(201)
self.send_header('Content-Type', 'application/json; charset=utf-8')
self.end_headers()
self.wfile.write(b'{"success": true}')
else:
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
pass
if __name__ == '__main__':
server = HTTPServer(('127.0.0.1', 8080), MessageBoardHandler)
print("🎉 留言板已启动!访问 http://127.0.0.1:8080")
print("你可以用两个浏览器窗口同时打开,体验多人留言的效果")
server.serve_forever()
Go版留言板
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"time"
)
type Message struct {
Name string `json:"name"`
Content string `json:"content"`
Time string `json:"time"`
}
const dataFile = "messages.json"
func loadMessages() []Message {
data, err := os.ReadFile(dataFile)
if err != nil {
return []Message{}
}
var messages []Message
json.Unmarshal(data, &messages)
return messages
}
func saveMessages(messages []Message) {
data, _ := json.MarshalIndent(messages, "", " ")
os.WriteFile(dataFile, data, 0644)
}
func handleHome(w http.ResponseWriter, r *http.Request) {
messages := loadMessages()
messagesJSON, _ := json.Marshal(messages)
html := fmt.Sprintf(`<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Go留言板</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.message { border: 1px solid #ddd; padding: 15px; margin: 10px 0; border-radius: 8px; }
.message .author { font-weight: bold; color: #333; }
.message .time { color: #999; font-size: 12px; }
.form { background: #f5f5f5; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
input, textarea { width: 100%; padding: 10px; margin: 5px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
button { background: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
</style>
</head>
<body>
<h1>📝 Go留言板</h1>
<div class="form">
<h3>发一条新留言</h3>
<form id="messageForm">
<input type="text" id="name" placeholder="你的名字" required>
<textarea id="content" placeholder="写点什么吧..." rows="3" required></textarea>
<button type="submit">提交留言</button>
</form>
</div>
<div id="messages"></div>
<script>
var messages = %s;
function render() {
var container = document.getElementById('messages');
var html = '<h3>所有留言 (' + messages.length + '条)</h3>';
messages.slice().reverse().forEach(function(msg) {
html += '<div class="message">';
html += '<span class="author">' + msg.name + '</span> ';
html += '<span class="time">' + msg.time + '</span>';
html += '<p>' + msg.content + '</p>';
html += '</div>';
});
container.innerHTML = html;
}
document.getElementById('messageForm').addEventListener('submit', function(e) {
e.preventDefault();
var name = document.getElementById('name').value;
var content = document.getElementById('content').value;
fetch('/api/messages', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: name, content: content})
}).then(function() { location.reload(); });
});
render();
</script>
</body>
</html>`, string(messagesJSON))
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write([]byte(html))
}
func handleMessagesGET(w http.ResponseWriter, r *http.Request) {
messages := loadMessages()
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(messages)
}
func handleMessagesPOST(w http.ResponseWriter, r *http.Request) {
var data struct {
Name string `json:"name"`
Content string `json:"content"`
}
json.NewDecoder(r.Body).Decode(&data)
messages := loadMessages()
messages = append(messages, Message{
Name: data.Name,
Content: data.Content,
Time: "刚刚",
})
saveMessages(messages)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
func main() {
http.HandleFunc("/", handleHome)
http.HandleFunc("/api/messages", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
handleMessagesGET(w, r)
} else if r.Method == "POST" {
handleMessagesPOST(w, r)
} else {
http.Error(w, "Method not allowed", 405)
}
})
fmt.Println("🎉 Go留言板已启动!访问 http://127.0.0.1:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Python和Go的对比
| 特性 | Python | Go |
|---|---|---|
| 启动速度 | 较慢 | 极快 |
| 代码量 | 较少 | 略多 |
| 并发能力 | 一般(GIL限制) | 强大(goroutine) |
| 学习难度 | 低,像写英语 | 中等 |
| 适合场景 | 快速原型、脚本 | 高性能服务器 |
| 部署 | 需要Python环境 | 单个二进制文件 |
实战:做一个支持并发的聊天室
最后来个更有挑战性的——一个支持多人同时聊天的聊天室!
Python并发聊天室
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import threading
from urllib.parse import urlparse
import queue
import time
# 共享数据 - 用锁保护
messages_lock = threading.Lock()
chat_messages = []
client_queues = {}
client_id_counter = 0
def generate_client_id():
global client_id_counter
client_id_counter += 1
return f"user_{client_id_counter}"
class ChatRoomHandler(BaseHTTPRequestHandler):
def do_GET(self):
parsed = urlparse(self.path)
if parsed.path == '/':
self.serve_chat_page()
elif parsed.path.startswith('/api/messages'):
self.handle_messages_get()
elif parsed.path.startswith('/api/send'):
self.handle_message_send()
else:
self.send_error(404)
def serve_chat_page(self):
client_id = generate_client_id()
client_queues[client_id] = queue.Queue()
html = f'''<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Python聊天室</title>
<style>
body {{ font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }}
#chat {{ height: 400px; overflow-y: auto; border: 1px solid #ddd; padding: 15px; margin-bottom: 10px; background: #fafafa; }}
.msg {{ padding: 8px 12px; margin: 5px 0; border-radius: 8px; display: inline-block; max-width: 80%; }}
.msg.me {{ background: #4CAF50; color: white; float: right; clear: both; }}
.msg.other {{ background: #e0e0e0; float: left; clear: both; }}
.msg .author {{ font-size: 12px; opacity: 0.7; }}
#inputArea {{ display: flex; gap: 10px; }}
#name {{ padding: 10px; width: 150px; border: 1px solid #ddd; border-radius: 4px; }}
#message {{ flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }}
#send {{ padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }}
</style>
</head>
<body>
<h1>💬 Python聊天室</h1>
<p>你的ID: <b>{client_id}</b> | 在线人数: <span id="online">1</span></p>
<div id="chat"></div>
<div id="inputArea">
<input type="text" id="name" placeholder="你的名字" value="{client_id}">
<input type="text" id="message" placeholder="输入消息..." onkeypress="if(event.key==='Enter')sendMessage()">
<button id="send" onclick="sendMessage()">发送</button>
</div>
<script>
var clientId = '{client_id}';
function sendMessage() {{
var name = document.getElementById('name').value || clientId;
var content = document.getElementById('message').value;
if (!content.trim()) return;
fetch('/api/send?name=' + encodeURIComponent(name) + '&content=' + encodeURIComponent(content))
.then(function() {{
document.getElementById('message').value = '';
}});
}}
// 每2秒轮询新消息
function pollMessages() {{
fetch('/api/messages')
.then(function(r) {{ return r.json(); }})
.then(function(data) {{
var chat = document.getElementById('chat');
var lastMsgIndex = chat.dataset.lastIndex || 0;
for (var i = parseInt(lastMsgIndex); i < data.length; i++) {{
var msg = data[i];
var div = document.createElement('div');
div.className = 'msg ' + (msg.id === clientId ? 'me' : 'other');
div.innerHTML = '<div class="author">' + msg.name + '</div>' + msg.content;
chat.appendChild(div);
}}
chat.dataset.lastIndex = data.length;
chat.scrollTop = chat.scrollHeight;
}});
}}
setInterval(pollMessages, 2000);
pollMessages();
</script>
</body>
</html>'''
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(html.encode('utf-8'))
def handle_messages_get(self):
with messages_lock:
self.send_json(chat_messages)
def handle_message_send(self):
parsed = urlparse(self.path)
params = parse_qs(parsed.query)
name = params.get('name', ['匿名'])[0]
content = params.get('content', [''])[0]
if content.strip():
msg = {
'id': generate_client_id(),
'name': name,
'content': content,
'time': time.strftime('%H:%M:%S')
}
with messages_lock:
chat_messages.append(msg)
# 广播给所有客户端
for q in client_queues.values():
q.put(msg)
self.send_json({'success': True})
def send_json(self, data):
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(data, ensure_ascii=False).encode('utf-8'))
def log_message(self, format, *args):
pass
if __name__ == '__main__':
# 使用多线程服务器,支持多个客户端同时连接
server = HTTPServer(('127.0.0.1', 8080), ChatRoomHandler)
server.socket = threading.Lock()
# 用ThreadingHTTPServer替代
from http.server import ThreadingHTTPServer
thread_server = ThreadingHTTPServer(('127.0.0.1', 8080), ChatRoomHandler)
print("🎉 Python聊天室已启动!")
print("打开多个浏览器标签页访问 http://127.0.0.1:8080 体验多人聊天")
thread_server.serve_forever()
Go高性能聊天室
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"sync"
"time"
)
type Message struct {
ID string `json:"id"`
Name string `json:"name"`
Content string `json:"content"`
Time string `json:"time"`
}
// 聊天室状态
type ChatRoom struct {
mu sync.Mutex
messages []Message
clients map[string]chan Message
idCounter int
}
func NewChatRoom() *ChatRoom {
return &ChatRoom{
clients: make(map[string]chan Message),
}
}
func (cr *ChatRoom) Broadcast(msg Message) {
cr.mu.Lock()
cr.messages = append(cr.messages, msg)
// 只保留最近100条消息
if len(cr.messages) > 100 {
cr.messages = cr.messages[len(cr.messages)-100:]
}
// 广播给所有客户端
for _, ch := range cr.clients {
select {
case ch <- msg:
default:
// 客户端处理不过来就跳过
}
}
cr.mu.Unlock()
}
func (cr *ChatRoom) AddClient(id string) chan Message {
cr.mu.Lock()
ch := make(chan Message, 10)
cr.clients[id] = ch
cr.mu.Unlock()
return ch
}
func (cr *ChatRoom) RemoveClient(id string) {
cr.mu.Lock()
if ch, ok := cr.clients[id]; ok {
close(ch)
delete(cr.clients, id)
}
cr.mu.Unlock()
}
func (cr *ChatRoom) GetMessages() []Message {
cr.mu.Lock()
defer cr.mu.Unlock()
msgs := make([]Message, len(cr.messages))
copy(msgs, cr.messages)
return msgs
}
func (cr *ChatRoom) NextID() string {
cr.idCounter++
return fmt.Sprintf("user_%d", cr.idCounter)
}
var room = NewChatRoom()
func handleHome(w http.ResponseWriter, r *http.Request) {
clientID := room.NextID()
clientChan := room.AddClient(clientID)
// 客户端断开时清理
defer room.RemoveClient(clientID)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
html := fmt.Sprintf(`<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Go聊天室</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
#chat { height: 400px; overflow-y: auto; border: 1px solid #ddd; padding: 15px; margin-bottom: 10px; background: #fafafa; }
.msg { padding: 8px 12px; margin: 5px 0; border-radius: 8px; display: inline-block; max-width: 80%; }
.msg.me { background: #4CAF50; color: white; float: right; clear: both; }
.msg.other { background: #e0e0e0; float: left; clear: both; }
.msg .author { font-size: 12px; opacity: 0.7; }
#inputArea { display: flex; gap: 10px; }
#name { padding: 10px; width: 150px; border: 1px solid #ddd; border-radius: 4px; }
#message { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
#send { padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
.status { color: #999; font-size: 14px; }
</style>
</head>
<body>
<h1>💬 Go聊天室</h1>
<p>你的ID: <b>%s</b> | 在线人数: <span id="online">?</span> <span class="status">⚡ 实时推送</span></p>
<div id="chat"></div>
<div id="inputArea">
<input type="text" id="name" placeholder="你的名字" value="%s">
<input type="text" id="message" placeholder="输入消息..." onkeypress="if(event.key==='Enter')sendMessage()">
<button id="send" onclick="sendMessage()">发送</button>
</div>
<script>
var clientId = '%s';
var lastIndex = 0;
// 用EventSource实现服务器推送(比轮询更高效)
var source = new EventSource('/api/stream');
source.onmessage = function(e) {
var msg = JSON.parse(e.data);
var chat = document.getElementById('chat');
var div = document.createElement('div');
div.className = 'msg ' + (msg.id === clientId ? 'me' : 'other');
div.innerHTML = '<div class="author">' + msg.name + '</div>' + msg.content;
chat.appendChild(div);
chat.scrollTop = chat.scrollHeight;
};
source.onerror = function() {
console.log('连接断开,3秒后重连...');
setTimeout(function() { location.reload(); }, 3000);
};
function sendMessage() {
var name = document.getElementById('name').value || clientId;
var content = document.getElementById('message').value;
if (!content.trim()) return;
fetch('/api/send?name=' + encodeURIComponent(name) + '&content=' + encodeURIComponent(content))
.then(function() { document.getElementById('message').value = ''; });
}
</script>
</body>
</html>`, clientID, clientID, clientID)
w.Write([]byte(html))
}
func handleMessagesGET(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(room.GetMessages())
}
func handleStream(w http.ResponseWriter, r *http.Request) {
clientID := room.NextID()
clientChan := room.AddClient(clientID)
defer room.RemoveClient(clientID)
// 设置SSE响应头
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
flusher, _ := w.(http.Flusher)
// 发送现有消息
for _, msg := range room.GetMessages() {
fmt.Fprintf(w, "data: %s\n\n", mustMarshal(msg))
flusher.Flush()
}
// 持续推送新消息
for {
select {
case msg, ok := <-clientChan:
if !ok {
return
}
fmt.Fprintf(w, "data: %s\n\n", mustMarshal(msg))
flusher.Flush()
case <-r.Context().Done():
return
}
}
}
func handleSend(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
content := r.URL.Query().Get("content")
if content == "" {
http.Error(w, "Content is required", 400)
return
}
msg := Message{
ID: room.NextID(),
Name: name,
Content: content,
Time: time.Now().Format("15:04:05"),
}
room.Broadcast(msg)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
func mustMarshal(v interface{}) string {
b, _ := json.Marshal(v)
return string(b)
}
func main() {
http.HandleFunc("/", handleHome)
http.HandleFunc("/api/messages", handleMessagesGET)
http.HandleFunc("/api/stream", handleStream)
http.HandleFunc("/api/send", handleSend)
fmt.Println("🎉 Go高性能聊天室已启动!")
fmt.Println("打开多个浏览器标签页访问 http://127.0.0.1:8080 体验实时聊天")
fmt.Println("按 Ctrl+C 停止服务器")
log.Fatal(http.ListenAndServe(":8080", nil))
}
运行和测试指南
Python项目运行步骤
# 1. 确保安装了Python 3.6+
python --version
# 2. 运行服务器
python server.py
# 3. 打开浏览器访问
# http://127.0.0.1:8080
Go项目运行步骤
# 1. 确保安装了Go 1.18+
go version
# 2. 初始化模块(首次需要)
go mod init chatroom
# 3. 编译并运行
go run main.go
# 4. 或者编译成可执行文件(推荐,方便部署)
go build -o chatroom main.go
./chatroom
# 5. 打开浏览器访问
# http://127.0.0.1:8080
常见问题排查
问题1:端口被占用
如果看到 "address already in use" 错误:
- Python: 改端口号,比如改成 8081
- Go: 同样改端口号
- 或者查占用端口的进程: netstat -ano | findstr :8080
问题2:浏览器打不开页面
- 检查服务器是否启动(看命令行有没有报错)
- 检查防火墙设置
- 试试访问 http://127.0.0.1:8080 而不是 localhost
问题3:消息发不出去
- 检查URL参数格式
- Python: 确保用 utf-8 编码
- Go: 检查路由匹配
下一步学习方向
学会了基础之后,你可以继续探索:
- WebSocket - 真正的实时双向通信,比轮询更高效
- 数据库集成 - 用SQLite或MySQL持久化存储数据
- RESTful API设计 - 学习规范的接口设计
- 身份验证 - 给聊天室加登录功能
- 部署上线 - 把程序部署到服务器上让别人也能访问
HTTP编程其实就像学骑自行车——刚开始觉得难,一旦掌握了平衡感,就能骑得飞快。多动手实践,多改代码,多看错误信息,很快你就能独立写出自己的网络应用了。
记住:代码是写出来的,不是看出来的。把你喜欢的功能亲手实现一遍,比看十篇文章都管用。加油!
