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)
  • 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

# 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

该函数只适用于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']

该函数只适用于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

完全限定域名是指包括主机名、域名和顶级域名的完整标识,用于唯一地标识主机在网络中的位置。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)

如果无法解析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)
'''
  • 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

2.3绑定socket对象到IP地址

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

socket.bind(address)
  • 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))

2.4服务器端socket开始监听

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

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

2.5连接和接受连接

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

client_sock.connect(address)

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

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

clientsocket, address = socket.accept()

2.6发送和接收数据

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

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

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

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

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

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

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

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


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

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

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

socket.recvfrom(buffer_size)
  • 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

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

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

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
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shlyyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值