feat: 添加 TCP 服务端与客户端示例项目
包含基于 Python socket 的多线程 TCP 服务端、交互式客户端、README 文档、MIT 许可证及 .gitignore。
这个提交包含在:
+29
@@ -0,0 +1,29 @@
|
|||||||
|
# Python 字节码 / 缓存
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# 打包 / 安装产物
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.egg-info/
|
||||||
|
.eggs/
|
||||||
|
|
||||||
|
# 虚拟环境
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# 代码编辑器 / IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# 操作系统文件
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# 本地测试日志(如有)
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 wangchuanli
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
# TCP Server & Client (Python)
|
||||||
|
|
||||||
|
一个基于 Python 标准库 `socket` 实现的轻量级 TCP 通信示例项目,包含服务端和交互式客户端。
|
||||||
|
|
||||||
|
适合用于:
|
||||||
|
|
||||||
|
- 学习 TCP Socket 编程的基础流程
|
||||||
|
- 本地调试网络程序、验证端口连通性
|
||||||
|
- 作为其他 TCP 服务(如 Netty、自定义协议)的简易测试客户端
|
||||||
|
|
||||||
|
> ⚠️ 本项目仅用于学习和测试,未做并发处理、鉴权及安全防护,请勿直接用于生产环境。
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
tcp_server/
|
||||||
|
├── tcp_server.py # TCP 服务端:监听端口、接收消息并返回确认
|
||||||
|
├── tcp_test.py # TCP 客户端:交互式发送消息、接收服务端响应
|
||||||
|
└── README.md # 项目文档
|
||||||
|
```
|
||||||
|
|
||||||
|
## 环境要求
|
||||||
|
|
||||||
|
- Python 3.6+(仅使用标准库,无需安装第三方依赖)
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 启动服务端
|
||||||
|
|
||||||
|
打开一个终端,运行服务端(默认监听 `0.0.0.0:30008`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python tcp_server.py
|
||||||
|
```
|
||||||
|
|
||||||
|
启动成功后会看到:
|
||||||
|
|
||||||
|
```
|
||||||
|
🚀 服务器正在监听 0.0.0.0:30008 ...
|
||||||
|
等待客户端连接...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 启动客户端
|
||||||
|
|
||||||
|
另开一个终端,运行客户端并输入消息:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python tcp_test.py
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
正在尝试连接到 127.0.0.1:30008 ...
|
||||||
|
✅ 连接成功!请输入要发送的消息(输入 'exit' 退出):
|
||||||
|
我: hello
|
||||||
|
📤 消息已发送。
|
||||||
|
📥 服务器响应: 服务器已收到: hello
|
||||||
|
我: exit
|
||||||
|
正在断开连接...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 本机自测
|
||||||
|
|
||||||
|
如果服务端和客户端在同一台机器,客户端默认连接 `127.0.0.1:30008` 即可。
|
||||||
|
跨机器测试时,请将 `tcp_test.py` 中的 `HOST` 改为服务端的实际 IP。
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
### 服务端 `tcp_server.py`
|
||||||
|
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
| ------- | ----------- | -------------------------------------- |
|
||||||
|
| `HOST` | `'0.0.0.0'` | 监听地址,`0.0.0.0` 表示接受所有接口 |
|
||||||
|
| `PORT` | `30008` | 监听端口,需与客户端保持一致 |
|
||||||
|
|
||||||
|
### 客户端 `tcp_test.py`
|
||||||
|
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
| ------- | ------------- | --------------------------------------------- |
|
||||||
|
| `HOST` | `'127.0.0.1'` | 服务端 IP,跨机测试请改为实际地址 |
|
||||||
|
| `PORT` | `30008` | 服务端端口,需与服务端保持一致 |
|
||||||
|
|
||||||
|
## 实现要点
|
||||||
|
|
||||||
|
### 服务端(`tcp_server.py`)
|
||||||
|
|
||||||
|
1. 创建 TCP Socket:`socket(AF_INET, SOCK_STREAM)`
|
||||||
|
2. 设置 `SO_REUSEADDR`,避免服务重启后端口处于 `TIME_WAIT` 导致绑定失败
|
||||||
|
3. `bind` 绑定地址,`listen(5)` 开始监听连接队列
|
||||||
|
4. 主循环不断 `accept` 新连接,每接受一个连接就创建一个守护线程 `handle_client` 处理,从而**支持并发多客户端**
|
||||||
|
5. 每个连接线程内循环 `recv(1024)` 接收数据,空数据表示客户端断开;收到消息后回送 `服务器已收到: <消息>`
|
||||||
|
6. 支持 `Ctrl+C` 优雅关闭;`finally` 中确保关闭监听套接字
|
||||||
|
|
||||||
|
> 使用 `threading.Thread(daemon=True)`,主线程退出时子线程会自动结束,便于调试与退出。
|
||||||
|
|
||||||
|
### 客户端(`tcp_test.py`)
|
||||||
|
|
||||||
|
1. 创建 TCP Socket 并 `connect` 到服务端
|
||||||
|
2. 通过 `input()` 循环读取用户输入并 `sendall` 发送(末尾追加 `\n`)
|
||||||
|
3. 发送后设置 5 秒超时等待服务端响应,超时或空响应均给出提示
|
||||||
|
4. 输入 `exit` 退出;任何情况下都在 `finally` 中关闭套接字
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
**Q: 客户端提示 `ConnectionRefusedError`?**
|
||||||
|
A: 确认服务端已启动、端口一致,且防火墙未拦截该端口。
|
||||||
|
|
||||||
|
**Q: 服务端只处理一个连接?**
|
||||||
|
A: 已支持并发。服务端采用多线程模型(`threading`),每接受一个客户端连接就启动一个守护线程独立处理,可同时服务多个客户端。监听队列 `backlog` 设为 5,高并发长连接场景可结合连接池或 `selectors`/`asyncio` 进一步优化。
|
||||||
|
|
||||||
|
**Q: 可以和 Java Netty 等服务端互通吗?**
|
||||||
|
A: 客户端发送以 `\n` 结尾的消息,可对接按行分隔(如 `LineBasedFrameDecoder`)的协议方;服务端的回送逻辑为简单的 echo 确认。
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# 本项目仅使用 Python 标准库(socket、threading),无第三方依赖。
|
||||||
|
# 因此本文件为空,仅作工程规范占位。
|
||||||
|
#
|
||||||
|
# 若后续引入第三方库,请在此按如下格式列出(建议锁定版本):
|
||||||
|
# some-package==1.2.3
|
||||||
|
#
|
||||||
|
# 安装方式:
|
||||||
|
# pip install -r requirements.txt
|
||||||
+81
@@ -0,0 +1,81 @@
|
|||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
|
def handle_client(conn, addr):
|
||||||
|
"""处理单个客户端连接(在独立线程中运行)。"""
|
||||||
|
print(f"✅ 客户端已连接: {addr}")
|
||||||
|
try:
|
||||||
|
# 接收数据循环
|
||||||
|
while True:
|
||||||
|
# 接收数据(缓冲区大小为 1024 字节)
|
||||||
|
data = conn.recv(1024)
|
||||||
|
|
||||||
|
# 如果没有收到数据,说明客户端已断开连接
|
||||||
|
if not data:
|
||||||
|
print(f"客户端已断开连接: {addr}")
|
||||||
|
break
|
||||||
|
|
||||||
|
# 解码并打印收到的消息
|
||||||
|
message = data.decode('utf-8')
|
||||||
|
print(f"📥 收到来自 {addr} 的消息: {message}")
|
||||||
|
|
||||||
|
# 可选:向客户端发送确认回复
|
||||||
|
response = f"服务器已收到: {message}"
|
||||||
|
conn.sendall(response.encode('utf-8'))
|
||||||
|
print(f"📤 已向 {addr} 发送确认回复。")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 与 {addr} 通信发生错误: {e}")
|
||||||
|
finally:
|
||||||
|
# 关闭该客户端连接
|
||||||
|
conn.close()
|
||||||
|
print(f"与 {addr} 的连接已关闭。")
|
||||||
|
|
||||||
|
|
||||||
|
def tcp_server():
|
||||||
|
# --- 配置区域 ---
|
||||||
|
HOST = '0.0.0.0' # 监听所有网络接口
|
||||||
|
PORT = 30008 # 与客户端保持一致的端口
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
# 用于指示服务是否正在运行(便于后续扩展为可关闭)
|
||||||
|
server_socket = None
|
||||||
|
try:
|
||||||
|
# 1. 创建 TCP Socket 对象
|
||||||
|
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
|
# 设置 SO_REUSEADDR 选项,允许重用地址和端口
|
||||||
|
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
|
||||||
|
# 2. 绑定地址和端口
|
||||||
|
server_socket.bind((HOST, PORT))
|
||||||
|
|
||||||
|
# 3. 开始监听连接(backlog 调大以支持并发等待队列)
|
||||||
|
server_socket.listen(5)
|
||||||
|
print(f"🚀 服务器正在监听 {HOST}:{PORT} ...(支持并发连接)")
|
||||||
|
print("等待客户端连接...")
|
||||||
|
|
||||||
|
# 4. 循环接受客户端连接,每来一个连接就启动一个新线程
|
||||||
|
while True:
|
||||||
|
conn, addr = server_socket.accept()
|
||||||
|
client_thread = threading.Thread(
|
||||||
|
target=handle_client,
|
||||||
|
args=(conn, addr),
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
|
client_thread.start()
|
||||||
|
print(f"🧵 当前活跃客户端线程数: {threading.active_count() - 1}")
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n⛔ 收到中断信号,正在关闭服务器...")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 发生错误: {e}")
|
||||||
|
finally:
|
||||||
|
# 5. 关闭监听套接字
|
||||||
|
if server_socket:
|
||||||
|
server_socket.close()
|
||||||
|
print("服务器已关闭。")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
tcp_server()
|
||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
import socket
|
||||||
|
|
||||||
|
def tcp_client():
|
||||||
|
# --- 配置区域 ---
|
||||||
|
# 服务器的 IP 地址或域名
|
||||||
|
HOST = '127.0.0.1' # 本地测试用;跨机器测试请改为服务端实际 IP
|
||||||
|
# 服务器的端口号
|
||||||
|
PORT = 30008 # 请确保此端口与服务器监听的端口一致
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. 创建 TCP Socket 对象
|
||||||
|
# AF_INET 表示使用 IPv4 地址,SOCK_STREAM 表示使用 TCP 协议
|
||||||
|
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
|
# 2. 连接到服务器
|
||||||
|
print(f"正在尝试连接到 {HOST}:{PORT} ...")
|
||||||
|
client_socket.connect((HOST, PORT))
|
||||||
|
print("✅ 连接成功!请输入要发送的消息(输入 'exit' 退出):")
|
||||||
|
|
||||||
|
# 3. 循环发送消息
|
||||||
|
while True:
|
||||||
|
message = input("我: ")
|
||||||
|
|
||||||
|
# 检查是否退出
|
||||||
|
if message.lower() == 'exit':
|
||||||
|
print("正在断开连接...")
|
||||||
|
break
|
||||||
|
|
||||||
|
# 4. 发送数据
|
||||||
|
# 网络传输的是字节流,所以需要将字符串编码为 bytes
|
||||||
|
# Netty服务端期望以换行符结尾的消息
|
||||||
|
client_socket.sendall(message.encode('utf-8')+b'\n')
|
||||||
|
print("📤 消息已发送。")
|
||||||
|
|
||||||
|
# 5. 接收服务端的响应
|
||||||
|
try:
|
||||||
|
# 设置超时时间,避免无限等待
|
||||||
|
client_socket.settimeout(5.0)
|
||||||
|
response = client_socket.recv(4096)
|
||||||
|
if response:
|
||||||
|
# 去除末尾的换行符并解码
|
||||||
|
response_str = response.decode('utf-8').rstrip('\n')
|
||||||
|
print(f"📥 服务器响应: {response_str}")
|
||||||
|
else:
|
||||||
|
print("⚠️ 服务端未返回响应")
|
||||||
|
except socket.timeout:
|
||||||
|
print("⚠️ 等待服务端响应超时")
|
||||||
|
finally:
|
||||||
|
# 恢复默认阻塞模式
|
||||||
|
client_socket.settimeout(None)
|
||||||
|
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
print(f"❌ 连接失败:无法连接到 {HOST}:{PORT}。请确认服务器是否已启动且端口正确。")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 发生错误:{e}")
|
||||||
|
finally:
|
||||||
|
# 5. 关闭连接
|
||||||
|
# 无论是否发生异常,都确保套接字被关闭
|
||||||
|
client_socket.close()
|
||||||
|
print("连接已关闭。")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
tcp_client()
|
||||||
在新工单中引用
屏蔽一个用户