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()