socket库是Python标准库的一部分,提供了底层的网络接口,可以用于实现各种网络协议的通信。在网络编程中,socket(套接字)是一种在网络设备之间通信的端点。Python的socket库使得网络编程变得简单且高效,能够处理TCP/IP、UDP等协议。

Python官方文档:socket — 低层级的网络接口

1. socket库的基本概念

  • Socket(套接字):Socket是网络通信的基本单元,既可以是一个客户端,也可以是一个服务器。Socket可以绑定到特定的IP地址和端口号,通过这个绑定来实现网络通信,接收来自该地址和端口的数据,或发送数据到该地址和端口。
  • Address Family:指定使用的地址类型,如IPv4或IPv6。
    • AF_INET:用于IPv4
    • AF_INET6:用于IPv6
  • Socket Type:指定使用的通信协议,如TCP或UDP。
    • SOCK_STREAM:面向连接的流式Socket,使用TCP协议。
    • SOCK_DGRAM:面向无连接的数据报Socket,使用UDP协议。

2. socket库常用类与方法

2.1 Socket对象

创建Socket对象的方法:

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

这是一个TCP Socket,指定了AF_INET(IPv4)和SOCK_STREAM(TCP)。

# 创建TCP Socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 创建UDP Socket
udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

2.2 绑定地址和端口

服务器端需要将Socket绑定到指定的地址和端口,使用bind()方法将Socket绑定到指定的IP地址和端口号。

s.bind(('localhost', 8080))

2.3 监听连接(仅适用于TCP服务器)

服务器端使用**listen()**方法来开始监听连接。

s.listen(5)  # 参数指定可以排队的最大连接数

2.4 接受连接(仅适用于TCP服务器)

服务器端使用accept()方法来接受客户端的连接,该方法会阻塞直到有客户端连接,返回一个新的Socket对象表示客户端连接。

connection, client_address = s.accept()
print(f"Connection from {client_address}")

2.5 连接到服务器(仅适用于TCP客户端)

客户端使用connect()方法连接到服务器。

s.connect(('localhost', 8080))

2.6 发送和接收数据

对于TCP,使用send()sendall()recv()方法来发送和接收数据包。

  • send():发送数据
  • sendall():发送数据,确保整个消息发送完毕
  • recv():接收数据
message = 'This is a message'
s.sendall(message.encode('utf-8'))
s.send(b'Hello, World!')  			# 发送字节数据

data = connection.recv(1024)        # 参数1024表示接收的最大字节数
print(f"Received {data.decode('utf-8')}")

对于UDP,使用sendto()recvfrom()方法来发送和接收数据包。

message = 'This is a UDP message'
s.sendto(message.encode('utf-8'), ('localhost', 8080))
s.sendto(b'Hello, UDP!', ('localhost', 8080))

data, server_addr = s.recvfrom(1024)
print(f"Received {data.decode('utf-8')} from {server_addr}")

2.7 关闭Socket

close()方法用于关闭Socket连接。

s.close()

2.8 常见套接字方法

  • socket.gethostname():获取当前主机名。
  • socket.gethostbyname(hostname):根据主机名获取 IPv4 地址。
  • socket.gethostbyname_ex(hostname):获取主机名、别名列表以及对应的所有 IP 地址。
  • socket.getaddrinfo(host, port):获取主机和端口的地址信息列表。

3. 高级功能

3.1 设置Socket选项

使用setsockopt()方法可以设置Socket的选项,可以设置重用地址选项:

s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

在频繁重启服务器或快速重新启动服务器时,可以避免由于端口被占用而导致的绑定失败。当运行一个服务器时,如果不设置 SO_REUSEADDR,当关闭服务器后,可能需要等待几分钟才能重新绑定到同一端口。但设置这个选项后,可以立即重新绑定同一端口。

3.2 设置超时

settimeout()方法设置Socket操作的超时时间,防止无限期阻塞。

s.settimeout(5.0)  # 设置超时为5秒

3.3 非阻塞模式

setblocking()方法可以将Socket设置为非阻塞模式,调用后立即返回,即使没有数据可读或写。

s.setblocking(False)

4. 常用异常处理

网络编程中常会遇到各种异常情况,如连接失败、数据发送失败等。socket库提供了相应的异常类来捕获和处理这些情况。

  • socket.error:所有Socket错误的基类。
  • socket.timeout:操作超时。
  • socket.herror:主机相关的错误。
  • socket.gaierror:地址相关的错误。
try:
    s.connect(('localhost', 8080))
except socket.timeout:
    print("Connection timed out!")
except socket.error as e:
    print(f"Socket error: {e}")

5. 常见用例

5.1 创建TCP服务器

import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('localhost', 8080))
server_socket.listen(5)

print("Server is listening on port 8080...")

while True:
    client_socket, client_address = server_socket.accept()
    print(f"Connection from {client_address}")
    data = client_socket.recv(1024)
    if data:
        print(f"Received: {data.decode('utf-8')}")
        client_socket.sendall(data)  # 回显接收到的数据
    client_socket.close()

5.2 创建TCP客户端

import socket

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('localhost', 8080))

client_socket.sendall(b'Hello, Server!')
data = client_socket.recv(1024)
print(f"Received: {data.decode('utf-8')}")

client_socket.close()

5.3 创建UDP服务器

import socket

udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.bind(('localhost', 8080))

print("UDP server is listening on port 8080...")

while True:
    data, client_address= udp_socket.recvfrom(1024)
    print(f"Received from {client_address}: {data.decode('utf-8')}")
    udp_socket.sendto(data, client_address)

5.4 创建UDP客户端

import socket

udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_address = ('localhost', 8080)

udp_socket.sendto(b'Hello, UDP Server!', server_address)
data, server = udp_socket.recvfrom(1024)
print(f"Received {data.decode('utf-8')} from {server}")

udp_socket.close()

客户端不使用 bind() 是因为它只需要发送数据,而不需要监听特定的端口来接收数据。
在UDP客户端中,设置 bind() 并不是总是必须的,只有在某些特定情况下需要使用。通常,UDP客户端发送数据时并不需要显式地绑定一个本地端口,操作系统会自动分配一个临时的端口。如果不关心客户端使用哪个端口,只需要发送数据,那么是可以不使用 bind() 的。
但是,在某些情况下,客户端也需要使用 bind() 来绑定特定的本地端口,具体原因如下:

  1. 接收数据
    • 如果客户端不仅需要发送UDP数据,还要接收响应(比如服务器回发的数据),则需要绑定一个本地端口来监听这些数据。例如,UDP广播的客户端往往需要在某个端口上接收来自服务器或其他设备的响应。
  2. 特定端口通信
    • 如果协议要求客户端通过特定的端口进行通信,或者需要与防火墙和NAT等设备协作,客户端可能需要绑定到一个已知的端口。这样,服务器知道客户端将使用哪个端口发送和接收数据。
  3. 服务器也可能向客户端发送数据
    • 在某些情况下,服务器可能会主动向客户端发送数据包。如果客户端没有绑定一个已知端口,服务器就不知道该将数据包发送到哪个端口。
  4. 广播通信
    • 如果你正在实现的客户端使用UDP广播,通常会绑定一个固定的端口来监听所有设备的响应。广播消息会被所有接收端收到,客户端需要知道哪个端口可以接收广播响应。
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)		# 创建UDP套接字
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)	# 允许广播
socket.bind(("0.0.0.0", 8410))  							# 绑定本地端口8410

# 设置广播地址和端口
broadcast_address = ("192.168.1.255", 7408)  # 或者 ("255.255.255.255", 7408)

# 发送广播消息
message = b"Hello, this is a broadcast message!"
sock.sendto(message, broadcast_address)

sock.close()

6. 多线程或多进程处理

在实现高并发服务器时,通常需要使用多线程或多进程来处理多个客户端的连接。Python的threading和multiprocessing模块可以与socket库结合使用。

7. socket库的扩展

Python还提供了socketserver模块,这是socket库的高级封装,提供了更加方便的方式来创建网络服务器。你可以根据需要选择使用。

09-05 03:16