• Foundations of Python Network Programming 读书笔记系列(1) LowLevel Networking


    以前,人们热衷于如何将两台机器互相连接,许多连接的方法在今天已经过时,还有很多方法沿用至今。TCP/IP就是之一,可以说,TCP/IP协议是当今使用范围最广的协议,这本书所有的内容都是基于TCP/IP的。TCP/IP的数据传输层是TCP和UDP,我们通过TCP和UDP连接远程机器时,只需要远程机器的IP和端口号,然后建立连接传输数据。其中TCP和UDP又有着许多不同之处。
    何时使用TCP?
        1. 你需要确保传输的数据准确的到达并且保持完整。
        2. 你需要发送大量的数据,而不是简单的请求和返回。
        3. 你能忍受建立连接时消耗的时间。(效率低)
    何时使用UDP?
        1. 你不关心你发送的包是否准确的到达,或者你能自己处理这些问题。(不稳定)
        2. 你只是希望得到一个简单的请求和返回。
        3. 你需要快速的建立连接。(效率高)
        4. 你发送的数据量不是很大。UDP限制每个包不能超过64KB,通常人们使用UDP时只使用了低于1KB。
    在Python中建立一个TCP或UDP连接是一件非常简单的事情,需要使用Socket模块,这是Python的标准模块。

    客户端(Network Clients)

    1. 创建一个socket对象
    = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    第一个参数socket.AF_INET说明我们使用的是IPv4,第二个参数socket.SOCK_STREAM指的是我们使用TCP进行数据传输,如果要使用UDP,则使用socket.SOCK_DGRAM,如:
    = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    2. connect连接远程服务器
    s.connect(("www.example.com", 80))
    连接远程服务器需要远程服务器的IP和端口,注意到上面我们使用了服务器的域名也是可以的,因为Python为我们做了DNS的解析。同时,注意到connect的参数是一个tuple。
    我们上面连接的是一个http站点,默认端口是80,我们可以通过下面的方法获取到默认的端口号:
    port = socket.getservbyname('http''tcp')
    相应的,你可以查询诸如:smtp,ftp等等端口号。
    3. 连接后,从一个socket对象获取信息
    比如,获取本机的IP地址和端口号,获取远程机器的IP地址和端口号,如:
    #!/usr/bin/env python
    #
     Information Example - Chapter 2

    import socket

    print "Creating socket",
    = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    print "done."

    print "Looking up port number",
    port 
    = socket.getservbyname('http''tcp')
    print "done."

    print "Connecting to remote host on port %d" % port,
    s.connect((
    "www.google.com", port))
    print "done."

    print "Connected from", s.getsockname()
    print "Connected to", s.getpeername()
    输出结果会显示:
    Creating socket... done.
    Looking up port number... done.
    Connecting to remote host on port 80... done.
    Connected from ('192.168.XX.XX', 2548)
    Connected to ('64.233.189.104', 80)
    可以看到,我的本机使用的是一个随机的端口号(2548),每次执行端口号都会不同。
    4. File-like 对象
    我们可以通过Socket对象来执行一些比如发送(send(), sendto()),接收数据的操作(recv(), recvfrom()),同时,我们还可以把Socket对象转换为一个类似文件的对象(File-like Object),然后使用其中的write()来发送数据,read(), readline()来接收数据。
    File-like对象更适合TCP连接,因为TCP连接必须保证数据流能够完整正确的到达,数据流表现的更像是一个文件。而UDP却不是,它是一个基于包的连接,它只管把这些包发送出去,如果使用File-like对象来处理,将很难追踪定位出现的错误。生成一个File-like对象通过下面的语句:
    fd = s.makefile('rw', 0) #s 是前面的创建的socket对象,rw表示可读和可写权限
    然后,就可以调用fd的write(), readines()等方法了。例子如下,同时注意细节的错误处理,这里不详细介绍:
    #!/usr/bin/env python
    #
     Error Handling Example With Shutdown and File-Like Objects - Chapter 2

    import socket, sys, time

    host 
    = sys.argv[1]
    textport 
    = sys.argv[2]
    filename 
    = sys.argv[3]

    try:
        s 
    = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    except socket.error, e:
        
    print "Strange error creating socket: %s" % e
        sys.exit(
    1)

    # Try parsing it as a numeric port number.

    try:
        port 
    = int(textport)
    except ValueError:
        
    # That didn't work.  Look it up instread.
        try:
            port 
    = socket.getservbyname(textport, 'tcp')
        
    except socket.error, e:
            
    print "Couldn't find your port: %s" % e
            sys.exit(
    1)

    try:
        s.connect((host, port))
    except socket.gaierror, e:
        
    print "Address-related error connecting to server: %s" % e
        sys.exit(
    1)
    except socket.error, e:
        
    print "Connection error: %s" % e
        sys.exit(
    1)

    fd 
    = s.makefile('rw', 0)

    print "sleeping"
    time.sleep(
    10)
    print "Continuing."

    try:
        fd.write(
    "GET %s HTTP/1.0\r\n\r\n" % filename)
    except socket.error, e:
        
    print "Error sending data: %s" % e
        sys.exit(
    1)

    try:
        fd.flush()
    except socket.error, e:
        
    print "Error sending data (detected by flush): %s" % e
        sys.exit(
    1)

    try:
        s.shutdown(
    1)
    except socket.error, e:
        
    print "Error sending data (detected by shutdown): %s" % e
        sys.exit(
    1)

    while 1:
        
    try:
            buf 
    = fd.read(2048)
        
    except socket.error, e:
            
    print "Error receiving data: %s" % e
            sys.exit(
    1)
        
    if not len(buf):
            
    break
        sys.stdout.write(buf)

    注意上面在我们发送了数据之后,使用了shutdown方法,是为了保证发送的数据成功到达目标机器。因为shutdown()会等待,直到接收到一个准确的退出代码。

    服务器端(Network Server)

    通过TCP创建一个服务端可以总结为如下四个步骤:
    1. 创建一个socket对象。(create socket object)
    2. 设置socket对象的属性。(set options)
    3. 绑定一个端口。(bind to a port)
    4. 监听来自客户端的连接。(listen for connection)
    针对上面的四个步骤,下面是一个最简单的实现:
    host = ''    #接受来自任何端口的连接
    port = 51423

    #第一步,创建一个socket对象
    = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    #第二步,设置socket属性
    s.setsockopt(socket.SOL_SOCKET, socket.SO_RESUSEADDR, 1)

    #第三步,绑定一个端口
    s.bind((host, port))

    #第四步,监听来自客户端的连接
    s.listen(5)     #参数5表示同时监听5个连接

    通过UDP创建一个服务端步骤也差不多,创建一个socket,设置option,bind端口,然而,UDP不需要listen()和accept(),而是使用recvfrom()就足够了。recvfrom()函数返回两个信息:接受的数据(data)和客户端的地址(address)和端口(port)。下面是UDP服务端的例子:
    #!/usr/bin/env python
    #
     UDP Echo Server - Chapter 3 - udpechoserver.py
    import socket, traceback

    host 
    = ''                               # Bind to all interfaces
    port = 51423

    = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 
    1)
    s.bind((host, port))

    while 1:
        
    try:
            message, address 
    = s.recvfrom(8192)
            
    print "Got data from", address
            
    # Echo it back
            s.sendto(message, address)
        
    except (KeyboardInterrupt, SystemExit):
            
    raise
        
    except:
            traceback.print_exc()

    Domain Name System(DNS)

    我们能很轻松的记住博客园的域名,却基本上很难说出它的IP地址来,因为DNS为我们解析了域名。
    socket.getaddrinfo()根据主机名或域名等来获取相应的信息。
    socket.getaddrinfo(host, port[, family[, socktype[, proto[, flags]]]]))

    返回值是一个tuple的列表,每个tuple返回如下信息:
    (family, socktype, proto, canonname, sockaddr)

    同时,gethostbyaddr()根据IP地址获取相应的信息,同时使用getaddrinfo()和gethostbyaddr()可以实现对域名的双重验证。如下面的例子:
    import sys, socket

    def getipaddrs(hostname):
        
    """Get a list of IP addresses from a given hostname.  This is a standard
        (forward) lookup.
    """
        result 
    = socket.getaddrinfo(hostname, None, 0, socket.SOCK_STREAM)
        
    return [x[4][0] for x in result]

    def gethostname(ipaddr):
        
    """Get the hostname from a given IP address.  This is a reverse
        lookup.
    """
        
    return socket.gethostbyaddr(ipaddr)[0]

    try:
        
    # First, do the reverse lookup and get the hostname.
        hostname = gethostname(sys.argv[1]) # could raise socket.herror

        
    # Now, do a forward lookup on the result from the earlier reverse
        # lookup.
        ipaddrs = getipaddrs(hostname)      # could raise socket.gaierror
    except socket.herror, e:
        
    print "No host names available for %s; this may be normal." % sys.argv[1]
        sys.exit(0)
    except socket.gaierror, e:
        
    print "Got hostname %s, but it could not be forward-resolved: %s" % \
              (hostname, str(e))
        sys.exit(
    1)

    # If the forward lookup did not yield the original IP address anywhere,
    #
     someone is playing tricks.  Explain the situation and exit.
    if not sys.argv[1in ipaddrs:
        
    print "Got hostname %s, but on forward lookup," % hostname
        
    print "original IP %s did not appear in IP address list." % sys.argv[1]
        sys.exit(
    1)

    # Otherwise, show the validated hostname.
    print "Validated hostname:", hostname

    OK,第一部分就到这里。
  • 相关阅读:
    费用流
    平面最近点对
    纸牌均分问题
    cdq分治模板
    费解的开关
    斐波那契和排列组合性质
    主席树
    Springboot使用EasyExcel(仅限自己收藏)
    vue项目中h5移动端中通过flex布局实现首尾固定,中间滚动(借鉴)
    vue路由参数的获取、添加和替换
  • 原文地址:https://www.cnblogs.com/coderzh/p/1223287.html
Copyright © 2020-2023  润新知