一,打印机器名与 IPv4 地址
可以用python-sokect库快速地查看一些机器信息:
dang@DFL:~/test$ python3
Python 3.8.10 (default, Jun 2 2021, 10:49:15)
[GCC 10.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket # 导入 socket 库
>>> host_name = socket.gethostname() # 获取主机名
>>> print("Host name: %s" %host_name)
Host name: DFL
>>> print("IP address: %s"%socket.gethostbyname(host_name))# 根据主机名获取主机IP地址
IP address: 127.0.1.1
>>> print("IP address: %s"%(socket.gethostbyname_ex(host_name),)) # 将主机名转换为 IPv4 地址格式的扩展接口。
IP address: ('DFL', [], ['127.0.1.1'])
>>> print("IP address: %s"%(socket.gethostbyaddr('127.0.0.1'),)) # 根据IP地址获取主机主机名
IP address: ('localhost', [], ['127.0.0.1'])
或者在Linux中写成一个python脚本:
dang@DFL:~/test$ cat show_ip.py
#!/usr/bin/env python
# show host's name and ip address.
import socket
def print_machine_info():
host_name = socket.gethostname()
ip_address = socket.gethostbyname(host_name)
print ("Host name: %s" %host_name)
print ("IP address: %s" %ip_address)
if __name__ == '__main__':
print_machine_info()
dang@DFL:~/test$ python3 show_ip.py
Host name: DFL
IP address: 127.0.1.1
- socket.gethostname()返回一个字符串,包含当前正在运行 Python 解释器的机器的主机名。
- socket.gethostbyname(hostname)将主机名转换为 IPv4 地址格式。
- socket.gethostbyname_ex(hostname)将主机名转换为 IPv4 地址格式的扩展接口。返回三元组 (hostname, aliaslist, ipaddrlist) —— hostname 是响应给定 ip_address 的主要主机名;aliaslist 是相同地址的其他可用主机名的列表(可能为空); ipaddrlist 是 IPv4 地址列表,包含相同主机名、相同接口的不同地址(通常是一个地址,但不总是如此)。
- socket.gethostbyaddr(ip_address)是支持 IPv4/v6 的
socket.gethostbyname_ex(hostname)
。
Linux查看本机 IP 地址:
dang@DFL:~/test$ hostname
DFL
dang@DFL:~/test$ hostname -i
127.0.1.1
dang@DFL:~/test$ ifconfig
Windows10查看本机 IP 地址:
C:\Users\PC>ipconfig
C:\Users\PC>ipconfig /all
二,检索远程机器的 IP 地址
dang@DFL:~/test$ cat show_ip.py
#!/usr/bin/env python
# show host's ip address.
import socket
def get_remote_machine_info(host_name):
try:
print ("IP address of %s: %s" %(host_name, socket.gethostbyname(host_name)))
except socket.error as err_msg:
print ("%s: %s" %(remote_host, err_msg))
if __name__ == '__main__':
get_remote_machine_info('www.python.org')
dang@DFL:~/test$ python3 show_ip.py
IP address of www.python.org: 151.101.76.223
- 常用
try...except
语句捕获sokect通信中出现的异常。
三,转换 IPv4 地址的格式
当处理低级网络功能时,有时一般的 IP 地址字符串表示法不是很有用,需要将它转换为 32 位二进制或者十六进制格式:
dang@DFL:~/test$ cat ip4_address_conversion.py
#!/usr/bin/python3
import socket
from binascii import hexlify
def get_remote_machine_ip4(host_name):
# get ip4_address from host_name
ip4_address = socket.gethostbyname(host_name)
try:
print("IPv4 address of %s: %s" % (host_name, ip4_address))
except socket.error as err_msg:
print("%s: %s" % (host_name, err_msg))
return ip4_address
def convert_ip4_address(host_name, mod):
# convert ip4_address decimal to different formats
mod = int(mod)
ipd_addr = get_remote_machine_ip4(host_name) # 获取点分十进制字符串格式的IPv4地址。
binary_ipd_addr = socket.inet_aton(ipd_addr) # 获取十转二的IPv4地址。
# b2d_ip_addr = socket.inet_ntoa (binary_ipd_addr) # 二转十
if mod == 16:
hexadecimal_ip4_addr = hexlify(binary_ipd_addr) # 获取二转十六的IPv4地址。
# h2b_ip4_addr = binascii.unhexlify(hexstr)
print("Hexadecimal IPv4 Address: %s" % hexadecimal_ip4_addr)
if mod == 2:
print("Binary IPv4 address: %s" % binary_ip4_addr)
if __name__ == '__main__':
host_name = input('input host\'s name:')
base = input('input base,2 or 16:')
convert_ip4_address(host_name, base)
dang@DFL:~/test$ python3 ip4_address_conversion.py
input host's name:www.python.org
input base,2 or 16:2
IPv4 address of www.python.org: 151.101.76.223
Binary IPv4 address: b'\x97eL\xdf'
-
socket.inet_aton(ip_string)将 IPv4 地址从点分十进制字符串格式转换为 32 位压缩二进制格式,转换后为字节对象,长度为四个字符。
-
socket.inet_ntoa(packed_ip)将 32 位字节对象的 IPv4 地址转换为点分十进制字符串形式。
-
binascii.hexlify(data[, sep[, bytes_per_sep=1]])返回二进制数据 data 的十六进制表示形式。
-
binascii.unhexlify(hexstr)返回由十六进制字符串 hexstr 表示的二进制数据。
四,端口、协议和服务名称
dang@DFL:~/test$ cat find_service_name.py
#!/usr/bin/python3
import socket
def find_service_name(port, protocol):
# find service by port and protocol
service_name = socket.getservbyport(int(port), protocol)
print("Port: %s Protocol: %s => service name: %s" % (port, protocol, service_name))
def find_port_name(service, protocol):
# find port by service and protocol
port_name = socket.getservbyname(service, protocol)
print("Service: %s Protocol: %s => port name: %s" % (service, protocol, port_name))
if __name__ == '__main__':
port = input('port:')
protocol = input('protocol:')
find_service_name(port, protocol)
service = input('service:')
protocol = input('protocol:')
find_port_name(service, protocol)
dang@DFL:~/test$ python3 find_service_name.py
port:80
protocol:tcp
Port: 80 Protocol: tcp => service name: http
service:http
protocol:tcp
Service: http Protocol: tcp => port name: 80
- socket.getservbyport(port[, protocolname])
将 Internet 端口号和协议名称转换为该服务的服务名称。 - socket.getservbyname(servicename[, protocolname])
将 Internet 服务名称和协议名称转换为该服务的端口号。
五,转换字节序
数据在网络中以字节的形式按照一定顺序进行传输,这是网络字节序。
由于历史原因,不同操作系统或CPU处理不同的字节序,这是主机字节序。
字节序有两种类型:
1. 小端(little endian):将低序字节存储在起始地址
2. 大端(big endian):将高序字节存储在起始地址
dang@DFL:~/test$ cat byte_order_conversion.py
#!/usr/bin/python3
import socket
def convert_integer():
data = 1234
# 32-bit
print("Original: %s => Long host byte order: %s, Network byte order:%s" % (
data, socket.ntohl(data), socket.htonl(data)))
# 16-bit
print("Original: %s => Short host byte order: %s, Network byte order:%s" % (
data, socket.ntohs(data), socket.htons(data)))
if __name__ == '__main__':
convert_integer()
dang@DFL:~/test$ python3 byte_order_conversion.py
Original: 1234 => Long host byte order: 3523477504, Network byte order:3523477504
Original: 1234 => Short host byte order: 53764, Network byte order:53764
- socket.ntohl(x)将 32 位正整数从网络字节序转换为主机字节序。
- socket.htonl(x)将 32 位正整数从主机字节序转换为网络字节序。
- 参数都是待转换数据的长度。
Byte Ordering And Conversion Functions In Python Socket Module,网络字节序理解
六,套接字超时与阻塞
对一个套接字对象的读写操作默认是阻塞的,如果当前套接字还不可读/写,那么这个操作会一直阻塞下去,这就叫套接字超时;
对需要高性能的服务器来说,是不能接受的。所以,需要在进行读写操作的时候指定超时值。
dang@DFL:~/test$ cat socket_timeout.py
#!/usr/bin/python3
import socket
def socket_timeout(timeout=None):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建socket对象
print("Default socket timeout: %s" % s.gettimeout())
print(s.getblocking())
s.settimeout(timeout)
print("Current socket timeout: %s" % s.gettimeout())
if __name__ == '__main__':
socket_timeout(timeout=100)
dang@DFL:~/test$ python3 socket_timeout.py
Default socket timeout: None
True
Current socket timeout: 100.0
- socket.gettimeout()返回套接字操作相关的超时秒数(浮点数),未设置超时则返回 None。
- socket.settimeout(value)为阻塞套接字的操作设置超时。value 参数可以是非负浮点数,表示秒,也可以是 None。
- socket.getdefaulttimeout()返回用于新套接字对象的默认超时(以秒为单位的浮点数)。值 None 表示新套接字对象没有超时。首次导入 socket 模块时,默认值为 None。
- socket.setdefaulttimeout(timeout)设置用于新套接字对象的默认超时(以秒为单位的浮点数)。首次导入 socket 模块时,默认值为 None。
dang@DFL:~/test$ cat test_socket_modes.py
#!/usr/bin/python3
import socket
def test_socket_modes():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建套接字
s.setblocking(True) # 设置为阻塞型套接字
s.settimeout(0.5) # 设置超时时间
s.bind(("127.0.0.1", 0)) # 将套接字绑定到 address
socket_address = s.getsockname() # 获取套接字本身的地址
print("Trivial Server launched on socket: %s" % str(socket_address))
while 1:
s.listen(1) # 启动一个服务器用于接受套接字连接。
if __name__ == '__main__':
test_socket_modes()
dang@DFL:~/test$ python3 test_socket_modes.py
Trivial Server launched on socket: ('127.0.0.1', 40971)
- socket.getblocking()如果套接字处于阻塞模式,返回 True,非阻塞模式返回 False。
- socket.setblocking(flag)设置套接字为阻塞或非阻塞模式:如果 flag 为 false,则将套接字设置为非阻塞,否则设置为阻塞。
- socket.bind(address)将套接字绑定到 address(IP,Port)。
- socket.getsockname()返回套接字本身的地址。
- socket.listen([backlog])启动一个服务器用于接受连接。如果指定 backlog,则它最低为 0(小于 0 会被置为 0),它指定系统允许暂未 accept 的连接数,超过后将拒绝新连接。
这里创建了服务器端套接字对象,看一下步骤:
- 调用
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
方法通过给定的地址簇、套接字类型和协议实例化 TCP 套接字对象。 - 调用
套接字对象.socket.bind(address)
方法绑定(IP,Port)。 - 调用
套接字对象.socket.listen(\[backlog\])
方法启动监听,等待客户端套接字连接。
七,使用套接字并优雅地处理错误
在任何网络应用程序中,一端正在尝试连接,但另一方由于网络媒体故障或任何其他原因而没有响应是很常见的。
Python socket库有一种通过 socket.error
异常处理这些错误的优雅方法:
dang@DFL:~/test$ cat socket_errors.py
#!/usr/bin/python3
import sys
import socket
import argparse
def main():
# setup argument parsing
parser = argparse.ArgumentParser(description='Socket Error Examples') # 1,创建 ArgumentParser 对象
parser.add_argument('--host', action="store", dest="host", required=False) # 2,调用 add_argument() 方法传递命令行参数
parser.add_argument('--port', action="store", dest="port", type=int, required=False)
parser.add_argument('--file', action="store", dest="file", required=False)
given_args = parser.parse_args() # 3,调用parse_args() 方法来解析参数
host = given_args.host
port = given_args.port
filename = given_args.file
# First try-except block -- create socket
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 1,创建套接字对象
except socket.error as e:
print("Error creating socket: %s" % e)
sys.exit(1)
# Second try-except block -- connect to given host/port
try:
s.connect((host, port)) # 2,连接套接字,创建管道
except socket.gaierror as e: # 表示与地址相关的错误。
print("Address-related error connecting to server: % s" % e)
sys.exit(1)
except socket.error as e:
print("Connection error: %s" % e)
sys.exit(1)
# Third try-except block -- sending data
try:
msg = "GET %s HTTP/1.0\r\n\r\n" % filename
s.sendall(msg.encode('utf-8')) # 3,向管道中送入数据
except socket.error as e:
print("Error sending data: %s" % e)
sys.exit(1)
while 1:
# Fourth tr-except block -- waiting to receive data from remote host
try:
buf = s.recv(2048) # 4,接收管道中的数据
except socket.error as e:
print("Error receiving data: %s" % e)
sys.exit(1)
if not len(buf):
break
# write the received data
sys.stdout.write(buf.decode('utf-8'))
if __name__ == '__main__':
main()
dang@DFL:~/test$ python3 socket_errors.py --host=www.pytgo.org --port=8080 --file=1_7_socket_errors.py
Connection error: [Errno 111] Connection refused
dang@DFL:~/test$ python3 socket_errors.py --host=www.python.org --port=8080 --file=socket_errors.py
Connection error: [Errno 111] Connection refused
dang@DFL:~/test$ python3 socket_errors.py --host=www.python.org --port=80 --file=socket_errors.py
HTTP/1.1 500 Domain Not Found
Server: Varnish
Retry-After: 0
content-type: text/html
Cache-Control: private, no-cache
X-Served-By: cache-hkg17925-HKG
Content-Length: 221
Accept-Ranges: bytes
Date: Sat, 16 Oct 2021 12:17:41 GMT
Via: 1.1 varnish
Connection: close
<html>
<head>
<title>Fastly error: unknown domain </title>
</head>
<body>
<p>Fastly error: unknown domain: . Please check that this domain has been added to a service.</p>
<p>Details: cache-hkg17925-HKG</p>
</body>
</html>
- argparse库能够实现对python脚本命令行参数的处理。首先要创建
ArgumentParser
对象、然后通过调用add_argument()
方法向ArgumentParser
添加命令行参数,最后通过调用parse_args()
方法来解析参数。 - socket.error能够处理多种 socket 异常。
这里创建了客户端套接字对象,并通过它进行了网络数据通信,简单看一下步骤:
- 调用
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
方法通过给定的地址簇、套接字类型和协议实例化 TCP 套接字对象。 - 调用
套接字对象.connect(address)
方法连接到address(host, port)
指定的远程套接字以创建套接字管道。 - 调用
套接字对象.sendall(bytes[, flags])
方法发送数据到套接字管道。本方法持续发送字节格式的数据,直到所有数据都已发送或发生错误为止。成功后会返回 None。 - 调用
套接字对象.recv(bufsize\[, flags\])
方法从套接字管道接收字节格式的数据。bufsize
指定一次接收的最大数据量。
这就是一个简单的使用套接字进行通信并处理错误的例子。
八,修改套接字发送与接受数据的缓冲区大小
在许多情况下,默认套接字缓冲区大小可能不合适,所以需要修改默认套接字缓冲区大小。
可以在套接字对象上调用 getsockopt()
和 setsockopt()
方法来分别检索和修改套接字对象的选项信息。
首先来看看socket.getsockopt(level, optname\[, buflen\])
方法,返回该套接字对象的选项信息。参数:
- 参数均为整型数值,且 socket 库中有对应的常量定义。
- level表示选项所在的协议层级。
- optname表示需要访问的选项名。当操作套接字选项时,必须指定选项所在的层级和选项的名称。
- buflen表示缓冲区大小。
level参数说明:
常量名 | 对应值 | 说明 |
---|---|---|
SOL_IP | 0 | 在 IP 协议层级上操作选项 |
SOL_SOCKET | 65535 | 在套接字API级别上操作选项 |
SOL_TCP | 6 | 在 TCP 协议层级上操作选项 |
SOL_UDP | 17 | 在 UDP 协议层级上操作选项 |
optname参数说明:
常量名 | 对应值 | 说明 |
---|---|---|
IP_OPTIONS | 1 | |
IP_RECVDSTADDR | 25 | |
IP_TOS | 3 | |
IP_TTL | 4 | |
SO_ACCEPTCONN | 2 | 是否是一个监听套接字。0是,1否。 |
SO_BROADCAST | 32 | 设置或获取广播标志。1启用时,数据报套接字被允许发送数据包到广播地址。对面向流的套接字没有影响。 |
SO_DEBUG | 1 | 是否使用调试。只允许具有CAP_NET_ADMIN能力或有效用户ID为0的进程使用。 |
SO_DONTROUTE | 16 | 是否不通过网关而发送到直接连接的主机。 |
SO_ERROR | 4103 | 获取套接字错误。 |
SO_KEEPALIVE | 8 | 是否在面向连接的套接字上发送keep-alive消息。 |
SO_LINGER | 128 | 是否延迟关闭连接 |
SO_OOBINLINE | 256 | 是否将带外数据将直接放置到接收数据流中。 |
SO_RCVBUF | 4098 | 设置或获取以字节为单位的最大套接字接收缓冲区,值是256的正整数倍。 |
SO_RCVLOWAT | 4100 | 指定接收缓冲区最小字节数,初始值为1。 |
SO_RCVTIMEO | 4102 | 指定接收超时。 |
SO_REUSEADDR | 4 | 是否允许重用本地地址和端口。 |
SO_SNDBUF | 4097 | 设置或获取以字节为单位的最大套接字发送缓冲区,值是2048的正整数倍。 |
SO_SNDLOWAT | 4099 | 指定发送缓冲区最小字节数,初始值为1。 |
SO_SNDTIMEO | 4101 | 指定发送超时。 |
SO_TYPE | 4104 | 获取套接字类型。 |
TCP_FASTOPEN | 15 | 是否开启TCP快速握手特性 |
TCP_KEEPCNT | 16 | TCP在放弃连接之前应该发送的最大keepalive探测数。 |
TCP_KEEPIDLE | 3 | 在socket设置SO_KEEPALIVE选项后,设置TCP在开始发送keepalive探测之前需要保持空闲的时间,单位为秒。 |
TCP_KEEPINTVL | 17 | 设置每个keepalive探测之间的时间,单位为秒。 |
TCP_MAXSEG | 4 | 设置TCP最大数据段的大小 |
TCP_NODELAY | 1 | 是否不使用Nagle算法 |
optname参数说明:socket(7) — Linux manual page
getsockopt(2) — Linux manual page
再看看socket.setsockopt(level, optname, value: buffer)
方法,参数说明同上。
dang@DFL:~/test$ cat modify_buff_size.py
#!/usr/bin/python3
import socket
# 缓冲区区大小
SEND_BUF_SIZE = 4096
RECV_BUF_SIZE = 4096
def modify_buff_size():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建套接字
sndbufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF) # 获取该套接字的发送缓冲区大小
print("send buffer size [Before]:%d" % sndbufsize)
rcvbufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) # 获取该套接字的接收缓冲区大小
print("receive buffer size [Before]:%d" % rcvbufsize)
sock.setsockopt( # 设置套接字选项。
socket.SOL_TCP, # 在 TCP 层级中,
socket.TCP_NODELAY, # 是否不使用Nagle算法,
1) # 是。
sock.setsockopt( # 设置套接字选项。
socket.SOL_SOCKET, # 在该套接字层级中,
socket.SO_SNDBUF, # 设置发送缓冲区大小,
SEND_BUF_SIZE) # 为 4096 字节。
sock.setsockopt( # 设置套接字选项。
socket.SOL_SOCKET, # 在该套接字层级中,
socket.SO_RCVBUF, # 设置接收缓冲区大小,
RECV_BUF_SIZE) # 为 4096 字节。
sndbufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF) # 获取该套接字的发送缓冲区大小
print("send buffer size [After]:%d" % sndbufsize)
rcvbufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) # 获取该套接字的接收缓冲区大小
print("receive buffer size [After]:%d" % rcvbufsize)
if __name__ == '__main__':
modify_buff_size()
dang@DFL:~/test$ python3 modify_buff_size.py
send buffer size [Before]:16384
receive buffer size [Before]:131072
send buffer size [After]:8192
receive buffer size [After]:8192
九,服务器端重用socket地址
一般情况下,在特定端口上运行 Python 套接字服务器并尝试在关闭一次后重新运行它时,将无法使用相同的端口:
Traceback (most recent call last):
File "reuse_socket_address.py",
line 40, in <module>
reuse_socket_addr()
File "reuse_socket_address.py",
line 25, in reuse_socket_addr
srv.bind( ('', local_port) )
File "<string>", line 1, in bind
socket.error: [Errno 98] Address
already in use
解决此问题的方法是调用启用套接字选项 SO_REUSEADDR
:
服务器端创建套接字对象后,查询地址重用的状态。 然后调用setsockopt()
方法来更改其地址重用状态的值。 最后按照通常的步骤绑定socket地址并侦听客户端连接。
这里假设我们运行前面的test_socket_modes()函数来创建一个服务器端套接字,按下 ctrl+c 后报错如下:
dang@DFL:~/test$ python3 test_socket_modes.py
Trivial Server launched on socket: ('127.0.0.1', 40971)
^C
Traceback (most recent call last):
File "test_socket_modes.py", line 19, in <module>
test_socket_modes()
File "test_socket_modes.py", line 15, in test_socket_modes
s.listen(1) # 启动一个服务器用于接受套接字连接。
KeyboardInterrupt
这表明在程序执行过程中被键盘中断了执行。我们可以捕获 KeyboardInterrupt 异常,则再按下 ctrl+c 后,Python 脚本就会终止而不显示任何异常消息。
服务器端重用地址并捕获 KeyboardInterrupt 异常:
dang@DFL:~/test$ cat reuse_socket_addr.py
#!/usr/bin/python3
import socket
def reuse_socket_addr():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Get the old state of the SO_REUSEADDR option
old_state = sock.getsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR)
print("Old sock state: %s" % old_state)
# Enable the SO_REUSEADDR option
sock.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR,
1)
new_state = sock.getsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR)
print("New sock state: %s" % new_state)
local_port = 8282
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 1,创建服务器端套接字
srv.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, # 2,允许端地址用
1)
srv.bind(('127.0.0.1', local_port)) # 3,服务器端绑定地址
srv.listen(1) # 4,服务器端监听连接
print("Listening on port: %s " % local_port)
while True:
try:
connection, addr = srv.accept() # 5,服务器端接受客户端的连接
print('Connected by %s:%s' % (addr[0], addr[1])) # 服务器端获取客户端连接信息
ret = connection.recv(1024) # 6,服务器端接收客户端数据
ret = ret.decode("gbk") # 服务器解码端客户端数据
print(ret) # 打印端客户端数据
except KeyboardInterrupt: # 处理异常
break
except socket.error as msg: # 处理异常
print('%s' % (msg,))
if __name__ == '__main__':
reuse_socket_addr()
dang@DFL:~/test$ python3 reuse_socket_addr.py
Old sock state: 0
New sock state: 1
Listening on port: 8282
Connected by 127.0.0.1:34598 # 客户端第一次连接
Received from client: 你好服务端,我是客户端小黑!
Connected by 127.0.0.1:34600 # 客户端第二次连接
Received from client: 你好服务端,我是客户端小黑!
这个服务器端程序比前面的哪个稍微完整了一些:
- 创建套接字。
- 调用
套接字对象.etsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
方法允许端地址用。 - 绑定地址。
- 监听连接。
- 调用
套接字对象.accept()
方法接受连接。此 socket 必须绑定到一个地址上并且监听连接。返回值是一个 (conn, address) 对:conn
表示新的套接字对象,用于在此连接上收发数据,address
表示连接另一端的套接字所绑定的地址。 - 调用
新的套接字对象.recv(bufsize\[, flags\])
方法从新的套接字接收数据。返回一个字节对象来表示接收到的数据。bufsize
指定一次接收的最大数据量。
十,UDP 客户端从互联网时间服务器打印当前时间
许多程序依赖于准确的机器时间,例如 UNIX 中的 make
命令,这就需要与网络中的另一个时间服务器同步,可以为此编写一个 Python 函数:
dang@DFL:~/test$ python-pip install ntplib
python-pip:未找到命令
dang@DFL:~/test$ pip install ntplib
Collecting ntplib
Downloading ntplib-0.4.0-py2.py3-none-any.whl (6.8 kB)
Installing collected packages: ntplib
Successfully installed ntplib-0.4.0
dang@DFL:~/test$ cat print_time.py
#!/usr/bin/python3
import ntplib
from time import ctime
def print_time():
ntp_client = ntplib.NTPClient()
response = ntp_client.request('0.cn.pool.ntp.org')
print(ctime(response.tx_time))
if __name__ == '__main__':
print_time()
dang@DFL:~/test$ python3 print_time.py
Sun Oct 17 19:14:15 2021
有时,又不需要从 NTP 服务器获取精确时间,则可以使用更简单的被称为简单网络时间协议SNPT 的 NTP 版本。
首先定义两个常量:NTP_SERVER 和 TIME1970。前者表示客户端将连接到的服务器地址,后者表示参考时间 1970 年 1 月 1 日。可以在http://www.epochconverter.com/找到参考时间的值或转换为参考时间。
然后客户端使用SOCK_DGRAM
选项创建一个 UDP 套接字以按照 UDP 协议连接到服务器。
然后客户端需要在数据包中发送 SNTP 协议数据 (’\x1b’ 47 * ‘\0’),并使用
和 recvfrom()
方法发送和接收数据。
然后,客户端需要一个专门的 struct 库来解包服务器以打包数组的形式返回时间信息。 唯一有趣的数据位于数组的第 11 个元素中。
最后,我们需要从解压后的值中减去参考时间值 TIME1970 以获得实际的当前时间。
dang@DFL:~/test$ cat print_time.py
#!/usr/bin/python3
import socket
import struct
import time
NTP_SERVER = "cn.pool.ntp.org"
TIME1970 = 2208988800
def print_time():
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 创建套接字对象
data = '\x1b' + 47 * '\0'
client.sendto(data.encode('utf-8'), (NTP_SERVER, 123)) # 发送数据
data, address = client.recvfrom(1024) # 获取时间服务器的响应
if data:
print('Response received from:', address)
t = struct.unpack('!12I', data)[10] # 解包数据
t -= TIME1970
print('\tTime=%s' % time.ctime(t))
if __name__ == '__main__':
print_time()
dang@DFL:~/test$ python3 print_time.py
Response received from: ('5.79.108.34', 123)
Time=Sun Oct 17 19:19:18 2021
十一,编写一个简单的 TCP 回显客户端/服务器端应用
服务器将回显它从客户端接收到的任何内容,将使用 Python argparse 模块从命令行指定 TCP 端口。 服务器和客户端脚本都将采用此参数。
- 首先,创建服务器 TCP 套接字对象,并设置重用地址,以便我们可以根据需要多次运行服务器。
- 将服务器套接字绑定到本地机器上的给定端口。 在侦听阶段,我们确保使用 listen() 方法的 backlog参数侦听队列中的多个客户端。
- 最后,等待客户端连接并发送一些数据到服务器。 当接收到数据时,服务器将数据回显给客户端。
dang@DFL:~/test$ cat echo_server.py
#!/usr/bin/python3
import socket
import argparse
host = 'localhost'
data_payload = 2048
backlog = 5
def echo_server(port):
""" 一个简单的回显服务器 """
# 1,创建 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2,启用地址可重用
sock.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR,
1)
# 3,绑定套接字地址
server_address = (host, port)
print("启动回显服务器 %s:%s" % server_address)
sock.bind(server_address)
# 4,监听客户端,backlog 参数指定最大排队连接数
sock.listen(backlog)
while True:
print("等待从客户端接收信息...")
# 5,接受客户端连接
client, address = sock.accept()
# 6,接收客户端信息
data = client.recv(data_payload)
if data:
print("接收到的客户端数据为: %s" % data.decode('gbk')) # 接收字节后解码为字符
# 7,向客户端回显信息
client.send(data) # 返回接搜到的字节数据
print("已将该 %s 字节串返回给 %s" % (data, address))
# 断开与客户端的连接
client.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Socket Server Example')
parser.add_argument('--port', action="store", dest="port", type=int, required=True)
given_args = parser.parse_args()
port = given_args.port
echo_server(port)
dang@DFL:~/test$ python3 echo_server.py --port=9900
启动回显服务器 localhost:9900
等待从客户端接收信息...
接收到的客户端数据为: 测试数据,该信息会被回显!
已将该 b'\xb2\xe2\xca\xd4\xca\xfd\xbe\xdd\xa3\xac\xb8\xc3\xd0\xc5\xcf\xa2\xbb\xe1\xb1\xbb\xbb\xd8\xcf\xd4\xa3\xa1' 字节串返回给 ('127.0.0.1', 57516)
等待从客户端接收信息...
dang@DFL:~/test$ cat echo_client.py
#!/usr/bin/python3
import socket
import argparse
host = 'localhost' # 127.0.0.1
def echo_client(port):
""" 一个简单的回显客户 """
# 1,创建 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2,连接到服务器socket
server_address = (host, port)
print("正在连接到 %s:%s" % server_address)
sock.connect(server_address)
try:
# 3,向服务器发送信息
message = "测试数据,该信息会被回显!"
print("正在发送 %s" % message)
sock.sendall(message.encode('gbk')) # 字符串编码为字节后发送。
# 4,等待从服务器接收响应信息
amount_received = 0
amount_expected = len(message)
while amount_received < amount_expected:
data = sock.recv(2018)
amount_received += len(data)
print("接收到的服务器端数据为: %s" % data.decode('gbk')) # 接收字节后解码为字符
except socket.error as e:
print("Socket 错误: %s" % str(e))
except Exception as e:
print("其他错误: %s" % str(e))
finally:
print("正在关闭到服务器端的链接...")
sock.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Socket Server Example')
parser.add_argument('--port', action="store", dest="port", type=int, required=True)
given_args = parser.parse_args()
port = given_args.port
echo_client(port)
dang@DFL:~/test$ python3 echo_client.py --port=9900
正在连接到 localhost:9900
正在发送 测试数据,该信息会被回显!
接收到的服务器端数据为: 测试数据,该信息会被回显!
正在关闭到服务器端的链接...
在了解了创建 socket 进行数据交互的过程后,再来看看被传输的数据的数据类型。
客户端:
- 发送数据前调用
encode()
方法将原字符串str 数据编码为字节串bytes 数据。 - 接收数据后调用
decode()
方法将字节串bytes 数据解码为字符串str 数据。
服务器端同样地:
- 接收数据后调用
decode()
方法将字节串bytes 数据解码为字符串str 数据。 - 发送数据前调用
encode()
方法将原字符串str 数据编码为字节串bytes 数据。
字符串str 对象是文本序列类型,Python处理文本数据就使用字符串对象。
字节串bytes 对象是一种二进制序列类型,字节串对象只负责以二进制字节序列的形式来存储数据,至于这些数据到底表示什么内容(字符串、数字、图片、音频等),完全由程序的解析方式决定。
采用合适的字符编码方式(字符集),字节串可以恢复成字符串,反之亦然。就像上面服务器与客户进行的那样。