专栏名称: 新语数据故事汇
《新语数据故事汇,数说新语》科普数据科学、讲述数据故事,深层次挖掘数据价值。
目录
相关文章推荐
51好读  ›  专栏  ›  新语数据故事汇

一文带您了解Python 网络编程:socket 示例

新语数据故事汇  · 公众号  ·  · 2024-08-22 18:22

正文

套接字(Sockets )和套接字 API 用于在网络上传递消息,它们提供了一种进程间通信 (inter-process communication,IPC) 的形式。网络可以是计算机上的一个逻辑本地网络,也可以是一个物理上连接到外部网络的网络,并通过该外部网络连接到其他网络。

套接字(Sockets )有着悠久的历史。它们的使用起源于1971年的ARPAnet,后来在1983年发布的伯克利软件发布版(Berkeley Software Distribution,BSD)操作系统中成为了一种API,被称为伯克利套接字(Berkeley sockets)。

在90年代,随着万维网(World Wide Web)的兴起,网络编程也迅速发展。利用新连接的网络并使用套接字的不仅仅是Web服务器和浏览器。各种类型和规模的客户端-服务器应用程序也得到了广泛应用。

今天,尽管套接字API使用的底层协议多年来有所演变,并且出现了新协议,但底层API本身保持不变。

最常见的套接字应用程序类型是客户端-服务器应用程序,其中一方充当服务器并等待来自客户端的连接,这是主要的网络模式。

Python Socket API 概述

Python 的 socket 模块提供了一组API接口,用于访问套接字 API(the Berkeley sockets API)。该模块中的主要API 函数和方法包括:

  • socket()

  • .bind()

  • .listen()

  • .accept()

  • .connect()

  • .connect_ex()

  • .send()

  • .recv()

  • .close()

Python 提供了一个方便且一致的 API,它直接映射到系统调用及其对应的 C 函数。作为其标准库的一部分,Python 还提供了一些类,使得使用这些底层套接字函数更加简单,比如 socketserver 模块,这是一个用于网络服务器的框架;此外,还有许多模块实现了更高级的互联网协议,如 HTTP 和 SMTP。

TCP 套接字

使用 socket.socket() 创建一个套接字对象,并将套接字类型指定为 socket.SOCK_STREAM 。默认使用的协议是传输控制协议 ( Transmission Control Protocol ,TCP)。

传输控制协议 (TCP) 具有以下特点:

  • 可靠性 :网络中丢失的数据包会被发送方检测并重新传输。

  • 按序数据传递 :您的应用程序将按发送方写入数据的顺序读取数据。

相比之下,也可以使用 socket.SOCK_DGRAM 创建的用户数据报协议 (User Datagram Protocol,UDP) 套接字不具备可靠性,接收方读取的数据可能会与发送方写入的数据顺序不一致。TCP 让您无需担心数据包丢失、数据到达顺序混乱以及其他在网络通信中不可避免的陷阱。下图是 TCP 的套接字 API 调用顺序和数据流:

左侧列表示服务器。右侧列表示客户端。从左上角开始,注意服务器为设置“监听”套接字所进行的 API 调用:

  • socket()

  • .bind()

  • .listen()

  • .accept()

监听套接字的作用正如其名称所示:它监听来自客户端的连接。当客户端连接时,服务器调用 .accept() 来接受或完成连接。客户端调用 .connect() 来建立与服务器的连接,并启动三次握手。握手步骤很重要,因为它确保连接的每一端在网络中是可达的,换句话说,客户端可以到达服务器,反之亦然。有时,可能只有一个主机、客户端或服务器可以到达另一个。在中间部分是往返通信阶段,客户端和服务器通过调用 .send() .recv() 来交换数据。最后,客户端和服务器关闭各自的套接字。

Echo Client and Server

上面介绍了套接字 API 以及客户端和服务器如何通信,下面是一个最为简单的第一个客户端和服务器。将从一个简单的实现开始。服务器将简单地将接收到的内容原样返回给客户端。

以下是服务器的代码:

import socketHOST = "127.0.0.1"  # Standard loopback interface address (localhost)PORT = 65432  # Port to listen on (non-privileged ports are > 1023)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind((HOST, PORT)) s.listen() while True: conn, addr = s.accept() with conn: print(f"Connected by {addr}") while True: data = conn.recv(1024) if not data: break conn.sendall(data) print(data)

这段代码实现了一个简单的回显服务器,功能如下:

  1. 导入模块 :使用 socket 模块来进行网络编程。

  2. 定义地址和端口 :服务器监听本地主机 ( 127.0.0.1 ) 和端口 65432

  3. 创建套接字 :使用 IPv4 和 TCP 协议创建一个套接字。

  4. 绑定和监听 :将套接字绑定到指定的地址和端口,然后开始监听连接请求。

  5. 处理连接 :接受客户端连接并打印客户端地址。

  6. 数据接收与回显 :接收客户端发送的数据并将其回显给客户端,直到客户端断开连接。

以下是client 代码:

import socket
HOST = "127.0.0.1" # The server's hostname or IP addressPORT = 65432 # The port used by the server
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((HOST, PORT)) s.sendall(b"Hello, world") data = s.recv(1024)
print(f"Received {data!r}")

这段代码实现了一个简单的客户端,功能如下:

  1. 导入模块 :使用 socket 模块进行网络编程。

  2. 定义服务器地址和端口 :客户端连接到本地主机 ( 127.0.0.1 ) 的端口 65432

  3. 创建并连接套接字 :使用 IPv4 和 TCP 协议创建套接字,并连接到指定的服务器地址和端口。

  4. 发送数据 :向服务器发送字节数据 b"Hello, world"

  5. 接收数据 :从服务器接收最多 1024 字节的数据。

  6. 打印接收到的数据 :将接收到的数据以原始格式输出。

通信过程解析

现在,您将更详细地了解客户端和服务器之间是如何进行通信的:

使用回环接口( loopback interface )(IPv4 地址 127.0.0.1 或 IPv6 地址 ::1)时,数据不会离开主机或接触到外部网络。在上面的示意图中,回环接口( loopback interface )位于主机内部。这代表了回环接口的内部特性,显示了穿越它的连接和数据仅在主机内部。这也是为什么回环接口和 IP 地址 127.0.0.1 或 ::1 被称为“localhost”。

应用程序使用回环接口( loopback interface )与在主机上运行的其他进程进行通信,同时确保安全性和与外部网络的隔离。因为它是内部的,仅从主机内部可以访问,所以不会暴露在外部。

当您在应用程序中使用 127.0.0.1 或 ::1 以外的 IP 地址时,它通常绑定到连接到外部网络的以太网接口。这是通向“localhost”之外的其他主机的网关。

多连接客户端和服务器

在接下来的两个部分中,您将创建一个服务器和客户端,使用来自 selectors 模块的选择器对象来处理多个连接。服务器示例代码如下:

# multiconn-server.py
import sysimport socketimport selectorsimport types
sel = selectors.DefaultSelector()
host = "127.0.0.1" port = 65432 lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)lsock.bind((host, port))lsock.listen()print(f"Listening on {(host, port)}")lsock.setblocking(False)sel.register(lsock, selectors.EVENT_READ, data=None)
def accept_wrapper(sock): conn, addr = sock.accept() # Should be ready to read print(f"Accepted connection from {addr}") conn.setblocking(False) data = types.SimpleNamespace(addr=addr, inb=b"", outb=b"") events = selectors.EVENT_READ | selectors.EVENT_WRITE sel.register(conn, events, data=data)
def service_connection(key, mask): sock = key.fileobj data = key.data if mask & selectors.EVENT_READ: recv_data = sock.recv(1024) # Should be ready to read if recv_data: data.outb += recv_data else: print(f"Closing connection to {data.addr}") sel.unregister(sock) sock.close() if mask & selectors.EVENT_WRITE: if data.outb: print(f"Echoing {data.outb!r} to {data.addr}") sent = sock.send(data.outb) # Should be ready to write data.outb = data.outb[sent:]
try: while True: events = sel.select(timeout=None) for key, mask in events: if key.data is None: accept_wrapper(key.fileobj) else: service_connection(key, mask)except KeyboardInterrupt: print("Caught keyboard interrupt, exiting")finally: sel.close()

这段代码实现了一个多连接的服务器,能够同时处理多个客户端的连接。

导入和初始化

  • socket selectors 模块用于创建和管理网络连接。

  • sel 是选择器对象,用于管理多个套接字的 I/O 事件。

  • 服务器设置

  • 创建一个监听套接字 lsock ,绑定到 127.0.0.1 地址和端口 65432

  • 设置套接字为非阻塞模式,并使用选择器 sel 注册监听套接字,监控其 EVENT_READ 事件(即有新的连接请求)。

accept_wrapper 函数
  • 当有新连接到来时,接受连接,并为每个连接创建一个新的非阻塞套接字。

  • 为新连接创建一个数据对象 data ,包含连接的地址以及输入和输出缓冲区。

  • 使用选择器注册新连接,监控其 EVENT_READ EVENT_WRITE 事件。

service_connection 函数: 处理现有连接的 I/O 操作:
  • 如果有数据可读 ( EVENT_READ ),从客户端读取数据并将其存储在输出缓冲区中。

  • 如果可以写入 ( EVENT_WRITE ),将输出缓冲区中的数据发送回客户端(回显)。

事件循环
  • try 块中,持续监听和处理所有注册的套接字的 I/O 事件。

  • 如果有新的连接请求,调用 accept_wrapper 处理。

  • 如果有现有连接的读写事件,调用 service_connection 处理。

  • 使用 except KeyboardInterrupt 捕获键盘中断,安全地关闭选择器并退出。

该服务器可以同时处理多个客户端连接,接收并回显客户端发送的数据。通过使用 selectors 模块,服务器能够高效地管理多个非阻塞套接字,从而实现多连接处理。

client示例代码如下:

import






请到「今天看啥」查看全文