您的位置:首页 > 编程语言 > Python开发

Python 中的 socket 编程 分类: socket 2013-08-10 17:17 377人阅读 评论(0) 收藏

2013-08-10 17:17 645 查看
Python 中的 socket 编程

在所有具有 socket 的语言中,socket 都是相同的 —— 这是两个应用程序彼此进行通信的管道。

前提条件

不管是使用 Python、Perl、Ruby、Scheme 还是其他有用的语言(此处 有用 的意思是这种语言有 socket 接口)来编写 socket 程序,socket 通常都是相同的。这是两个应用程序彼此进行通信的管道(这两个应用程序可以在同一台机器上,也可以位于两台不同的机器上)。

使用 Python 这种具有 socket 编程功能的语言的区别在于,它有一些辅助的类和方法,可以简化 socket 编程。在本节中,我们将展示 Python 的
socket
API。可以使用一个脚本来执行 Python 的解释器,如果您要自己执行 Python,就可以一次只输入一行代码。这样,就可以看到每个方法调用之后的结果了。

下面这个例子展示了如何与 Python 解释器进行交互。此处我们使用了
socket
类方法
gethostbyname
将一个完整的域名(www.ibm.com)解析成一个使用点号分隔的 IP 地址字符串('129.42.19.99'):

清单 3. 从解释器命令行中使用 socket

[camus]$ python
Python 2.4 (#1, Feb 20 2005, 11:25:45)
[GCC 3.2.2 20030222 (Red Hat Linux 3.2.2-5)] on linux2
Type "help", "copyright", "credits" or "license" for more
information.
>>> import socket
>>> socket.gethostbyname('www.ibm.com')
'129.42.19.99'
>>>

在导入
socket
模块之后,我调用了
gethostbyname
类方法将这个域名解析成 IP 地址。

现在,我们要讨论基本的
socket
方法,并通过 socket 进行通信。您应该熟悉 Python 解释器。



回页首
创建和销毁 socket

要新创建一个 socket,可以使用
socket
类的
socket
方法。这是一个类方法,因为还没有得到可以应用方法的
socket
对象。
socket
方法与 BSD API 类似,下面是创建流(TCP) socket 和数据报(UDP)socket 的方法:

清单 4. 创建流和数据报 socket

streamSock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )

dgramSock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )

在这种情况中,会返回一个
socket
对象。
AF_INET
符号(第一个参数)说明您正在请求的是一个 Internet 协议(IP) socket,具体来说是
IPv4。第二个参数是传输协议的类型(
SOCK_STREAM
对应 TCP socket,
SOCK_DGRAM
对应 UDP socket)。如果底层操作系统支持 IPv6,那么还可以指定
socket.AF_INET6
来创建一个 IPv6 socket。

要关闭一个已经连接的 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 )


这个例子非常简单,当然比使用 C 编写相同功能的程序时对
sockaddr_in
进行操作要简单很多。下面的讨论给出了 Python 中地址的例子。



回页首
服务器 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()

对于这个服务器来说,使用地址
('', 2525)
就意味着接口地址中使用了通配符
('')
,这样可以接收来自这个主机上的任何接口的连接。还说明要绑定到端口 2525 上。

注意此处
accept
方法不但要返回一个新的
socket
对象,它表示了客户机连接(
newsock
);而且还要返回一个地址对(socket 端的远程地址和端口号)。Python 的
SocketServer
模块可以对这个过程进一步进行简化,正如上面展示的一样。

虽然也可以创建数据报服务器,不过这是无连接的,因此没有对应的
accept
方法。下面的例子创建一个数据报服务器 socket:

清单 6. 创建一个数据报服务器 socket

sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
sock.bind( ('', 2525) )

后面对于 socket I/O 的讨论将说明 I/O 是如何为流 socket 和数据报 socket 工作的。

现在,让我们来看一下客户机是如何创建 socket 并将其连接到服务器上的。



回页首
客户机 socket

客户机 socket 的创建和连接机制与服务器 socket 相似。在创建 socket 之前,都需要一个地址 —— 不是本地绑定到这个 socket 上(就像服务器 socket 的情况那样),而是标识这个 socket 应该连接到什么地方。假设在这个主机的 IP 地址 '192.168.1.1' 和端口 2525 上有一个服务器。下面的代码可以创建一个新的 socket,并将其连接到定义的服务器上:

清单 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
来重新指定数据报客户机消息的目标。



回页首
流 socket I/O

通过流 socket 发送和接收数据在 Python 中是很简单的。有几个方法可以用来通过流 socket 传递数据(例如
send
recv
read

write
)。

第一个例子展示了流 socket 的服务器和客户机。在这个例子中,服务器会回显从客户机接收到的信息。

回显流服务器如清单 9 所示。在创建一个新的流 socket 之前,需要先绑定一个地址(接收来自任何接口和 45000 端口的连接),然后调用
listen
方法来启用到达的连接。这个回显服务器然后就可以循环处理各个客户机连接了。它会调用
accept
方法并阻塞(即不会返回),直到有新的客户机连接到它为止,此时会返回新的客户机 socket,以及远程客户机的地址信息。使用这个新的客户机 socket,我们可以调用
recv
来从另一端接收一个字符串,然后将这个字符串写回这个 socket。然后立即关闭这个 socket。

清单 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 显示了与清单 9 的回显服务器对应的客户机。在创建一个新的流程 socket 之前,需要使用
connect
方法将这个 socket 连接到服务器上。当连接之后(
connect
方法返回),客户机就会使用
send
方法输出一条简单的文本消息,然后使用
recv
方法等待回显。
print
语句用来显示所读取的内容。当这个过程完成之后,就执行
close
方法关闭 socket。

清单 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 接收消息时,必须同时返回数据源。
recvfrom

sendto
方法可以支持其他地址,正如您在数据报回显服务器和客户机实现中可以看到的一样。

清单 11 显示了数据报回显服务器的代码。首先创建一个 socket,然后使用
bind
方法绑定到一个地址上。然后进入一个无限循环来处理客户机的请求。
recvfrom
方法从一个数据报 socket 接收消息,并返回这个消息以及发出消息的源地址。这些信息然后会被传入
sendto
方法,将这些消息返回到源端。

清单 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 选项的值。

在 Python 中使用 socket 选项非常简单,正如清单 13 所示。在第一个例子中,我们读取的是 socket 发送缓冲区的大小。在第二个例子中,我们获取
SO_REUSEADDR
选项的值(重用
TIME_WAIT
中的地址),然后来启用它。

清单 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 )

SO_REUSEADDR
选项通常是在 socket 服务器的开发中使用的。可以增大 socket 的发送和接收缓冲区,从而获得更好的性能,但是记住您是在一个解释脚本中进行操作的,因此可能不会带来太多益处。



回页首
异步 I/O

Python 作为
select
模块的一部分提供了异步 I/O 的功能。这种特性与 C 的 select 机制类似,但是更加简单。我们首先对
select
进行简介,然后解释如何在 Python 中使用。

select
方法允许对多个 socket 产生多个事件或多个不同的事件。例如,您可以告诉
select
当 socket 上有数据可用时、当可以通过一个 socket 写入数据时以及在 socket 上发生错误时,都要通知您;可以同时为多个 socket 执行这些操作。

在 C 使用位图的地方,Python 使用列表来表示要监视的描述符,并且返回那些满足约束条件的描述符。在下面的例子中,等待从标准输入设备上输入信息:

清单 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 )

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐