http://pythonic.zoomquiet.io/data/20051121170755/
>>> import socket
>>> socket.gethostbyname('www.ibm.com')
'129.42.19.99'
>>>
streamSock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
dgramSock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
要关闭一个已经连接的 socket,可以使用 close 方法:
streamSock.close()
最后,可以使用 del 语句删除一个 socket:
del streamSock
这个语句可以永久地删除 socket 对象。之后再试图引用这个对象就会产生错误。
Socket 地址
socket 地址是一个组合,包括一个接口地址和一个端口号。由于 Python 可以很简单地表示元组,因此地址和端口也可以这样表示。下面表示的是接口地址 192.168.1.1 和端口 80:
( '192.168.1.1', 80 )
也可以使用完整的域名,例如:
( 'www.ibm.com', 25 )
服务器 socket
服务器 socket 通常会在网络上提供一个服务。由于服务器和客户机的 socket 是使用不同的方式创建的,因此我们将分别进行讨论。
在创建 socket 之后,可以使用 bind
方法来绑定一个地址,listen
方法可以将其设置为监听状态,最后 accept
方法可以接收一个新的客户机连接。下面展示了这种用法:
清单 5. 使用服务器 socket
sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
sock.bind( ('', 2525) )
sock.listen( 5 )
newsock, (remhost, remport) = sock.accept()
|
虽然也可以创建数据报服务器,不过这是无连接的,因此没有对应的 accept
方法。下面的例子创建一个数据报服务器 socket:
清单 6. 创建一个数据报服务器 socket
sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
sock.bind( ('', 2525) )
|
清单 7. 创建一个流 socket 并将其连接到服务器上
sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
sock.connect( ('192.168.1.1', 2525) )
|
对于数据报 socket 来说,处理过程稍有不同。回想一下,数据报从本质上来说都是没有连接的。可以这样考虑:流 socket 是两个点之间的通信管道,而数据报 socket 是基于消息的,可以同时与多个点进行通信。下面是一个数据报客户机的例子。
清单 8. 创建一个数据报 socket 并将其连接到服务器上
sock = socket.socket( socket.AF_INET, sock.sock_DGRAM )
sock.connect( ('192.168.1.1', 2525) )
|
尽管我们使用了 connect
方法,但是此处是有区别的:在客户机和服务器之间并不存在真正的 连接。此处的连接是对以后 I/O 的一个简化。通常在数据报 socket 中,必须在所发送的数据中提供目标地址的信息。通过使用 connect
,我们可以使用客户机对这些信息进行缓存,并且 send
方法的使用可以与流 socket 情况一样(只不过不需要目标地址)。可以再次调用 connect
来重新指定数据报客户机消息的目标。
清单 9. 简单的 Python 流回显服务器
import socket
srvsock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
srvsock.bind( ('', 23000) )
srvsock.listen( 5 )
while 1:
clisock, (remhost, remport) = srvsock.accept()
str = clisock.recv(100)
clisock.send( str )
clisock.close()
|
清单 10. 简单的 Python 流回显客户机
import socket
clisock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
clisock.connect( ('', 23000) )
clisock.send("Hello World\n")
print clisock.recv(100)
clisock.close()
|
数据报 socket I/O
数据报 socket 天生就是无连接的,这意味着通信需要提供一个目标地址。类似,当通过一个 socket 接收消息时,必须同时返回数据源。
清单 11. 简单的 Python 数据报回显服务器
import socket
dgramSock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
dgramSock.bind( ('', 23000) )
while 1:
msg, (addr, port) = dgramSock.recvfrom( 100 )
dgramSock.sendto( msg, (addr, port) )
|
数据报客户机更加简单。在创建数据报 socket 之后,我们使用 sendto
方法将一条消息发送到一个指定的地址。(记住:数据报是无连接的。)在 sendto
完成之后,我们使用 recv
来等待回显的响应,然后打印所收到的信息。注意此处我们并没有使用 recvfrom
,这是因为我们对两端的地址信息并不感兴趣。
清单 12. 简单的 Python 数据报回显客户机
import socket
dgramSock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
dgramSock.sendto( "Hello World\n", ('', 23000) )
print dgramSock.recv( 100 )
dgramSock.close()
|
socket 选项
socket 在缺省情况下有一些标准的行为,但是可以使用一些选项来修改 socket 的行为。我们可以使用 setsockopt
方法来修改 socket 的选项,并使用 getsockopt
方法来读取 socket 选项的值。
清单 13. 使用 socket 选项
sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
# Get the size of the socket's send buffer
bufsize = sock.getsockopt( socket.SOL_SOCKET, socket.SO_SNDBUF )
# Get the state of the SO_REUSEADDR option
state = sock.getsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR )
# Enable the SO_REUSEADDR option
sock.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
|
清单 14. 等待 stdin 的输入
rlist, wlist, elist = select.select( [sys.stdin], [], [] )
print sys.stdin.read()
|
传递给 select
的参数是几个列表,分别表示读事件、写事件和错误事件。select
方法返回三个列表,其中包含满足条件的对象(读、写和异常)。在这个例子中,返回的 rlist
应该是 [sys.stdin]
,说明数据在 stdin 上可用了。然后就可以使用 read
方法来读取这些数据。
select
方法也可以处理 socket 描述符。在下面的例子(请参阅清单 15)中,我们创建了两个客户机 socket,并将其连接到一个远程端上。然后使用 select
方法来确定哪个 socket 可以读取数据了。接着可以读取这些数据,并将其显示到 stdout 上。
清单 15. 展示处理多个 socket 的 select 方法
import socket
import select
sock1 = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
sock2 = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
sock1.connect( ('192.168.1.1', 25) )
sock2.connect( ('192.168.1.1', 25) )
while 1:
# Await a read event
rlist, wlist, elist = select.select( [sock1, sock2], [], [], 5 )
# Test for timeout
if [rlist, wlist, elist] == [ [], [], [] ]:
print "Five seconds elapsed.\n"
else:
# Loop through each socket in rlist, read and print the available data
for sock in rlist:
print sock.recv( 100 )
|