SocketServer-实现并发处理2

转发自MnCu的博客:

http://www.cnblogs.com/MnCu8261/p/5546823.html

python基础之socket与socketserver

---引入

Socket的英文原义是“孔”或“插座”,在Unix的进程通信机制中又称为‘套接字’。套接字实际上并不复杂,它是由一个ip地址以及一个端口号组成。Socket正如其英文原意那样,像一个多孔插座。一台主机犹如布满各种插座(ip地址)的房间,每个插座有很多插口(端口),通过这些插口接入电线(进程)我们可以烧水,看电视,玩电脑……  

应用程序通常通过"套接字"向网络发出请求或者应答网络请求。

套接字的作用之一就是用来区分不同应用进程,当某个进程绑定了本机ip的某个端口,那么所有传送至这个ip地址上的这个端口的所有数据都会被内核送至该进程进行处理。

---python中的socket

Python 提供了两个基本的 socket 模块。

   第一个是 Socket,它提供了标准的 BSD Sockets API。

   第二个是 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发。

----socket

   先来说第一个。

我们知道,现在的应用程序大多为C/S架构,也就是分为客户端/服务器端。

  服务器端:服务器端进程需要申请套接字,然后自己绑定在这个套接字上,并对这个套接字进行监听。当有客户端发送数据了,则接受数据进行处理,处理完成后对客户端进行响应。

 

 

python编写服务器端的步骤:

  1  创建套接字

import socket
s1=socket.socket(family,type)
#family参数代表地址家族,可为AF_INET或AF_UNIX。AF_INET家族包括Internet地址,AF_UNIX家族用于同一台机器上的进程间通信。
#type参数代表套接字类型,可为SOCK_STREAM(流套接字,就是TCP套接字)和SOCK_DGRAM(数据报套接字,就是UDP套接字)。 
#默认为family=AF_INET  type=SOCK_STREM     #返回一个整数描述符,用这个描述符来标识这个套接字

  2  绑定套接字

s1.bind( address ) 
#由AF_INET所创建的套接字,address地址必须是一个双元素元组,格式是(host,port)。host代表主机,port代表端口号。 #如果端口号正在使用、主机名不正确或端口已被保留,bind方法将引发socket.error异常。 
#例: ('192.168.1.1',9999)

  3  监听套接字

s1.listen( backlog ) 
#backlog指定最多允许多少个客户连接到服务器。它的值至少为1。收到连接请求后,这些请求需要排队,如果队列满,就拒绝请求。 

  4  等待接受连接

connection, address = s1.accept()
#调用accept方法时,socket会时入“waiting”状态,也就是处于阻塞状态。客户请求连接时,方法建立连接并返回服务器。
#accept方法返回一个含有两个元素的元组(connection,address)。
#第一个元素connection是所连接的客户端的socket对象(实际上是该对象的内存地址),服务器必须通过它与客户端通信;
#第二个元素 address是客户的Internet地址。

  5  处理阶段

connection.recv(bufsize[,flag]) #注意此处为connection
#接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略

connection.send(string[,flag])
#将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。

  6  传输结束,关闭连接

s1.close()
#关闭套接字

python编写客户端

  1  创建socket对象

import socket
s2=socket.socket()

  2  连接至服务器端

s2.connect(address)
#连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

  3  处理阶段

s2.recv(bufsize[,flag])
#接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略

s2.send(string[,flag])
#将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。

  4  连接结束,关闭套接字

s2.close()

socket中还有许多方法 :

 

 

socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) #获取要连接的对端主机地址

sk.bind(address)
  将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。

sk.listen(backlog)
  开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。
  backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5,这个值不能无限大,因为要在内核中维护连接队列

sk.setblocking(bool)
  是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。

sk.accept()
  接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。接收TCP 客户的连接(阻塞式)等待连接的到来

sk.connect(address)
  连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

sk.connect_ex(address)
  同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061

sk.close()
  关闭套接字

sk.recv(bufsize[,flag])
  接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。

sk.recvfrom(bufsize[.flag])
  与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

sk.send(string[,flag])
  将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。

sk.sendall(string[,flag])
  将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。内部通过递归调用send,将所有内容发送出去。

sk.sendto(string[,flag],address)
  将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。

sk.settimeout(timeout)
  设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,
  因为它们可能用于连接的操作(如 client 连接最多等待5s )

sk.getpeername()
  返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

sk.getsockname()
  返回套接字自己的地址。通常是一个元组(ipaddr,port)

sk.fileno()
  套接字的文件描述符

socket.sendfile(file, offset=0, count=None)
  发送文件

 

 

好了,介绍完socket现在该介绍socketserver了。

----socketserver

  虽说用Python编写简单的网络程序很方便,但复杂一点的网络程序还是用现成的框架比较 好。这样就可以专心事务逻辑,而不是套接字的各种细节。SocketServer模块简化了编写网络服务程序的任务。同时SocketServer模块也 是Python标准库中很多服务器框架的基础。

socketserver在python2中为SocketServer,在python3种取消了首字母大写,改名为socketserver。

socketserver中包含了两种类,一种为服务类(server class),一种为请求处理类(request handle class)。前者提供了许多方法:像绑定,监听,运行…… (也就是建立连接的过程) 后者则专注于如何处理用户所发送的数据(也就是事务逻辑)。

  一般情况下,所有的服务,都是先建立连接,也就是建立一个服务类的实例,然后开始处理用户请求,也就是建立一个请求处理类的实例。

 

我们接下来介绍一下这两个类

先来看服务类:

5种类型:BaseServer,TCPServer,UnixStreamServer,UDPServer,UnixDatagramServer。

BaseServer不直接对外服务。

TCPServer针对TCP套接字流

UDPServer针对UDP数据报套接字

UnixStreamServer和UnixDatagramServer针对UNIX域套接字,不常用。

他们之间的继承关系:

服务类的方法:

 

class SocketServer.BaseServer:这是模块中的所有服务器对象的超类。它定义了接口,如下所述,但是大多数的方法不实现,在子类中进行细化。

    BaseServer.fileno():返回服务器监听套接字的整数文件描述符。通常用来传递给select.select(), 以允许一个进程监视多个服务器。

    BaseServer.handle_request():处理单个请求。处理顺序:get_request(), verify_request(), process_request()。如果用户提供handle()方法抛出异常,将调用服务器的handle_error()方法。如果self.timeout内没有请求收到, 将调用handle_timeout()并返回handle_request()。

    BaseServer.serve_forever(poll_interval=0.5): 处理请求,直到一个明确的shutdown()请求。每poll_interval秒轮询一次shutdown。忽略self.timeout。如果你需要做周期性的任务,建议放置在其他线程。

    BaseServer.shutdown():告诉serve_forever()循环停止并等待其停止。python2.6版本。

    BaseServer.address_family: 地址家族,比如socket.AF_INET和socket.AF_UNIX。

    BaseServer.RequestHandlerClass:用户提供的请求处理类,这个类为每个请求创建实例。

    BaseServer.server_address:服务器侦听的地址。格式根据协议家族地址的各不相同,请参阅socket模块的文档。

    BaseServer.socketSocket:服务器上侦听传入的请求socket对象的服务器。

服务器类支持下面的类变量:

    BaseServer.allow_reuse_address:服务器是否允许地址的重用。默认为false ,并且可在子类中更改。

    BaseServer.request_queue_size

请求队列的大小。如果单个请求需要很长的时间来处理,服务器忙时请求被放置到队列中,最多可以放request_queue_size个。一旦队列已满,来自客户端的请求将得到 “Connection denied”错误。默认值通常为5 ,但可以被子类覆盖。

    BaseServer.socket_type:服务器使用的套接字类型; socket.SOCK_STREAM和socket.SOCK_DGRAM等。

    BaseServer.timeout:超时时间,以秒为单位,或 None表示没有超时。如果handle_request()在timeout内没有收到请求,将调用handle_timeout()。

下面方法可以被子类重载,它们对服务器对象的外部用户没有影响。

    BaseServer.finish_request():实际处理RequestHandlerClass发起的请求并调用其handle()方法。 常用。

    BaseServer.get_request():接受socket请求,并返回二元组包含要用于与客户端通信的新socket对象,以及客户端的地址。

    BaseServer.handle_error(request, client_address):如果RequestHandlerClass的handle()方法抛出异常时调用。默认操作是打印traceback到标准输出,并继续处理其他请求。

    BaseServer.handle_timeout():超时处理。默认对于forking服务器是收集退出的子进程状态,threading服务器则什么都不做。

    BaseServer.process_request(request, client_address) :调用finish_request()创建RequestHandlerClass的实例。如果需要,此功能可以创建新的进程或线程来处理请求,ForkingMixIn和ThreadingMixIn类做到这点。常用。

    BaseServer.server_activate():通过服务器的构造函数来激活服务器。默认的行为只是监听服务器套接字。可重载。

    BaseServer.server_bind():通过服务器的构造函数中调用绑定socket到所需的地址。可重载。

    BaseServer.verify_request(request, client_address):返回一个布尔值,如果该值为True ,则该请求将被处理,反之请求将被拒绝。此功能可以重写来实现对服务器的访问控制。默认的实现始终返回True。client_address可以限定客户端,比如只处理指定ip区间的请求。 常用。

 

 

这个几个服务类都是同步处理请求的:一个请求没处理完不能处理下一个请求。要想支持异步模型,可以利用多继承让server类继承ForkingMixIn 或 ThreadingMixIn mix-in classes。

ForkingMixIn利用多进程(分叉)实现异步。

ThreadingMixIn利用多线程实现异步。

 

请求处理器类:

要实现一项服务,还必须派生一个handler class请求处理类,并重写父类的handle()方法。handle方法就是用来专门是处理请求的。该模块是通过服务类和请求处理类组合来处理请求的。

SocketServer模块提供的请求处理类有BaseRequestHandler,以及它的派生类StreamRequestHandler和DatagramRequestHandler。从名字看出可以一个处理流式套接字,一个处理数据报套接字。

 

请求处理类有三种方法:

 

setup()

Called before the handle() method to perform any initialization actions required. The default implementation does nothing.

也就是在handle()之前被调用,主要的作用就是执行处理请求之前的初始化相关的各种工作。默认不会做任何事。(如果想要让其做一些事的话,就要程序员在自己的请求处理器中覆盖这个方法(因为一般自定义的请求处理器都要继承python中提供的BaseRequestHandler,ps:下文会提到的),然后往里面添加东西即可)

 

handle()

This function must do all the work required to service a request. The default implementation does nothing. Several instance attributes are available to it; the request is available as self.request; the client address as self.client_address; and the server instance as self.server, in case it needs access to per-server information.

The type of self.request is different for datagram or stream services. For stream services,self.request is a socket object; for datagram services, self.request is a pair of string and socket.

handle()的工作就是做那些所有与处理请求相关的工作。默认也不会做任何事。他有数个实例参数:self.request    self.client_address   self.server

 

finish()

Called after the handle() method to perform any clean-up actions required. The default implementation does nothing. If setup() raises an exception, this function will not be called.

在handle()方法之后会被调用,他的作用就是执行当处理完请求后的清理工作,默认不会做任何事

 

 

从源码中可以看出,BaseRequestHandler中的setup()/handle()/finish()什么内容都没有定义,而他的两个派生类StreamRequestHandler和DatagramRequestHandler则都重写了setup()/finish()。

 

因此当我们需要自己编写socketserver程序时,只需要合理选择StreamRequestHandler和DatagramRequestHandler之中的一个作为父类,然后自定义一个请求处理类,并在其中重写handle()方法即可。

 

 

用socketserver创建一个服务的步骤:

 

1  创建一个request handler class(请求处理类),合理选择StreamRequestHandler和DatagramRequestHandler之中的一个作为父类(当然,使用BaseRequestHandler作为父类也可),并重写它的handle()方法。

 

2  实例化一个server class对象,并将服务的地址和之前创建的request handler class传递给它。

 

3  调用server class对象的handle_request() 或 serve_forever()方法来开始处理请求。

 

 

 

 

 

 

 

posted on 2017-08-09 16:24  momo8238  阅读(283)  评论(0编辑  收藏  举报