3、TCP服务端开发:socket创建、bind绑定、listen监听、accept接受连接、收发数据

好,咱们今天来聊聊TCP服务端开发。说实话,这是网络编程里最基础、也最核心的一块。你想想看,几乎所有互联网应用——从Web服务器到聊天软件——底层都是这套流程。

我个人习惯把TCP服务端开发总结为五个步骤:创建socket、绑定地址、监听端口、接受连接、收发数据。这五步走完,一个能用的服务端就搭起来了。

核心流程速览

socket() → bind() → listen() → accept() → recv()/send()

每一步都有它的坑,咱们一个一个来拆。

TCP 服务端开发五步流程 ① socket() 创建套接字 ② bind() 绑定地址和端口 ③ listen() 开始监听 ④ accept() 接受连接 ⑤ recv()/send() 收发数据 ⚠️ 常见坑点 • 端口被占用 → 换端口或SO_REUSEADDR • 地址格式错误 → 用AF_INET • backlog太小 → 设5~10 • accept阻塞 → 多线程处理 • recv返回0 → 客户端断开 • send缓冲区满 → 循环发送 • 粘包问题 → 自定义协议 • 未处理异常 → try/except • 忘记关闭socket → 资源泄漏 • 多线程共享变量 → 加锁 • 防火墙拦截 → 检查端口放行

3.1 创建socket——一切的开端

第一步,创建socket。说白了就是跟操作系统说:「嘿,给我一个网络通信的句柄。」

在Python里,一行代码搞定:

import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

这里有两个参数:AF_INET 表示IPv4地址族,SOCK_STREAM 表示TCP协议。我记得刚学的时候老搞混,后来记住一句话:「AF_INET是地址类型,SOCK_STREAM是传输类型」,就再也没错过。

小提示:如果你要支持IPv6,把AF_INET换成AF_INET6就行。不过目前大部分场景还是IPv4为主。

3.2 bind绑定——给socket一个身份

socket创建好了,它还是个「无名氏」。你得给它绑定一个IP地址和端口号,这样客户端才知道往哪连。

server_address = ('0.0.0.0', 8888)
server_socket.bind(server_address)

这里'0.0.0.0'表示监听所有网络接口。如果你只想让本机访问,可以改成'127.0.0.1'。端口号我习惯用8888,当然你也可以用其他未被占用的端口。

注意:端口号范围是0~65535,但0~1023是系统保留端口,需要管理员权限才能用。我曾经在项目里图省事用了80端口,结果部署时发现没权限,折腾了半天才换成8080。嗯,这个坑我替你们踩过了。

3.3 listen监听——准备好接客

绑定完地址,socket就进入「待机状态」。但你还得告诉它:开始监听吧!

server_socket.listen(5)
print("服务器已启动,等待客户端连接...")

参数5是backlog,表示最大等待连接数。说白了就是:当服务器忙不过来时,最多允许5个客户端在门口排队。超过这个数,新连接会被拒绝。

我个人建议backlog设5~10就够了。设太大反而浪费资源,因为操作系统有上限。

3.4 accept接受连接——终于等到你

这一步是阻塞的。程序会停在这里,直到有客户端连上来。

client_socket, client_address = server_socket.accept()
print(f"收到来自 {client_address} 的连接")

accept()返回两个东西:一个是新的socket对象(用于和这个客户端通信),另一个是客户端的地址信息。注意,这里返回的client_socket和原来的server_socket不是同一个东西——server_socket继续监听,client_socket专门服务这个客户端。

关键点:accept()会阻塞当前线程。如果你希望同时服务多个客户端,就得用多线程或异步IO。这个后面会讲。

3.5 收发数据——真正的业务逻辑

连接建立后,就可以和客户端聊天了。收数据用recv(),发数据用send()。

# 接收数据
data = client_socket.recv(1024)
print(f"收到消息: {data.decode('utf-8')}")

# 发送数据
response = "消息已收到!"
client_socket.send(response.encode('utf-8'))

recv(1024)里的1024是缓冲区大小,单位是字节。如果消息超过1024字节,需要多次接收。我建议根据实际业务调整这个值——传小文本1024够用,传文件就得设大点。

注意:recv()返回空字节串(b'')时,表示客户端已经断开连接。一定要处理这个情况,否则程序会卡死。我曾经在早期项目里没处理这个,结果客户端一断线,服务端就死循环了……

3.6 完整示例——一个能跑的服务端

把上面五步串起来,就是一个完整的TCP服务端:

import socket

def start_server():
    # 1. 创建socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 2. 绑定地址
    server_address = ('0.0.0.0', 8888)
    server_socket.bind(server_address)
    
    # 3. 开始监听
    server_socket.listen(5)
    print(f"服务器启动,监听 {server_address}")
    
    while True:
        # 4. 接受连接
        client_socket, client_address = server_socket.accept()
        print(f"新连接: {client_address}")
        
        try:
            # 5. 收发数据
            data = client_socket.recv(1024)
            if data:
                print(f"收到: {data.decode('utf-8')}")
                client_socket.send("你好,客户端!".encode('utf-8'))
        finally:
            client_socket.close()

if __name__ == '__main__':
    start_server()

这个例子虽然简单,但五脏俱全。你把它跑起来,然后用telnet或自己写个客户端连上去试试。

3.7 避坑指南——我踩过的那些雷

做TCP服务端开发,有几个坑我几乎每次都会遇到:

  • 端口被占用:服务端退出后,端口可能还处于TIME_WAIT状态。加个server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)就能解决。
  • 粘包问题:TCP是流式协议,没有消息边界。我习惯在消息前加4字节的长度头,这样接收方就知道该读多少数据。
  • 异常处理:网络随时可能断开,所有recv/send都要包在try里。别偷懒,否则线上出问题你都不知道咋回事。
  • 资源泄漏:用完的socket记得close。我见过有人开了几万个连接不关,直接把服务器搞崩了。

我的习惯:每次写服务端,我都会先画个流程图,把异常路径标出来。这样写代码时心里有数,不容易漏掉边界情况。

3.8 小结

TCP服务端开发,说白了就是五步走:创建、绑定、监听、接受、收发。每一步都有它的讲究,但核心逻辑并不复杂。你只要记住:socket是门,bind是门牌号,listen是开门营业,accept是接待客人,recv/send是聊天。

把这个流程练熟了,后面学多线程、异步IO、高并发就轻松多了。嗯,今天就到这,动手试试吧。

可可精品资料库