当前位置: 首页 > news >正文

python多线程网络编程

背景

使用过flask框架后,我对request这个全局实例非常感兴趣。它在客户端发起请求后会保存着所有的客户端数据,例如用户上传的表单或者文件等。那么在很多客户端发起请求时,服务器是怎么去区分不同的request对象呢?当查看了大量的资料后,发现它使用了一种称为thread local的技术。关于thread local的实现原理其实很简单,就是声明一个全局的字典并且以线程的名字作为字典的键,然后其值就是该线程下的私有数据。具体可以参考这篇ThreadLocal文章。我们都知道http服务器相对socket服务器要更加上层的网络服务,所以研究flaskrequest实现原理前最好能够先理解scoket是怎么实现的。

运行平台与python版本要求

  • 1.使用的是python版本;
  • 2.在mac下测试通过(理论上在所有系统上都能够运行);
  • 3.可以github上获得所有源代码;
  • 4.使用nc命令模拟客户端;

研究目标

  • 1.我将会使用socket完成一个echo服务器的设计;
  • 2.这个服务器使用多线程实现非阻塞;
  • 3.我要使用thread local技术使数据独立安全;

socket的阻塞式实现

程序的设计应该是从实现最简单核心的任务开始的,所以下面我先实现一个简单的socket服务器。

import socketclass ThreadSocket(object):""""""def __init__(self, host, port):self.host = hostself.port = portself.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)self.sock.bind((self.host, self.port))def listen(self):self.sock.listen(5)while True:client, address = self.sock.accept()client.settimeout(60)try:data = client.recv(1024)if data:client.send(data)else:raise error("Client has disconnected")except:client.close()if __name__ == '__main__':server=ThreadSocket('',9000)server.listen()

你也可以直接通过git checkout v0.1命令获得这个版本的代码,代码写完后可以通过下面的方式完成测试。

python simpleSocketServer.py

新打开一个终端,输入下面这个命令进行测试。

nc 127.0.0.1 9000

下面可以看看客户端运行后的输出结果。

可以发现在第二次输入的数据并没有得到服务器的反馈,这是因为服务器还没有切换到读取等待模式。可以通过下面的方式修改从而实现不间断的发送接收任务。

import socketclass ThreadSocket(object):""""""def __init__(self, host, port):self.host = hostself.port = portself.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)self.sock.bind((self.host, self.port))def listen(self):self.sock.listen(5)while True:client, address = self.sock.accept()client.settimeout(60)while True:try:data = client.recv(1024)if data:client.send(data)else:raise error("Client has disconne
cted")except:client.close()if __name__ == '__main__':server=ThreadSocket('',9000)server.listen()

现在再看到我们的客户端输出结果如下。

代码可以通过下面的的命令git checkout v0.2获得。但当你启动多个客户端时,你会发现这种实现方式的局限性。
下面启动两个客户端进行演示的输出结果。

你会发现当再使用nc启动一个进程访问服务器的时候是无法与服务器通讯的,因此这种阻塞的实现方式是非常低效的。那么如何才能编写出支持多个客户端的服务器呢?请继续看下去。

socket的多线程实现

为了使每一个客户端都能够得到服务器的响应我们的设计思路是让主线程等待客户端的连接,一旦连接成功就启动一个新的子线程,并且把读写相关的操作都扔给子线程处理。好有了思路下面可以编写代码了。

port socket
import threadingclass ThreadSocket(object):""""""def __init__(self, host, port):self.host = hostself.port = portself.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)self.sock.bind((self.host, self.port))def listen(self):self.sock.listen(5)while True:client, address = self.sock.accept()client.settimeout(60)threading.Thread(target=self.handleClientRequest, args=(
client, address)).start()def handleClientRequest(self, client, address):while True:try:data = client.recv(1024)if data:client.send(data)else:raise error("Client has disconnected")except:client.close()if __name__ == '__main__':server=ThreadSocket('',9000)server.listen()

注意在顶部导入threading模块,然后把读写网络套接字部分放入线程回调函数里,你可以使用git checkout v0.3获取这个版本的代码。
下面看看运行的结果。

现在我们的服务器可以多人同时访问,并且能够同时提供服务了。

使用现有的socket实现一个ToDo服务器

在编写api时通常我们都会以实现一个ToDo服务功能作为演示示例,下面我们来简单设计一下。

  • 1.使用字典作为数据库保存用户提交的数据;
  • 2.使用GET方法实现获取数据功能;
  • 3.使用POST实现提交数据功能;

要实现上面这几个点也不难,首先我们先实现第一个目标。使用字典保存我们的数据,我们的服务器将不会再返回一个用户的输入数据,而是返回一个字典格式的数据。实现方式如下。

import socket
import threadingclass ThreadSocket(object):""""""todo_list = {'task_01':'see someone','task_02':'read book','task_03':'play basketball'}def __init__(self, host, port):self.host = hostself.port = portself.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)self.sock.bind((self.host, self.port))def listen(self):self.sock.listen(5)while True:client, address = self.sock.accept()client.settimeout(60)threading.Thread(target=self.handleClientRequest, args=(
client, address)).start()def handleClientRequest(self, client, address):while True:try:data = client.recv(1024)if data:#client.send(data)response = str(self.todo_list)client.send(response)else:raise error("Client has disconnected")except:client.close()if __name__ == '__main__':server=ThreadSocket('',9000)server.listen()

通过字典保存了一些数据,当客户发送数据请求时就把这个字典反馈给客户。在把字典发送给客户之前需要使用str()函数强制转换数据为字符串,不然就会出现问题。这里的代码可以使用git checkout v0.4获得。
下面可以查看运行结果如下所示。

可以看到只要随便输入一些数据,它就会反馈服务器中的数据给客户端。这种请求的方式也太简单了,所以我们现在要使用一些方式进行限制,只有特定的命令才能请求到我们的数据。下面我们来实现一个GET方法。

if data:#client.send(data)if 'GET' in data:response = str(self.todo_list)else:response = 'data no found'client.send(response)
else:raise error("Client has disconnected")

现在只是简单的通过判断客户端的请求数据中是否包含有关键字GET字符,为了能够在客户端中输出结果完后换行,现在我们可以通过下面这种方式优化一下代码。

response = response+'\r\n'
client.send(response)

运行后可以看到改进后的运行结果如下。

为了使GET方法能够起到真正的查询特定数据的作用,可以先构造一种查询方式。

GET/task_id/status

我们设计的查询语句非常简单,每一个域都是通过斜线分割,那么服务器应该如何解析这种查询语句呢?请看下面的实现方式。

if 'GET' in data:method,task_id,status = data.split('/')result = self.todo_list.get(task_id,'no key match')response = str(result)

可以发现,我使用字符串内置函数直接分割客户的请求数据,然后通过简单的字典查询得到用户指定的数据。
全部代码如下:

import socket
import threadingclass ThreadSocket(object):""""""todo_list = {'task_01':'see someone','task_02':'read book','task_03':'play basketball'}def __init__(self, host, port):self.host = hostself.port = portself.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)self.sock.bind((self.host, self.port))def listen(self):self.sock.listen(5)while True:client, address = self.sock.accept()client.settimeout(60)threading.Thread(target=self.handleClientRequest, args=(client,
address)).start()def handleClientRequest(self, client, address):while True:try:data = client.recv(1024)if data:#client.send(data)if 'GET' in data:method,task_id,status = data.split('/')result = self.todo_list.get(task_id,'no 
key match')print 'result',resultresponse = str(result)else:response = 'data no found'response = response+'\r\n'client.send(response)else:raise error("Client has disconnected")except:client.close()if __name__ == '__main__':server=ThreadSocket('',9000)server.listen()

当前版本的代码可以通过git checkout v0.5获得。下面来看运行的结果。

如何更新我们的数据呢?我们需要实现一个POST方法,下面我们可以设计一下我们的POST语句了。

POST/task_id=value/status

可以看到语句设计的非常简单,下面我们来实现它。

if 'GET' in data:method,task_id,status = data.split('/')result = self.todo_list.get(task_id,'no key match')print 'result',resultresponse = str(result)
elif 'POST' in data:method,command,status = data.split('/')key,value = command.split('=')self.todo_list[key] = valueresponse = 'submit success'
else:response = 'data no found'response = response+'\r\n'client.send(response)

所有代码将不再粘贴到文章中,可以通过git checkout v0.6获得,下面是运行截图。

总结

目前我们已经实现了socket在多线程下的一个简单的todo应用,通过一点一点的实现整个框架能够深入理解网络编程的原理。那么现在我们还有什么方面可以改进,还有什么地方需要考虑呢?下面给大家列出下一章将会覆盖的话题。

  • 1.使用正则表达式解析用户输入;
  • 2.使用Thread Local技术;
  • 3.如何让浏览器也可以访问我们的服务器;

相关文章:

python多线程网络编程

背景 使用过flask框架后,我对request这个全局实例非常感兴趣。它在客户端发起请求后会保存着所有的客户端数据,例如用户上传的表单或者文件等。那么在很多客户端发起请求时,服务器是怎么去区分不同的request对象呢?当查看了大量的…...

BFS-走迷宫

题目描述 给定一个 NM 的网格迷宫 G。G 的每个格子要么是道路,要么是障碍物(道路用 1 表示,障碍物用 0 表示)。 已知迷宫的入口位置为 (x1,y1),出口位置为 (x2...

【蓝牙mesh】Lower协议层介绍

【蓝牙mesh】Lower协议层介绍 Lower层简介 Lower协议层用于处理网络层以下的功能,包括节点的广播、重传、路由和网络拓扑等,是实现蓝牙mesh网络的关键协议之一。其中Lower协议层中最主要的一部分工作就是mesh数据的分片和组包。 Lower层是将Upper层发过…...

Java-重排序,happens-before 和 as-if-serial 语义

目录1. 如何解决重排序带来的问题2. happens-before1. 如何解决重排序带来的问题 对于编译器,JMM 的编译器重排序规则会禁止特定类型的编译器重排序。对于处理器重排序,JMM 的处理器重排序规则会要求编译器在生成指令序列时,插入特定类型的内…...

Nginx安装及介绍

前言:传统结构上(如下图所示)我们只会部署一台服务器用来跑服务,在并发量小,用户访问少的情况下基本够用但随着用户访问的越来越多,并发量慢慢增多了,这时候一台服务器已经不能满足我们了,需要我们增加服务…...

【华为OD机试模拟题】用 C++ 实现 - 寻找路径 or 数组二叉树(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 获得完美走位(2023.Q1) 文章目录 最近更新的博客使用说明寻找路径 or 数组二叉树题目输入输出描述示例一输入输出示例二输入输出Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过…...

LINUX学习记录

回顾系列:两天的时间(2023.2.24-2023.2.25)重新学了遍Linux基础课,收获非常多,以前只会一些简单的Linux命令,对shell,git,管道,复杂Linux命令都不熟悉,学完之…...

华为OD机试用Python实现 -【狼羊过河 or 羊、狼、农夫过河】(2023-Q1 新题)

华为OD机试题 华为OD机试300题大纲狼羊过河 or 羊、狼、农夫过河题目描述输入描述输出描述说明示例一输入输出说明Python 代码实现代码实现思路华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址…...

【SAP Abap】X-DOC:SAP ABAP 语法更新之Open SQL

SAP ABAP 语法更新之Open SQL1、前言2、演示1、前言 自从 SAP 推出 SAP ON HANA,与之相随的 AS ABAP NW 7.40 版本以后,ABAP 语法也有了较多的更新,本篇对 Open Sql的语法更新部分做一个DEMO演示。 NW 7.40 以前 OpenSQL 的限制&#xff1a…...

leetcode 困难 —— 数组中的逆序对(分治法)

题目: 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。 题解: ① 我最开始想的蠢方法(会超时,可跳过&#xff…...

02.24:图片的风格转换

Github网址:https://github.com/lengstrom/fast-style-transfer 在anaconda prompt中切换环境命令:activate 环境名 列出所有环境名:conda info --envs 安装环境:conda create -n 环境名 pythonx.x.x 删除某环境:co…...

[SSD综述 1.3] SSD及固态存储技术半个世纪发展史

在我们今天看来,SSD已不再是个新鲜事物。这多亏了存储行业的前辈们却摸爬滚打了将近半个世纪,才有了SSD的繁荣, 可惜很多前辈都没有机会看到。所有重大的技术革新都是这样,需要长期的技术积累,一代一代的工程师们默默的…...

PAT 1023 组个最小数(分数20)题目有bug

目录 题目描述: 题目讲解: 框架构建: 代码部分: 一个bug: 题目描述: 给定数字 0-9 各若干个。你可以以任意顺序排列这些数字,但必须全部使用。目标是使得最后得到的数尽可能小(…...

QML 中的 5 大布局

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 在 QML 中,可以通过多种方式对元素进行布局 - 手动定位、坐标绑定定位、锚定位(anchors)、定位器和布局管理器。 说到 anchors,可能很多人都不太了解,它是 QML 中一个非常重要的概念,主要提供了一种相…...

使用Python进行数据分析——线性回归分析

大家好,线性回归是确定两种或两种以上变量之间互相依赖的定量关系的一种统计分析方法。根据自变量的个数,可以将线性回归分为一元线性回归和多元线性回归分析。一元线性回归:就是只包含一个自变量,且该自变量与因变量之间的关系是…...

我眼中的柔宇科技

关注、星标公众号,直达精彩内容来源:技术让梦想更伟大作者:李肖遥很早就知道了柔宇科技,当时是因为知道创始人刘自鸿,23岁清华本硕毕业,26岁获斯坦福大学电子工程博士学位,历时不超过3年&#x…...

Allegro如何快速把视图居中显示操作指导

Allegro如何快速把视图居中显示操作指导 用Allegro进行PCB设计的时候,为了方便检查和设计,时常需要将视图居中显示。一般地,会使用鼠标的中键进行放大和缩小,或者使用Zoom in和Zoom out来调整视图 Allegro还支持快速将视图居中 具体操作如下 点击View...

搜索相关功能

一、进入搜索页面 1.1 在pages下创建搜索页面为:search 1.2 在index.vue中点击进入搜素页面 onNavigationBarButtonTap(e){if(e.floatleft){uni.navigateTo({url:/pages/search/search})}},1.3 在pages.json中配置搜索页面头部 {"path" : "pages/…...

【从零开始制作 bt 下载器】一、了解 torrent 文件

【从零开始制作 bt 下载器】一、了解 torrent 文件写作背景了解 torrent 文件认识 bencodepython 解析 torrent 文件解密 torrent 文件结尾写作背景 最先开始是朋友向我诉说使用某雷下载结果显示因为版权无法下载,找其他的下载器有次数限制,于是来询问我…...

SystemVerilog-时序逻辑建模(5)多个时钟和时钟域交叉

数字硬件建模SystemVerilog-时序逻辑建模(5)多个时钟和时钟域交叉数字门级电路可分为两大类:组合逻辑和时序逻辑。锁存器是组合逻辑和时序逻辑的一个交叉点,在后面会作为单独的主题处理。组合逻辑描述了门级电路,其中逻…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

如何理解 IP 数据报中的 TTL?

目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...

腾讯云V3签名

想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...

c++第七天 继承与派生2

这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...