Foundations of Python Network Programming - 读书笔记系列(1) - Low-Level 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,第一部分就到这里。
posted @ 2008-06-16 20:45  CoderZh  阅读(4304)  评论(3编辑  收藏  举报