socket.socket模块--网络通信

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宿主机作为客户端来进行交互。

【注意:由于是跨机器通信,需要确保虚拟机和宿主机之间的网络是连通的,这里我的虚拟机使用的是桥接网络

步骤:

  1. 在Ubuntu虚拟机上运行服务器代码,绑定到虚拟机的IP地址(例如192.168.0.6)和某个端口(比如12345)。
  2. 在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()

python 复制代码
with 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) 。即:

python 复制代码
s.bind((HOST, PORT))
  • .host 可以是主机名、IP 地址或空字符串。IP 地址 127.0.0.1 是回环接口的标准 IPv4 地址,因此只有主机上的进程才能连接到服务器。如果你传递空字符串,服务器将在所有可用的 IPv4 接口上接受连接。
  • .port 代表从客户端接受连接的 TCP 端口号。它应该是一个介于 165535 之间的整数,因为 0 被保留。如果端口号小于 1024 ,某些系统可能需要超级用户权限。

5..listen() 方法有一个 backlog 参数。它指定系统在拒绝新的连接之前允许未接受的连接数。如果没有指定,将选择一个默认的 backlog 值。

如果您的服务器同时收到大量连接请求,通过设置挂起连接队列的最大长度来增加 backlog 值可能会有所帮助。最大值取决于系统。例如,在 Linux 上,请参阅 /proc/sys/net/core/somaxconn

python 复制代码
s.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

此时你有两个问题:

  1. 如何处理多个连接的并发?
  2. 你需要调用 .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

相关推荐
一只旭宝3 小时前
Linux专题十:I/O 复用进阶(LT/ET 模式)同步,异步阻塞,以及 libevent 库核心知识点
linux·服务器·网络
菩提小狗4 小时前
第1天:基础入门-操作系统&名词&文件下载&反弹SHELL&防火墙绕过|小迪安全笔记|网络安全|
网络·笔记·学习·安全·web安全
wniuniu_4 小时前
ceph修改
网络·ceph
codists4 小时前
《Grokking Concurrency》读后感
python
就叫飞六吧4 小时前
wrk:现代 HTTP 性能测试工具(类cc)
网络协议·测试工具·http
坤岭4 小时前
Python内存溢出问题
python
三木彤4 小时前
Python 反爬 UA 检测真实案例(3个典型场景,可复现、合法合规)
python
SCBAiotAigc4 小时前
MinerU离线推理
人工智能·python·mineru
94620164zwb54 小时前
关于应用模块 Cordova 与 OpenHarmony 混合开发实战
python·学习