python socket编程详细教程,python中socket模块的作用

Socket 提供了标准的 BSD Socket API,以便使用 BSD 套接字接口通过网络进行通信。它包括用于处理实际数据通道的类,还包括与网络相关的功能,

importsocketHOSTS=[ \’apu\’, \’pymotw.com\’, \’www.python.org\’, \’no suchname\’,]for HOSTS: 中的主机try: print(\'{} : {}\’.format(host,socket.gethostbyname(主机))) msg: print(\'{} : {}\’.format(host, msg)) socket as.error #output# apu : 10.9.0.10# pymotw.com : 66.33.211.242# www .python.org : 151.101 .32.223# no suchname : [Errno 8] 节点名称和服务名称均未指定或未知如果当前系统的DNS 配置在搜索中包含一个或多个域,则name 参数不必是完整名称(即name 参数不必是完整的名称)。 (必须包括域名和基本主机名)。如果未找到该名称,则会引发socket.error 类型的异常。

要访问服务器的更多命名信息,请使用gethostbyname_ex()。返回服务器的规范主机名、别名以及可用于访问服务器的所有可用IP 地址。

import socket HOSTS=[ \’apu\’, \’pymotw.com\’, \’www.python.org\’, \’no suchname\’,] for host of HOSTS: print(host) try: name,alias,address=socket.gethostbyname_ex(host ) print( \’hostname :\’, name) print(\’alias :\’, alias) print(\’address :\’, address) socket as msg: except error print(\’ERROR:\’, msg) print( ) # 输出# apu# 主机名: apu.地狱飞。 net# 别名: [\’apu\’]# 地址: [\’10.9.0.10\’]# # pymotw.com# 主机名: pymotw.com# 别名: []# 地址: [\’66.33.211.242\’]# # www.python .org#主机名: prod.python.map.fastlylb.net# 别名: [\’www.python.org\’, \’python.map.fastly.net\’]# 地址: [\’151.101.32.223\’]# # 没有这样的名称# ERROR: [Errno 8] 节点名称或服务名称指定或未知。 在获得所有已知的服务器IP地址后,客户端可以实现自己的负载平衡或故障转移算法。

使用getfqdn() 将部分名称转换为完整域名。

导入主机套接字[\’apu\’, \’pymotw.com\’] : print(\'{:10} : {}\’.format(host,socket.getfqdn(host))) #output# apu : hellfly.net# pymotw.com : apache2-echo.catalina.dreamhost.com 如果输入是别名,则返回的名称不一定与输入参数匹配(例如此处的www )。

如果服务器地址可用,请使用gethostbyaddr() 执行名称的“反向”查找。

import socket hostname, alias, address=socket.gethostbyaddr(\’10.9.0.10\’)print(\’主机名:\’, hostname)# 主机名: apu.hellfly.netprint(\’alias :\’, alias)# 别名: [ \’apu\’ ] print(\’Addresses:\’, Addresses)# Addresses: [\’10.9.0.10\’]返回值是一个元组,其中包含完整的主机名、别名以及与该名称关联的所有IP 地址。

查找服务信息

除了IP地址之外,每个套接字地址还包括一个整数端口号。许多应用程序可以在同一主机上运行并侦听单个IP 地址,但一次只有一个套接字可以使用该地址处的端口。 IP 地址、协议和端口号的组合唯一地标识通信通道,并确保通过套接字发送的消息到达正确的目的地。

某些端口号是预先分配给特定协议的。例如,使用SMTP 的电子邮件服务器之间的通信通过端口号25 上的TCP 进行,而Web 客户端和服务器使用端口80 进行HTTP 通信。 getservbyname() 允许您查找具有标准化名称的网络服务的端口号。

从urllib.parse 导入套接字导入urlparseURLS=[ \’http://www.python.org\’, \’https://www.mybank.com\’, \’ftp://prep.ai.mit.edu\’, \’gopher://gopher.micro.umn.edu \’, \’smtp://mail.example.com\’, \’imap://mail.example.com\’, \’imaps://mail.example.com\’, \’pop3://pop.example.com\’, \’pop3s://pop .example.com\’,]for URLS: parsed_url=urlparse(url) port=socket .getservbyname(parsed_url.scheme) print(\'{:6} : {}\’.format(parsed_url.scheme, port)) #output# http : 80# https : 443# ftp : 21# gopher : 70# smtp : 25# imap : 143# imap : 993# Pop3 : 110# Pop3s : 995 标准化服务会改变虽然不太可能是一个港口但在未来,当我们添加新服务时,通过系统调用搜索它们将比硬编码更灵活。

要反向查找服务端口,请使用getservbyport()。

import storagefrom urllib.parse import urlunparsefor port in [80, 443, 21, 70, 25, 143, 993, 110, 995]: url=\'{}://example.com/\’.format(socket.getservbyport(port) )) print(url) # 输出# http://example.com/# https://example.com/# ftp://example.com/# gopher://example.com/# smtp://example.com/# imap://example.com/#imaps://example.com/#pop3://example.com/#pop3s://example.com/反向查找对于从任意地址构造服务URL 非常有用。

分配给传输协议的编号可以使用getprotobyname() 获得。

import socket def get_constants(prefix): \’\’\’\’创建一个字典,将套接字模块常量映射到它们的名称\’\’\’\’ return { getattr(socket, n): n for n in dir(socket) if n. ) }protocols=get_constants(\’IPPROTO_\’)for name in [\’icmp\’, \’udp\’, \’tcp\’]: proto_num=socket.getprotobyname(name) const_name=协议[proto_num] print(\'{:4} – {:2d } (socket) .{:12}={:2d})\’.format( name, proto_num, const_name, getattr(socket, const_name))) # 输出# icmp – 1 (socket.IPPROTO_ICMP=1)# udp – 17 (socket .IPPROTO_UDP=17) )# tcp – 6 (socket.IPPROTO_TCP=6) 协议编号值已标准化并定义为带有前缀IPPROTO_ 的常量。

查找服务器地址

getaddrinfo() 将服务的基地址转换为包含建立连接所需的所有信息的元组列表。每个元组的内容不同并且包括不同的网络地址族或协议。

import socket def get_constants(prefix): \’\’\’\’创建一个字典,将套接字模块常量映射到它们的名称\’\’\’\’ return { getattr(socket, n): n for n in dir(socket) if n. ) }family=get_constants(\’AF_\’)types=get_constants(\’SOCK_\’)protocols=get_constants(\’IPPROTO_\’)socket.getaddrinfo(\’www.python.org\’, \’http\’): for response # 解压响应元组家庭。 socktype, proto, canonname, sockaddr=response print(\’family :\’, family[family]) print(\’type :\’, type[socktype]) print(\’protocol :\’, protocol[protocol]) print(\’规范名称: \’, canonname) print(\’套接字地址:\’, sockaddr) print() # 输出# 系列: AF_INET# 类型: SOCK_DGRAM# 协议: IPPROTO_UDP# 规范名称:# 套接字地址: (\’151.101.32.223\’, 80)# # 系列3336 0 AF _INET # 类型: SOCK_STREAM# 协议: IPPROTO_TCP# 规范名称:# 套接字地址: (\’151.101.32.223\’, 80)# # 系列: AF_INET6# 类型: SOCK_DGRAM# 协议3 3360 IP PROTO_UDP#名称:# 套接字地址(\’2a 04:4e42:8:223\’, 80, 0, 0) 这个程序如下。查找www.python.org 的连接信息。

getaddrinfo() 接受多个参数来过滤结果列表。主机和端口是必需参数。可选参数有family、socktype、proto 和flags。该选项的值必须是0 或套接字上定义的常量之一。

import socket def get_constants(prefix): \’\’\’\’创建一个字典,将套接字模块常量映射到它们的名称\’\’\’\’ return { getattr(socket, n): n for n in dir(socket) if n. ) }family=get_constants(\’AF_\’)types=get_constants(\’SOCK_\’)protocols=get_constants(\’IPPROTO_\’)responses=socket.getaddrinfo( host=\’www.python.org\’, port=\’http\’ , family=socket.AF_INET , type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, flags=socket.AI_CANONNAME,) for response inresponse: # 展开响应元组family, socktype, proto, canonname, sockaddr=response print(\’ Family :\’,family[ family]) print(\’类型:\’, type[socktype]) print(\’协议:\’, 协议[协议]) print(\’规范名称:\’, canonname) print(\’套接字地址:\’ , sockaddr) print() # Output#Family : AF_INET# Type : SOCK_STREAM# Protocol : IPPROTO_TCP# Canonical name : prod.python.map.fastlylb.net# Socket address : (\’151.101.32.223\’, 80) Flags include AI_CANONNAME 包含在内,以便服务器的规范名称name(主机别名(用于查找的不同值)包含在结果中。如果没有此标志,规范名称将保持为空。

IP地址表示

用C 语言编写的网络程序使用数据类型将IP 地址表示为二进制值(而不是Python 程序中通常出现的字符串地址)。使用structsockaddr、inet_aton() 和inet_ntoa() 在Python 和C 表示形式之间转换IPv4 地址。

import binasciiimportsocketimport structimport sysfor string_address in [\’192.168.1.1\’, \’127.0.0.1\’]: Packed=socket.inet_aton(string_address) print(\’Original:\’, string_address) print(\’Packed :\’, binascii.hexlify(packed) ) print(\’Unpacked:\’,socket.inet_ntoa(packed)) print() #output# Original: 192.168.1.1# 打包: b\’c0a80101\’# Unpacked: 192.168.1.1# # Original: 127.0.0.1# 打包: f0 0 0001\’ # 已解压: 127.0 .0.1 打包格式的4 个字节可以传递到C 库、通过网络安全发送或紧凑地存储在数据库中。

相关函数inet_pton()和inet_ntop()支持IPv4和IPv6,并根据传递的地址族参数生成适当的格式。

导入binasciiimport socket 导入structimport sysstring_address=\’2002:ac10:10a:1234:21e:52ff:fe74:40e\’packed=socket.inet_pton(socket.AF_INET6, string_address)print(\’Original:\’, print(\’打包的:\’, binascii.hexlify(packed))print(\’未打包的:\’, socket .inet_ntop(socket.AF_INET6, Packed))# 输出# 原始: 2002:ac10:10a:1234:21e:52ff:fe74:40e# 打包: b\’2002ac10010a12 3402 1e5 2fffe74040e\’# Unpacked: 2002:ac10:10a:1234:21e:52ff:fe74:40eIPv6地址已经是一个十六进制值,因此将打包版本转换为一系列十六进制数字。产生类似字符串底座的东西。基于原始值。

TCP/IP 客户端和服务器

套接字可以充当服务器,侦听传入消息,也可以充当客户端,连接到其他应用程序。如果TCP/IP 套接字的两端都已连接,则通信是双向的。

服务器

该示例程序基于标准库文档中的示例程序,接收传入消息并将其发送回发送者。首先创建一个TCP/IP套接字,然后使用bind()将套接字与服务器地址关联起来。地址是localhost 指向当前服务器,端口号是10000。

#socket_echo_server.pyimportsocketimportsys#创建TCP/IP套接字sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#将套接字绑定到portserver_address=(\’localhost\’, 10000)print(\'{} on port Start {}\’ .format(*server_address))sock.bind(server_address)# 监听传入连接sock.listen(1)while True: # 等待连接print(\’等待连接\’) connection, client_address=sock.accept( ) try: print (\’connection from\’, client_address) # True: data=connection.recv(16) print(\’收到{!r}\’.format(data)) if data: print(\’来自客户端的数据发送回

\’) connection.sendall(data) else: print(\’no data from\’, client_address) break finally: # Clean up the connection connection.close()调用listen()将套接字置于服务器模式,并用 accept()等待传入连接。整数参数表示后台排队的连接数,当连接数超出时,系统会拒绝。此示例仅期望一次使用一个连接。
accept()返回服务器和客户端之间的开放连接以及客户端的地址。该连接实际上是另一个端口上的不同套接字(由内核分配)。从连接中用 recv() 读取数据并用 sendall() 传输数据。
与客户端通信完成后,需要使用 close() 清理连接。此示例使用 try:finally块来确保close()始终调用,即使出现错误也是如此。
客户端
客户端程序的 socket 设置与服务端的不同。它不是绑定到端口并监听,而是用于connect()将套接字直接连接到远程地址。
# socket_echo_client.pyimport socketimport sys# Create a TCP/IP socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Connect the socket to the port where the server is listeningserver_address = (\’localhost\’, 10000)print(\’connecting to {} port {}\’.format(*server_address))sock.connect(server_address)try: # Send data message = b\’This is the message. It will be repeated.\’ print(\’sending {!r}\’.format(message)) sock.sendall(message) # Look for the response amount_received = 0 amount_expected = len(message) while amount_received < amount_expected: data = sock.recv(16) amount_received += len(data) print(\’received {!r}\’.format(data))finally: print(\’closing socket\’) sock.close()建立连接后,数据可以通过 sendall() 发送 recv() 接收。发送整个消息并收到同样的回复后,关闭套接字以释放端口。
运行客户端和服务端
客户端和服务端应该在单独的终端窗口中运行,以便它们可以相互通信。服务端输出显示传入的连接和数据,以及发送回客户端的响应。
$ python3 socket_echo_server.pystarting up on localhost port 10000waiting for a connectionconnection from (\’127.0.0.1\’, 65141)received b\’This is the mess\’sending data back to the clientreceived b\’age. It will be\’sending data back to the clientreceived b\’ repeated.\’sending data back to the clientreceived b\’\’no data from (\’127.0.0.1\’, 65141)waiting for a connection客户端输出显示传出消息和来自服务端的响应。
$ python3 socket_echo_client.pyconnecting to localhost port 10000sending b\’This is the message. It will be repeated.\’received b\’This is the mess\’received b\’age. It will be\’received b\’ repeated.\’closing socket简易客户端连接
通过使用便捷功能create_connection()连接到服务端,TCP/IP 客户端可以节省一些步骤 。该函数接受一个参数,一个包含服务器地址的双值元组,并派生出用于连接的最佳地址。
import socketimport sysdef get_constants(prefix): \”\”\”Create a dictionary mapping socket module constants to their names. \”\”\” return { getattr(socket, n): n for n in dir(socket) if n.startswith(prefix) }families = get_constants(\’AF_\’)types = get_constants(\’SOCK_\’)protocols = get_constants(\’IPPROTO_\’)# Create a TCP/IP socketsock = socket.create_connection((\’localhost\’, 10000))print(\’Family :\’, families[sock.family])print(\’Type :\’, types[sock.type])print(\’Protocol:\’, protocols[sock.proto])print()try: # Send data message = b\’This is the message. It will be repeated.\’ print(\’sending {!r}\’.format(message)) sock.sendall(message) amount_received = 0 amount_expected = len(message) while amount_received < amount_expected: data = sock.recv(16) amount_received += len(data) print(\’received {!r}\’.format(data))finally: print(\’closing socket\’) sock.close() # output# Family : AF_INET# Type : SOCK_STREAM# Protocol: IPPROTO_TCP# # sending b\’This is the message. It will be repeated.\’# received b\’This is the mess\’# received b\’age. It will be\’# received b\’ repeated.\’# closing socketcreate_connection()用getaddrinfo() 方法获得可选参数,并socket使用创建成功连接的第一个配置返回已打开的连接参数。family,type和proto属性可以用来检查返回的类型是 socket 类型。
选择监听地址
将服务端绑定到正确的地址非常重要,以便客户端可以与之通信。前面的示例都用 \’localhost\’ 作为 IP 地址,但这样有一个限制,只有在同一服务器上运行的客户端才能连接。使用服务器的公共地址(例如 gethostname() 的返回值)来允许其他主机进行连接。修改上面的例子,让服务端监听通过命令行参数指定的地址。
# socket_echo_server_explicit.pyimport socketimport sys# Create a TCP/IP socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Bind the socket to the address given on the command lineserver_name = sys.argv[1]server_address = (server_name, 10000)print(\’starting up on {} port {}\’.format(*server_address))sock.bind(server_address)sock.listen(1)while True: print(\’waiting for a connection\’) connection, client_address = sock.accept() try: print(\’client connected:\’, client_address) while True: data = connection.recv(16) print(\’received {!r}\’.format(data)) if data: connection.sendall(data) else: break finally: connection.close()对客户端程序进行类似的修改。
# socket_echo_client_explicit.pyimport socketimport sys# Create a TCP/IP socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Connect the socket to the port on the server# given by the callerserver_address = (sys.argv[1], 10000)print(\’connecting to {} port {}\’.format(*server_address))sock.connect(server_address)try: message = b\’This is the message. It will be repeated.\’ print(\’sending {!r}\’.format(message)) sock.sendall(message) amount_received = 0 amount_expected = len(message) while amount_received < amount_expected: data = sock.recv(16) amount_received += len(data) print(\’received {!r}\’.format(data))finally: sock.close()使用参数 hubert 启动服务端, netstat命令显示它正在监听指定主机的地址。
$ host hubert.hellfly.nethubert.hellfly.net has address 10.9.0.6$ netstat -an | grep 10000Active Internet connections (including servers)Proto Recv-Q Send-Q Local Address Foreign Address (state)…tcp4 0 0 10.9.0.6.10000 *.* LISTEN…在另一台主机上运行客户端,hubert.hellfly.net 作为参数:
$ hostnameapu$ python3 ./socket_echo_client_explicit.py hubert.hellfly.netconnecting to hubert.hellfly.net port 10000sending b\’This is the message. It will be repeated.\’received b\’This is the mess\’received b\’age. It will be\’received b\’ repeated.\’服务端输出是:
$ python3 socket_echo_server_explicit.py hubert.hellfly.netstarting up on hubert.hellfly.net port 10000waiting for a connectionclient connected: (\’10.9.0.10\’, 33139)received b\’\’waiting for a connectionclient connected: (\’10.9.0.10\’, 33140)received b\’This is the mess\’received b\’age. It will be\’received b\’ repeated.\’received b\’\’waiting for a connection许多服务端具有多个网络接口,因此也会有多个 IP 地址连接。为每个 IP 地址运行服务端肯定是不明智的,可以使用特殊地址INADDR_ANY 同时监听所有地址。尽管 socket 为 INADDR_ANY 定义了一个常量,但它是一个整数,必须先将其转换为点符号分隔的字符串地址才能传递给 bind()。作为更方便的方式,使用“ 0.0.0.0”或空字符串(\’\’)就可以了,而不是进行转换。
import socketimport sys# Create a TCP/IP socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Bind the socket to the address given on the command lineserver_address = (\’\’, 10000)sock.bind(server_address)print(\’starting up on {} port {}\’.format(*sock.getsockname()))sock.listen(1)while True: print(\’waiting for a connection\’) connection, client_address = sock.accept() try: print(\’client connected:\’, client_address) while True: data = connection.recv(16) print(\’received {!r}\’.format(data)) if data: connection.sendall(data) else: break finally: connection.close()要查看套接字使用的实际地址,可以使用 getsockname() 方法。启动服务后,再次运行 netstat 会显示它正在监听任何地址上的传入连接。
$ netstat -anActive Internet connections (including servers)Proto Recv-Q Send-Q Local Address Foreign Address (state)…tcp4 0 0 *.10000 *.* LISTEN…用户数据报客户端和服务端
用户数据报协议(UDP)与 TCP/IP 的工作方式不同。TCP 是面向字节流的,所有数据以正确的顺序传输,UDP 是面向消息的协议。UDP 不需要长连接,因此设置 UDP 套接字更简单一些。另一方面,UDP 消息必须适合单个数据报(对于 IPv4,这意味着它们只能容纳 65,507 个字节,因为 65,535 字节的数据包也包含头信息)并且不能保证传送与 TCP 一样可靠。
服务端
由于没有连接本身,服务器不需要监听和接收连接。它只需要 bind() 地址和端口,然后等待单个消息。
# socket_echo_server_dgram.py import socketimport sys# Create a UDP socketsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# Bind the socket to the portserver_address = (\’localhost\’, 10000)print(\’starting up on {} port {}\’.format(*server_address))sock.bind(server_address)while True: print(\’\\nwaiting to receive message\’) data, address = sock.recvfrom(4096) print(\’received {} bytes from {}\’.format(len(data), address)) print(data) if data: sent = sock.sendto(data, address) print(\’sent {} bytes back to {}\’.format(sent, address))使用 recvfrom() 从套接字读取消息,然后按照客户端地址返回数据。
客户端
UDP 客户端与服务端类似,但不需要 bind()。它用 sendto()将消息直接发送到服务算,并用 recvfrom()接收响应。
# socket_echo_client_dgram.py import socketimport sys# Create a UDP socketsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)server_address = (\’localhost\’, 10000)message = b\’This is the message. It will be repeated.\’try: # Send data print(\’sending {!r}\’.format(message)) sent = sock.sendto(message, server_address) # Receive response print(\’waiting to receive\’) data, server = sock.recvfrom(4096) print(\’received {!r}\’.format(data))finally: print(\’closing socket\’) sock.close()运行客户端和服务端
运行服务端会产生:
$ python3 socket_echo_server_dgram.pystarting up on localhost port 10000waiting to receive messagereceived 42 bytes from (\’127.0.0.1\’, 57870)b\’This is the message. It will be repeated.\’sent 42 bytes back to (\’127.0.0.1\’, 57870)waiting to receive message客户端输出是:
$ python3 socket_echo_client_dgram.pysending b\’This is the message. It will be repeated.\’waiting to receivereceived b\’This is the message. It will be repeated.\’closing socketUnix 域套接字
从程序员的角度来看,使用 Unix 域套接字和 TCP/IP 套接字有两个本质区别。首先,套接字的地址是文件系统上的路径,而不是包含服务器名称和端口的元组。其次,在套接字关闭后,在文件系统中创建的表示套接字的节点仍然存在,并且每次服务端启动时都需要删除。通过在设置部分进行一些更改,使之前的服务端程序支持 UDS。
创建 socket 时使用地址族 AF_UNIX。绑定套接字和管理传入连接方式与 TCP/IP 套接字相同。
# socket_echo_server_uds.py import socketimport sysimport osserver_address = \’./uds_socket\’# Make sure the socket does not already existtry: os.unlink(server_address)except OSError: if os.path.exists(server_address): raise# Create a UDS socketsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)# Bind the socket to the addressprint(\’starting up on {}\’.format(server_address))sock.bind(server_address)# Listen for incoming connectionssock.listen(1)while True: # Wait for a connection print(\’waiting for a connection\’) connection, client_address = sock.accept() try: print(\’connection from\’, client_address) # Receive the data in small chunks and retransmit it while True: data = connection.recv(16) print(\’received {!r}\’.format(data)) if data: print(\’sending data back to the client\’) connection.sendall(data) else: print(\’no data from\’, client_address) break finally: # Clean up the connection connection.close()还需要修改客户端设置以使用 UDS。它应该假定套接字的文件系统节点存在,因为服务端通过绑定到该地址来创建它。发送和接收数据在 UDS 客户端中的工作方式与之前的 TCP/IP 客户端相同。
# socket_echo_client_uds.py import socketimport sys# Create a UDS socketsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)# Connect the socket to the port where the server is listeningserver_address = \’./uds_socket\’print(\’connecting to {}\’.format(server_address))try: sock.connect(server_address)except socket.error as msg: print(msg) sys.exit(1)try: # Send data message = b\’This is the message. It will be repeated.\’ print(\’sending {!r}\’.format(message)) sock.sendall(message) amount_received = 0 amount_expected = len(message) while amount_received < amount_expected: data = sock.recv(16) amount_received += len(data) print(\’received {!r}\’.format(data))finally: print(\’closing socket\’) sock.close()程序输出大致相同,并对地址信息进行适当更新。服务端显示收到的消息并将其发送回客户端。
$ python3 socket_echo_server_uds.pystarting up on ./uds_socketwaiting for a connectionconnection fromreceived b\’This is the mess\’sending data back to the clientreceived b\’age. It will be\’sending data back to the clientreceived b\’ repeated.\’sending data back to the clientreceived b\’\’no data fromwaiting for a connection客户端一次性发送消息,并以递增方式接收部分消息。
$ python3 socket_echo_client_uds.pyconnecting to ./uds_socketsending b\’This is the message. It will be repeated.\’received b\’This is the mess\’received b\’age. It will be\’received b\’ repeated.\’closing socket权限
由于 UDS 套接字由文件系统上的节点表示,因此可以使用标准文件系统权限来控制服务端的访问。
$ ls -l ./uds_socketsrwxr-xr-x 1 dhellmann dhellmann 0 Aug 21 11:19 uds_socket$ sudo chown root ./uds_socket$ ls -l ./uds_socketsrwxr-xr-x 1 root dhellmann 0 Aug 21 11:19 uds_socket以非root用户运行客户端会导致错误,因为该进程无权打开套接字。
$ python3 socket_echo_client_uds.pyconnecting to ./uds_socket[Errno 13] Permission denied父子进程之间的通信
为了在 Unix 下进行进程间通信,通过 socketpair() 函数来设置 UDS 套接字很有用。它创建了一对连接的套接字,当子进程被创建后,在父进程和子进程之间进行通信。
import socketimport osparent, child = socket.socketpair()pid = os.fork()if pid: print(\’in parent, sending message\’) child.close() parent.sendall(b\’ping\’) response = parent.recv(1024) print(\’response from child:\’, response) parent.close()else: print(\’in child, waiting for message\’) parent.close() message = child.recv(1024) print(\’message from parent:\’, message) child.sendall(b\’pong\’) child.close() # output# in parent, sending message# in child, waiting for message# message from parent: b\’ping\’# response from child: b\’pong\’默认情况下,会创建一个 UDS 套接字,但也可以传递地址族,套接字类型甚至协议选项来控制套接字的创建方式。
组播
点对点连接可以处理大量通信需求,但随着直接连接数量的增加,同时给多个接收者传递相同的信息变得具有挑战性。分别向每个接收者发送消息会消耗额外的处理时间和带宽,这对于诸如流式视频或音频之类的应用来说可能是个问题。使用组播一次向多个端点传递消息可以使效率更高。
组播消息通过 UDP 发送,因为 TCP 是一对一的通信系统。组播地址(称为 组播组)是为组播流量保留的常规 IPv4 地址范围(224.0.0.0到230.255.255.255)的子集。这些地址由网络路由器和交换机专门处理,因此发送到该组的邮件可以通过 Internet 分发给已加入该组的所有收件人。
注意:某些托管交换机和路由器默认禁用组播流量。如果在使用示例程序时遇到问题,请检查网络硬件设置。
发送组播消息
修改上面的客户端程序使其向组播组发送消息,然后报告它收到的所有响应。由于无法知道预期会有多少响应,因此它会使用套接字的超时值来避免在等待答案时无限期地阻塞。
套接字还需要配置消息的生存时间值(TTL)。TTL 控制接收数据包的网络数量。使用IP_MULTICAST_TTL 和 setsockopt() 设置 TTL。默认值1表示路由器不会将数据包转发到当前网段之外。该值最大可达 255,并且应打包为单个字节。
# socket_multicast_sender.py import socketimport structimport sysmessage = b\’very important data\’multicast_group = (\’224.3.29.71\’, 10000)# Create the datagram socketsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# Set a timeout so the socket does not block# indefinitely when trying to receive data.sock.settimeout(0.2)# Set the time-to-live for messages to 1 so they do not# go past the local network segment.ttl = struct.pack(\’b\’, 1)sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)try: # Send data to the multicast group print(\’sending {!r}\’.format(message)) sent = sock.sendto(message, multicast_group) # Look for responses from all recipients while True: print(\’waiting to receive\’) try: data, server = sock.recvfrom(16) except socket.timeout: print(\’timed out, no more responses\’) break else: print(\’received {!r} from {}\’.format(data, server))finally: print(\’closing socket\’) sock.close()发件人的其余部分看起来像 UDP 客户端,除了它需要多个响应,因此使用循环调用 recvfrom() 直到超时。
接收组播消息
建立组播接收器的第一步是创建 UDP 套接字。创建常规套接字并绑定到端口后,可以使用setsockopt()更改IP_ADD_MEMBERSHIP选项将其添加到组播组。选项值是组播组地址的 8 字节打包表示,后跟服务端监听流量的网络接口,由其 IP 地址标识。在这种情况下,接收端使用 INADDR_ANY 所有接口。
# socket_multicast_receiver.py import socketimport structimport sysmulticast_group = \’224.3.29.71\’server_address = (\’\’, 10000)# Create the socketsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# Bind to the server addresssock.bind(server_address)# Tell the operating system to add the socket to# the multicast group on all interfaces.group = socket.inet_aton(multicast_group)mreq = struct.pack(\’4sL\’, group, socket.INADDR_ANY)sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)# Receive/respond loopwhile True: print(\’\\nwaiting to receive message\’) data, address = sock.recvfrom(1024) print(\’received {} bytes from {}\’.format(len(data), address)) print(data) print(\’sending acknowledgement to\’, address) sock.sendto(b\’ack\’, address)接收器的主循环就像常规的 UDP 服务端一样。
示例输出
此示例显示在两个不同主机上运行的组播接收器。A地址192.168.1.13和B地址 192.168.1.14。
[A]$ python3 socket_multicast_receiver.pywaiting to receive messagereceived 19 bytes from (\’192.168.1.14\’, 62650)b\’very important data\’sending acknowledgement to (\’192.168.1.14\’, 62650)waiting to receive message[B]$ python3 source/socket/socket_multicast_receiver.pywaiting to receive messagereceived 19 bytes from (\’192.168.1.14\’, 64288)b\’very important data\’sending acknowledgement to (\’192.168.1.14\’, 64288)waiting to receive message发件人正在主机上运行B。
[B]$ python3 socket_multicast_sender.pysending b\’very important data\’waiting to receivereceived b\’ack\’ from (\’192.168.1.14\’, 10000)waiting to receivereceived b\’ack\’ from (\’192.168.1.13\’, 10000)waiting to receivetimed out, no more responsesclosing socket消息被发送一次,并且接收到两个传出消息的确认,分别来自主机A和B。
发送二进制数据
套接字传输字节流。这些字节可以包含编码为字节的文本消息,如前面示例中所示,或者它们也可以是由 struct 打包到缓冲区中的二进制数据。
此客户端程序将整数,两个字符的字符串和浮点值,编码为可传递到套接字以进行传输的字节序列。
# socket_binary_client.py import binasciiimport socketimport structimport sys# Create a TCP/IP socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server_address = (\’localhost\’, 10000)sock.connect(server_address)values = (1, b\’ab\’, 2.7)packer = struct.Struct(\’I 2s f\’)packed_data = packer.pack(*values)print(\’values =\’, values)try: # Send data print(\’sending {!r}\’.format(binascii.hexlify(packed_data))) sock.sendall(packed_data)finally: print(\’closing socket\’) sock.close()在两个系统之间发送多字节二进制数据时,重要的是要确保连接的两端都知道字节的顺序,以及如何将它们解压回原来的结构。服务端程序使用相同的 Struct说明符来解压缩接收的字节,以便按正确的顺序还原它们。
# socket_binary_server.py import binasciiimport socketimport structimport sys# Create a TCP/IP socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server_address = (\’localhost\’, 10000)sock.bind(server_address)sock.listen(1)unpacker = struct.Struct(\’I 2s f\’)while True: print(\’\\nwaiting for a connection\’) connection, client_address = sock.accept() try: data = connection.recv(unpacker.size) print(\’received {!r}\’.format(binascii.hexlify(data))) unpacked_data = unpacker.unpack(data) print(\’unpacked:\’, unpacked_data) finally: connection.close()运行客户端会产生:
$ python3 source/socket/socket_binary_client.pyvalues = (1, b\’ab\’, 2.7)sending b\’0100000061620000cdcc2c40\’closing socket服务端显示它收到的值:
$ python3 socket_binary_server.pywaiting for a connectionreceived b\’0100000061620000cdcc2c40\’unpacked: (1, b\’ab\’, 2.700000047683716)waiting for a connection浮点值在打包和解包时会丢失一些精度,否则数据会按预期传输。要记住的一件事是,取决于整数的值,将其转换为文本然后传输而不使用 struct 可能更高效。整数1在表示为字符串时使用一个字节,但在打包到结构中时使用四个字节。
非阻塞通信和超时
默认情况下,配置 socket 来发送和接收数据,当套接字准备就绪时会阻塞程序执行。调用send()等待缓冲区空间可用于传出数据,调用recv()等待其他程序发送可读取的数据。这种形式的 I/O 操作很容易理解,但如果两个程序最终都在等待另一个发送或接收数据,则可能导致程序低效,甚至死锁。
有几种方法可以解决这种情况。一种是使用单独的线程分别与每个套接字进行通信。但是,这可能会引入其他复杂的问题,即线程之间的通信。另一个选择是将套接字更改为不阻塞的,如果尚未准备好处理操作,则立即返回。使用setblocking()方法更改套接字的阻止标志。默认值为1,表示阻止。0表示不阻塞。如果套接字已关闭阻塞并且尚未准备好,则会引发 socket.error 错误。
另一个解决方案是为套接字操作设置超时时间,调用 settimeout() 函数,参数是一个浮点值,表示在确定套接字未准备好之前要阻塞的秒数。超时到期时,引发 timeout 异常。
相关文档:
https://pymotw.com/3/socket/index.html

原创文章,作者:小条,如若转载,请注明出处:https://www.sudun.com/ask/85351.html

(0)
小条的头像小条
上一篇 2024年6月1日 下午12:51
下一篇 2024年6月1日 下午12:51

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注