曲一

socketserver 是为了简化服务器端开发而产生的,是一个高级的标准库。(背景介绍完毕,开始干)

一些概念

来自源码的一张图片,简洁又FengSao

     +------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+

这是socketserver的一个基本框架,BaseServer 是基类,是不可以初始化的。然后我们可以利用BaseServer的子类就是下面的四个,来实例化我们需要的服务器类型。(本人太菜只知道一点TCP的相关知识,所以从TCPserver入手学习)。

看到这个图开始还是很开心的,以为就这图上的5个类,结果大神写的源码把我一个英语不好的“澜”差点看哭了。我开始发现没那么简单,有好多的类,好多好多的类!

一行一行的看绝对懵逼,所以我决定不看其他的内容只看关于TCPserver的(好机智,好吧其实还是一脸懵逼),如果你也和我一样看到源码不知从何下手,下面给出一个我的查看路线吧(一行一行爬出来的一条路,当然只是TCPserver相关的内容)。

我用的pycharm,鼠标放在想查看源码的对象上,ctrl + b 就可以查看源码,还是非常方便的,其他的编辑器我就不知道了,下面开始。

不要一行一行的看了(想想当初憨憨的我...)先搜关键词BaseRequestHandler ,你会找到一个独立的类,看下面的说明有这样一段话

This class is instantiated for each request to be handled. The constructor sets the instance variables request, client_address and server, and then calls the handle() method. To implement a specific service, all you need to do is to derive a class which defines a handle() method.

The handle() method can find the request as self.request, the client address as self.client_address, and the server (in case it needs access to per-server information) as self.server. Since a separate instance is created for each request, the handle() method can define other arbitrary instance variables.

注意加粗的句子,貌似告诉我们只需要派生一个BaseRequestHandler的子类,并且重写一下handle()方法就可以了,怎么样很简单的样子有没有。好写一个这个类的子类先上代码

# Bianry_socketserver_v1
import socketserver
class my_tcphandler(socketserver.BaseRequestHandler):
def handle(self):
pass # 不知道这个函数干嘛的的,先占个坑

继续看源码(心情沉重没有办法),继续搜关键字BaseRequestHandler,让我们把视线转到源码里的第一个关键词位置

To implement a service, you must derive a class from BaseRequestHandler and redefine its handle() method. You can then run various versions of the service by combining one of the server classes with your request handler class.

看到还是让我们重写一个BaseRequestHandlerhandle()方法,然后注意有一个关键词server classes,嗯!我们不是要一个TCPserver吗?貌似看到希望了啊。提示告诉我们要把自己写的request handler classserver class结合一下,怎么结合啊,别慌,关键字搜索TCPServer目光再次转移...

直接看TCPServer的构造函数(为什么?因为我在它的参数里看到了BaseRequestHandlerClass

# constractor of TCPServer
def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
"""Constructor. May be extended, do not override.""" # 别人的函数叫你不要随便该
BaseServer.__init__(self, server_address, RequestHandlerClass)
self.socket = socket.socket(self.address_family,
self.socket_type)
if bind_and_activate:
try:
self.server_bind()
self.server_activate()
except:
self.server_close()
raise

看到他又调用了父类的构造函数,而且把我们想要的东西传了进去,那就查看它父类的构造函数,走起...

# BaseServer constractor
def __init__(self, server_address, RequestHandlerClass):
"""Constructor. May be extended, do not override."""
self.server_address = server_address
self.RequestHandlerClass = RequestHandlerClass
self.__is_shut_down = threading.Event()
self.__shutdown_request = False

看到父类就把它存了起来...就没了吗?不行继续搜RequestHandlerClass找到一个finish_request函数

def finish_request(self, request, client_address):
"""Finish one request by instantiating RequestHandlerClass."""
self.RequestHandlerClass(request, client_address, self)

终于看到一点实际的了,看到这个函数把我们传入的我们自己写的类my_tcphandler(继承的BaseRequestHandler)实例化了,好激动赶紧回去看看BaseRequestHandler构造函数有什么,因为自己写的my_tcphandler类没有构造函数,所以实例化会执行父类的构造函数先。

# BaseRequestHandler constractor
def __init__(self, request, client_address, server):
self.request = request
self.client_address = client_address
self.server = server
self.setup()
try:
self.handle()
finally:
self.finish()

看到它加了几个变量进去request,clinet_address,server,而且先调用setup()函数,然后再handle()'(还占着一个坑的函数,等下去填上),最后你呢回去执行finish(),这三个函数都是没有写任何内容的,是给我们发挥的呀,刚刚只写了一个handle(),那怎么行,待会儿把这两个也加进去。现在的问题是request,clinet_address,server这三个变量是用来干嘛的呀,我们的三个函数怎么重构啊,我们要的是一个TCPServer啊。

上面的问题先不管了(船到桥头自然直,安慰一下自己),下面来尝试搭建我们的第一个TCPServer,看能不能行,先缕一缕思路

首先,我们要写一个BaseRequestHandler的子类并重写handle()方法,其他两个函数不是必须写的先不管。

然后,我们要实例化一个TCPServer的对象,并且把我们写的类作为参数传进去。(而实例化的过程非常的坎坷就是我们上面一步一步分析得到那样子。)

最后...没了耶,就两部这么简单

好开始写

# Bianry_socketserver_v1
import socketserver
class my_tcphandler(socketserver.BaseRequestHandler):
def handle(self):
print('request:',self.request)
print('clinet_address:',self.client_address)
print('server:',self.server)
# 不是不晓得这是三个什么东西吗,打印它,不解释
if __name__ == '__main__':
addr,port = 'localhost',9999
tcp_server = socketserver.TCPServer((addr,port),my_tcphandler)
tcp_server.serve_forever() # keep running...

运行程序发现没反应,没打印我们想要内容,这东西就像个憨憨一样,憨憨的在等一个人的到来。于是我们找了一个人(客户端)来尝试去告诉他一些真相(连接TCPserver),客户端程序

import socket
clinet = socket.socket()
clinet.connect(('localhost',9999))
clinet.close()

只是连接一下,就断开了,就是个过客...

回首看看服务器端,打印了我们想要的东西

request: <socket.socket fd=424, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 61933)>
clinet_address: ('127.0.0.1', 61933)
server: <socketserver.TCPServer object at 0x000001DE0F2DF5C0>

看到request原来是一个套接字,clinet_address原来是客户端留下的ipportsrever原来就是自己(服务端)啊。

现在貌似知道handle()里应该写什么内容了,应该是和客户端交互的内容,因为我们有一个套接字(request),有了套接字就可以和和客户端交互了。而且handle()方法在客户端建立应该连接时才会执行,是不是想到了accept()... ,个人理解就是差不多的(当然对单线程服务器,多线程的原理还不懂,菜哭...),源码上是这么说的

Since a separate instance is created for each request, the handle() method can define other arbitrary instance variables.

是差不多吧,不多废话了,上代码来。

# Bianry_socketTCPserver_v1
import socketserver
from time import ctime
class my_tcphandler(socketserver.BaseRequestHandler):
def handle(self):
try:
while True:
clinet_data = self.request.recv(1024)
if not clinet_data:
break
print('clinet:',clinet_data.decode())
self.request.sendall(b'[%s] %s' % (clinet_data,ctime().encode('utf-8')))
except Exception as e:
exit(e)
finally:
self.request.close()
if __name__ == '__main__':
addr,port = 'localhost',9999
tcp_server = socketserver.TCPServer((addr,port),my_tcphandler) # TCPServer
tcp_server.serve_forever() # keep running...

客户端

# Bianry_socketTCPclinet_v1
import socket
clinet = socket.socket()
clinet.connect(('localhost',9999))
while True:
data = input()
if not data:
break
clinet.send(data.encode('utf-8'))
server_data = clinet.recv(1024)
print('server:',server_data.decode())
clinet.close()

这是单线程的,不能同时处理多个连接请求,想要处理多个怎么办,加几个字母就可以了

# Bianry_socketThreadingTCPserver_v1
import socketserver
from time import ctime
class my_tcphandler(socketserver.BaseRequestHandler):
def handle(self):
try:
while True:
clinet_data = self.request.recv(1024)
if not clinet_data:
break
print('clinet:',clinet_data.decode())
self.request.sendall(b'[%s] %s' % (clinet_data,ctime().encode('utf-8')))
except Exception as e:
exit(e)
finally:
self.request.close()
if __name__ == '__main__':
addr,port = 'localhost',9999
tcp_server = socketserver.ThreadingTCPServer((addr,port),my_tcphandler) #ThreadingTCPServer
tcp_server.serve_forever() # keep running...

多线程还不会...菜鸡去学习了...

python --- Socketserver N部曲(1)的更多相关文章

  1. docker-compose下的java应用启动顺序两部曲之二:实战

    上篇回顾 本文是<docker-compose下的java应用启动顺序两部曲>的终篇,在上一篇<docker-compose下的java应用启动顺序两部曲之一:问题分析>中,我 ...

  2. c语言项目开发流程二部曲

    一.在第一部曲中我们介绍了电子词典项目开发的前5步,下面继续我们的步伐. 6.函数接口设计,这一步不是一蹴而就的,在项目进行中得不断修改,下面是我电子词典项目接口. /**************函数 ...

  3. JDBC编程六部曲

    今天初学jdbc,明白了大致的编程流程,在此总结一下: JDBC编程可以分为六步——六部曲: * 第一步:注册驱动. * 1.1 获取驱动对象 * 1.2 注册驱动 * 第二步:获取数据库连接 * 第 ...

  4. 使用Python SocketServer快速实现多线程网络服务器

    Python SocketServer使用介绍 1.简介: SocketServer是python的一个网络服务器框架,可以减少开发人员编写网络服务器程序的工作量. SocketServer总共有4个 ...

  5. docker-compose下的java应用启动顺序两部曲之一:问题分析

    在docker-compose编排多个容器时,需要按实际情况控制各容器的启动顺序,本文是<docker-compose下的java应用启动顺序两部曲>的第一篇,文中会分析启动顺序的重要性, ...

  6. python socketserver实现客户端多并发

    直接看代码 server #!/usr/bin/env python # -*- coding:utf-8 -*- import socketserver import subprocess clas ...

  7. python socketserver框架解析

    socketserver框架是一个基本的socket服务器端框架, 使用了threading来处理多个客户端的连接, 使用seletor模块来处理高并发访问, 是值得一看的python 标准库的源码之 ...

  8. python - socketserver 模块应用

    server端: import socketserver import subprocess import json import struct class MyTCPHandler(socketse ...

  9. Python SocketServer源码分析

    1      XXXServer 1.1      BaseSever 提供基础的循环等待请求的处理框架.使用serve_forever启动服务,使用shutdown停止.同时提供了一些可自行扩展的方 ...

随机推荐

  1. clickjacking 攻击

    文章:Web安全之点击劫持(ClickJacking) 点击劫持(ClickJacking)是一种视觉上的欺骗手段.大概有两种方式,一是攻击者使用一个透明的iframe,覆盖在一个网页上,然后诱使用户 ...

  2. Two-Stream Adaptive Graph Convolutional Network for Skeleton-Based Action Recognition

    Two-Stream Adaptive Graph Convolutional Network for Skeleton-Based Action Recognition 摘要 基于骨架的动作识别因为 ...

  3. 嵌入式linux开发uboot启动内核的机制(二)

    一.嵌入式系统的分区 嵌入式系统部署在Flash设备上时,对于不同SoC和Flash设备,bootloader.kernel.rootfs的分区是不同的.三星S5PV210规定启动设备的分区方案如下: ...

  4. JDOJ 2982: 最大连续子段和问题

    洛谷 P1115 最大子段和 洛谷传送门 JDOJ 2982: 最大连续子段和问题 JDOJ传送门 题目描述 给出一段序列,选出其中连续且非空的一段使得这段和最大. 输入格式 第一行是一个正整数NN, ...

  5. [codevs3044]矩形面积求并

    题目描述 Description 输入n个矩形,求他们总共占地面积(也就是求一下面积的并) 输入描述 Input Description 可能有多组数据,读到n=0为止(不超过15组) 每组数据第一行 ...

  6. Fish eating fruit 沈阳网络赛(树形dp)

    Fish eating fruit \[ Time Limit: 1000 ms \quad Memory Limit: 262144 kB \] 题意 大体的题意就是给出一棵树,求每一对点之间的距离 ...

  7. ZROI 暑期高端峰会 A班 Day3 图论

    最短路 NOI2019 D2T1 弹跳 KD 树 线段树套set -> 线段树套并查集? POI2014/2015 ??? \(n\) 个点 \(m\) 条边 DAG.求删掉每个点后 \(1\) ...

  8. UID、PID、PPID是什么?

    UID是用户ID,PID是进程ID,PPID是父进程ID. UID UID 用户身份证明(User Identification)的缩写.UID用户在注册后,系统会自动的给你一个UID的数值.意思就是 ...

  9. TDD具体实施过程,可以看作两个层次

    在代码层次,在编码之前写测试脚本,可以称为单元测试驱动开发(Unit Test Driven Development,UTDD) 在业务层次,在需求分析时就确定需求(如用户故事)的验收标准,即验收测试 ...

  10. libevent笔记5:水位watermarks

    bufferevent中提供了对读写回调的触发条件及最大缓存长度的设置,即低高水位: 低水位:是读写回调函数的最低触发数据长度,当输入/输出缓存区中的数据长度小于低水位时,读/写回调函数不会被触发: ...