Skip to content

11. Python 网络函数

接下来我们将讨论 Python 的网络函数,这些函数使我们能够进行 TCP/IP(传输控制协议/互联网协议)编程。

  

11.1. 获取互联网上某台机器的名称或 IP 地址


程序 (inet_01)

import sys, socket

#------------------------------------------------
def getIPandName(nomMachine):
    # nomMachine: name of the machine whose address is required IP: name of the machine whose address is required IP: name of the machine whose address is required
    #    nomMachine-->adresse IP
    try:
        ip=socket.gethostbyname(nomMachine)
        print "ip[%s]=%s" % (nomMachine,ip)
    except socket.error, erreur:
        print "ip[%s]=%s" % (nomMachine,erreur)
        return

    #    address 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


#    ---------------------------------------- main

#    constant
HOTES=["istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","localhost","","xx"]

#    IP addresses of HOTES machines
for i in range(len(HOTES)):
    getIPandName(HOTES[i])
#    end
sys.exit()

注:

  • 第 1 行:Python 的网络函数封装在 socket 模块中。

结果

1
2
3
4
5
6
7
8
9
ip[istia.univ-angers.fr]=193.49.146.171
name[193.49.146.171]=istia.istia.univ-angers.fr
ip[www.univ-angers.fr]=193.49.144.40
name[193.49.144.40]=ametys-fo.univ-angers.fr
ip[www.ibm.com]=129.42.58.216
name[129.42.58.216]=[Errno 11004] host not found
ip[localhost]=127.0.0.1
name[127.0.0.1]=Gportpers3.ad.univ-angers.fr
ip[xx]=[Errno 11004] getaddrinfo failed

11.2. 一个 Web 客户端

一个用于获取网站首页内容的脚本。


程序 (inet_02)

import sys,socket

  #-----------------------------------------------------------------------
def getIndex(site):
    #  reads the URL site/ and stores it in the site.html file

    #  initially no error
    erreur=""
    #  creation of the site.html file
    try:
        html=open("%s.html" % (site),"w")
    except IOError, erreur:
        pass
    #    mistake?
    if erreur:
        return "Erreur (%s) lors de la création du fichier %s.html" % (erreur,site)

    #    open a connection on site port 80
    try:
        connexion=socket.create_connection((site,80))
    except socket.error, erreur:
        pass
    #  return if error
    if erreur:
        return "Echec de la connexion au site (%s,80) : %s" % (site,erreur)

    #    connection represents a bidirectional communication flow
    #  between the client (this program) and the contacted web server
    #  this channel is used for the exchange of orders and information
    #  the dialog protocol is HTTP

    #  the customer sends the get command to request URL /
    #    syntax get URL HTTP/1.0
    #  protocol HTTP headers must end with an empty line
    connexion.send("GET / HTTP/1.0\n\n")

    #  the server will now respond on the connection channel. It will send all
    #  then close the channel. The customer reads everything that comes in from the connection
    #  until the channel closes
    ligne=connexion.recv(1000)
    while(ligne):
        html.write(ligne)
        ligne=connexion.recv(1000)

    #  the customer in turn closes the connection
    connexion.close()
    #    close html file
    html.close()
    #    return
    return "Transfert reussi du paragraphe index du site %s" % (site)

#    --------------------------------------------- main    

#  get the text HTML from URL

#    list of websites
SITES=("istia.univ-angers.fr","www.univ-angers.fr","www.ibm.com","xx")

#  reading the index pages of the sites in the SITES table
for i in range(len(SITES)):
      #    read site index page SITES[i]
      resultat=getIndex(SITES[i])
      #    result display
      print resultat

#    end
sys.exit()

注:

  • 第 57 行:包含我们要获取其首页的网站 URL 列表。该列表存储在文本文件 [sitename.html] 中;
  • 第 62 行:getIndex 函数负责执行相关操作;
  • 第 4 行:getIndex 函数
  • 第 20 行:create_connection((site,port)) 方法允许您与运行在目标机器 port 端口上的 TCP/IP 服务建立连接
  • 第 35 行:send 方法允许通过 TCP/IP 连接发送数据。此处发送的是文本,该文本遵循 HTTP(超文本传输协议)协议;
  • 第 40 行:recv 方法用于通过 TCP/IP 连接接收数据。此处,Web 服务器的响应以 1,000 个字符为单位分块读取,并保存到文本文件 [sitename.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] 接收到的文件:

HTTP/1.1 302 Found
Date: Wed, 08 Jun 2011 15:43:56 GMT
Server: IBM_HTTP_Server
Content-Type: text/html
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Pragma: no-cache
Cache-Control: no-cache, must-revalidate
Location: http://www.ibm.com/us/en/
Content-Length: 209
Kp-eeAlive: timeout=10, max=14
Connection: Keep-Alive

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="http://www.ibm.com/us/en/">here</a>.</p>
</body></html>
  • 第 1–11 行是服务器响应的 HTTP 头;
  • 第 1 行:服务器指示客户端重定向至第 8 行指定的 URL;
  • 第2行:响应的日期和时间;
  • 第 3 行:Web 服务器标识;
  • 第 4 行:服务器发送的内容。此处为从第 13 行开始的 HTML 页面;
  • 第 12 行:结束 HTTP 头信息的空行;
  • 第13–19行:Web服务器发送的HTML页面。

11.3. SMTP 客户端

在TCP/IP协议中,SMTP(简单邮件传输协议)是用于邮件投递服务的通信协议。


程序 (inet_03)

#    -*- coding=utf-8 -*-

import sys,socket

#-----------------------------------------------------------------------
def getInfos(fichier):
    #  returns the information (smtp,sender,recipient,message) taken from the text file [file]
    #  line 1: smtp, sender, recipient
    #  next lines: message text

    #    open [file]
    erreur=""
    try:
        infos=open(fichier,"r")
    except IOError, erreur:
        return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))

    #  read the 1st line
    ligne=infos.readline()
    #  delete end-of-line mark
    ligne=cutNewLineChar(ligne)
    #  retrieving smtp, sender and recipient fields
    champs=ligne.split(",")
    #  do we have the right number of fields?
    if len(champs)!=3 :
        return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
    #  "processing" the recovered information
    #  we remove from each of the 3 fields the "blanks" that precede or follow the useful information
    for i in range(3):
        champs[i]=champs[i].strip()
    #  field recovery
    (smtpServer,expediteur,destinataire)=champs
    message=""
    #    read rest of message
    ligne=infos.readline()
    while ligne!='':
        message+=ligne
        ligne=infos.readline()
    infos.close()
    #    return
    return ("",smtpServer,expediteur,destinataire,message)

#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
    #  sends message to smtp server smtpserver from sender
    #    as recipient. If verbose=True, tracks client-server exchanges

    #  retrieve the customer's name
    try:
        client=socket.gethostbyaddr(socket.gethostbyname("localhost"))[0]
    except socket.error, erreur:
        return "Erreur IP / Nom du client : %s" % (erreur)

    #    open a connection on port 25 of smtpServer
    try:
        connexion=socket.create_connection((smtpServer,25))
    except socket.error, erreur:
        return "Echec de la connexion au site (%s,25) : %s" % (smtpServer,erreur)

    #    connection represents a bidirectional communication flow
    #  between the client (this program) and the smtp server contacted
    #  this channel is used for the exchange of orders and information

    #  after connection, the server sends a welcome message which is read as follows
    erreur=sendCommand(connexion,"",verbose,1)
    if(erreur) :
        connexion.close()
        return erreur
    #    cmde ehlo:
    erreur=sendCommand(connexion,"EHLO %s" % (client),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    # cmde mail from:
    erreur=sendCommand(connexion,"MAIL FROM: <%s>" % (expediteur),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    #    cmde rcpt to:
    erreur=sendCommand(connexion,"RCPT TO: <%s>" % (destinataire),verbose,1)
    if erreur :
        connexion.close()
        return erreur
    #    cmde data
    erreur=sendCommand(connexion,"DATA",verbose,1)
    if erreur :
        connexion.close()
        return erreur
    #    prepare message to send
    #  it must contain the lines
    # From: expéditeur
    #    To: recipient
    #  empty line
    #    Message
    # .
    data="From: %s\r\nTo: %s\r\n%s\r\n.\r\n" % (expediteur,destinataire,message)
    #    send message
    erreur=sendCommand(connexion,data,verbose,0)
    if erreur :
        connexion.close()
        return erreur
    #    cmde quit
    erreur=sendCommand(connexion,"QUIT",verbose,1)
    if erreur :
        connexion.close()
        return erreur
    #    end
    connexion.close()
    return "Message envoye"

# --------------------------------------------------------------------------
def sendCommand(connexion,commande,verbose,withRCLF):
    #  sends command to connection channel
    #    verbose mode if verbose=1
    #  if withRCLF=1, adds sequence RCLF to command

    #    data
    RCLF="\r\n" if withRCLF else ""
    #  send cmde if order not empty
    if commande:
        connexion.send("%s%s" % (commande,RCLF))
        #    possible echo
        if verbose:
            affiche(commande,1)
        #    read response of less than 1000 characters
        reponse=connexion.recv(1000)
        #    possible echo
        if verbose:
            affiche(reponse,2)
        #  error code recovery
        codeErreur=reponse[0:3]
        #  error returned by the server?
        if int(codeErreur) >=500:
            return reponse[4:]
        #  error-free return
        return ""

# --------------------------------------------------------------------------
def affiche(echange,sens):
    #    displays exchange ? screen
    #    if sens=1 displays -->change
    #    if sens=2 displays <-- exchange without last 2 characters RCLF
    if sens==1:
        print "--> [%s]" % (echange)
        return
    elif sens==2:
        l=len(echange)
        print "<-- [%s]" % echange[0:l-2]
        return


# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
    #  delete the [line] end-of-line mark if it exists
    l=len(ligne)
    while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
        l-=1
    return(ligne[0:l])

#    main ----------------------------------------------------------------

#  client SMTP (SendMail Transfer Protocol) for sending a message
#  information is taken from a INFOS file containing the following lines
#  line 1: smtp, sender, recipient
#  next lines: message text

#    sender:email sender
#    recipient: email recipient
#  smtp: name of smtp server to use


#    communication protocol SMTP client-server
#    -> client connects to smtp server port 25
#    <- server sends him a welcome message
#  -> customer sends command EHLO: machine name
#  <- server responds OK or not
#  -> customer sends mail from: <exp?diteur> command
#  <- server responds OK or not
#  -> client sends the rcpt to command: <recipient>
#  <- server responds OK or not
#    -> customer sends data order
#  <- server responds OK or not
#  -> client sends all the lines of its message and ends with a line containing the single character .
#  <- server responds OK or not
#    -> customer sends quit order
#  <- server responds OK or not

#  server responses have the form xxx text where xxx is a 3-digit number. Any number xxx >=500
#  indicates an error. The answer may consist of several lines all beginning with xxx- except the last one
#  of the form xxx(space)

#  exchanged text lines must end with RC(#13) and LF(#10) characters

#    # Mailing parameters
MAIL="mail2.txt" 
#    retrieve mail parameters
res=getInfos(MAIL)
#    mistake?
if res[0]:
      print "%s" % (erreur)
      sys.exit()
#  sending mail in verbose mode
(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)
#    end
sys.exit()

  • 在安装了杀毒软件的 Windows 计算机上,杀毒软件可能会阻止 Python 脚本连接到 SMTP 服务器的 25 号端口。因此,您必须禁用杀毒软件。以 McAfee 为例,您可以执行以下操作:
  • 在 [1] 中,打开 VirusScan 控制台
  • 在 [2] 中,停止 [On-Access Protection] 服务
  • 在 [3] 中,该服务已停止

结果

infos.txt 文件:

smtp.univ-angers.fr, serge.tahe@univ-angers.fr , serge.tahe@univ-angers.fr
Subject: test

ligne1
ligne2

ligne3

屏幕结果:

Envoi du message [smtp.univ-angers.fr,serge.tahe@univ-angers.fr,serge.tahe@univ-angers.fr]
--> [EHLO Gportpers3.ad.univ-angers.fr]
<-- [220 smtp.univ-angers.fr ESMTP Postfix
250-smtp.univ-angers.fr
250-PIPELINING
250-SIZE 20480000
250-VRFY
250-ETRN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN]
--> [MAIL FROM: <serge.tahe@univ-angers.fr>]
<-- [250 2.1.0 Ok]
--> [RCPT TO: <serge.tahe@univ-angers.fr>]
<-- [250 2.1.5 Ok]
--> [DATA]
<-- [354 End data with <CR><LF>.<CR><LF>]
--> [From: serge.tahe@univ-angers.fr
To: serge.tahe@univ-angers.fr
Subject: test

ligne1
ligne2

ligne3

.
]
<-- [250 2.0.0 Ok: queued as 63412114203]
--> [QUIT]
<-- [221 2.0.0 Bye]
Resultat de l'envoi : Message envoye

Thunderbird 邮件客户端显示的消息:

  

11.4. 第二个 SMTP 客户端

这个第二个脚本与前一个脚本功能相同,但使用了 [smtplib] 模块的功能。


程序 (inet_04)

#    -*- coding=utf-8 -*-

import sys,socket, smtplib

#-----------------------------------------------------------------------
def getInfos(fichier):
    #  returns the information (smtp,sender,recipient,message) taken from the text file [file]
    #  line 1: smtp, sender, recipient
    #  next lines: message text

    #    open [file]
    erreur=""
    try:
        infos=open(fichier,"r")
    except IOError, erreur:
        return ("Le fichier %s n'a pu etre ouvert en lecture : %s" % (fichier, erreur))

    #  read the 1st line
    ligne=infos.readline()
    #  delete end-of-line mark
    ligne=cutNewLineChar(ligne)
    #  retrieve smtp, sender, recipient fields
    champs=ligne.split(",")
    #  do we have the right number of fields?
    if len(champs)!=3 :
        return ("La ligne 1 du fichier %s (serveur smtp, expediteur, destinataire) a un nombre de champs incorrect" % (fichier))
    #  "processing" the information recovered - removing the "blanks" that precede or follow them
    for i in range(3):
        champs[i]=champs[i].strip()
    #  field recovery
    (smtpServer,expediteur,destinataire)=champs
    #    read message to send
    message=""
    ligne=infos.readline()
    while ligne!='':
        message+=ligne
        ligne=infos.readline()
    infos.close()
    #    return
    return ("",smtpServer,expediteur,destinataire,message)

#-----------------------------------------------------------------------
def sendmail(smtpServer,expediteur,destinataire,message,verbose):
    #  sends message to smtp server smtpserver from sender
    #    as recipient. If verbose=True, tracks client-server exchanges

    #  we use the smtplib library 
    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)
    #    end
    return "Message envoye"

# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
    #  delete the [line] end-of-line mark if it exists
    l=len(ligne)
    while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
        l-=1
    return(ligne[0:l])

#    main ----------------------------------------------------------------

#  client SMTP (SendMail Transfer Protocol) for sending a message
#  information is taken from a INFOS file containing the following lines
#  line 1: smtp, sender, recipient
#  next lines: message text

#    sender:email sender
#    recipient: email recipient
#  smtp: name of smtp server to use


#    communication protocol SMTP client-server
#    -> client connects to smtp server port 25
#    <- server sends him a welcome message
#  -> customer sends command EHLO: machine name
#  <- server responds OK or not
#  -> customer sends mail order from: <sender>
#  <- server responds OK or not
#  -> client sends the rcpt to command: <recipient>
#  <- server responds OK or not
#    -> customer sends data order
#  <- server responds OK or not
#  -> client sends all the lines of its message and ends with a line containing the single character .
#  <- server responds OK or not
#    -> customer sends quit order
#  <- server responds OK or not

#  server responses have the form xxx text where xxx is a 3-digit number. Any number xxx >=500
#  indicates an error. The answer may consist of several lines all beginning with xxx- except the last one
#  of the form xxx(space)

#  exchanged text lines must end with RC(#13) and LF(#10) characters

#    # Mailing parameters
MAIL="mail2.txt" 
#    retrieve mail parameters
res=getInfos(MAIL)
#    mistake?
if res[0]:
      print "%s" % (erreur)
      sys.exit()
#  sending mail in verbose mode
(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)
#    end
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

屏幕结果:

Envoi du message [smtp.univ-angers.fr,serge.tahe@univ-angers.fr,serge.tahe@univ-
angers.fr]
send: 'ehlo Gportpers3.ad.univ-angers.fr\r\n'
reply: '250-smtp.univ-angers.fr\r\n'
reply: '250-PIPELINING\r\n'
reply: '250-SIZE 20480000\r\n'
reply: '250-VRFY\r\n'
reply: '250-ETRN\r\n'
reply: '250-ENHANCEDSTATUSCODES\r\n'
reply: '250-8BITMIME\r\n'
reply: '250 DSN\r\n'
reply: retcode (250); Msg: smtp.univ-angers.fr
PIPELINING
SIZE 20480000
VRFY
ETRN
ENHANCEDSTATUSCODES
8BITMIME
DSN
send: 'mail FROM:<serge.tahe@univ-angers.fr> size=99\r\n'
reply: '250 2.1.0 Ok\r\n'
reply: retcode (250); Msg: 2.1.0 Ok
send: 'rcpt TO:<serge.tahe@univ-angers.fr>\r\n'
reply: '250 2.1.5 Ok\r\n'
reply: retcode (250); Msg: 2.1.5 Ok
send: 'data\r\n'
reply: '354 End data with <CR><LF>.<CR><LF>\r\n'
reply: retcode (354); Msg: End data with <CR><LF>.<CR><LF>
data: (354, 'End data with <CR><LF>.<CR><LF>')
send: 'From: serge.tahe@univ-angers.fr\r\nTo: serge.tahe@univ-angers.fr\r\nSubje
ct: test\r\n\r\nligne1\r\nligne2\r\n\r\nligne3\r\n.\r\n'
reply: '250 2.0.0 Ok: queued as D4977114358\r\n'
reply: retcode (250); Msg: 2.0.0 Ok: queued as D4977114358
data: (250, '2.0.0 Ok: queued as D4977114358')
send: 'quit\r\n'
reply: '221 2.0.0 Bye\r\n'
reply: retcode (221); Msg: 2.0.0 Bye
Resultat de l'envoi : Message envoye

11.5. 客户端/服务器回显

我们创建了一个回显服务。服务器将客户端发送的所有文本行以大写形式返回。该服务可通过线程同时为多个客户端提供服务。


服务器程序 (inet_08)

#    -*- coding=utf-8 -*-

#    generic tcp server on Windows

#    loading header files
import re,sys,SocketServer,threading

#    multi-threaded tcp server
class ThreadedTCPRequestHandler(SocketServer.StreamRequestHandler):

  def handle(self):
    #    current thread
    cur_thread = threading.currentThread()
    #    customer data
    self.data="on"
    #    stop on empty chain
    while self.data:
      #  the client's text lines are read using the readline method
      self.data =self.rfile.readline().strip()
      #    console monitoring
      print "client %s : %s (%s)" % (self.client_address[0],self.data,cur_thread.getName())
      #    reply to customer
      response = "%s: %s" % (cur_thread.getName(), self.data.upper())
      #  self.wfile is the write stream to client
      self.wfile.write(response)                    

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

#    ------------------------------------------------------ main        
#    call syntax: argv[0] port
#  the server is launched on the port named

#    Data
syntaxe="syntaxe : %s port" % (sys.argv[0])

# ------------------------------------- check the call
#  there must be one argument and one argument alone
nbArguments=len(sys.argv)
if nbArguments!=2:
    print syntaxe
    sys.exit(1)

#  the port must be digital
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)

#    server launch - serves each client on a thread
host="localhost"
server = ThreadedTCPServer((host,int(port)), ThreadedTCPRequestHandler)

#  the server is launched in a thread
#  each customer will be served in an additional thread
server_thread = threading.Thread(target=server.serve_forever)
#  starts the server - infinite waiting loop for clients
server_thread.start()
#    follow-up
print "Serveur d'echo a l'ecoute sur le port % s" % (port)

注:

  • 第 39 行:sys.argv 表示脚本的参数。此处,参数必须采用 script_name port 的形式。因此必须有两个参数。此时,sys.argv[0] 即为 script_name,sys.argv[1] 即为 port
  • 第 53 行:echo 服务器是 ThreadedTcpServer 类的实例。该类的构造函数需要两个参数:
    • 参数 1:一个包含两个元素的元组 (主机, 端口),用于指定机器和服务器监听端口;
    • 参数 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 行:对客户端的响应包含两个部分——服务该客户端的线程名称,以及客户端发送的命令(已转换为大写)。

客户端程序 (inet_06)

#    -*- coding=utf-8 -*-
import re,sys,socket

#    ------------------------------------------------------ main        
#    generic tcp client
#    call syntax: argv[0] host port
#    client connects to echo service (host,port)
#  the server returns the lines typed by the client

#    syntax
syntaxe="syntaxe : %s hote port" % (sys.argv[0])

# ------------------------------------- check the call
#  there must be two arguments
nbArguments=len(sys.argv)
if nbArguments!=3:
    print syntaxe
    sys.exit(1)

#  we recover the arguments
hote=sys.argv[1]
#  the port must be digital
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:
  #    client connection to server
  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:
    #    input loop
    ligne=raw_input("Commande (rien pour arreter): ").strip()
    while ligne!="":
        #  send the line to the server
        connexion.send("%s\n" % (ligne))
        #  we're waiting for the answer
        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:
    #  close the connection
    connexion.close()

结果

服务器在命令窗口中启动:

cmd>%python% inet_08.py 100
Serveur d'echo a l'ecoute sur le port 100

在第二个窗口中启动第一个客户端:

1
2
3
4
5
6
7
cmd>%python% inet_06.py localhost 100
Commande (rien pour arreter): cmde 1 du client 1
[cmde 1 du client 1]
<-- Thread-2: CMDE 1 DU CLIENT 1
Commande (rien pour arreter): cmde 2 du client 1
<-- Thread-2: CMDE 2 DU CLIENT 1
Commande (rien pour arreter):

客户端向服务器发送命令后,收到服务器返回的相同命令(大写形式)。在第三个窗口中启动第二个客户端:

1
2
3
4
5
6
cmd>%python% inet_06.py localhost 100
Commande (rien pour arreter): cmde 1 du client 2
<-- Thread-3: CMDE 1 DU CLIENT 2
Commande (rien pour arreter): cmde 2 du client 2
<-- Thread-3: CMDE 2 DU CLIENT 2
Commande (rien pour arreter):

此时服务器控制台显示如下:

1
2
3
4
5
6
cmd>%python% inet_08.py 100
Serveur d'echo a l'ecoute sur le port 100
client 127.0.0.1 : cmde 1 du client 1 (Thread-2)
client 127.0.0.1 : cmde 2 du client 1 (Thread-2)
client 127.0.0.1 : cmde 1 du client 2 (Thread-3)
client 127.0.0.1 : cmde 2 du client 2 (Thread-3)

客户端在不同的线程中得到正确处理。要停止一个客户端,只需输入一个空命令。

11.6. 通用 TCP 服务器

我们建议编写一个 Python 脚本,

  • 将作为 TCP 服务器运行,能够同时服务一个客户端,
  • 接收客户端发送的文本行,
  • 并能接收来自键盘的文本行并将它们发送回客户端。

因此,键盘前的用户充当服务器:

  • 他们在控制台上看到客户端发送的文本行;
  • 并通过在键盘上输入回复来响应客户端。

因此,它能够适应任何类型的客户端。这就是为什么我们将它称为“通用 TCP 服务器”。这是一个用于探索 TCP 通信协议的实用工具。在下面的示例中,TCP 客户端将是一个 Web 浏览器,这将使我们能够探索 Web 客户端使用的 HTTP 协议。


程序(inet_10)

#    -*- coding=utf-8 -*-

#    generic tcp server

#    loading header files
import re,sys,SocketServer,threading

#    generic tcp server
class MyTCPHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        #  the customer is displayed
        print "client %s" % (self.client_address[0])
        #  create a thread for reading customer orders
        thread_lecture = threading.Thread(target=self.lecture)
        thread_lecture.start()
        #    stop on cmde 'bye
        #    cmde reading typed on keyboard
        cmde=raw_input("--> ")
        while cmde!="bye":
            #  send cmde to customer. self.wfile is the write flow to the customer
            self.wfile.write("%s\n" % (cmde))
            #    next cmde reading
            cmde=raw_input("--> ")

    def lecture(self):
        #  displays all lines sent by the client until the bye command is received
        ligne=""
        while ligne!="bye":
            #  the client's text lines are read using the readline method
            ligne = self.rfile.readline().strip()
            #    console monitoring
            print "<--- %s : %s" % (self.client_address[0], ligne)

#    ------------------------------------------------------ main        
#    call syntax: argv[0] port
#  the server is launched on the port named

#    Data
syntaxe="syntaxe : %s port" % (sys.argv[0])

# ------------------------------------- check the call
#  there must be one argument and one argument alone
nbArguments=len(sys.argv)
if nbArguments!=2:
    print syntaxe
    sys.exit(1)

#  the port must be digital
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)

#    server launch
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 类的实例。其构造函数接受两个参数:
    • 第一个参数是一个包含两个元素的元组 (主机, 端口),其中“主机”是服务运行的机器(通常为 localhost),而“端口”是服务用于等待(监听)客户端请求的端口;
    • 第二个参数指定客户端的服务类。当客户端连接时,会创建该服务类的实例,其 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 行:现在我们处于向客户端写入数据的线程中。其目的是将用户在键盘上输入的所有文本行发送给客户端。

结果

服务器

dos>%python% inet_10.py 100
Service tcp generique lance sur le port 100. Arret par Ctrl-C

客户端浏览器

 

服务器接收到的请求

dos>%python% inet_10.py 100
Service tcp generique lance sur le port 100. Arret par Ctrl-C
client 127.0.0.1
<--- 127.0.0.1 : GET / HTTP/1.1
<--> --- 127.0.0.1 : Host: localhost:100
<--- 127.0.0.1 : User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10 GTB7.1 ( .NET CLR 3.5.30729)
<--- 127.0.0.1 : Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
<--- 127.0.0.1 : Accept-Language: fr,fr-fr;q=0.8,en;q=0.5,en-us;q=0.3
<--- 127.0.0.1 : Accept-Encoding: gzip,deflate
<--- 127.0.0.1 : Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
<--- 127.0.0.1 : Keep-Alive: 115
<--- 127.0.0.1 : Connection: keep-alive
<--- 127.0.0.1 : Cache-Control: max-age=0
<--- 127.0.0.1 :

用户通过键盘输入的服务器响应(不含 --> 符号)

1
2
3
4
5
6
7
HTTP/1.1 200 OK
--> Server: serveur tcp generique
--> Connection: close
--> Content-Type: text/html
-->
--> <html><body><h2>Serveur tcp generique</h2></body></html>
--> bye
  • 第1-5行:发送给客户端的HTTP响应;
  • 第6行:向客户端发送HTML页面;
  • 第7行:与客户端的对话结束。客户端服务将终止,连接将被关闭,这将突然中断正在读取客户端发送的文本行的线程;
  • 发送给客户端的HTTP响应的第1–5行具有以下含义:
  • 第1行:找到了客户端请求的资源;
  • 第2行:服务器标识;
  • 第 3 行:服务器将在发送资源后关闭连接;
  • 第4行:服务器发送的资源类型:HTML文档;
  • 第 5 行:空行。

浏览器显示的页面 [1]:

如果查看浏览器接收到的源代码[2],我们会发现网页浏览器接收到的HTML代码确实就是我们发送给它的那个。