以前,人们热衷于如何将两台机器互相连接,许多连接的方法在今天已经过时,还有很多方法沿用至今。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的标准模块。
我们上面连接的是一个http站点,默认端口是80,我们可以通过下面的方法获取到默认的端口号:
3. 连接后,从一个socket对象获取信息
比如,获取本机的IP地址和端口号,获取远程机器的IP地址和端口号,如:
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对象通过下面的语句:
注意上面在我们发送了数据之后,使用了shutdown方法,是为了保证发送的数据成功到达目标机器。因为shutdown()会等待,直到接收到一个准确的退出代码。
1. 创建一个socket对象。(create socket object)
2. 设置socket对象的属性。(set options)
3. 绑定一个端口。(bind to a port)
4. 监听来自客户端的连接。(listen for connection)
针对上面的四个步骤,下面是一个最简单的实现:
通过UDP创建一个服务端步骤也差不多,创建一个socket,设置option,bind端口,然而,UDP不需要listen()和accept(),而是使用recvfrom()就足够了。recvfrom()函数返回两个信息:接受的数据(data)和客户端的地址(address)和端口(port)。下面是UDP服务端的例子:
socket.getaddrinfo()根据主机名或域名等来获取相应的信息。
返回值是一个tuple的列表,每个tuple返回如下信息:
同时,gethostbyaddr()根据IP地址获取相应的信息,同时使用getaddrinfo()和gethostbyaddr()可以实现对域名的双重验证。如下面的例子:
何时使用TCP?
1. 你需要确保传输的数据准确的到达并且保持完整。
2. 你需要发送大量的数据,而不是简单的请求和返回。
3. 你能忍受建立连接时消耗的时间。(效率低)
何时使用UDP?
1. 你不关心你发送的包是否准确的到达,或者你能自己处理这些问题。(不稳定)
2. 你只是希望得到一个简单的请求和返回。
3. 你需要快速的建立连接。(效率高)
4. 你发送的数据量不是很大。UDP限制每个包不能超过64KB,通常人们使用UDP时只使用了低于1KB。
在Python中建立一个TCP或UDP连接是一件非常简单的事情,需要使用Socket模块,这是Python的标准模块。
客户端(Network Clients)
1. 创建一个socket对象
s
=
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
第一个参数socket.AF_INET说明我们使用的是IPv4,第二个参数socket.SOCK_STREAM指的是我们使用TCP进行数据传输,如果要使用UDP,则使用socket.SOCK_DGRAM,如:
s
=
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 " ,
s = 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()
输出结果会显示:
# Information Example - Chapter 2
import socket
print " Creating socket " ,
s = 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)
# 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对象
s = 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个连接
port = 51423
# 第一步,创建一个socket对象
s = 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
s = 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()
# UDP Echo Server - Chapter 3 - udpechoserver.py
import socket, traceback
host = '' # Bind to all interfaces
port = 51423
s = 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
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,如需转载请自行联系原作者