1.快速了解
socket.socket 主要指的是 Python 编程语言中 socket 模块里的函数,用于创建一个新的 socket 对象。socket 是一个通信端点,用于在网络上发送和接收数据,或在同一台机器上的进程之间进行通信。
在 Python 中,你必须首先导入 socket 模块,然后调用 socket() 函数来实例化一个新的 socket:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
这行代码使用常见的默认参数创建了一个套接字对象 s :
socket.AF_INET:指定地址族为 IPv4(互联网协议版本 4)。
socket.SOCK_STREAM : 指定套接字类型为流式套接字,该套接字使用传输控制协议(TCP)进行可靠、面向连接的通信。
创建后,套接字对象 s 可以使用各种方法(如 bind() 、 listen() 、 connect() 、 send() 和 recv() )来促进应用程序之间的网络通信。
在网络(一般概念)
通常,套接字是通信信道的一个端点。它由 IP 地址和端口号的组合定义。这种组合作为一个唯一标识符,允许不同的应用程序和设备在网络中交换数据,就像特定街道地址上的特定邮箱一样。
- 流式套接字(TCP):提供可靠、有序的双向连接,确保数据不会丢失或重复。这用于网页浏览和电子邮件等应用程序。
- 数据报套接字(UDP):使用无连接通信,不保证顺序或可靠性,常用于视频流和在线游戏等应用程序。
Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯.
Python 中,我们用 socket()函数来创建套接字,语法格式如下:
socket.socket( [family[, type[, proto]] ])
参数
- family: 套接字家族可以使 AF_UNIX 或者 AF_INET。
- type: 套接字类型可以根据是面向连接的还是非连接分为 SOCK_STREAM 或 SOCK_DGRAM。
- protocol: 一般不填默认为 0。
2.Socket 对象(内建)方法
| 函数 | 描述 |
|---|---|
| 服务器端套接字 | |
| s.bind() | 绑定地址(host,port)到套接字, 在 AF_INET下,以元组(host,port)的形式表示地址。 |
| s.listen() | 开始 TCP 监听。backlog 指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为 1,大部分应用程序设为 5 就可以了。 |
| s.accept() | 被动接受TCP客户端连接,(阻塞式)等待连接的到来 |
| 客户端套接字 | |
| s.connect() | 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。 |
| s.connect_ex() | connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 |
| 公共用途的套接字函数 | |
| s.recv() | 接收 TCP 数据,数据以字符串形式返回,bufsize 指定要接收的最大数据量。flag 提供有关消息的其他信息,通常可以忽略。 |
| s.send() | 发送 TCP 数据,将 string 中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于 string 的字节大小。 |
| s.sendall() | 完整发送 TCP 数据。将 string 中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回 None,失败则抛出异常。 |
| s.recvfrom() | 接收 UDP 数据,与 recv() 类似,但返回值是(data,address)。其中 data 是包含接收数据的字符串,address 是发送数据的套接字地址。 |
| s.sendto() | 发送 UDP 数据,将数据发送到套接字,address 是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。 |
| s.close() | 关闭套接字 |
| s.getpeername() | 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。 |
| s.getsockname() | 返回套接字自己的地址。通常是一个元组(ipaddr,port) |
| s.setsockopt(level,optname,value) | 设置给定套接字选项的值。 |
| s.getsockopt(level,optname[.buflen]) | 返回套接字选项的值。 |
| s.settimeout(timeout) | 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect()) |
| s.gettimeout() | 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。 |
| s.fileno() | 返回套接字的文件描述符。 |
| s.setblocking(flag) | 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。 |
| s.makefile() | 创建一个与该套接字相关连的文件 |
Python Internet 模块
以下列出了 Python 网络编程的一些重要模块:
| 协议 | 功能用处 | 端口号 | Python 模块 |
|---|---|---|---|
| HTTP | 网页访问 | 80 | httplib, urllib, xmlrpclib |
| NNTP | 阅读和张贴新闻文章,俗称为"帖子" | 119 | nntplib |
| FTP | 文件传输 | 20 | ftplib, urllib |
| SMTP | 发送邮件 | 25 | smtplib |
| POP3 | 接收邮件 | 110 | poplib |
| IMAP4 | 获取邮件 | 143 | imaplib |
| Telnet | 命令行 | 23 | telnetlib |
| Gopher | 信息查找 | 70 | gopherlib, urllib |
3.案例实战
第一个入门案例:
https://www.runoob.com/python/python-socket.html
https://www.geeksforgeeks.org/python/socket-programming-python/
玩完上面的两个入门案例,觉得也就那样,了解一下整个socket的C/S交互过程即可。
接下来咱们玩高级一点Socket玩法,我的本地Ubuntu虚拟机(192.168.0.x)作为服务器,我的Windows宿主机作为客户端来进行交互。
【注意:由于是跨机器通信,需要确保虚拟机和宿主机之间的网络是连通的,这里我的虚拟机使用的是桥接网络】
步骤:
- 在Ubuntu虚拟机上运行服务器代码,绑定到虚拟机的IP地址(例如192.168.0.6)和某个端口(比如12345)。
- 在Windows宿主机上运行客户端代码,连接到虚拟机的IP地址和端口。
由于是简单的示例,我们只实现一个简单的消息发送和接收。
Ubuntu虚拟机(服务器) + Windows宿主机(客户端) Socket通信Demo
A:Ubuntu虚拟机 - 服务器端代码 (server_ubuntu.py)
python
import socket
import threading
import time
import sys
class UbuntuServer:
def __init__(self, host='0.0.0.0', port=12345):
"""
初始化Ubuntu服务器
:param host: 监听地址,0.0.0.0表示监听所有网络接口
:param port: 监听端口
"""
self.host = host
self.port = port
self.server_socket = None
self.clients = []
self.running = True
def start(self):
"""
启动服务器
"""
try:
# 创建socket对象
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置地址重用,避免"Address already in use"错误
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定地址和端口
self.server_socket.bind((self.host, self.port))
# 开始监听连接
self.server_socket.listen(5)
# 设置非阻塞模式,以便能够响应键盘中断
self.server_socket.settimeout(1)
print("=" * 50)
print(f"Ubuntu Socket服务器启动成功!")
print(f"监听地址: {self.host}")
print(f"监听端口: {self.port}")
print(f"服务器IP: {socket.gethostbyname(socket.gethostname())}")
print("等待客户端连接...")
print("=" * 50)
# 启动监控线程
monitor_thread = threading.Thread(target=self.monitor_clients, daemon=True)
monitor_thread.start()
# 接受客户端连接
self.accept_connections()
except Exception as e:
print(f"服务器启动失败: {e}")
sys.exit(1)
def accept_connections(self):
"""
接受客户端连接
"""
while self.running:
try:
# 接受客户端连接
client_socket, client_address = self.server_socket.accept()
# 获取客户端信息
client_info = {
'socket': client_socket,
'address': client_address,
'connected_time': time.strftime("%Y-%m-%d %H:%M:%S")
}
# 添加到客户端列表
self.clients.append(client_info)
print(f"[+] 新的客户端连接: {client_address}")
print(f" 连接时间: {client_info['connected_time']}")
print(f" 当前在线客户端数: {len(self.clients)}")
# 创建线程处理客户端
client_thread = threading.Thread(
target=self.handle_client,
args=(client_info,),
daemon=True
)
client_thread.start()
# 发送欢迎消息
welcome_msg = f"欢迎来自 {client_address} 的客户端!\n"
welcome_msg += f"您已成功连接到Ubuntu服务器\n"
welcome_msg += f"服务器时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n"
welcome_msg += f"发送 'quit' 可以断开连接\n"
welcome_msg += f"发送 'list' 查看在线客户端\n"
welcome_msg += f"发送 'sysinfo' 获取系统信息\n"
client_socket.send(welcome_msg.encode('utf-8'))
except socket.timeout:
# 超时,继续循环检查running状态
continue
except Exception as e:
if self.running:
print(f"接受连接时出错: {e}")
def handle_client(self, client_info):
"""
处理单个客户端连接
"""
client_socket = client_info['socket']
client_address = client_info['address']
try:
while self.running:
# 接收客户端消息
try:
data = client_socket.recv(1024).decode('utf-8')
# 如果客户端断开连接
if not data:
break
data = data.strip()
print(f"[消息] 来自 {client_address}: {data}")
# 处理特殊命令
response = self.process_command(data, client_info)
# 发送响应
if response:
client_socket.send(response.encode('utf-8'))
# 如果是退出命令,断开连接
if data.lower() == 'quit':
break
except socket.timeout:
continue
except ConnectionResetError:
print(f"[-] 客户端 {client_address} 异常断开")
except Exception as e:
print(f"处理客户端 {client_address} 时出错: {e}")
finally:
# 关闭连接
self.disconnect_client(client_info)
def process_command(self, command, client_info):
"""
处理客户端命令
"""
cmd = command.lower()
if cmd == 'hello':
return f"你好,{client_info['address']}!我是Ubuntu服务器\n"
elif cmd == 'time':
return f"服务器当前时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n"
elif cmd == 'sysinfo':
import platform
info = f"=== 系统信息 ===\n"
info += f"系统: {platform.system()}\n"
info += f"版本: {platform.version()}\n"
info += f"架构: {platform.machine()}\n"
info += f"处理器: {platform.processor()}\n"
info += f"Python版本: {platform.python_version()}\n"
return info
elif cmd == 'list':
if len(self.clients) == 0:
return "当前没有其他客户端在线\n"
result = f"=== 在线客户端 ({len(self.clients)}个) ===\n"
for i, client in enumerate(self.clients, 1):
result += f"{i}. {client['address']} - 连接时间: {client['connected_time']}\n"
return result
elif cmd == 'file':
# 返回一个示例文件内容
example_content = """这是一个示例文件内容:
服务器配置文件示例:
port: 12345
max_clients: 10
timeout: 30
日志级别: INFO
"""
return f"文件内容:\n{example_content}"
elif cmd == 'help':
help_msg = """=== 可用命令 ===
hello - 打招呼
time - 获取服务器时间
sysinfo - 获取系统信息
list - 查看在线客户端
file - 获取示例文件
help - 显示帮助信息
quit - 断开连接
"""
return help_msg
else:
# 默认回复
return f"收到消息: {command}\n发送 'help' 查看可用命令\n"
def disconnect_client(self, client_info):
"""
断开客户端连接
"""
if client_info in self.clients:
client_socket = client_info['socket']
client_address = client_info['address']
# 从列表中移除
self.clients.remove(client_info)
# 关闭socket
try:
client_socket.close()
except:
pass
print(f"[-] 客户端 {client_address} 已断开")
print(f" 当前在线客户端数: {len(self.clients)}")
def monitor_clients(self):
"""
监控客户端状态
"""
while self.running:
time.sleep(30) # 每30秒检查一次
if self.clients:
print(f"[监控] 当前在线客户端: {len(self.clients)}个")
def shutdown(self):
"""
关闭服务器
"""
print("\n正在关闭服务器...")
self.running = False
# 关闭所有客户端连接
print("断开所有客户端连接...")
for client in self.clients[:]:
self.disconnect_client(client)
# 关闭服务器socket
if self.server_socket:
self.server_socket.close()
print("服务器已关闭")
def main():
# 创建服务器实例
# 使用0.0.0.0监听所有网络接口,或者使用具体的IP地址
server = UbuntuServer(host='0.0.0.0', port=12345)
try:
# 启动服务器
server.start()
except KeyboardInterrupt:
print("\n接收到中断信号...")
finally:
# 关闭服务器
server.shutdown()
if __name__ == "__main__":
main()
B: Windows宿主机 - 客户端代码 (client_windows.py)
注意:代码里面的host要换成你自己的虚拟机ip
python
import socket
import threading
import time
import sys
import os
class WindowsClient:
def __init__(self, server_ip='填你自己的虚拟机IP', server_port=12345, client_name="Windows客户端"):
"""
初始化Windows客户端
:param server_ip: Ubuntu服务器的IP地址
:param server_port: 服务器端口
:param client_name: 客户端名称
"""
self.server_ip = server_ip
self.server_port = server_port
self.client_name = client_name
self.client_socket = None
self.connected = False
self.receive_thread = None
def connect(self):
"""
连接到Ubuntu服务器
"""
try:
print("=" * 50)
print(f"Windows Socket客户端启动!")
print(f"尝试连接到服务器: {self.server_ip}:{self.server_port}")
print("=" * 50)
# 创建socket对象
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置超时
self.client_socket.settimeout(5)
# 连接到服务器
self.client_socket.connect((self.server_ip, self.server_port))
self.connected = True
print(f"[+] 已成功连接到Ubuntu服务器!")
print(f" 服务器地址: {self.server_ip}:{self.server_port}")
# 启动接收消息线程
self.receive_thread = threading.Thread(target=self.receive_messages, daemon=True)
self.receive_thread.start()
# 接收欢迎消息
time.sleep(0.5) # 等待一下,确保欢迎消息已经到达
return True
except socket.timeout:
print(f"[-] 连接超时,请检查:")
print(f" 1. 服务器IP地址是否正确: {self.server_ip}")
print(f" 2. 服务器是否已启动")
print(f" 3. 防火墙是否允许端口 {self.server_port}")
return False
except ConnectionRefusedError:
print(f"[-] 连接被拒绝,请确保服务器正在运行")
return False
except Exception as e:
print(f"[-] 连接失败: {e}")
return False
def receive_messages(self):
"""
接收服务器消息的线程函数
"""
while self.connected:
try:
# 接收数据
data = self.client_socket.recv(4096).decode('utf-8')
if data:
print(f"\n[服务器消息] {data}", end="")
print(f"[{self.client_name}] 请输入命令: ", end="", flush=True)
except socket.timeout:
continue
except:
break
def send_message(self, message):
"""
发送消息到服务器
"""
if not self.connected:
print("[-] 未连接到服务器")
return False
try:
self.client_socket.send(message.encode('utf-8'))
return True
except Exception as e:
print(f"[-] 发送失败: {e}")
self.connected = False
return False
def interactive_mode(self):
"""
交互模式
"""
if not self.connected:
print("[-] 未连接到服务器,无法进入交互模式")
return
print("\n" + "=" * 50)
print("进入交互模式!")
print("输入 'help' 查看可用命令")
print("输入 'quit' 退出")
print("=" * 50 + "\n")
while self.connected:
try:
# 显示提示符
command = input(f"[{self.client_name}] 请输入命令: ").strip()
if not command:
continue
# 发送命令到服务器
if not self.send_message(command):
break
# 如果是退出命令,断开连接
if command.lower() == 'quit':
print("[*] 正在断开连接...")
time.sleep(1) # 等待服务器响应
break
except KeyboardInterrupt:
print("\n[*] 中断连接...")
self.send_message('quit')
break
except EOFError:
print("\n[*] 输入结束")
break
def auto_test_mode(self):
"""
自动测试模式
"""
if not self.connected:
print("[-] 未连接到服务器,无法进行自动测试")
return
print("\n[*] 开始自动测试...")
test_commands = [
"hello",
"time",
"sysinfo",
"list",
"file",
"help"
]
for cmd in test_commands:
print(f"\n[*] 发送命令: {cmd}")
self.send_message(cmd)
time.sleep(2) # 等待服务器响应
print("\n[*] 自动测试完成!")
print("[*] 输入 'quit' 断开连接")
def disconnect(self):
"""
断开连接
"""
if self.connected:
print("[*] 正在断开与服务器的连接...")
self.connected = False
# 发送退出命令
try:
self.send_message('quit')
except:
pass
# 关闭socket
if self.client_socket:
self.client_socket.close()
print("[*] 连接已关闭")
def menu(self):
"""
显示菜单
"""
while True:
print("\n" + "=" * 50)
print("Windows Socket客户端 - 主菜单")
print("=" * 50)
print("1. 连接到Ubuntu服务器")
print("2. 进入交互模式")
print("3. 自动测试模式")
print("4. 设置服务器地址")
print("5. 退出程序")
print("=" * 50)
choice = input("请选择操作 (1-5): ").strip()
if choice == '1':
# 连接到服务器
if not self.connected:
if self.connect():
print("[*] 连接成功!")
else:
print("[-] 连接失败,请检查配置")
else:
print("[*] 已经连接到服务器")
elif choice == '2':
# 交互模式
if self.connected:
self.interactive_mode()
else:
print("[-] 请先连接到服务器")
elif choice == '3':
# 自动测试模式
if self.connected:
self.auto_test_mode()
else:
print("[-] 请先连接到服务器")
elif choice == '4':
# 设置服务器地址
new_ip = input(f"请输入Ubuntu服务器IP地址 (当前: {self.server_ip}): ").strip()
if new_ip:
self.server_ip = new_ip
print(f"[*] 服务器地址已更新为: {self.server_ip}")
elif choice == '5':
# 退出
print("[*] 正在退出程序...")
self.disconnect()
break
else:
print("[-] 无效选择,请重新输入")
def main():
# 清屏
os.system('cls' if os.name == 'nt' else 'clear')
# 显示标题
print("=" * 60)
print("Windows Socket客户端 - 连接到Ubuntu虚拟机服务器")
print("=" * 60)
# 默认服务器IP(根据你的实际情况修改)
default_ip = '填你自己的虚拟机IP'
# 询问用户是否使用默认IP
use_default = input(f"是否使用默认服务器IP ({default_ip})? (y/n): ").strip().lower()
if use_default == 'n' or use_default == 'no':
server_ip = input("请输入Ubuntu服务器IP地址: ").strip()
else:
server_ip = default_ip
# 创建客户端
client = WindowsClient(server_ip=server_ip, server_port=12345)
try:
# 显示菜单
client.menu()
except KeyboardInterrupt:
print("\n[*] 程序被中断")
finally:
client.disconnect()
print("[*] 程序已退出")
if __name__ == "__main__":
main()
必须要掌握的几个函数:
socket().bind().listen().accept().connect().connect_ex().send().recv().close()
4.深入研究socket的整个过程
上面的案例自己完成之后,请再转到下面的文档(它们绝对值得花一点时间来熟悉,你会得到回报),更加深入的了解一下套接字的过程。
Python 中的套接字编程(指南): https://realpython.com/python-sockets/

TCP 套接字流程
左侧列表示服务器,右侧是客户端。从左上角开始,注意服务器为设置"监听"套接字所进行的 API 调用:
socket().bind().listen().accept()
一个监听套接字的作用正如其名所示。它会监听来自客户端的连接。当客户端连接时,服务器会调用 .accept() 来接受或完成该连接。
客户端调用 .connect() 与服务器建立连接并启动三次握手。握手步骤很重要,因为它确保了连接的每一方在网络中都是可达的,换句话说,即客户端可以到达服务器,反之亦然。可能只有一台主机、客户端或服务器可以互相到达。
在中间是往返部分,客户端和服务器之间通过调用 .send() 和 .recv() 进行数据交换。在底部,客户端和服务器关闭各自的套接字。
以下面的服务端代码为例,我们详细讲解一下过程:
python
import socket
HOST = "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()
conn, addr = s.accept()
with conn:
print(f"Connected by {addr}")
while True:
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
值得注意的几点:
1.
socket.socket()创建一个支持上下文管理器类型的套接字对象,因此您可以在with语句中使用它。无需调用s.close()。
pythonwith socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: pass # Use the socket object without calling s.close().2.
.bind()方法用于将套接字与特定的网络接口和端口号关联。传递给 .bind() 的值取决于套接字地址族。比如:你使用的是 socket.AF_INET (IPv4)。因此,它期望一个两元组: (host, port) 。即:
pythons.bind((HOST, PORT))
- .
host可以是主机名、IP 地址或空字符串。IP 地址127.0.0.1是回环接口的标准 IPv4 地址,因此只有主机上的进程才能连接到服务器。如果你传递空字符串,服务器将在所有可用的 IPv4 接口上接受连接。- .
port代表从客户端接受连接的 TCP 端口号。它应该是一个介于1到65535之间的整数,因为0被保留。如果端口号小于1024,某些系统可能需要超级用户权限。5.
.listen()方法有一个backlog参数。它指定系统在拒绝新的连接之前允许未接受的连接数。如果没有指定,将选择一个默认的backlog值。如果您的服务器同时收到大量连接请求,通过设置挂起连接队列的最大长度来增加
backlog值可能会有所帮助。最大值取决于系统。例如,在 Linux 上,请参阅/proc/sys/net/core/somaxconn。
pythons.listen() conn, addr = s.accept()
.accept()方法会阻塞执行并等待 一个传入的连接。当客户端连接时,它将返回一个新的套接字对象 来表示该连接,以及一个包含客户端地址的元组。该元组将包含(host, port)(IPv4 连接)或(host, port, flowinfo, scopeid)(IPv6 连接)。很重要的一点是,从
.accept()获得了一个新的套接字对象,这是你用来与客户端通信的套接字,它与服务器用来接受新连接的监听套接字是不同的。8.在
.accept()提供客户端套接字对象conn后,使用无限while循环来循环处理conn.recv()的阻塞调用。这会读取客户端发送的任何数据,并使用conn.sendall()将其回显回来。
with conn: print(f"Connected by {addr}") while True: data = conn.recv(1024) if not data: break conn.sendall(data)如果
conn.recv()返回一个空的bytes对象,b'',则表示客户端已关闭连接,循环将终止。with语句与conn配合使用,用于在代码块结束时自动关闭套接字。
以下面的客户端代码为例,我们详细讲解一下过程:
python
import socket
HOST = "127.0.0.1" # The server's hostname or IP address
PORT = 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}")
与服务器相比,客户端相当简单。它创建一个套接字对象,使用 .connect() 连接到服务器,并调用 s.sendall() 发送其消息。最后,它调用 s.recv() 读取服务器的回复,然后将其打印出来。
# 查看套接字状态
- 要查看您主机上套接字的状态,请使用 netstat 。它默认在 macOS、Linux 和 Windows 上可用。
- 另一种访问方式以及更多有用信息,是使用 lsof (列出打开的文件)
如果你要查看这两个命令的help手册,在 macOS 和 Linux 上,使用 man netstat 和 man lsof 。在 Windows 上,使用 netstat /?

在使用回环接口(IPv4 地址 127.0.0.1 或 IPv6 地址 ::1 )时,数据永远不会离开主机或接触外部网络。在上面的图中,回环接口包含在主机内部。这代表了回环接口的内部特性,并表明通过它的连接和数据都是主机的本地连接。
注意:这也是为什么你会听到回环接口和 IP 地址 127.0.0.1 或 ::1 被称为"localhost"的原因。
当您在应用程序中使用除 127.0.0.1 或 ::1 之外的其他 IP 地址时,它很可能绑定到一个连接到外部网络的以太网接口。这是您通往"localhost"王国之外的其他主机的门户:

小心行事。这是一个险恶、残酷的世界。在离开"localhost"这个安全区域之前,务必阅读"使用主机名"这一节。即使你只使用 IP 地址而不使用主机名,也有一个安全提示适用于你。
5.处理多个连接
Socket服务器确实有其局限性。最大的一个限制是它只服务一个客户端然后退出。Socket客户端也有这个局限性,但还有一个额外的问题。当客户端使用 s.recv() 时,它可能会只返回一个字节 b'H' ,从 b'Hello, world' :
python
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}")
上述 bufsize 参数用于 1024 的最大数据接收量。它并不意味着 .recv() 将返回 1024 字节。
.send() 方法也以这种方式表现。它返回发送的字节数,这可能小于传入数据的长度。您需要检查这一点,并多次调用 .send() 以发送所有数据:
应用程序负责检查所有数据是否已发送;如果只有部分数据被传输,应用程序需要尝试发送剩余的数据。
在上述示例中,您通过使用 .sendall() 避免了这种情况:
与 send() 不同,此方法会继续从 bytes 发送数据,直到所有数据已发送或发生错误。成功时返回 None 。
此时你有两个问题:
- 如何处理多个连接的并发?
- 你需要调用
.send()和.recv(),直到所有数据发送或接收完毕。
并发有很多方法。一种流行的方法是使用异步 I/O 。`asyncio`在 Python 3.4 中被引入到标准库中。传统的选择是使用线程。
如果你的应用程序需要扩展,如果你想要使用多个处理器或多个核心,那么并发编程是必不可少的。然而,对于这个教程,你将使用比线程更传统、更容易推理的东西。你将要使用系统调用的鼻祖: .select() 。
.select() 方法允许你检查多个套接字的 I/O 完成情况。所以你可以调用 .select() 来查看哪些套接字有 I/O 准备好读取和/或写入。但是这是 Python,所以还有更多。你将使用标准库中的 selectors 模块,这样无论你在哪个操作系统上运行,都会使用最有效的实现:
这个模块允许高级和高效的 I/O 多路复用,基于 select 模块的底层机制。鼓励用户使用这个模块,除非他们需要精确控制底层操作系统机制。
多连接客户端和服务器这部分属于进阶内容,目前本人主任务无需涉及,我先跳过了!
有关使用 Python 套接字处理多个客户端可以通过使用非阻塞套接字和
selectors模块实现并发连接的案例与详解请见原文档:https://realpython.com/python-sockets/#historical-background

