python网络编程基础3——套接字、IPv4 和简单的客户/服务器编程

本文介绍了Python网络编程的基础,包括使用socket库创建TCP和UDP套接字,查看和转换IP地址,处理端口、协议和服务名称,设置套接字超时,以及优雅地处理错误。示例涵盖了从获取本地主机名和IP地址到检索远程机器IP,修改套接字缓冲区大小,以及创建简单的TCP回显客户端和服务器。同时,还展示了如何处理套接字通信中的异常,如使用try-except语句捕获socket.error。
摘要由CSDN通过智能技术生成

一,打印机器名与 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'

四,端口、协议和服务名称

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

五,转换字节序

数据在网络中以字节的形式按照一定顺序进行传输,这是网络字节序。
由于历史原因,不同操作系统或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 的连接数,超过后将拒绝新连接。

这里创建了服务器端套接字对象,看一下步骤:

  1. 调用socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)方法通过给定的地址簇、套接字类型和协议实例化 TCP 套接字对象。
  2. 调用 套接字对象.socket.bind(address)方法绑定(IP,Port)。
  3. 调用套接字对象.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>

这里创建了客户端套接字对象,并通过它进行了网络数据通信,简单看一下步骤:

  1. 调用socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)方法通过给定的地址簇、套接字类型和协议实例化 TCP 套接字对象。
  2. 调用套接字对象.connect(address)方法连接到 address(host, port) 指定的远程套接字以创建套接字管道。
  3. 调用 套接字对象.sendall(bytes[, flags])方法发送数据到套接字管道。本方法持续发送字节格式的数据,直到所有数据都已发送或发生错误为止。成功后会返回 None。
  4. 调用 套接字对象.recv(bufsize\[, flags\])方法从套接字管道接收字节格式的数据。bufsize 指定一次接收的最大数据量。

python网络编程基础:网络客户端

这就是一个简单的使用套接字进行通信并处理错误的例子。

八,修改套接字发送与接受数据的缓冲区大小

在许多情况下,默认套接字缓冲区大小可能不合适,所以需要修改默认套接字缓冲区大小。
可以在套接字对象上调用 getsockopt()setsockopt() 方法来分别检索和修改套接字对象的选项信息。

首先来看看socket.getsockopt(level, optname\[, buflen\])方法,返回该套接字对象的选项信息。参数:

  • 参数均为整型数值,且 socket 库中有对应的常量定义。
  • level表示选项所在的协议层级。
  • optname表示需要访问的选项名。当操作套接字选项时,必须指定选项所在的层级和选项的名称。
  • buflen表示缓冲区大小。

level参数说明:

常量名对应值说明
SOL_IP0在 IP 协议层级上操作选项
SOL_SOCKET65535在套接字API级别上操作选项
SOL_TCP6在 TCP 协议层级上操作选项
SOL_UDP17在 UDP 协议层级上操作选项

optname参数说明:

常量名对应值说明
IP_OPTIONS1
IP_RECVDSTADDR25
IP_TOS3
IP_TTL4
SO_ACCEPTCONN2是否是一个监听套接字。0是,1否。
SO_BROADCAST32设置或获取广播标志。1启用时,数据报套接字被允许发送数据包到广播地址。对面向流的套接字没有影响。
SO_DEBUG1是否使用调试。只允许具有CAP_NET_ADMIN能力或有效用户ID为0的进程使用。
SO_DONTROUTE16是否不通过网关而发送到直接连接的主机。
SO_ERROR4103获取套接字错误。
SO_KEEPALIVE8是否在面向连接的套接字上发送keep-alive消息。
SO_LINGER128是否延迟关闭连接
SO_OOBINLINE256是否将带外数据将直接放置到接收数据流中。
SO_RCVBUF4098设置或获取以字节为单位的最大套接字接收缓冲区,值是256的正整数倍。
SO_RCVLOWAT4100指定接收缓冲区最小字节数,初始值为1。
SO_RCVTIMEO4102指定接收超时。
SO_REUSEADDR4是否允许重用本地地址和端口。
SO_SNDBUF4097设置或获取以字节为单位的最大套接字发送缓冲区,值是2048的正整数倍。
SO_SNDLOWAT4099指定发送缓冲区最小字节数,初始值为1。
SO_SNDTIMEO4101指定发送超时。
SO_TYPE4104获取套接字类型。
TCP_FASTOPEN15是否开启TCP快速握手特性
TCP_KEEPCNT16TCP在放弃连接之前应该发送的最大keepalive探测数。
TCP_KEEPIDLE3在socket设置SO_KEEPALIVE选项后,设置TCP在开始发送keepalive探测之前需要保持空闲的时间,单位为秒。
TCP_KEEPINTVL17设置每个keepalive探测之间的时间,单位为秒。
TCP_MAXSEG4设置TCP最大数据段的大小
TCP_NODELAY1是否不使用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: 你好服务端,我是客户端小黑!

这个服务器端程序比前面的哪个稍微完整了一些:

  1. 创建套接字。
  2. 调用 套接字对象.etsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)方法允许端地址用。
  3. 绑定地址。
  4. 监听连接。
  5. 调用 套接字对象.accept()方法接受连接。此 socket 必须绑定到一个地址上并且监听连接。返回值是一个 (conn, address) 对: conn表示新的套接字对象,用于在此连接上收发数据,address表示连接另一端的套接字所绑定的地址。
  6. 调用 新的套接字对象.recv(bufsize\[, flags\])方法从新的套接字接收数据。返回一个字节对象来表示接收到的数据。bufsize 指定一次接收的最大数据量。

python网咯编程基础:网络服务器

十,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 端口。 服务器和客户端脚本都将采用此参数。

  1. 首先,创建服务器 TCP 套接字对象,并设置重用地址,以便我们可以根据需要多次运行服务器。
  2. 将服务器套接字绑定到本地机器上的给定端口。 在侦听阶段,我们确保使用 listen() 方法的 backlog参数侦听队列中的多个客户端。
  3. 最后,等待客户端连接并发送一些数据到服务器。 当接收到数据时,服务器将数据回显给客户端。
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 对象是一种二进制序列类型,字节串对象只负责以二进制字节序列的形式来存储数据,至于这些数据到底表示什么内容(字符串、数字、图片、音频等),完全由程序的解析方式决定。
采用合适的字符编码方式(字符集),字节串可以恢复成字符串,反之亦然。就像上面服务器与客户进行的那样。

一个简单的基于TCP通信的服务器端与客户端程序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值