与Python的网络连接基础
#网络开发人员 #编程 #python #networking

访问此站点时,正在进行很多事情。在当今以云为中心的世界中,这种低级沟通的大部分都被抽象了。在本系列中,我们将研究网络通信的一些基础,从基本连接的工作原理开始。

客户端和服务器

首先,我们将采用一台基本服务器,该服务器将发送给它的内容并将其返回为高层情况。这将使用Python内置的socketserver module来处理详细信息,该示例来自以下示例的文档:

import socketserver

class MyTCPHandler(socketserver.StreamRequestHandler):
    """
    The request handler class for our server.

    It is instantiated once per connection to the server, and must
    override the handle() method to implement communication to the
    client.
    """

    def handle(self):
        # self.request is the TCP socket connected to the client
        self.data = self.rfile.readline().strip()
        print("{} wrote:".format(self.client_address))
        print(self.data)
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 5555

    # Create the server, binding to localhost on port 9999
    with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
        # Activate the server; this will keep running until you
        # interrupt the program with Ctrl-C
        server.serve_forever()

这里的第一个重要部分是服务器进行绑定:

with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as

这将注册该程序要使用特定端口(5555)的程序,因此操作系统试图保留它,直到程序关闭并也注册了处理程序为止。当客户端连接以确定请求将完成的操作时,这将被执行。在这种情况下,正在使用StreamRequestHandler,它将将客户端的连接视为像对象这样的文件:

self.rfile.readline().strip()

此处的处理程序有一条线正在阅读中。我会注意到,在您不知道谁发送数据的现实世界中,某人永远不会发送新线,并且连接将被卡住。放大了这一点,很快,您可能会有一台连接过多的服务器,直到潜在的资源耗尽点,从而有效地成为拒绝服务(DOS)攻击。

虽然显示了readline,但在后端,它实际上是在执行一系列recv呼叫。这些呼叫会接收一定数量的字节,直到没有剩余数据为止。在类似的说明中,sendall将继续发送数据,直到没有剩下的数据为止。现在在客户端:

import socket

MSG = bytearray("Hello World", 'utf-8')

connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connection.connect(("127.0.0.1", 5555))
print(connection.getsockname())
connection.send(MSG)
result = connection.recv(len(MSG.upper()))
print(result)

这里要注意的一件事是,网络涉及低级别的字节。 bytearray是一种特殊的python类型,可让您将字符串(字符数组)变成一系列字节,该字节由给定的字符编码(UTF-8)指定。

socket.socket(socket.AF_INET, socket.SOCK_STREAM)

这会创建一个套接字连接。 AF_INET表示我们将通过IPv4(Internet协议版本4)地址处理连接。 SOCK_STREAM是指示TCP(传输控制协议)连接的一种奇特方式。这意味着我们正在通过TCP/IP连接。

connection.send(MSG)
result = connection.recv(len(MSG.upper()))

这里发送消息,然后我们切换到接收模式以获取从服务器发送的数据。鉴于我们知道会返回的内容(作为大量情况的消息)我们可以使用它来检索上层案例版本的确切字节数。现在一起运行所有内容:

> python .\server.py
('127.0.0.1', 57990) wrote:
b'Hello World'

> python client.py
('127.0.0.1', 57990)
b'HELLO WORLD'

两种方式连接完成。

端口

操作系统上的端口实际上具有Request For Comments (RFC) 8335中的Internet工程工作组(IETF)的规范。这谈到了端口和服务名称的工作原理。服务名称是由IANA管理的特定端口(Internet分配的数字授权)的特殊标签。 IANA网站上包含service names to ports的当前映射。操作系统通常会使用此功能来提供这些特殊端口的用户友好名称。例如,Linux经常将此列表存储在/etc/services

tcpmux          1/tcp                           # TCP port service multiplexer
echo            7/tcp
echo            7/udp
discard         9/tcp           sink null
discard         9/udp           sink null
systat          11/tcp          users
daytime         13/tcp
daytime         13/udp
netstat         15/tcp
qotd            17/tcp          quote
chargen         19/tcp          ttytst source
chargen         19/udp          ttytst source
ftp-data        20/tcp
ftp             21/tcp
fsp             21/udp          fspd
ssh             22/tcp                          # SSH Remote Login Protocol
telnet          23/tcp
smtp            25/tcp          mail
time            37/tcp          timserver
time            37/udp          timserver
whois           43/tcp          nicname
tacacs          49/tcp                          # Login Host Protocol (TACACS)

内置的socket python模块甚至具有getservbynamegetservbyport方法可以使用此信息:

>>> import socket
>>> print(socket.getservbyname('http'))
80
>>> print(socket.getservbyport(80))
http

也有designations for port ranges。端口0-1023是系统端口。运行服务器需要在Windows上进行管理访问或 *NIX系统上的根/特权访问。如果没有管理特权运行,将出现访问拒绝的消息。这是按照您不想说的,一个随机用户放置自己的SSH服务器的完成。

端口1024-49151旨在供非Admin用户运行服务。这就是为什么绑定到5555不需要管理访问的原因。这也是许多正在安装在本地环境中的Web应用程序的原因,倾向于使用诸如8080或8888之类的端口,因此用户不必担心其管理权限。最后,有“动态端口”,可以在输出中看到:

('127.0.0.1', 57990) wrote:

这些端口由操作系统保留用于客户通信。没有这样的端口,服务器无法与客户端进行通信。从本质上讲,动态端口使客户在连接持续时间内也可以作为“服务器”。虽然RFC将这些端口列为49152-65535,但实际范围是OS特定的,在某些情况下可以配置。以后版本的Windows使用IANA建议,而我的Ubuntu实例为:

# sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768    60999

IP地址

如前所述,网络通信在字节中起作用。那么IP地址呢?我们是否只是将字符串“ 127.0.0.1”变成一系列字节?事实证明,IP地址是指示32位数字的一种特殊方法。 .分离值是8位/1个字节段:

[8 bits].[8 bits].[8 bits].[8 bits]

其中每个1个字节段是该段指示的小数的二进制版本。 socket.inet_aton可用于展示以下内容:

>>> import socket
>>> ip_binary = socket.inet_aton('127.0.0.1')
>>> import struct
>>> struct.unpack('BBBB', ip_binary)
(127, 0, 0, 1)
>>> ip_binary
b'\x7f\x00\x00\x01'

ip_binary是字节和struct.unpackis的序列,设置为1个字节(b代表的b)4个无符号字符,该字节具有0-255,与IPv4的每个IP段的允许值范围匹配。 IPv6更复杂,一个完整的示例看起来像这样:

0123:4567:89ab:cdef:0123:4567:89ab:cdef

在这种情况下,:分解了段。每个片段的4个基本值从0(0000二进制)到F(1111二进制)的值16个值,每个值占4位。这为您提供了16位,每段8个部分,总共提供128位。这使得它的大小是IPv4地址的4倍。由于socket.inet_aton仅用于IPv4地址,而是使用socket.inet_pton,它允许我们指定IPv6地址:

>>> import socket
>>> socket.inet_pton(socket.AF_INET6, '0123:4567:89ab:cdef:0123:4567:89ab:cdef')
b'\x01#Eg\x89\xab\xcd\xef\x01#Eg\x89\xab\xcd\xef'

以二进制形式的IP地址可以作为构造函数传递给ipaddress模块类之一,以使对象返回:

>>> import ipaddress
>>> import socket
>>> ipv6_bytes = socket.inet_pton(socket.AF_INET6, '0123:4567:89ab:cdef:0123:4567:89ab:cdef')
>>> ipv4_bytes = socket.inet_aton('127.0.0.1')
>>> ipaddress.IPv4Address(ipv4_bytes)
IPv4Address('127.0.0.1')
>>> ipaddress.IPv6Address(ipv6_bytes)
IPv6Address('123:4567:89ab:cdef:123:4567:89ab:cdef')
>>> ipaddress.IPv6Address(ipv6_bytes).exploded
'0123:4567:89ab:cdef:0123:4567:89ab:cdef'

尤其是IPv6,默认情况下未在输出中显示0。缺少值仅意味着0000。使用exploded属性将显示完整的地址,并显示0s。还有一些有用信息的帮助人功能:

>>> ipaddress.IPv4Address(ipv4_bytes).is_loopback
True
>>> ipaddress.IPv4Address(ipv4_bytes).is_global
False
>>> ipaddress.IPv4Address(ipv4_bytes).is_private

现在这意味着is_privateis_global,我们该怎么说?事实证明,IANA还处理IP地址。在这种情况下,尽管他们主要处理第一个8 bit value in an IPv4 address的分配。因此,例如,如果我使用Google DNS IP地址之一8.8.8.8

008/8   Administered by ARIN    1992-12     whois.arin.net  https://rdap.arin.net/registry
http://rdap.arin.net/registry   LEGACY

我被告知它是由Arin管理的。现在,Arin是美国大多数北美地区的Internet号码注册表,并处理IP地址分配。这意味着IANA充当前缀区域当局的分配权限。 regional authorities是:

  • Afrinic:非洲地区
  • apnic:亚太地区/太平洋地区
  • 阿林:加拿大,美国和一些加勒比群岛
  • lacnic:拉丁美洲和一些加勒比群岛
  • 成熟的NCC:欧洲,中东和中亚

现在,重要的是要注意,如果您使用其区域WHOIS服务搜索IP地址,通常会获得最佳信息。例如,如果我尝试使用ARIN Whois搜索日语IP地址:

ARIN whois recommending that a Japanese IP address search be done using APNIC whois

它会告诉我我应该使用apnic。使用这些我可以获得有关IP地址所有权的更多信息。在Google DNS的情况下:

ARIN whois search showing 8.8.8.8 as Google owned

它显示了Google对IP地址的所有权。您还可以查看组织拥有的IP块。例如,这是Google owned IP blocks的列表。要注意的一件事是,这也是恶意演员的不幸工具。他们发现了属于组织的IP块,并在其上启动大规模扫描。这就是AWS上的EC2实例不断被大量扫描。拥有所有权的另一个相当尴尬的情况是,一些Early Registration Transfers是启动的,将某些IP块从一个区域权威转移到另一个区域权威(成熟到Arin)。

DNS

域名系统或DNS允许将特定名称解析到IP地址。这是由全球服务器网络和本地覆盖提供的。 *nix Systems和Windows系统上的c:\Windows\System32\Drivers\etc\hosts文件上的/etc/hosts文件允许在本地设置主机名 - > IP地址映射的本地设置。作为我的Ubuntu实例上的一个例子:

127.0.0.1       localhost
127.0.0.1       gitserver

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback

此将localhost映射到127.0.0.1,而另一个条目则可以使Gitolite服务器以更具人为友好的名称在本地访问。请注意,由于DNS查找的发生程度如此之多,因此通常会缓存是为了性能目的,因为依靠DNS查找的网络流量在不解决IP地址的情况下无法继续进行。例如,这是我的Windows DNS缓存中的条目之一:

example.org
    ----------------------------------------
    Record Name . . . . . : example.org
    Record Type . . . . . : 1
    Time To Live  . . . . : 61854
    Data Length . . . . . : 4
    Section . . . . . . . : Answer
    A (Host) Record . . . : 93.184.216.34

这告诉我示例.org解决了93.184.216.34,并且(以秒为单位)的生活时间表示该条目应缓存约17个小时。请注意,根据支持DNS条目的内容,此值会波动。完成此操作后,查找将继续上升到一系列服务器,以找出IP是什么。这可以是:

之一
  • 手动设置服务器,例如某人设置Google DNS
  • 路由器,通常向您的ISP转发
  • 您的ISP
  • 全局DNS网络的服务器一部分
  • 特定于域名 /组织的服务器< / li>

值得注意的是,Python确实提供了一种从主机名获得IP的方法:

>>> import socket
>>> dns_result = socket.getaddrinfo('google.com', 80)
>>> dns_result
[(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('142.250.191.238', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('142.250.191.238', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_RAW: 3>, 0, '', ('142.250.191.238', 80)), (<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('2607:f8b0:4009:819::200e', 80, 0, 0)), (<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('2607:f8b0:4009:819::200e', 80, 0, 0)), (<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_RAW: 3>, 0, '', ('2607:f8b0:4009:819::200e', 80, 0, 0))]

但是,返回的值并未完全映射到用户期望与DNS一起使用的标准方式(更不用说需要端口了)。值得庆幸的是,有一个Python软件包可以以更友好的布局友好方法呈现DNS。这将需要通过PIP安装dnspythonpip install dnspython

from dns.resolver import resolve
from dns.rdatatype import RdataType

for query_type in RdataType:
    try:    
        answers = resolve('dev.to', query_type)
        for rdata in answers:
            print(f"{RdataType.to_text(query_type)}:{rdata.to_text()}")
    except:
        continue

将输出:

> python .\dns_list.py
A:151.101.194.217
A:151.101.66.217
A:151.101.130.217
A:151.101.2.217
NS:josh.ns.cloudflare.com.
NS:jill.ns.cloudflare.com.
SOA:jill.ns.cloudflare.com. dns.cloudflare.com. 2309864129 10000 2400 604800 3600
MX:10 alt4.aspmx.l.google.com.
MX:5 alt1.aspmx.l.google.com.
MX:5 alt2.aspmx.l.google.com.
MX:10 alt3.aspmx.l.google.com.
MX:1 aspmx.l.google.com.
TXT:"v=spf1 a mx include:_spf.google.com include:sendgrid.net include:servers.mcsv.net include:shops.shopify.com ~all"
TXT:"facebook-domain-verification=1xzy1qk89qs7ngxdt5e4s0kvvqw701"
TXT:"_globalsign-domain-verification=VzRovTWhxjedMqXFfoiZ-UNRnlnuTXYHgjKemPNt33"
TXT:"google-site-verification=oTtYzW83zP_41DlUrb_VXtAjLTW1p71RBmWR2g5ctrk"

因此,如果有很多有趣的记录类型,您会在许多查询中找到一个数字:

  • 记录:主机的映射 - > IP地址
  • AAA记录:相同,但对于IPv6
  • cname记录:用于别名
  • txt记录:简单的文本信息,但通常用于域所有权验证目的
  • ns记录:域名人物
  • MX记录:用于指示电子邮件服务器
  • SAO记录:所需的记录,指示具有一般所有权和行政联系信息的授权开始

这些是最常用或修改的。也就是说,在AWS中有一些服务,例如Route53,它们基于某些条件会动态返回A和AAAA记录的IP地址。这可以是从服务器加载到地理位置的任何东西。对于dev.to的DNS答案,我们可以推论一些事情:

  • 他们将Cloudflare用于DNS。
  • 快速提供CDN(内容输送网络)
  • Google Mail Services被用于电子邮件
  • SPF(发送者策略框架)允许SendGrid,Mail Chimp和Shopify代表Dev.to.to
  • 发送电子邮件
  • 验证Facebook API,Verisign Global标志和Google网站以显示所有权

鉴于不存在AAAA记录,您将无法使用IPv6直接连接。 A记录的IP地址也很有趣,因为它们在技术上应该属于RIPE administration,但似乎是早期注册转移计划的一部分(足够到bug was filed关于Redhat的bug was filed)。另外,如果您尝试访问A录制IP地址之一:

Fastly error showing no setup

这是由于单个IP能够托管多个域的网站。因此,仅IP可能还不够,因此请求将实际域包含在请求中,以便服务器知道其实际上应该使用的内容。

结论

这将结论一下Python的网络基本面。尽管大部分信息都从现代云计算中抽象出来,但知道幕后发生了什么仍然很有趣。有一天,它甚至可能有助于解决一个具有挑战性的调试课程!