pin_drop当前位置:知识文库 ❯ 图文

Python TCP客户端与服务端编程 - 网络通信实战教程

一、TCP协议概述

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在Python网络编程中,TCP是最常用的通信方式之一,广泛应用于Web服务器、文件传输、即时通讯等场景。

TCP的核心特性包括:面向连接、可靠传输、流量控制、拥塞控制和全双工通信。这些特性确保了数据在网络传输中的完整性和有序性。

小贴士

互联网上超过90%的流量使用TCP协议。我们日常使用的HTTP、HTTPS、FTP、SMTP、SSH等协议都运行在TCP之上。了解TCP编程是掌握网络编程的基石。

二、TCP三次握手与四次挥手

TCP连接的建立和断开都遵循严格的过程。理解这些过程有助于编写更健壮的网络程序。

三次握手(建立连接)

  • 第一次:客户端发送SYN包到服务器,进入SYN_SENT状态

  • 第二次:服务器收到SYN后回复SYN+ACK,进入SYN_RECV状态

  • 第三次:客户端收到SYN+ACK后发送ACK,双方进入ESTABLISHED状态

四次挥手(断开连接)

  • 第一次:主动关闭方发送FIN,进入FIN_WAIT_1

  • 第二次:被动关闭方回复ACK,进入CLOSE_WAIT

  • 第三次:被动关闭方发送FIN,进入LAST_ACK

  • 第四次:主动关闭方回复ACK,进入TIME_WAIT

阶段 Python Socket方法 说明
创建连接 socket() → connect() 客户端发起连接
服务端监听 socket() → bind() → listen() 服务端准备接收连接
接受连接 accept() 三次握手完成
数据传输 send() / recv() 双向通信
关闭连接 close() 四次挥手

三、服务端开发步骤

TCP服务端的基本开发流程包括创建Socket、绑定地址、监听连接、接受连接和数据处理五个步骤。

代码示例

import socket

def create_tcp_server(host='localhost', port=8080):
    """创建TCP服务端的基本流程"""
    # 步骤1:创建TCP Socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 步骤2:设置地址重用(避免重启时端口占用)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
    # 步骤3:绑定IP地址和端口
    server_socket.bind((host, port))
    print(f"服务器绑定到 {host}:{port}")
    
    # 步骤4:开始监听(参数为最大等待连接数)
    server_socket.listen(5)
    print("服务器正在监听...")
    
    # 步骤5:接受客户端连接
    client_socket, client_address = server_socket.accept()
    print(f"客户端连接: {client_address}")
    
    # 步骤6:接收和发送数据
    data = client_socket.recv(1024)
    print(f"收到数据: {data.decode('utf-8')}")
    
    response = "Hello from server!"
    client_socket.sendall(response.encode('utf-8'))
    
    # 步骤7:关闭连接
    client_socket.close()
    server_socket.close()

四、客户端开发步骤

相比服务端,TCP客户端的开发更为简单,只需要创建Socket并连接到服务端即可开始通信。

代码示例

import socket

def create_tcp_client(server_host='localhost', server_port=8080):
    """创建TCP客户端的基本流程"""
    # 步骤1:创建TCP Socket
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 步骤2:连接到服务器
    client_socket.connect((server_host, server_port))
    print(f"已连接到服务器 {server_host}:{server_port}")
    
    # 步骤3:发送数据
    message = "Hello, Server!"
    client_socket.sendall(message.encode('utf-8'))
    print(f"发送: {message}")
    
    # 步骤4:接收服务器响应
    response = client_socket.recv(1024)
    print(f"收到响应: {response.decode('utf-8')}")
    
    # 步骤5:关闭连接
    client_socket.close()
    print("连接已关闭")

五、多客户端并发处理

实际应用中,服务端需要同时处理多个客户端的连接。Python提供了多种并发处理方式,包括多线程、多进程和异步I/O。

多线程方式处理并发

代码示例

import socket
import threading

def handle_client(client_socket, client_address):
    """处理单个客户端连接"""
    print(f"客户端 {client_address} 已连接")
    try:
        while True:
            data = client_socket.recv(1024)
            if not data:
                break
            message = data.decode('utf-8')
            print(f"来自 {client_address}: {message}")
            
            # 回显消息
            response = f"服务器收到: {message}"
            client_socket.sendall(response.encode('utf-8'))
    except ConnectionResetError:
        print(f"客户端 {client_address} 断开连接")
    finally:
        client_socket.close()
        print(f"客户端 {client_address} 连接已关闭")

def multi_client_server(host='localhost', port=8080):
    """支持多客户端的TCP服务器"""
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server_socket.bind((host, port))
        server_socket.listen(5)
        print(f"服务器启动,监听 {host}:{port}")
        
        while True:
            client_socket, client_address = server_socket.accept()
            # 为每个客户端创建新线程
            client_thread = threading.Thread(
                target=handle_client,
                args=(client_socket, client_address)
            )
            client_thread.daemon = True
            client_thread.start()

小贴士

对于高并发场景,建议使用selectepoll或Python的asyncio模块,它们比多线程更高效,能处理数万个并发连接。

六、完整实战代码示例

示例1:简易聊天服务器

代码示例

# chat_server.py - 简易聊天服务器
import socket
import threading

class ChatServer:
    def __init__(self, host='localhost', port=8080):
        self.host = host
        self.port = port
        self.clients = {}  # {client_address: client_socket}
        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}")
        
        while True:
            client_socket, client_address = self.server_socket.accept()
            self.clients[client_address] = client_socket
            print(f"新客户端加入: {client_address}")
            
            # 广播消息
            self.broadcast(f"[系统] {client_address} 加入了聊天室")
            
            # 启动线程处理客户端
            thread = threading.Thread(
                target=self.handle_client,
                args=(client_socket, client_address)
            )
            thread.daemon = True
            thread.start()
    
    def handle_client(self, client_socket, client_address):
        """处理客户端消息"""
        try:
            while True:
                data = client_socket.recv(1024)
                if not data:
                    break
                message = data.decode('utf-8')
                self.broadcast(f"[{client_address}] {message}")
        except ConnectionResetError:
            pass
        finally:
            del self.clients[client_address]
            client_socket.close()
            self.broadcast(f"[系统] {client_address} 离开了聊天室")
    
    def broadcast(self, message):
        """广播消息给所有客户端"""
        for addr, sock in list(self.clients.items()):
            try:
                sock.sendall(message.encode('utf-8'))
            except:
                pass

if __name__ == '__main__':
    server = ChatServer()
    server.start()

示例2:聊天客户端

代码示例

# chat_client.py - 聊天客户端
import socket
import threading

def receive_messages(client_socket):
    """接收消息的线程函数"""
    while True:
        try:
            data = client_socket.recv(1024)
            if not data:
                break
            print(f"\n{data.decode('utf-8')}")
            print("请输入消息: ", end='', flush=True)
        except:
            break

def start_client(host='localhost', port=8080):
    """启动聊天客户端"""
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_socket.connect((host, port))
    print("已连接到聊天服务器!")
    
    # 启动接收消息线程
    recv_thread = threading.Thread(target=receive_messages, args=(client_socket,))
    recv_thread.daemon = True
    recv_thread.start()
    
    # 主线程发送消息
    while True:
        message = input("请输入消息: ")
        if message.lower() == 'quit':
            break
        client_socket.sendall(message.encode('utf-8'))
    
    client_socket.close()
    print("已断开连接")

if __name__ == '__main__':
    start_client()

示例3:HTTP服务器简化版

代码示例

# simple_http_server.py - 简易HTTP服务器
import socket
import threading

def handle_http_request(client_socket):
    """处理HTTP请求"""
    request = client_socket.recv(4096).decode('utf-8')
    print(f"收到请求:\n{request}")
    
    # 构建HTTP响应
    html_content = """
    <html>
    <head><title>Python TCP服务器</title></head>
    <body>
        <p>这是一个使用Python Socket实现的简易HTTP服务器</p>
    </body>
    </html>
    """
    
    response = f"""HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: {len(html_content)}

{html_content}"""
    
    client_socket.sendall(response.encode('utf-8'))
    client_socket.close()

def start_http_server(host='localhost', port=8000):
    """启动HTTP服务器"""
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server.bind((host, port))
        server.listen(5)
        print(f"HTTP服务器启动: http://{host}:{port}")
        
        while True:
            client, addr = server.accept()
            thread = threading.Thread(
                target=handle_http_request, args=(client,)
            )
            thread.start()

if __name__ == '__main__':
    start_http_server()

七、注意事项与最佳实践

注意1:使用sendall()而不是send(),确保所有数据都被发送出去,避免部分发送导致的数据丢失。

注意2:始终设置合理的超时时间settimeout(),防止连接长时间阻塞导致程序无响应。

注意3:处理粘包问题。TCP是字节流协议,没有消息边界,需要通过长度前缀、分隔符或固定长度来区分消息。

注意4:生产环境应使用with语句或try-finally确保Socket正确关闭,防止文件描述符泄漏。

八、小结与练习题

小结

  • 服务端流程:socket() → bind() → listen() → accept() → recv()/send() → close()

  • 客户端流程:socket() → connect() → send()/recv() → close()

  • 并发处理:使用多线程、多进程或异步I/O实现多客户端并发

练习题

练习1

编写一个TCP文件传输服务器,支持客户端上传和下载文件。要求实现文件大小确认、传输进度显示和断点续传功能。

练习2

使用asyncio模块重写聊天服务器,实现异步高并发处理。对比多线程版本,分析两者的性能差异。

常见问题

TCP粘包问题是什么?如何解决?

TCP是字节流协议,发送方多次send的数据可能被接收方一次recv收到(粘包),或一次send的数据被分多次recv(半包)。解决方法:①使用固定长度消息;②在消息头添加长度字段;③使用特殊分隔符(如\n)标记消息边界。

listen()参数5是什么意思?

listen(5)中的5指定了等待连接队列(backlog)的最大长度。当队列满时,新连接会被拒绝。实际值可能被系统限制,Linux默认通常为128。高并发服务器应适当增大此值,但更重要的是快速accept()处理连接。

如何优雅地关闭TCP连接?

①发送方先调用shutdown(socket.SHUT_WR)关闭写端,通知对方数据发送完毕;②等待对方确认并关闭;③最后调用close()释放资源。使用with语句可以自动处理关闭操作,但异常情况下仍需手动shutdown。

为什么要设置SO_REUSEADDR?

当TCP连接关闭后,端口会进入TIME_WAIT状态(通常持续1-4分钟),在此期间无法重新绑定该端口。设置SO_REUSEADDR允许立即重用处于TIME_WAIT状态的端口,方便开发调试时频繁重启服务器。

accept()会阻塞吗?如何避免阻塞?

accept()默认是阻塞的,会一直等待直到有新连接到来。避免阻塞的方法:①设置socket.settimeout();②使用select模块监控socket可读状态;③使用非阻塞模式socket.setblocking(False);④使用asyncio异步框架。

标签: TCP编程 客户端服务端 三次握手 并发处理 多线程 Python教程

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: Socket编程基础 - Python网络编程入门教程 下一篇: Python UDP编程详解 - 广播与多播实战教程

poll相关推荐