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[ 1 in  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,第一部分就到这里。



本文转自CoderZh博客园博客,原文链接:http://www.cnblogs.com/coderzh/archive/2008/06/16/1223287.html,如需转载请自行联系原作者

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值