使用 Python 进行 socket 编程(4)

数据报 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 )

 

构建一个 Python 聊天服务器

一个简单的聊天服务器

现在您已经了解了 Python 中基本的网络 API;接下来可以在一个简单的应用程序中应用这些知识了。在本节中,将构建一个简单的聊天服务器。使用 Telnet,客户机可以连接到 Python 聊天服务器上,并在全球范围内相互进行通信。提交到聊天服务器的消息可以由其他人进行查看(以及一些管理信息,例如客户机加入或离开聊天服务器)。这个模型如图 1 所示。

图 1. 聊天服务器使用 select 方法来支持任意多个客户机

聊天服务器的一个重要需求是必须可以伸缩。服务器必须能够支持任意个流(TCP)客户机。

要支持任意个客户机,可以使用 select 方法来异步地管理客户机的列表。不过也可以使用服务器 socket 的 select 特性。select 的读事件决定了一个客户机何时有可读数据,而且它也可以用来判断何时有一个新客户机要连接服务器 socket 了。可以利用这种行为来简化服务器的开发。

接下来,我们将展示聊天服务器的 Python 源代码,并说明 Python 怎样帮助简化这种实现。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wwgggx.html