【Python 内置模块】socket

2024-01-20 00:00:00

目录:

socket 模块介绍

socket 是进行底层网络编程的核心工具,允许开发者直接控制数据包的发送与接收,从而实现自定义的网络协议和通信应用。

什么是套接字

IP 地址和端口的组合叫做套接字,IP 可以找到一台联网的电脑,端口可以找到这台电脑上指定的程序。

Alice

  • ip: 192.1.1.1
  • 微信占用端口号 100
  • QQ 占用端口号 200

Bob

  • ip: 192.1.1.2
  • 微信占用端口号 100
  • QQ 占用端口号 200

现在 Alice 给 Bob 的维信发一条消息,知道 Bob 的 ip 地址 192.1.1.2 就可以找到 Bob 的电脑,知道 Bob 的微信端口是 100,就不会发消息错发给 QQ 了。

IP + 端口(Port) 是不同主机间进程进行通信的基本单位。

创建套接字对象

socket.socket() 创建套接字对象,函数需要指定两个关键参数:地址家族(Address Family)和套接字类型(Socket Type)。

地址家族:定义了网络地址的格式。

  • socket.AF_INET: 用于 IPv4 网络通信(最常用);
  • socket.AF_INET6: 用于 IPv6 网络通信;
  • socket.AF_UNIX 或 AF_LOCAL: 用于同一台机器上的进程间通信。

套接字类型:定义了数据传输的方式。

  • socket.SOCK_STREAM: 流式套接字,提供面向连接的、可靠的 TCP 服务,保证数据有序且无差错地到达;
  • socket.SOCK_DGRAM: 数据报套接字,提供无连接的 UDP 服务,不保证顺序和可靠性,但速度较快。

示例:

import socket

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

服务端流程

服务器端扮演被动的监听者角色,其标准流程如下:

  • 创建套接字:使用 socket.socket();
  • 绑定地址:使用 bind((host, port)) 方法将套接字与特定的本地 IP 地址和端口号关联;
  • 监听连接:调用 listen(backlog) 方法使套接字进入监听状态,参数 backlog 指定最大排队等待接受的连接数;
  • 接受连接:调用 accept() 方法。此方法会阻塞程序,直到有客户端连接请求到来。它返回一个新的套接字对象(用于与此客户端通信)和客户端的地址信息;
  • 数据交换:使用新套接字的 send()/sendall() 和 recv(bufsize) 方法与客户端进行数据收发;
  • 关闭连接:通信完成后,调用 close() 方法关闭套接字。
import socket

# 创建 TCP 套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接到服务器
client_socket.connect(('localhost', 9999))

# 发送消息,字符串需要编码为字节
message = 'Hello, Server!'
client_socket.sendall(message.encode())

# 接收回复(最多 1024 字节)
response = client_socket.recv(1024)
print(f'Received: {response.decode()}')

# 关闭连接
client_socket.close()

上述代码运行后,服务端会一直处于启动状态,等待客户端发来请求。

问题来了,客户端怎么找到这个服务端?

服务端可以理解成一个类似于 QQ 和微信的软件,启动服务端就相当于启动微信,服务端使用的 IP 是本地地址 localhost,端口号是 9999,有了 IP + 端口,客户端就可以连接服务端了,详情看下面客户端流程的示例。

客户端流程

客户端主动发起连接,流程更为简洁:

  • 创建套接字:同样使用 socket.socket();
  • 连接服务器:使用 connect((host, port)) 方法主动连接到服务器地址;
  • 数据交换:使用 send()/sendall() 和 recv() 方法与服务器通信;
  • 关闭连接:调用 close() 方法。
import socket

# 创建 TCP 套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接到服务器
client_socket.connect(('localhost', 9999))

# 发送消息,字符串需要编码为字节
message = 'Hello, Server!'
client_socket.sendall(message.encode())

# 接收回复(最多 1024 字节)
response = client_socket.recv(1024)
print(f'Received: {response.decode()}')

# 关闭连接
client_socket.close()

开一个新的终端,运行上面代码,客户端会向服务端发送一条消息,切回运行服务端的终端,可以看到有打印从客户端传过来的消息。

示例: 多线程聊天室

在实际应用中,如聊天室,服务器需要同时处理多个客户端连接。这可以通过为每个连接创建一个独立的线程来实现。

import socket
import threading

# 用于存储所有活跃客户端连接的列表
clients = []

def broadcast(message, sender_client):
    """向除发送者外的所有客户端广播消息"""
    for client in clients:
        if client != sender_client:
            try:
                client.send(message)
            except:
                # 如果发送失败,移除该客户端
                clients.remove(client)
                client.close()

def handle_client(client):
    """处理单个客户端连接的线程函数"""
    while True:
        try:
            message = client.recv(1024)
            if message:
                broadcast(message, client)
            else:
                # 接收到空消息,表示客户端断开
                raise Exception("Client disconnected")
        except:
            # 发生异常,移除客户端并结束线程
            if client in clients:
                clients.remove(client)
            client.close()
            break

def receive():
    """主函数,接受新连接并启动处理线程"""
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind(('localhost', 9999))
    server_socket.listen()
    print("Server started...")
    while True:
        client, addr = server_socket.accept()
        print(f"Connected with {str(addr)}")
        clients.append(client)
        # 为每个新连接创建一个线程
        thread = threading.Thread(target=handle_client, args=(client,))
        thread.start()

if __name__ == "__main__":
    receive()

返回首页

本文总阅读量  次
皖ICP备17026209号-3
总访问量: 
总访客量: