• Python9-基于socket的网络编程



    1.socket概述

    套接字(Socket)是计算机网络编程中的一种抽象概念,用于在网络中传输数据。它提供了一种通信机制,使得不同计算机上的进程(或线程)能够通过网络进行相互通信和数据交换。

    套接字可以看作是网络通信的端点,类似于两个进程之间的通信通道。每个套接字都与一个特定的IP地址和端口号相关联,它们用于标识网络中的进程和服务。

    套接字可以分为两种类型:流套接字(Stream Socket)和数据报套接字(Datagram Socket)。

    1. 流套接字(Stream Socket):也称为面向连接的套接字,它提供了一种可靠的、面向连接的数据传输方式。使用流套接字时,数据可以按照顺序传输,确保传输的可靠性和完整性。这种套接字通常基于传输控制协议(TCP)实现,如Web浏览器和服务器之间的通信。
    2. 数据报套接字(Datagram Socket):也称为无连接套接字,它提供了一种不可靠的、无连接的数据传输方式。通过数据报套接字发送的数据以数据包(Datagram)的形式进行传输,不保证数据的顺序和可靠性。这种套接字通常基于用户数据报协议(UDP)实现,如视频流传输和实时游戏中的数据传输。

    总结起来,套接字是计算机网络编程中用于实现进程间通信的一种机制,它提供了不同计算机上进程之间的数据传输通道。通过套接字,可以使用流套接字或数据报套接字进行可靠或不可靠的数据传输。

    2.相关api说明

    Python中的socket模块提供了用于网络编程的函数和类。

    2.1创建socket对象

    socket():创建一个套接字对象,语法如下:

    socket.socket(family, type, proto=0, fileno=None)
    
    • 1
    • family:指定套接字地址族,常用的是socket.AF_INET(IPv4)和socket.AF_INET6(IPv6)。
    • type:指定套接字类型,常用的是socket.SOCK_STREAM(TCP)和socket.SOCK_DGRAM(UDP)。
    • proto:可选参数,指定套接字的协议。默认为0,根据地址族和类型自动选择协议。
    • fileno:可选参数,指定现有文件描述符。通常不需要使用。

    2.2主机名和ip地址

    socket模块提供包含下列若干函数,用于获取主机名和ip地址等信息。

    # 1.获取本地主机名。该函数返回一个字符串,表示本地主机的主机名。
    socket.gethostname()
    
    import socket
    
    hostname = socket.gethostname()
    print("本地主机名:", hostname) # 本地主机名: shlyyy
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    # 2.通过主机名获取IP地址。hostname:要获取其IP地址的主机名。
    # 该函数接受一个主机名作为参数,并返回对应的IPv4地址的字符串形式。
    socket.gethostbyname(hostname)
    
    import socket
    
    hostname = "www.example.com"
    ip_address = socket.gethostbyname(hostname)
    print("主机名:", hostname)  # 主机名: www.example.com
    print("IPv4地址:", ip_address)  # IPv4地址: 93.184.216.34
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    该函数只适用于IPv4地址,对于IPv6地址,请使用socket.getaddrinfo()函数。


    # 3.通过主机名获取IP地址及相关信息。hostname:要获取其IP地址的主机名
    # 该函数与gethostbyname()类似,不同之处在于它返回一个包含主机名、别名列表和IP地址列表的三元组。
    socket.gethostbyname_ex(hostname)
    
    import socket
    
    hostname = "www.example.com"
    host_info = socket.gethostbyname_ex(hostname)
    print(host_info)  # ('www.example.com', [], ['93.184.216.34'])
    print("主机名:", host_info[0])  # 主机名: www.example.com
    print("别名列表:", host_info[1])  # 别名列表: []
    print("IP地址列表:", host_info[2])  # IP地址列表: ['93.184.216.34']
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    该函数只适用于IPv4地址,对于IPv6地址,请使用socket.getaddrinfo()函数。


    # 4.通过主机名获取一个完全限定的域名(Fully Qualified Domain Name,FQDN)。
    # hostname:要获取其完全限定域名的主机名。
    socket.getfqdn(hostname)
    
    import socket
    
    hostname = "www.example.com"
    fqdn = socket.getfqdn(hostname)
    print("主机名:", hostname)  # 主机名: www.example.com
    print("完全限定域名:", fqdn)  # 完全限定域名: www.example.com
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    完全限定域名是指包括主机名、域名和顶级域名的完整标识,用于唯一地标识主机在网络中的位置。socket.getfqdn()函数会使用系统的网络配置和DNS解析,获取指定主机名的完全限定域名。返回的完全限定域名可能是主机名的一部分或与主机名完全相同,具体取决于系统配置和DNS解析结果。


    # 5.通过IP地址获取主机名。ip_address:要获取其主机名的IP地址。
    # 该函数接受一个IPv4地址作为参数,并返回一个包含主机名、别名列表和IP地址的三元组。
    socket.gethostbyaddr(ip_address)
    
    import socket
    
    hostname = "ustc.edu.cn"
    host_info = socket.gethostbyname_ex(hostname)
    print(host_info)  # ('www.example.com', [], ['93.184.216.34'])
    
    ip_address = str(host_info[2])
    print(ip_address)
    
    try:
        host_name, alias_list, ip_list = socket.gethostbyaddr(ip_address)
        print("Host name:", host_name)
        print("Alias list:", alias_list)
        print("IP list:", ip_list)
    except socket.herror as e:
        print("Unable to resolve host:", e)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    如果无法解析IP地址或找不到对应的主机名,该函数可能引发socket.herror异常。该函数只适用于IPv4地址,对于IPv6地址,请使用socket.getnameinfo()函数。socket.gethostbyaddr() 函数执行 DNS 查询来获取主机名和别名。因此,您的计算机必须能够访问 DNS 服务器,并且目标 IP 地址必须具有正确的 DNS 记录才能成功解析主机名。

    不知道为什么我在使用这个函数时一直报错:socket.gaierror: [Errno 11001] getaddrinfo failed


    # 6.获取主机名和服务名的信息。
    # 该函数根据提供的主机名和服务名,返回一个包含多个元组的列表,每个元组包含五个元素,分别表示地址族(family)、套接字类型(socktype)、协议(proto)、规范名称(canonname)和套接字地址(sockaddr)。
    socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0)
    
    
    import socket
    
    host = "ustc.edu.cn"
    port = 80
    
    try:
        addrinfo_list = socket.getaddrinfo(host, port)
    
        for addrinfo in addrinfo_list:
            family, socktype, proto, canonname, sockaddr = addrinfo
            print("Family:", family)
            print("Socket type:", socktype)
            print("Protocol:", proto)
            print("Canonical name:", canonname)
            print("Socket address:", sockaddr)
    
    except socket.gaierror as e:
        print("Address resolution failed:", e)
    '''
    Family: AddressFamily.AF_INET6
    Socket type: 0
    Protocol: 0
    Canonical name: 
    Socket address: ('2001:da8:d800:642::248', 80, 0, 0)
    Family: AddressFamily.AF_INET
    Socket type: 0
    Protocol: 0
    Canonical name: 
    Socket address: ('202.38.64.246', 80)
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • host:主机名或IP地址。
    • port:端口号。
    • family:可选参数,指定地址族。
    • type:可选参数,指定套接字类型。
    • proto:可选参数,指定协议。
    • flags:可选参数,指定标志。

    # 7.根据服务名获取对应的端口号。
    socket.getservbyname(servicename, protocolname=None)
    '''
    servicename:要获取端口号的服务名,例如http、ftp等。
    protocolname:可选参数,指定协议名,例如tcp、udp等。如果未指定,将返回服务名的默认协议的端口号。
    '''
    import socket
    
    port = socket.getservbyname('http')
    print("HTTP端口号:", port)  # 输出:HTTP端口号: 80
    
    port = socket.getservbyname('ssh', 'tcp')
    print("SSH端口号:", port)  # 输出:SSH端口号: 22
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2.3绑定socket对象到IP地址

    bind():将套接字绑定到指定的地址和端口,语法如下:

    socket.bind(address)
    
    • 1
    • address:一个包含主机和端口号的元组,例如('127.0.0.1', 12345)
    sock = socket.socket()
    sock.bind(('localhost', 8000))
    
    sock1 = socket.socket()
    sock1.bind((socket.gethostname(), 8001))
    
    sock2 = socket.socket()
    sock2.bind(('127.0.0.1', 8001))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.4服务器端socket开始监听

    listen():开始监听连接请求,语法如下:

    socket.listen(backlog)
    
    • 1
    • backlog:指定连接请求的最大数量。

    2.5连接和接受连接

    客户机端socket对象通过 connect() 方法尝试建立到服务器端socket的连接:

    client_sock.connect(address)
    
    • 1

    连接成功时,该函数会返回None,连接失败时可能引发socket.error异常。

    服务器端socket对象通过accept()方法进入 waiting(阻塞)状态。当接收到来自客户端的连接请求时,该函数返回一个新的套接字和客户端地址(元组形式),语法如下:

    clientsocket, address = socket.accept()
    
    • 1

    2.6发送和接收数据

    对于面向连接的TCP通信程序,客户端与服务器建立连接以后,通过socket对象的 send()recv() 方法分别发送和接收数据:

    socket.send(data, flags=0)
    
    • 1
    • data:要发送的数据,通常是字节流(bytes)形式。
    • flags:可选参数,用于指定发送的标志。默认为0,表示没有特殊标志。

    如果发送成功,它返回发送的字节数。如果发送失败,它可能引发socket.error异常。socket.send()函数是一个阻塞函数,意味着在发送数据期间,程序会一直等待。如果需要非阻塞的发送操作,可以使用socket.settimeout(timeout)函数设置超时时间,或者使用非阻塞的套接字(通过socket.setblocking(False)设置)进行发送。

    socket.sendall(data, flags=0)
    
    • 1
    • data:要发送的数据,通常是字节流(bytes)形式。
    • flags:可选参数,用于指定发送的标志。默认为0,表示没有特殊标志。

    socket.send()函数不同,socket.sendall()函数会自动处理数据发送的细节,确保所有数据都被发送完整而不会丢失。如果发送成功,该函数不返回任何值。如果发送失败,它可能引发socket.error异常。socket.sendall()函数也是一个阻塞函数。

    socket.recv(buffer_size, flags=0)
    
    • 1
    • buffer_size:接收缓冲区的大小,表示一次接收的最大字节数。
    • flags:可选参数,用于指定接收的标志。默认为0,表示没有特殊标志。

    该函数从已连接的套接字接收数据,并将接收到的数据存储在一个字节流(bytes)中返回。如果没有数据可接收,该函数会阻塞程序,直到有数据到达或连接关闭。如果接收到的数据超过了指定的缓冲区大小,多余的数据可能会在后续的接收中返回。

    如果接收成功,该函数返回接收到的字节流。如果接收失败,它可能引发socket.error异常。socket.recv()函数也是一个阻塞调用。


    对于非面向连接的TCP通信程序,客户机和服务器不需要预先建立连接,直接通过socket对象的 sendto()方法指定发送目标地址参数,recvfrom() 函数方法返回接受的数据以及发送源地址。

    socket.sendto(data, address)
    
    • 1
    • data:要发送的数据,通常是字节流(bytes)形式。
    • address:目标地址,表示数据发送的目标主机和端口号,可以是一个元组 (host, port)

    如果发送成功,它返回发送的字节数。如果发送失败,它可能引发socket.error异常。

    socket.recvfrom(buffer_size)
    
    • 1
    • buffer_size:接收缓冲区的大小,表示一次接收的最大字节数。

    该函数从套接字接收数据,并返回一个元组 (data, address),其中 data 是接收到的字节流(bytes),address 是数据来源的地址。

    如果没有数据可接收,该函数会阻塞程序,直到有数据到达或连接关闭。如果接收到的数据超过了指定的缓冲区大小,多余的数据可能会在后续的接收中返回。

    3.简单TCP的网络程序

    3.1TCP开发流程

    TCP 服务器端步骤:

    • 1.创建套接字对象:使用 socket.socket() 函数创建一个套接字对象。
    • 2.绑定地址和端口:使用 bind() 函数绑定服务器的地址和端口。
    • 3.监听连接:使用 listen() 函数开始监听连接请求。
    • 4.接受连接:使用 accept() 函数接受客户端的连接请求,并返回一个新的套接字对象和客户端地址。
    • 5.接收数据:使用 recv() 函数接收从客户端发送过来的数据。
    • 6.发送响应:使用 send() 函数向客户端发送响应数据。
    • 7.关闭连接:关闭客户端的套接字和服务器端的套接字。

    TCP 客户端步骤:

    • 1.创建套接字对象:使用 socket.socket() 函数创建一个套接字对象。
    • 2.目标服务器地址和端口:设置目标服务器的地址和端口。
    • 3.连接服务器:使用 connect() 函数连接到服务器。
    • 4.发送数据:使用 send() 函数向服务器发送数据。
    • 5.接收响应:使用 recv() 函数接收从服务器端发送过来的响应数据。
    • 6.关闭连接:关闭客户端的套接字。

    3.2TCP服务器端代码实现

    import socket
    
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serversocket.bind(('127.0.0.1', 8001))
    serversocket.listen(1)  # 开始侦听,队列长度为1
    
    clientsocket, clientaddress = serversocket.accept()  # 使用阻塞方法以等待客户机连接请求
    
    print('Connection from ', clientaddress)
    
    while 1:
        data = clientsocket.recv(1024)
        if not data:
            break  # 接收到空数据时终止循环
        print('Received from client: ', repr(data))  # 输出接收到的数据,用repr()函数转换为字符串
        print('Echo: ', repr(data))
        clientsocket.send(data)
    
    clientsocket.close()  # 关闭客户机socket
    serversocket.close()  # 关闭服务器socket
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.3TCP客户端代码实现

    import socket
    
    clientsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    clientsocket.connect(('127.0.0.1', 8001))  # 连接到服务器
    
    while 1:
        data = input('>')  # 接收用户输入数据
        clientsocket.send(data.encode())  # 数据转换为bytes对象并发送到服务器
        if not data:
            break  # 如果数据为空,终止循环
        newdata = clientsocket.recv(1024)  # 接收服务器的回送数据
        print('Received from server: ', repr(newdata))  # 输出接收到数据
    
    clientsocket.close()  # 关闭客户机socket
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4.基于UDP的网络程序

    4.1UDP开发流程

    基于UDP的网络程序是无连接的,发送数据时直接指定地址参数,接收数据时同时返回地址。

    UDP 服务器端步骤:

    • 1.创建套接字对象:使用 socket.socket() 函数创建一个套接字对象。
    • 2.绑定地址和端口:使用 bind() 函数绑定服务器的地址和端口。
    • 3.接收数据:使用 recvfrom() 函数接收从客户端发送过来的数据。
    • 4.发送响应:使用 sendto() 函数向客户端发送响应数据。
    • 5.关闭套接字:关闭服务器端的套接字。

    UDP 客户端步骤:

    • 1.创建套接字对象:使用 socket.socket() 函数创建一个套接字对象。
    • 2.目标服务器地址和端口:设置目标服务器的地址和端口。
    • 3.发送数据:使用 sendto() 函数向服务器发送数据。
    • 4.接收响应:使用 recvfrom() 函数接收从服务器端发送过来的响应数据。
    • 5.关闭套接字:关闭客户端的套接字。

    4.2UDP服务器端代码实现

    import socket
    
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    serversocket.bind(('127.0.0.1', 8000))
    
    while 1:
        data, address = serversocket.recvfrom(1024)  # 接收数据返回数据和客户机地址
        if not data:  # 接收到空数据终止循环
            break
    
        print('Received from client: ', address, repr(data))
        print('Echo: ', repr(data))
        serversocket.sendto(data, address)
    
    serversocket.close()  # 关闭服务器socket
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    4.3UDP客户端代码实现

    import socket
    
    clientsocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    while 1:
        data = input('>')  # 接收用户输入数据
        # 把数据转换为bytes对象,并发送到服务器
        clientsocket.sendto(data.encode(), ('127.0.0.1', 8000))
        if not data:  # 接收到空数据终止循环
            break
    
        newdata = clientsocket.recvfrom(1024)  # 接收服务器的回送数据
        print('Received from server: ', repr(newdata))  # 输出接收到数据
    
    clientsocket.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    易基因|RNA m7G甲基化测序(m7G-MeRIP-seq)
    【echarts】19、echarts+vue2 - 折线图柱状图
    使用BENCHMARKSQL工具对KingbaseES预热数据时执行:select sys_prewarm(‘NDX_OORDER_2 ‘)报错
    leetcode-621. 任务调度器
    MySQL备份与恢复
    噪声监测站
    spring使用模板模式
    Docker 搭建 mysql8 遇到的问题
    Promise——promise是什么?为什么要用promise?
    EASEX绘制卡通头像
  • 原文地址:https://blog.csdn.net/ArthurHai521/article/details/133935054