python多线程网络编程

news2024/12/19 18:53:30

背景

使用过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 socket

class ThreadSocket(object):
	"""
	
	"""
	def __init__(self, host, port):
		self.host = host
		self.port = port
		self.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 socket

class ThreadSocket(object):
        """
        
        """
        def __init__(self, host, port):
                self.host = host
                self.port = port
                self.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 threading

class ThreadSocket(object):
        """
        
        """
        def __init__(self, host, port):
                self.host = host
                self.port = port
                self.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 threading

class ThreadSocket(object):
        """
        
        """
        todo_list = {
                'task_01':'see someone',
                'task_02':'read book',
                'task_03':'play basketball'

        }

        def __init__(self, host, port):
                self.host = host
                self.port = port
                self.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 threading

class ThreadSocket(object):
        """
        
        """
        todo_list = {
                'task_01':'see someone',
                'task_02':'read book',
                'task_03':'play basketball'

        }

        def __init__(self, host, port):
                self.host = host
                self.port = port
                self.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',result
                                                response = 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',result
     response = str(result)
elif 'POST' in data:
     method,command,status = data.split('/')
     key,value = command.split('=')
     self.todo_list[key] = value
     response = '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.如何让浏览器也可以访问我们的服务器;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/371625.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android 8请求权限时弹窗BUG

弹窗BUG 应用使用requestPermissions申请权限时,系统会弹出一个选择窗口,可进行允许或拒绝, 此窗口中有一个”不再询问“的选择框, ”拒绝”及“允许”的按钮。 遇到一个Bug,单点击“不再询问”,“允许”这个按钮会变…

OpenAPI SDK组件介绍

背景 公司成立以来,积累了数以万计的可复用接口。上层的SaaS业务,原则上要复用这些接口开发自己的业务,为了屏蔽调用接口的复杂性,基础服务开发了apisdk组件,定义了一套声明OpenAPI的注解、注解解析器,实例…

【蓝牙mesh】Bearer层(承载层)介绍

【蓝牙mesh】Bearer层(承载层)介绍 Bearer层简介 蓝牙Mesh协议栈由多个不同的协议层组成,其中最底层的协议就是Bearer层,它负责提供数据传输的底层支持。蓝牙Mesh协议栈的最底层就是BLE协议栈,所以Bearer层是直接与BL…

GO 中的 defer 有哪些注意事项?下

上次一起写了 3 个案例,咱们这一次继续,这一次的会比上一次的稍微不太一样 案例 1 还有一个也非常常用的案例,使用 defer 来捕获异常 ,也就是当程序崩溃的时候,defer 语句可以帮我们兜底,可以捕获异常后按…

vscode 配置 codeql

1、安装配置 codeql 环境 1.1 下载 codeql-cli 和 codeql 标准库 1)下载安装 下载安装 codeql-cli: Releases github/codeql-cli-binaries GitHub 下载 codeql 标准库:https://github.com/gi thub/codeql 下载的安装包解压,codeql 可执…

二,从源代码开始编译安装iperf3

本文目录Linux系统中编译安装基本知识简介第一步,执行configure第二步,执行make第三步,make install其它功能说明Linux系统中编译安装基本知识简介 从前一文章"一,下载iPerf3最新源代码"我们已经知道如何通过git的方式…

Linux系统下命令行安装MySQL5.6+详细步骤

1、因为想在腾讯云的服务器上创建自己的数据库,所以我在这里是通过使用Xshell 7来连接腾讯云的远程服务器; 2、Xshell 7与服务器连接好之后,就可以开始进行数据库的安装了(如果服务器曾经安装过数据库,得将之前安装的…

干货 | 八条“黄金规则”解决RF电路寄生信号

PART 01 接地通孔应位于接地参考层开关处流经所布线路的所有电流都有相等的回流。耦合策略固然很多,不过回流通常流经相邻的接地层或与信号线路并行布置的接地。在参考层继续时,所有耦合都仅限于传输线路,一切都非常正常。不过,如…

MySQL关于NULL值,常见的几个坑

数据库版本MySQL8。 1.count 函数 觉得 NULL值 不算数 ,所以开发中要避免count的时候丢失数据。 如图所示,以下有7条记录,但是count(name)却只有6条。 为什么丢失数据?因为MySQL的count函数觉得 Null值不算数,就是说…

Shader(着色)

1.深度测试(Z-Buffer )每个像素需要一个深度来排序是否需要渲染,所以需要额外的buffer来存储,frame buffer 存颜色,depth buffer (z-buffer) 存深度。2.Lambert(漫反射)3.Blinn-Phong (高光)4.环…

Netty权威指南总结(一)

一、为什么选择Netty:API使用简单,开发门槛低,屏蔽了NIO通信的底层细节。功能强大,预制了很多种编解码功能,支持主流协议。定制能力强,可以通过ChannelHandler对通信框架进行灵活地拓展。性能高、成熟、稳定…

一文搞定Android Vsync原理简析

屏幕渲染原理"现代计算机之父"冯诺依曼提出了计算机的体系结构: 计算机由运算器,存储器,控制器,输入设备和输出设备构成,每部分各司其职,它们之间通过控制信号进行交互。计算机发展到现在,已经出…

【Python知识点桂电版】01基本数据类型

一、变量变量定义注:查看变量类型->type(变量)查看变量地址->id(变量)变量命名规则只允许出现:英文、中文、数字、下划线(不推荐用中文,不能以数字开头)大小写敏感不可使用关键字(如and)和…

代码随想录【Day24】| 开始回溯!77. 组合

回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。 那么既然回溯法并不高效为什么还要用它呢? 因为没得选&#xf…

cesium: 设置skybox透明并添加背景图 ( 003 )

第003个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中设置skybox透明并添加背景图。 我们不想要黑乎乎的背景,想自定义一个背景图,然后前面显示地球。 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共70…

奇妙的background-clip:text

我们在学习CSS3时,一个背景属性background-clip用来对背景进行裁剪,即指定背景绘制的区域,通常我们使用的几个属性如下:值说明border-box默认值。背景绘制在边框方框内(剪切成边框方框)。padding-box背景绘…

【C++入门(上篇)】C++入门学习

前言: 在之前的学习中,我们已经对初阶数据结构进行相应了学习,加上之前C语言的学习功底。今天,我们将会踏上更高一级“台阶”的学习-----即C的学习!!! 文章目录1.C 简介1.1什么是C1.2.C的发展史…

【数据库】join SQL语句原理优化

背景 在实际的开发中,业务相关表都是通过uid或者一个可以标记业务领域的一个属性转换成的字段进行关联的,但是对于一些后续的业务,比如数据分析、下游系统使用、金融对账等业务,需要进行多表联查,之前实际生产的时候就…

【安卓开发】内容提供器

内容提供器实现了不同程序之间实现数据共享的功能。 7.2 运行时权限 安卓6.0版本后引入了运行时权限 每个权限都属于一个组&#xff0c;授权了其中一个&#xff0c;一个组内的权限都将会被授权。 测试代码 // AndroidManifest.xml中加入以下代码 <uses-permission andr…

魔改hustoj源码使其支持显示队名和队员及女队标志

0. 起因&需求 本文涉及到的开源项目Github地址&#xff1a;https://github.com/zhblue/hustoj 事件的起因是&#xff0c;计算机学院要举办一个院级的ACM比赛&#xff0c;然后捏… 老师给我提了一个需求&#xff0c;就是能不能把比赛排行榜显示的队名下标注对应的队员&…