来,贴上一段代码让你仰慕一下欧socketserver的魅力,看欧怎么完美实现多并发的魅力

client

import socket

ip_port = ('127.0.0.1',8009)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5) while True:
data = sk.recv(1024)
print('receive:',data.decode())
inp = input('please input:')
sk.sendall(inp.encode())
if inp == 'exit':
break sk.close()

server

'''
对于socketserver,你需要做的事:
定义个一类,继续socketserver.BaseRequestHandler
重写handle方法
把写好的类和端口进行实例,启动程序
''' import socketserver class MyServer(socketserver.BaseRequestHandler): def handle(self):
# print self.request,self.client_address,self.server
conn = self.request
conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.'.encode())
Flag = True
while Flag:
data = conn.recv(1024).decode()
if data == 'exit':
Flag = False
elif data == '0':
conn.sendall('通过可能会被录音.balabala一大推'.encode())
else:
conn.sendall('请重新输入.'.encode()) if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
server.serve_forever()

代码执行看源码

  我们看到最后两句代码,第一句--带有括号,我们第一想到的是要么是个函数,要是是个类,那么看一下什么吧

  是个类,继承了ThreadingMixIn和TCPServer两个类,好!实例对象肯定要找构造方法,当前这个类没有,就需要到父类中找了,从左往右

  我们发现ThreadingMixIn类里没有,那肯定在TCPServer类里啦,果真在,并且还执行了TCPServer的父类BaseServer里的构造方法

  在BaseServer里只是做了一些数据初始化的事,那我们回到TCPServer里构造方法里接着往下看吧

  执行父类构造方法后,实例一个socket对象,然后就是在if下执行self.server_bind()方法,要想知道这个方法,必须弄清楚self是指代谁?

  说到这里,我必须屡屡类的继承关系了...

  self是指实例对象,谁实例的,是ThreadingTCPServer类,所以找server_bind方法,要从ThreadingTCPServer往上找

  我们发现这个方法还是TCPServer里,

  似乎是做了绑定socket的事,这里过,在找看构造方法里,后又执行self.server_activate方法,我们按照那继承关系又在TCPServer找到了

  似乎是做了有关监听数的事,好这里构造方法执行完毕,第一代码也就这样看完了

  看到第二句,开始找serve_forever方法在哪了

  在BaseServer里,这方法大概说的是,一次处理一个请求直到连接关闭,如果处理其他要求另外开启一个线程

  我发现下有个self._handle_request_noblock(),我们看看这个是做啥的吧?

  还是在BaseServer,好像是做不阻塞的工作

  在这里还是执行了一个process_request方法,这个方法我们在ThreadingMixIn找到了

  主要是实例线程和启动线程的...

  没错,似乎我有了一点感悟--

  socketserver是基于多线程来做的,并做到多并发处理

内部调用流程为:

  • 启动服务端程序
  • 执行 TCPServer.__init__ 方法,创建服务端Socket对象并绑定 IP 和 端口
  • 执行 BaseServer.__init__ 方法,将自定义的继承自SocketServer.BaseRequestHandler 的类 MyRequestHandle赋值给 self.RequestHandlerClass
  • 执行 BaseServer.server_forever 方法,While 循环一直监听是否有客户端请求到达 ...
  • 当客户端连接到达服务器
  • 执行 ThreadingMixIn.process_request 方法,创建一个 “线程” 用来处理请求
  • 执行 ThreadingMixIn.process_request_thread 方法
  • 执行 BaseServer.finish_request 方法,执行 self.RequestHandlerClass()  即:执行 自定义 MyRequestHandler 的构造方法(自动调用基类BaseRequestHandler的构造方法,在该构造方法中又会调用 MyRequestHandler的handle方法)

服务类:

  SocketServer提供了5个基本的服务类:

  BaseServer: 基础类,由于下面四个网络服务类的继承

  TCPServer:针对TCP套接字流

  UDPServer:针对UDP数据报套接字

  UnixStreamServer:处理流式套接字,与TCPServer配合

  UnixDatagramServer:处理数据报套接字,与UDPServer配合

异步处理类:

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

  ForkingMixIn: 利用多进程(分叉)实现异步。(Mix-in class to handle each request in a new process)

  ThreadingMixIn: 利用多线程实现异步。(Mix-in class to handle each request in a new thread)

请求处理类:

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

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

#服务器端

from SocketServer import TCPServer,StreamRequestHandler,\
ThreadingMixIn, ForkingMixIn #定义请求处理类
class Handler(StreamRequestHandler):
def handle(self):
addr = self.request.getpeername()
print 'connection:', addr
while 1:
self.request.sendall(self.request.recv(1024)) #实例化服务类对象
server = TCPServer(
server_address=('127.0.0.1', 8123), # address
RequestHandlerClass=Handler # 请求类
) #开启服务
server.serve_forever()
#客户端

import socket

def socketClient():
so = socket.socket()
so.connect(('127.0.0.1', 8123))
# so.close()
while 1:
so.sendall(raw_input('msg'))
print so.recv(1024) if __name__ == '__main__':
socketClient()

多线程服务端

from SocketServer import TCPServer,StreamRequestHandler,\
ThreadingMixIn, ForkingMixIn #定义基于多线程的服务类
class Server(ThreadingMixIn, TCPServer):
pass #定义请求处理类
class Handler(StreamRequestHandler):
def handle(self):
addr = self.request.getpeername()
print 'connection:', addr
while 1:
self.request.sendall(self.request.recv(1024)) #实例化服务类对象
server = Server(
server_address=('127.0.0.1', 8123), # address
RequestHandlerClass=Handler # 请求类
) #开启服务
server.serve_forever()

源码分析:

"""普通的Socket服务类.

socket服务:

- address family:
- AF_INET{,6}: IP (Internet Protocol) sockets (default)
- AF_UNIX: Unix domain sockets
- others, e.g. AF_DECNET are conceivable (see <socket.h>
- socket type:
- SOCK_STREAM (reliable stream, e.g. TCP)
- SOCK_DGRAM (datagrams, e.g. UDP) 请求服务类 (including socket-based): - 客户端地址验证之前进一步查看请求
(实际上是一个请求处理的钩子在请求之前,例如logging)
- 如何处理多个请求:
- synchronous (同步:同一时间只能有一个请求)
- forking (分叉:每个请求分配一个新的进程)
- threading (线程:每个请求分配一个新的线程) 五个类的继承关系如下: +------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+ 通过ForkingMixIn创建进程,通过ThreadingMixIn创建线程,如下实例: class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass """ __version__ = "0.4" import socket
import select
import sys
import os
import errno
try:
import threading
except ImportError:
import dummy_threading as threading __all__ = ["TCPServer","UDPServer","ForkingUDPServer","ForkingTCPServer",
"ThreadingUDPServer","ThreadingTCPServer","BaseRequestHandler",
"StreamRequestHandler","DatagramRequestHandler",
"ThreadingMixIn", "ForkingMixIn"] #family参数代表地址家族,比较常用的为AF_INET或AF_UNIX。
#AF_UNIX用于同一台机器上的进程间通信,AF_INET对于IPV4协议的TCP和UDP 。
if hasattr(socket, "AF_UNIX"):
__all__.extend(["UnixStreamServer","UnixDatagramServer",
"ThreadingUnixStreamServer",
"ThreadingUnixDatagramServer"]) def _eintr_retry(func, *args):
"""重新启动系统调用EINTR中断"""
while True:
try:
return func(*args)
except (OSError, select.error) as e:
if e.args[0] != errno.EINTR:
raise class BaseServer: """服务类的基类. 调用方法: - __init__(server_address, RequestHandlerClass)
- serve_forever(poll_interval=0.5)
- shutdown()
- handle_request() # if you do not use serve_forever()
- fileno() -> int # for select() 可以被重写的方法: - server_bind()
- server_activate()
- get_request() -> request, client_address
- handle_timeout()
- verify_request(request, client_address)
- server_close()
- process_request(request, client_address)
- shutdown_request(request)
- close_request(request)
- handle_error() 派生类(derived classes)方法: - finish_request(request, client_address) 可以由派生类或重写类变量实例: - timeout
- address_family
- socket_type
- allow_reuse_address 实例变量: - RequestHandlerClass
- socket """ timeout = None def __init__(self, server_address, RequestHandlerClass):
"""初始化,能被扩展但不要重写."""
self.server_address = server_address # 地址元祖如('127.0.0.1', 8123)
self.RequestHandlerClass = RequestHandlerClass # 请求处理类
self.__is_shut_down = threading.Event() # 多线程通信机制
self.__shutdown_request = False def server_activate(self):
"""通过构造函数激活服务器.可被重写."""
pass def serve_forever(self, poll_interval=0.5):
"""在一个时间段内处理一个请求直到关闭. 处理请求,直到一个明确的shutdown()请求。每poll_interval秒轮询一次shutdown。
忽略self.timeout。如果你需要做周期性的任务,建议放置在其他线程。
""" self.__is_shut_down.clear()
#Event是Python多线程通信的最简单的机制之一.一个线程标识一个事件,其他线程一直处于等待状态。
#一个事件对象管理一个内部标示符,这个标示符可以通过set()方法设为True,通过clear()方法重新设为False,wait()方法则使线程一直处于阻塞状态,直到标示符变为True
#也就是说我们可以通过 以上三种方法来多个控制线程的行为。
try:
while not self.__shutdown_request:
#考虑使用其他文件描述符或者连接socket去唤醒它取代轮询
#轮询减少在其他时间我们响应了关闭请求CPU。
r, w, e = _eintr_retry(select.select, [self], [], [],
poll_interval)
if self in r:
self._handle_request_noblock()
finally:
self.__shutdown_request = False
self.__is_shut_down.set() def shutdown(self):
"""终止serve_forever的循环. 阻塞直到循环结束. 当serve_forever()方法正运行在另外的线程中必须调用它,否则会发生死锁.
"""
self.__shutdown_request = True
self.__is_shut_down.wait() # - handle_request() 是顶层调用. 它调用select,get_request(),verify_request()和process_request()
# - get_request() 不同于流式和报文socket
# - process_request() 产生进程的位置,或者产生线程去结束请求
# - finish_request() 请求处理类的实例,此构造都将处理请求本身 def handle_request(self):
"""处理一个请求, 可能阻塞.考虑self.timeout."""
# Support people who used socket.settimeout() to escape
# handle_request before self.timeout was available.
timeout = self.socket.gettimeout() # 返回当前超时期的值,如果没有设置超时期,则返回None
if timeout is None:
timeout = self.timeout
elif self.timeout is not None:
timeout = min(timeout, self.timeout)
fd_sets = _eintr_retry(select.select, [self], [], [], timeout)
if not fd_sets[0]:
self.handle_timeout()
return
self._handle_request_noblock() def _handle_request_noblock(self):
"""处理一个请求, 非阻塞."""
try:
request, client_address = self.get_request()
except socket.error:
return
if self.verify_request(request, client_address):
try:
self.process_request(request, client_address)
except:
self.handle_error(request, client_address)
self.shutdown_request(request) def handle_timeout(self):
"""超时处理。默认对于forking服务器是收集退出的子进程状态,threading服务器则什么都不做"""
pass def verify_request(self, request, client_address):
"""
返回一个布尔值,如果该值为True ,则该请求将被处理,反之请求将被拒绝。
此功能可以重写来实现对服务器的访问控制。
默认的实现始终返回True。client_address可以限定客户端,比如只处理指定ip区间的请求。 常用。
"""
return True def process_request(self, request, client_address):
"""调用finish_request.被 ForkingMixIn and ThreadingMixIn重写
如果用户提供handle()方法抛出异常,将调用服务器的handle_error()方法。
如果self.timeout内没有请求收到, 将调用handle_timeout()并返回handle_request()。 """
self.finish_request(request, client_address)
self.shutdown_request(request) def server_close(self):
"""关闭并清理server."""
pass def finish_request(self, request, client_address):
"""通过请求类的实例结束请求,实际处理RequestHandlerClass发起的请求并调用其handle()方法。 常用."""
self.RequestHandlerClass(request, client_address, self) def shutdown_request(self, request):
"""关闭结束一个单独的请求."""
self.close_request(request) def close_request(self, request):
pass def handle_error(self, request, client_address):
"""优雅的操作错误,可重写,默认打印异常并继续
"""
print '-'*40
print 'Exception happened during processing of request from',
print client_address
import traceback
traceback.print_exc() # XXX But this goes to stderr!
print '-'*40

基于协程实现socket多并发

  在这里,首先我们先了解一下协程一个概念:

  • 协程又称微线程,单线程实现异步并发

  • 线程寄存在cpu里,而协程有自己的寄存器,上下文和栈

  • 由于协程本质上是单线程,所以不存在上下文切换花销,以及锁和同步的概念

  • 低成本,高并发,唯一不足的就是不能利用cpu的核资源

  • 你就记住协程干了这么一件事:遇IO阻塞就去做别的事了(socket连接就是IO操作)

  从上面的源码解析,我们知道,socketserver实现多并发本质就是多线程或多进程,但这样还是有些低效,你想啊,如果有几万客户连接过来,就要创建几万个线程,如果用的人其实不是很多,CPU还要不断的去检测socket客户端有没有传输数据,花销很大,效率就低

server

import sys
import socket
import time
import gevent from gevent import socket,monkey
monkey.patch_all() def server(port):
s = socket.socket()
s.bind(('0.0.0.0', port))
s.listen(500)
while True:
cli, addr = s.accept()
gevent.spawn(handle_request, cli) def handle_request(conn):
try:
while True:
data = conn.recv(1024)
print("recv:", data)
conn.send(data)
if not data:
conn.shutdown(socket.SHUT_WR) except Exception as ex:
print(ex)
finally:
conn.close()
if __name__ == '__main__':
server(8001)

client

import socket

HOST = 'localhost'    # The remote host
PORT = 8001 # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
msg = bytes(input(">>:"),encoding="utf8")
s.sendall(msg)
data = s.recv(1024)
#print(data) print('Received', repr(data))
s.close()

socketserver源码解析和协程版socketserver的更多相关文章

  1. Golang源码探索(二) 协程的实现原理(转)

    Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱,虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻底 ...

  2. skynet源码阅读<5>--协程调度模型

    注:为方便理解,本文贴出的代码部分经过了缩减或展开,与实际skynet代码可能会有所出入.    作为一个skynet actor,在启动脚本被加载的过程中,总是要调用skynet.start和sky ...

  3. Golang源码探索(二) 协程的实现原理

    Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱, 虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻 ...

  4. Redux系列x:源码解析

    写在前面 redux的源码很简洁,除了applyMiddleware比较绕难以理解外,大部分还是 这里假设读者对redux有一定了解,就不科普redux的概念和API啥的啦,这部分建议直接看官方文档. ...

  5. PureMVC(JS版)源码解析:总结

    PureMVC源码中设计到的11个类已经全部解析完了,回首想想,花了一周的时间做的这点事情还是挺值得的,自己的文字组织表达能力和对pureMVC的理解也在写博客的过程中得到了些提升.我也是第一次写系列 ...

  6. PureMVC(JS版)源码解析

    PureMVC(JS版)源码解析:总结   PureMVC源码中设计到的11个类已经全部解析完了,回首想想,花了一周的时间做的这点事情还是挺值得的,自己的文字组织表达能力和对pureMVC的理解也在写 ...

  7. Tensorflow版Faster RCNN源码解析(TFFRCNN) (2)推断(测试)过程不使用RPN时代码运行流程

    本blog为github上CharlesShang/TFFRCNN版源码解析系列代码笔记第二篇   推断(测试)过程不使用RPN时代码运行流程 作者:Jiang Wu  原文见:https://hom ...

  8. Tensorflow版Faster RCNN源码解析(TFFRCNN) (1) VGGnet_test.py

    本blog为github上CharlesShang/TFFRCNN版源码解析系列代码笔记第1篇   VGGnet_test.py ----作者:Jiang Wu(吴疆),未经允许,禁止转载--- -- ...

  9. python自动化开发-[第十天]-线程、协程、socketserver

    今日概要 1.线程 2.协程 3.socketserver 4.基于udp的socket(见第八节) 一.线程 1.threading模块 第一种方法:实例化 import threading imp ...

随机推荐

  1. QT把widget转换成图片后打印

    from PyQt5.QtWidgets import (QApplication, QWidget, QTableWidget,QPushButton, QVBoxLayout, QTableWid ...

  2. thinkphp 解析带html标签的内容

    1.实例一 <?php echo htmlspecialchars_decode($goodsinfo['Specification']);?> 2.实例二 {$show.article| ...

  3. 重载 CreateParams 方法[1]: 从一个例子开始(取消窗口最大化、最小化按钮的三种方法)

    方法1: 使用 TForm 的 BorderIcons 属性 unit Unit1; interface uses   Windows, Messages, SysUtils, Variants, C ...

  4. HTML5 直播技术

    https://segmentfault.com/a/1190000010440054

  5. hdu 5038 水题 可是题意坑

    http://acm.hdu.edu.cn/showproblem.php?pid=5038 就是求个众数  这个范围小 所以一个数组存是否存在的状态即可了 可是这句话真恶心  If not all ...

  6. Effective C++ Item 16 Use the same form in corresponding uses of new and delete

    1. When you created an array and want to return the memory to system. You need to explicitly add [] ...

  7. python处理文本文件

    在测试任务过程中都或多或少遇到自己处理文本文件的情况. 举个栗子: 客户端测试从异常日志中收集有用信息. 后端测试需要创建各种规则的压力的词表. ... 这里给大家分享一个使用python脚本处理文本 ...

  8. lua常用操作

    1 .Lua生成随机数: Lua 生成随机数需要用到两个函数:math.randomseed(xx), math.random([n [, m]]) 1. math.randomseed(n) 接收一 ...

  9. ubuntu 备忘

    卷组扩容 Linux mint采用默认卷组的安装方式 sain@Linux ~ $ df -hl Filesystem Size Used Avail Use% Mounted on udev .7G ...

  10. 时序数据库技术体系 – InfluxDB 多维查询之倒排索引

    本文转自: http://hbasefly.com/2018/02/09/timeseries-database-5/ 在时序数据库概述一文中,笔者提到时序数据库的基础技术栈主要包括高吞吐写入实现.数 ...