11. Python 的网络函数
现在我们来探讨 Python 的网络函数,这些函数使我们能够进行 TCP / IP(传输控制协议 / 互联网协议)编程。
![]() |
11.1. 获取互联网上某台机器的名称或地址
import sys, socket
#------------------------------------------------
def getIPandName(nomMachine):
#nomMachine:要获取其地址的机器名称 IP
# nomMachine-->地址 IP
try:
ip=socket.gethostbyname(nomMachine)
print "ip[%s]=%s" % (nomMachine,ip)
except socket.error, erreur:
print "ip[%s]=%s" % (nomMachine,erreur)
return
# 地址 IP --> nomMachine
try:
name=socket.gethostbyaddr(ip)
print "name[%s]=%s" % (ip,name[0])
except socket.error, erreur:
print "name[%s]=%s" % (ip,erreur)
return
# ---------------------------------------- 主
# 常量
HOTES=["istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","localhost","","xx"]
# HOTES 机器的 IP 地址
for i in range(len(HOTES)):
getIPandName(HOTES[i])
# 结束
sys.exit()
注:
- 第 1 行:Python 的网络函数封装在 socket 模块中。
11.2. 一个 Web 客户端
一个用于获取网站索引段落内容的脚本。
import sys,socket
#-----------------------------------------------------------------------
def getIndex(site):
# 读取网站 URL 并将其存储在文件 site.html 中
# 起初没有错误
erreur=""
# 创建文件 site.html
try:
html=open("%s.html" % (site),"w")
except IOError, erreur:
pass
# 错误?
if erreur:
return "Erreur (%s) lors de la création du fichier %s.html" % (erreur,site)
# 在网站80端口建立连接
try:
connexion=socket.create_connection((site,80))
except socket.error, erreur:
pass
# 若出错则返回
if erreur:
return "Echec de la connexion au site (%s,80) : %s" % (site,erreur)
# 连接代表双向通信流
# 在客户端(本程序)与所连接的Web服务器之间
# 该通道用于交换命令和信息
# 对话协议为 HTTP
# 客户端发送 get 命令以请求 URL /
# GET语法 URL HTTP/1.0
# HTTP 协议的头部(headers)必须以空行结尾
connexion.send("GET / HTTP/1.0\n\n")
# 服务器现在将在连接通道上进行响应。它将发送所有
# 数据,然后关闭通道。客户端会读取连接通道中接收到的所有数据
# 直至通道关闭
ligne=connexion.recv(1000)
while(ligne):
html.write(ligne)
ligne=connexion.recv(1000)
# 客户端随后关闭连接
connexion.close()
# 关闭 HTML 文件
html.close()
# 返回
return "Transfert reussi du paragraphe index du site %s" % (site)
# --------------------------------------------- main
# 从 URL 获取文本 HTML
# 网站列表
SITES=("istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","xx")
# 读取表格中网站的索引页面 SITES
for i in range(len(SITES)):
# 读取网站 SITES 的索引页 [i]
resultat=getIndex(SITES[i])
# 显示结果
print resultat
# 结束
sys.exit()
注:
- 第 57 行:要获取首页的网站 URL 列表。该列表存储在文本文件 [nomsite.html] 中;
- 第62行:由函数getIndex执行该操作;
- 第4行:函数 getIndex;
- 第20行:方法 create_connection((site,port)) 用于与在机器 site 的端口 port 上运行的 TCP / IP 服务建立连接;
- 第35行:方法 send 允许通过 TCP / IP 连接发送数据。 此处发送的是文本。该文本遵循 HTTP 协议(HyperText 传输协议);
- 第40行:recv方法可通过TCP / IP连接接收数据。 在此,Web 服务器的响应以 1000 个字符为单位读取,并存储在文本文件 [nomsite.html] 中。
Transfert reussi du paragraphe index du site istia.univ-angers.fr
Transfert reussi du paragraphe index du site www.univ-angers.fr
Transfert reussi du paragraphe index du site www.ibm.com
Echec de la connexion au site (xx,80) : [Errno 11001] getaddrinfo failed
网站 [www.ibm.com] 收到的文件:
- 第1-11行是服务器响应中的HTTP头部信息;
- 第1行:服务器要求客户端重定向至第8行指定的URL;
- 第2行:响应的日期和时间;
- 第3行:Web服务器的标识;
- 第4行:服务器发送的内容。此处为第13行开始的HTML页面;
- 第12行:结束HTTP标头的空行;
- 第13-19行:Web服务器发送的HTML页面。
11.3. 一个 SMTP 客户端
在 TCP / IP 协议中,SMTP(SendMail 传输协议)是消息发送服务的通信协议。
# -*- coding=utf-8 -*-
import sys,socket
#-----------------------------------------------------------------------
def getInfos(fichier):
# 返回从文本文件中提取的信息(smtp、发件人、收件人、消息)[fichier]
# 第1行:SMTP、发件人、收件人
# 后续行:邮件正文
# 打开 [fichier]
erreur=""
try:
infos=open(fichier,"r")
except IOError, erreur:
return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
# 读取第一行
ligne=infos.readline()
# 删除换行符
ligne=cutNewLineChar(ligne)
# 提取 smtp、发件人、收件人字段
champs=ligne.split(",")
# 字段数量是否正确?
if len(champs)!=3 :
return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
# 对检索到的信息进行“处理”
# 从这3个字段中分别删除有用信息前后多余的“空格”
for i in range(3):
champs[i]=champs[i].strip()
# 提取字段
(smtpServer,expediteur,destinataire)=champs
message=""
# 读取消息剩余部分
ligne=infos.readline()
while ligne!='':
message+=ligne
ligne=infos.readline()
infos.close()
# 返回
return ("",smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
# 代表发件人将消息发送至 SMTP 服务器 smtpserver
# 发送给收件人。如果 verbose=True,则记录客户端与服务器的交互
# 获取客户端名称
try:
client=socket.gethostbyaddr(socket.gethostbyname("localhost"))[0]
except socket.error, erreur:
return "Erreur IP / Nom du client : %s" % (erreur)
# 在 smtpServer 的 25 端口上建立连接
try:
connexion=socket.create_connection((smtpServer,25))
except socket.error, erreur:
return "Echec de la connexion au site (%s,25) : %s" % (smtpServer,erreur)
# 连接代表客户端(本程序)与所联系的 SMTP 服务器之间的双向通信流
# 在客户端(本程序)与所连接的SMTP服务器之间
# 该通道用于交换命令和信息
# 连接建立后,服务器会发送一条欢迎消息,我们读取该消息
erreur=sendCommand(connexion,"",verbose,1)
if(erreur) :
connexion.close()
return erreur
# ehlo命令:
erreur=sendCommand(connexion,"EHLO %s" % (client),verbose,1)
if erreur :
connexion.close()
return erreur
# 命令 mail from:
erreur=sendCommand(connexion,"MAIL FROM: <%s>" % (expediteur),verbose,1)
if erreur :
connexion.close()
return erreur
# 命令 rcpt to:
erreur=sendCommand(connexion,"RCPT TO: <%s>" % (destinataire),verbose,1)
if erreur :
connexion.close()
return erreur
# 数据命令
erreur=sendCommand(connexion,"DATA",verbose,1)
if erreur :
connexion.close()
return erreur
# 准备发送消息
# 它必须包含以下行
# 发件人:发件人
# 收件人:
# 空行
# 消息
# .
data="From: %s\r\nTo: %s\r\n%s\r\n.\r\n" % (expediteur,destinataire,message)
# 发送消息
erreur=sendCommand(connexion,data,verbose,0)
if erreur :
connexion.close()
return erreur
# 退出命令
erreur=sendCommand(connexion,"QUIT",verbose,1)
if erreur :
connexion.close()
return erreur
# 结束
connexion.close()
return "Message envoye"
# --------------------------------------------------------------------------
def sendCommand(connexion,commande,verbose,withRCLF):
# 在连接频道中发送命令
# 如果 verbose=1,则启用详细模式
# 如果 withRCLF=1,则将序列 RCLF 添加到命令中
# 数据
RCLF="\r\n" if withRCLF else ""
# 若命令不为空,则发送命令
if commande:
connexion.send("%s%s" % (commande,RCLF))
# 可能的回显
if verbose:
affiche(commande,1)
# 读取少于 1000 个字符的响应
reponse=connexion.recv(1000)
# 可能的回显
if verbose:
affiche(reponse,2)
# 获取错误代码
codeErreur=reponse[0:3]
# 服务器返回错误?
if int(codeErreur) >=500:
return reponse[4:]
# 无错误返回
return ""
# --------------------------------------------------------------------------
def affiche(echange,sens):
# 在屏幕上显示交互内容?
# 若 sens=1 则显示 --> 交换
# 如果 sens=2,则显示 <-- 交换,不包括最后两个字符 RCLF
if sens==1:
print "--> [%s]" % (echange)
return
elif sens==2:
l=len(echange)
print "<-- [%s]" % echange[0:l-2]
return
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
# 如果存在,则删除 [ligne] 中的行尾标记
l=len(ligne)
while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
l-=1
return(ligne[0:l])
# 主 ----------------------------------------------------------------
# 客户端 SMTP(SendMail 传输协议),用于发送消息
# 信息取自一个名为 INFOS 的文件,其中包含以下行
# 第 1 行:smtp、发件人、收件人
# 后续行:消息正文
# 发件人:发件人邮箱
# 收件人:收件人邮箱
# smtp:要使用的 smtp 服务器名称
# 通信协议 SMTP 客户端-服务器
# -> 客户端连接到 SMTP 服务器的 25 号端口
# <- 服务器向其发送欢迎信息
# -> 客户端发送命令 EHLO:其主机名
# <- 服务器返回 OK 或不返回
# -> 客户端发送邮件指令:mail from: <发件人>
# <- 服务器响应 OK 或不响应
# -> 客户端发送 rcpt to: <收件人> 命令
# <- 服务器响应 OK 或不响应
# -> 客户端发送 data 命令
# <- 服务器响应 OK 或不响应
# -> 客户端发送消息中的所有行,并以仅包含单个字符的行结尾。
# <- 服务器响应 OK 或不响应
# -> 客户端发送 quit 命令
# <- 服务器响应 OK 或不响应
# 服务器的响应格式为 xxx 文本,其中 xxx 是三位数。所有 xxx 数值 >=500
# 表示发生错误。响应可能包含多行,除最后一行外均以 xxx- 开头
# 格式为 xxx(空格)
# 交换的文本行必须以字符 RC(#13) 和 LF(#10) 结尾
# # 邮件发送参数
MAIL="mail2.txt"
# 获取邮件参数
res=getInfos(MAIL)
# 错误?
if res[0]:
print "%s" % (erreur)
sys.exit()
# 以详细模式发送邮件
(smtpServer,expediteur,destinataire,message)=res[1:]
print "Envoi du message [%s,%s,%s]" % (smtpServer,expediteur,destinataire)
resultat=sendmail(smtpServer,expediteur,destinataire,message,True)
print "Resultat de l'envoi : %s" % (resultat)
# 结束
sys.exit()
注:
- 在安装了杀毒软件的 Windows 机器上,杀毒软件可能会阻止 Python 脚本连接到 SMTP 服务器的 25 号端口。此时需要禁用杀毒软件。以 McAfee 为例,操作步骤如下:
![]() |
- 在 [1] 上,启用 VirusScan 控制台
- 在 [2] 时,停止 [Protection lors de l'accès] 服务
- 在 [3] 时,服务已停止
文件 infos.txt:
smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
Subject: test
ligne1
ligne2
ligne3
屏幕显示结果:
Thunderbird邮件客户端读取的消息:
![]() |
11.4. 第二个 SMTP 客户端
此第二个脚本与前一个功能相同,但使用了模块 [smtplib] 的功能。
# -*- coding=utf-8 -*-
import sys,socket, smtplib
#-----------------------------------------------------------------------
def getInfos(fichier):
# 返回从文本文件中提取的信息(SMTP、发件人、收件人、邮件内容)[fichier]
# 第 1 行:SMTP、发件人、收件人
# 后续行:邮件正文
# 打开 [fichier]
erreur=""
try:
infos=open(fichier,"r")
except IOError, erreur:
return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))
# 读取第一行
ligne=infos.readline()
# 删除换行符
ligne=cutNewLineChar(ligne)
# 提取 smtp、发件人、收件人字段
champs=ligne.split(",")
# 字段数量是否正确?
if len(champs)!=3 :
return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
# 对提取的信息进行“处理”——去除其前后的“空格”
for i in range(3):
champs[i]=champs[i].strip()
# 字段提取
(smtpServer,expediteur,destinataire)=champs
# 读取待发送的消息
message=""
ligne=infos.readline()
while ligne!='':
message+=ligne
ligne=infos.readline()
infos.close()
# 返回
return ("",smtpServer,expediteur,destinataire,message)
#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
# 代表发件人将消息发送至 SMTP 服务器 smtpserver
# 发送给收件人。如果 verbose=True,则记录客户端与服务器的交互
# 使用 smtplib 库
try:
server = smtplib.SMTP(smtpServer)
if verbose:
server.set_debuglevel(1)
server.sendmail(expediteur, destinataire, message)
server.quit()
except Exception, erreur:
return "Erreur envoi du message : %s" % (erreur)
# 结束
return "Message envoye"
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
# 若存在,则删除 [ligne] 中的行尾标记
l=len(ligne)
while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
l-=1
return(ligne[0:l])
# 主程序 ----------------------------------------------------------------
# SMTP 客户端(SendMail 传输协议),用于发送消息
# 信息取自一个名为 INFOS 的文件,其中包含以下行
# 第 1 行:smtp、发件人、收件人
# 后续行:消息正文
# 发件人:发件人邮箱
# 收件人:收件人邮箱
# smtp:要使用的 smtp 服务器名称
# 通信协议 SMTP 客户端-服务器
# -> 客户端连接到 SMTP 服务器的 25 号端口
# <- 服务器向其发送欢迎信息
# -> 客户端发送命令 EHLO:其主机名
# <- 服务器返回 OK 或不返回
# -> 客户端发送邮件指令:mail from: <发件人>
# <- 服务器响应 OK 或不响应
# -> 客户端发送 rcpt 命令,收件人:<收件人>
# <- 服务器响应 OK 或不响应
# -> 客户端发送 data 命令
# <- 服务器响应 OK 或不响应
# -> 客户端发送消息中的所有行,并以仅包含单个字符的行结尾。
# <- 服务器响应 OK 或不响应
# -> 客户端发送 quit 命令
# <- 服务器响应 OK 或不响应
# 服务器的响应格式为 xxx 文本,其中 xxx 是一个三位数。所有 xxx 数值均 >=500
# 表示发生错误。响应可能包含多行,除最后一行外均以 xxx- 开头
# 格式为 xxx(空格)
# 交换的文本行必须以字符 RC(#13) 和 LF(#10) 结尾
# # 邮件发送参数
MAIL="mail2.txt"
# 获取邮件参数
res=getInfos(MAIL)
# 错误?
if res[0]:
print "%s" % (erreur)
sys.exit()
# 以详细模式发送邮件
(smtpServer,expediteur,destinataire,message)=res[1:]
print "Envoi du message [%s,%s,%s]" % (smtpServer,expediteur,destinataire)
resultat=sendmail(smtpServer,expediteur,destinataire,message,True)
print "Resultat de l'envoi : %s" % (resultat)
# 结束
sys.exit()
注:
- 除sendmail函数外,该脚本与前一个完全相同。该函数现使用[smtplib]模块的功能(第3行)。
文件 mail2.txt:
smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
From: serge.tahe@univ-angers.fr
To: serge.tahe@univ-angers.fr
Subject: test
ligne1
ligne2
ligne3
屏幕显示结果:
11.5. 回显客户端/服务器
创建一个回显服务。服务器将客户端发送的所有文本行以大写形式返回。该服务可通过使用线程同时为多个客户端提供服务。
# -*- coding=utf-8 -*-
# Windows 上的通用 TCP 服务器
# 加载头文件
import re,sys,SocketServer,threading
# 多线程 TCP 服务器
class ThreadedTCPRequestHandler(SocketServer.StreamRequestHandler):
def handle(self):
# 当前线程
cur_thread = threading.currentThread()
# 客户端数据
self.data="on"
# 在空字符串处停止
while self.data:
# 使用 readline 方法读取客户端文本行
self.data =self.rfile.readline().strip()
# 控制台跟踪
print "client %s : %s (%s)" % (self.client_address[0],self.data,cur_thread.getName())
# 向客户端发送响应
response = "%s: %s" % (cur_thread.getName(), self.data.upper())
# self.wfile 是向客户端的写入流
self.wfile.write(response)
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
# ------------------------------------------------------ main
# 调用语法:argv[0] 端口
# 服务器在指定端口上启动
# 数据
syntaxe="syntaxe : %s port" % (sys.argv[0])
#------------------------------------- 验证调用
# 必须且仅有一个参数
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# 端口必须是数字
port=sys.argv[1]
modele=r"^\s*\d+\s*$"
if not re.match(modele,port):
print "Le port %s n'est pas un nombre entier positif\n" % (port)
sys.exit(2)
# 启动服务器 - 每个客户端由一个线程处理
host="localhost"
server = ThreadedTCPServer((host,int(port)), ThreadedTCPRequestHandler)
# 服务器在单个线程中启动
# 每个客户端将在一个额外的线程中得到服务
server_thread = threading.Thread(target=server.serve_forever)
# 启动服务器 - 无限循环等待客户端
server_thread.start()
# 后续
print "Serveur d'echo a l'ecoute sur le port % s" % (port)
注:
- 第 39 行:sys.argv 代表脚本参数。此处的参数格式应为:nom_du_script 端口号。因此必须有两个参数。 sys.argv[0] 将变为 nom_du_script,而 sys.argv[1] 将变为 port ;
- 第 53 行:回显服务器是 ThreadedTcpServer 类的实例。该类的构造函数需要两个参数:
- 参数 1:一个包含两个元素 (host,port) 的元组,用于确定服务器的机器和监听端口;
- 参数 2:负责处理客户端请求的类名。
- 第 57 行:创建了一个线程(但尚未启动)。该线程将执行 TCP 服务器中的 [serve_forever] 方法。该方法是一个监听客户端连接的循环。 一旦检测到客户端连接,将创建 ThreadedTCPRequestHandler 类的实例。其 handle 方法负责与客户端进行交互;
- 第 59 行:启动回显服务线程。从此时起,客户端即可连接到该服务;
- 第 27 行:回显服务器的类。它继承自两个类:SocketServer.ThreadingMixIn 和 SocketServer.TCPServer。 这使其成为一个多线程的 TCP 服务器:服务器在主线程中运行,每个客户端请求则在单独的子线程中处理;
- 第9行:处理客户端请求的类。它继承自SocketServer.StreamRequestHandler类,因此继承了两个属性:
- rfile:这是客户端发送数据的读取流——可视为文本文件;
- wfile:用于向客户端发送数据的写入流——可作为文本文件处理。
- 第 11 行:方法 handle 处理客户端的请求;
- 第 13 行:执行此方法的线程 handle;
- 第 17 行:处理客户端请求的循环。当客户端发送空行时,循环结束;
- 第 19 行:读取客户端请求;
- 第 21 行:self.client_address[0] 表示客户端的地址 IP。cur_thread.getName() 是执行 handle 方法的线程名称;
- 第 23 行:对客户端的响应包含两个部分——服务该客户端的线程名称,以及客户端发送的命令(以大写形式传递)。
# -*- coding=utf-8 -*-
import re,sys,socket
# ------------------------------------------------------ main
# 通用 TCP 客户端
# 调用语法:argv[0] 主机 端口
# 客户端连接到回显服务(主机,端口)
# 服务器返回客户端输入的行
# 语法
syntaxe="syntaxe : %s hote port" % (sys.argv[0])
#------------------------------------- 验证调用
# 必须有两个参数
nbArguments=len(sys.argv)
if nbArguments!=3:
print syntaxe
sys.exit(1)
# 获取参数
hote=sys.argv[1]
# 端口必须是数字
port=sys.argv[2]
modele=r"^\s*\d+\s*$"
if not re.match(modele,port):
print "Le port %s foit être un nombre entier positif" % (port)
sys.exit(2)
try:
# 客户端与服务器的连接
connexion=socket.create_connection((hote,int(port)))
except socket.error, erreur:
print "Echec de la connexion au site (%s,%s) : %s" % (hote,port,erreur)
sys.exit(3)
try:
# 输入循环
ligne=raw_input("Commande (rien pour arreter): ").strip()
while ligne!="":
# 将行发送至服务器
connexion.send("%s\n" % (ligne))
# 等待响应
reponse=connexion.recv(1000)
print "<-- %s" % (reponse)
ligne=raw_input("Commande (rien pour arreter): ")
except socket.error, erreur:
print "Echec de la connexion au site (%s,%s) : %s" % (hote,port,erreur)
sys.exit(3)
finally:
# 关闭连接
connexion.close()
服务器在第一个命令窗口中启动:
第一个客户端在第二个窗口中启动:
客户端成功收到了服务器对其发送的命令的响应,该响应正是该命令的全大写形式。第二个客户端在第三个窗口中启动:
此时服务器的控制台显示如下:
客户端确实在不同的线程中运行。要停止一个客户端,只需输入一个空命令即可。
11.6. 通用 TCP 服务器
我们计划编写一个 Python 脚本,该脚本
- 它将是一个TCP服务器,能够同时服务一个客户端,
- 能够接收客户端发送的文本行,
- 并接受来自键盘的文本行,将其作为响应发送给客户端。
因此,键盘前的用户充当了服务器:
- 他在控制台上看到客户端发送的文本行;
- 并通过键盘输入回复来回应客户端。
因此,它能够适应任何类型的客户端。正因如此,我们将它称为“通用TCP服务器”。这是一个用于探索TCP通信协议的实用工具。在下面的示例中,TCP客户端将是一个网页浏览器,这将使我们能够探索Web客户端使用的HTTP协议。
![]() |
# -*- coding=utf-8 -*-
# 通用 TCP 服务器
# 加载头文件
import re,sys,SocketServer,threading
# 通用 TCP 服务器
class MyTCPHandler(SocketServer.StreamRequestHandler):
def handle(self):
# 显示客户端
print "client %s" % (self.client_address[0])
# 创建读取客户端命令的线程
thread_lecture = threading.Thread(target=self.lecture)
thread_lecture.start()
# 收到“bye”命令后停止
# 读取键盘输入的命令
cmde=raw_input("--> ")
while cmde!="bye":
# 向客户端发送命令。self.wfile 是向客户端的写入流
self.wfile.write("%s\n" % (cmde))
# 读取下一条命令
cmde=raw_input("--> ")
def lecture(self):
# 显示客户端发送的所有行,直到收到 bye 命令
ligne=""
while ligne!="bye":
# 使用 readline 方法读取客户端的文本行
ligne = self.rfile.readline().strip()
# 控制台跟踪
print "<--- %s : %s" % (self.client_address[0], ligne)
# ------------------------------------------------------ main
# 调用语法:argv[0] 端口
# 服务器在指定端口上启动
# 数据
syntaxe="syntaxe : %s port" % (sys.argv[0])
#------------------------------------- 验证调用
# 必须且仅有一个参数
nbArguments=len(sys.argv)
if nbArguments!=2:
print syntaxe
sys.exit(1)
# 端口必须是数字
port=sys.argv[1]
modele=r"^\s*\d+\s*$"
if not re.match(modele,port):
print "Le port %s n'est pas un nombre entier positif\n" % (port)
sys.exit(2)
# 启动服务器
host="localhost"
server = SocketServer.TCPServer((host, int(port)), MyTCPHandler)
print "Service tcp generique lance sur le port %s. Arret par Ctrl-C" % (port)
server.serve_forever()
注:
- 第 57 行:TCP 服务器将是一个 SocketServer.TCPServer 类的实例。该类的构造函数接受两个参数:
- 第一个参数是一个包含两个元素的元组(主机名,端口号),其中 host 是运行该服务的机器(通常为 localhost),而 port 是服务等待(监听)客户端请求的端口;
- 第二个参数指定客户端的服务类。当客户端连接时,会创建该服务类的实例,并由其方法 handle 负责管理与客户端的连接。
TCP 服务器 SocketServer.TCPServer 并非多线程的。它每次只服务一个客户端;
- 第 59 行:执行 TCP 服务器的 serve_forever 方法。这是一个无限循环,用于等待客户端;
- 第 9 行:此处的 TCP 服务器是 SocketServer.StreamRequestHandler 类的派生类。这使得与客户端交换的数据流可以被视为文本文件。我们之前已经遇到过这个类。我们拥有以下方法:
- readline 用于读取来自客户端的一行文本;
- write 用于向客户端发送文本行。
- 第 12 行:self.client_address[0] 是客户端的 IP 地址;
- 第 14 行:TCP 服务器将通过两个线程与客户端进行通信
- 一个用于读取客户端行数据的线程;
- 一个向客户端写入行数据的线程。
- 第 14 行:创建用于读取客户端行数据的线程。其参数 target 确定了该线程执行的方法,即第 25 行定义的方法;
- 第 25 行:启动读取线程;
- 第25-32行:读取线程执行的方法;
- 第28行:读取线程读取客户端发送的所有文本行,直到接收到“bye”这一行;
- 第 18 行:此处处于向客户端写入的线程中。其原理是将用户通过键盘输入的所有文本行发送给客户端。
服务器
客户端浏览器
服务器接收到的请求
用户通过键盘输入的服务器响应(不含 --> 符号)
- 第1-5行:发送给客户端的HTTP响应;
- 第 6 行:发送给客户端的 HTML 页面;
- 第7行:与客户端的对话结束。客户服务将终止,连接将被关闭,这将突然中断读取客户端发送的文本行的线程;
- 发给客户端的HTTP响应的第1-5行含义如下:
- 第1行:已找到客户端请求的资源;
- 第2行:服务器标识;
- 第3行:服务器将在发送资源后关闭连接;
- 第 4 行:服务器发送的资源类型:HTML 文档;
- 第5行:空行。
[1] 浏览器显示的页面:
![]() |
如果查看 [2] 浏览器接收到的源代码,会发现该网页浏览器接收到的代码 HTML 确实就是我们发送给它的内容。




