接上一篇,当你尝试在另外一台机器上 telnet 到 SuperSimpleSocketServer 时,正如前面所说的,你会发现你没有办法连接到服务端。这是因为你是将服务端绑定到 localhost。"localhost”是一个特殊的主机名,它仅仅是一个内部的主机名,你没有办法从另外一台电脑上访问它。毕竟,从别人的角度看,"localhost”是指他们的电脑,而不是你的电脑。

      这样做在实际上非常有用,它可以让你在测试你的服务端的时候而不必承担连接暴露在 Internet 的风险。(当然啦,如果你将服务端远行在一个多用户的机器上,那你就要担心在这台机器上的其他用户,因此最好运行到一个你自己的系统)。当然啦如果有一个你准备为架一台实际有用的服务端,那么外部连接就是你想要的,你需要把你的服务端绑定到一台外部的主机上。

      如果你能够通过 SSH 远程登录到你的电脑,或者你已经运行一个网页服务器,或者你已经知道一台外部电脑并且通过那台电脑可以连接到你的电脑。从另一方面讲,如果你可以拨号或者宽带连接,当你连接到你的 ISP 提供商,你就可以获得一个主机名和一个 IP 地址。查看你电脑的 IP 地址,并通过 DNS 为你的电脑查找一个外部主机。如果这些都失效了,那你可以把你的服务端直接绑定到你的外部 IP 地址(不是127.0.0.1啦,那样的效果就跟绑定"localhost”一样)。

      镜像服务器

      下面我要展示的是一个比较复杂的例子(虽然也没有多大的用处),从这个例子中我们可以看到 Python 是如何像操作文件一样地操作 socket 连接。服务端从 socket 里接收文本,就像一个脚本获得标准输入那样。然后服务端逆反文本并像脚本标准输出一样将反过来的文本通过 socket 返回。当接到空白栏时,服务端终止连接。



MirrorServer#!/usr/bin/python
import socket

class MirrorServer:
	"""Receives text on a line-by-line basis and sends back a
	reversed version of the same text."""

	def __init__(self, port):
		"Binds the server to the given port."
		self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
		self.socket.bind(port)
		#Queue up to five requests befor turning clients away.
		self.socket.listen(5)

	def run(self):
		"Handles incoming requests forever."
		while True:
			request, client_address = self.socket.accept()
			#Turn the incoming and outgoing connections into files.
			input = request.makefile('rb', 0)
			output = request.makefile('wb', 0)
			l = True
			try:
				while l:
					l = input.readline().strip()
					if l:
						output.write(l[::-1] + '\r\n')
					else:
						#A blank line indicates a desire to terminate the connection.
						request.shutdown(2) #Shut down both reads and writes
			except socket.error:
				#Most likely the client disconnected.
				pass


if __name__ == '__main__':
	import sys
	if len(sys.argv) < 3:
		print 'Usage: %s [hostname] [port number]' % sys.argv[0]
		sys.exit(1)
	hostname = sys.argv[1]
	port = int(sys.argv[2])
	MirrorServer((hostname, port)).run()



试一试:用 MirrorServer 镜像文本

      跟测试 SuperSimpleSocketServer 一样,你专门写一个客户端来测试服务端。你可以 telnet 到服务端并输入一些文本。输入空白文本,服务端将中断连接。在终端,你可以这样启动服务端:



$ python MirrorServer.py localhost 2000



      在另一个终端,你可以将 telnet 当成客户端:



Telnet$ telnet localhost 2000
Trying 127.0.0.1...
Connected to rubberfish.
Escape character is ‘^]’.
Hello.
.olleH
Mirror this text!
!txet siht rorriM
Connection closed by foreign host.



镜像客户端

      虽然你已经看到通过 telnet 也可完美地跟镜像服务端交流,但并不是每个人都喜欢 telnet。我们需要的是一个可以拥有铃声提醒的镜像客户端,这样网络新手也会激动地输入文本,并看着服务端的回复。下面是一个简单的客户端,它接受服务端地址和待转换文本两个参数。客户端连接服务端,发送数据,并打印出逆反的文本:



MirrorClient#!/usr/bin/python
import socket

class MirrorClient:
	"A client for the mirror server."

	def __init__(self, server, port):
		"Connect to the given mirror server."
		self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		self.socket.connect((server, port))

	def mirror(self, s):
		"Sends the given string to the server, and prints the response."
		if s[-1] != '\n':
			s += '\r\n'
		self.socket.send(s)

		#Read server response in chunks until we get a newline; that 
		#indicates the end of the response.
		buf = []
		input = ''

		while not '\n' in input:
			try:
				input = self.socket.recv(1024)
				buf.append(input)
			except socket.error:
				break
			return ''.join(buf)[:-1]

	def close(self):
		self.socket.send('\r\n') #We don't want to mirror anything else.
		self.socket.close()

if __name__ == '__main__':
	import sys
	if len(sys.argv) < 4:
		print 'Usage: %s [host] [port] [text to be mirrored]' % sys.argv[0]
		sys.exit(1)
	hostname = sys.argv[1]
	port = int(sys.argv[2])
	toMirror = sys.argv[3]

	m = MirrorClient(hostname, port)
	print m.mirrlr(toMirror)
	m.close()



      服务端将 socket 连接转换成一对文件,而客户端直接对 socket 进行读写。这没有什么强制性理由。我只是觉得一个章节应该至少有一个使用底层 socket API。服务端读取一串,然后扫描字串是否有换行符,一个换行符意味着一个响应的结束。如果这个例子创建了一个文件来接收 socket 连接,那么代码就可以像调用 input.readline 那样简单。

      知道怎么时候响应结束非常的重要,因为调用 socket.rev (或者 input.readline )将暂停程序运行,直到服务端发送更多的数据。如果服务端也正停止等待更多客户端更多的数据,那么你的程序将永远的被阻。