本文基于Python内置socket库,不依赖任何第三方库,从零实现TCP客户端与服务端通信,涵盖短连接、长连接、端口存活检测、原始HTTP请求以及常见踩坑点。
一、TCP与UDP核心区别
TCP是面向连接的可靠传输协议:需要三次握手建立连接,保证数据有序不丢失;UDP直接发包,不保障送达。编程时TCP使用SOCK_STREAM,UDP使用SOCK_DGRAM。TCP适合文件传输、接口调用;UDP适合广播、实时流。
二、基础TCP客户端:短连接请求
短连接指连接建立后收发一次数据立即关闭。
核心方法:socket()创建套接字,connect()连接服务端,send()发送字节数据,recv()接收数据。- import socket
- client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- host = "127.0.0.1"
- port = 9000
- try:
- client.connect((host, port))
- client.send("Hello TCP Server".encode("utf-8"))
- recv_data = client.recv(1024)
- print("收到服务端返回:", recv_data.decode("utf-8"))
- except ConnectionRefusedError:
- print("端口未开放,TCP连接失败")
- finally:
- client.close()
复制代码
三、TCP服务端:监听端口并处理连接
服务端固定端口监听,等待客户端接入。
关键函数:bind()绑定IP和端口,listen()开启监听,accept()阻塞等待新客户端。- import socket
- server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- server.bind(("0.0.0.0", 9000))
- server.listen(5)
- print("TCP服务端已启动,监听9000端口")
- while True:
- conn, addr = server.accept()
- print(f"新客户端接入:{addr}")
- data = conn.recv(1024)
- print("收到消息:", data.decode())
- conn.send("消息已收到".encode("utf-8"))
- conn.close()
复制代码 运行顺序:先启动服务端,再运行客户端。
四、TCP长连接:不断开持续收发数据
短连接每次都要重新握手,开销大;长连接建立一次连接,反复收发多条消息。
客户端长连接代码:- import socket
- client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- client.connect(("127.0.0.1", 9000))
- for i in range(3):
- msg = f"第{i+1}条消息".encode("utf-8")
- client.send(msg)
- res = client.recv(1024)
- print("接收:", res.decode())
- client.close()
复制代码
服务端改造,持续读取数据:- import socket
- server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- server.bind(("0.0.0.0", 9000))
- server.listen(5)
- while True:
- conn, addr = server.accept()
- print("客户端已连接")
- while True:
- data = conn.recv(1024)
- if not data:
- break
- print(data.decode())
- conn.send("ok".encode())
- conn.close()
复制代码 注意:客户端关闭连接后,服务端recv会拿到空字节,据此退出内层循环。
五、实战场景1:TCP端口存活检测
运维常用场景:批量探测服务器端口是否开放,本质就是尝试建立TCP连接。- import socket
- def check_tcp_port(host, port, timeout=2):
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.settimeout(timeout)
- try:
- sock.connect((host, port))
- return True, "端口开放"
- except Exception:
- return False, "端口关闭或无法连接"
- finally:
- sock.close()
- print(check_tcp_port("www.baidu.com", 80))
- print(check_tcp_port("127.0.0.1", 9999))
复制代码 此探测比UDP更可靠,广泛用于内网资产扫描、节点健康检测。
六、实战场景2:手动发送原始HTTP报文(裸TCP访问网页)
HTTP是构建在TCP之上的应用层协议,可以直接用TCP套接字发送HTTP报文,无需requests:- import socket
- client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- client.connect(("www.baidu.com", 80))
- http_msg = b"GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n"
- client.send(http_msg)
- response = client.recv(4096)
- print(response.decode("utf-8", errors="ignore"))
- client.close()
复制代码 运行即可获取百度HTTP响应头和网页正文,直观理解HTTP是如何封装在TCP之上的。
七、常见问题与避坑指南
- recv阻塞卡死:默认无限等待,可调用sock.settimeout(3)设置超时。
- TCP粘包问题:TCP是流式字节流,无数据包边界。自定义通信协议时必须加长度或分隔符。
- TIME_WAIT端口占用:程序务必正常close;服务端可开启端口复用:server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- 数据必须为bytes字节串:字符串发送前必须encode(),不能直接发送str。
- 单线程服务端只能处理一个客户端:高并发场景需要搭配多线程/多协程处理每条连接。
八、总结
TCP核心流程:创建套接字 → connect建立连接 → send/recv收发数据 → close断开。短连接一发一关,长连接复用TCP通道减少握手开销。原生Socket可实现端口探测、自定义报文、裸HTTP请求。掌握裸Socket通信,才能真正理解互联网数据传输的底层逻辑。 |