Python Socket 基础多用户编程
简介
写下这篇小记的原因是想记录一下自己学习Python Socket编程的心路历程。之前在中专的时间学过一些基础的Socket编程,知道了一些比较基础的内容比如基础的socket.bind()
类似简单方法的使用。编写了较为基础的应用程序,例如DNS的客户端(能够发出正确请求,但是解析数据没有成功)。
这次学习呢,是借着大专中Python网络编程课的契机,我决定重新学习一下之前的内容,并且将内容分析整理记录下来。
由于这是一篇小记,因此它包含了我大量的主观想法和猜想在其中。读者可以通过查看文末的知识总结来刨去我的主观看法来获得需要的内容。
起因
那么为什么要重新深入学习Socket编程呢?因为在之前的学习中我发现,我的写出的服务端程序往往只能服务单个用户,而不能用于多个用户,从老师的提醒中我知道了一个东西叫做阻塞。
什么是阻塞?
一开始我也不清楚什么是阻塞,我便有了个猜想,那既然一个Socket只能服务于一个用户,那么阻塞是否就是分隔多个用户的原因呢?因为当时在我的脑海中,我认为用户发出的请求数据是像流一般的东西,它们到达了Socket,就像一堆人要进一个门,而他们只能一个一个进,而这个门就是Socket。但当我去查阅相关内容的时候,阻塞的含义与我想象的内容不同。
那么我们回到正题——什么是阻塞?
阻塞的概念其实并不只是存在Socket编程中,但我们可以用Socket编程举个例子。如同下方的代码,当我们创建Socket之后,conn, address = sock.accept()
,这一行,返回了两个对象,conn
是用于在连接上发送和接受数据而产生的新的socket
对象,而address
则是绑定到对端套接字的地址。
当程序运行到data = conn.recv(1024)
时,此时我们作为服务端正在等待对端发送内容,那么这个等待的时候就处于阻塞状态。只有当客户端发送了内容,有数据返回后,程序才能进行下去。
import socket
data = ''
ip_port = ("localhost", 9999)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, proto=0, fileno=None)
sock.bind(ip_port)
sock.listen(1)
conn, address = sock.accept()
while True:
data = conn.recv(1024)
if str(data,encoding="utf-8") == "exit\n":
break
rep = "你输入的内容是" + str(data, encoding="utf-8")
conn.send(rep.encode("utf-8"))
上为代码样例,下为Netcat
工具测试。
C:\Users\77653>chcp 65001
Active code page: 65001
C:\Users\77653>nc 127.0.0.1 9999
HelloWorld
你输入的内容是HelloWorld
exit
踩坑点:我个人使用Win11环境,喜欢使用PowerShell的终端,此处我使用Netcat工具进行连接,在CMD下能够正常显示中文而在PowerShell中则不能显示中文。原因可能是PowerShell并不支持原始字节流。
CMD需要切换字符集为UTF-8才能正常显示中文,chcp 65001即为切换的命令(临时命令,如需要永久切换则需要更改注册表,再次不多赘述。)
在创建Socket的外部嵌套一个循环即可完成持续创建Socket,不过同时只能服务一个用户。
非阻塞Socket
Python Socket库提供了非阻塞Socket的功能,那么非阻塞Socket和阻塞Socket有什么区别呢?conn, address = sock.accept()
当运行到这一行代码时,程序会阻塞在这一行等待一个连接,而如果我们使用非阻塞Socket则是会报错,并继续向下执行,这意味着我们可以通过try...except
和循环来实现一个简单的服务器。代码如下。
import socket
data = ''
ip_port = ("localhost", 9999)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, proto=0, fileno=None)
sock.setblocking(False) #setblocking方法可以设置Socket类型,设置为False则为非阻塞。
sock.bind(ip_port)
sock.listen(1)
while True:
try:
conn, address = sock.accept()
conn.setblocking(False)
while True:
try:
data = conn.recv(1024)
if str(data, encoding="utf-8") == "exit\n":
conn.close()
break
rep = "你输入的内容是" + str(data, encoding="utf-8")
conn.send(rep.encode("utf-8"))
except BlockingIOError as e:
continue
except BlockingIOError as e:
continue
这样写的好处在于循环是一直在运行的,不会阻塞在某一个方法中,我们可以在循环中运行其他的内容。但这并没有解决服务多用户的问题。接下来我们来思考如何服务多用户。
多用户
那么如何能够支持多用户呢,单个Socket只能支持一个用户,那我们多创建几个Socket不就好了?那我们如何管理多个Socket呢?有两种方法,多线程或使用select库。
select
它可以检查文件描述符的读写情况,因此我们可以利用它来管理我们的Socket,Socket本质上也属于文件,所以也有文件描述符。具体的代码如下。
import select
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('', 8888))
server_socket.listen(5)
print("Listening on port 8888")
read_list = [server_socket]
while True:
readable, writable, errored = select.select(read_list, [], [])
for s in readable:
if s is server_socket:
client_socket, address = server_socket.accept()
read_list.append(client_socket)
print("Connection from", address)
else:
data = s.recv(1024)
if data:
s.send(data)
else:
s.close()
read_list.remove(s)
首先我们在上面的代码定义了一个read_list
,并将server_socket放入其中。
select.select()
是程序中的关键函数,它需要三个可等待对象的可迭代对象作为参数,然后返回三个列表,分别是可读列表、可写列表、错误列表。它的作用是检查文件描述符的状态,在不设置可选参数时,它是阻塞的,当出现可读的文件描述符时阻塞结束。
那么server_socket.listen(5)
执行后,程序开始监听端口,随后在readable, writable, errored = select.select(read_list, [], [])
阻塞,那么当我们连接到端口后,server_socket变为可写状态,程序将继续执行。
那么我们创建的server_socket变为可写状态,程序进入到client_socket, address = server_socket.accept()
,这里我们获得了client_socket,并被加入了read_list,程序继续执行回到readable, writable, errored = select.select(read_list, [], [])
阻塞,如果客户端开始发送数据,那么client_socket变为可读状态,阻塞结束,client_socket被添加到readable中,进行数据的交互。如果server_socket又收到了一个连接,阻塞取消,将继续上面client_socket的过程。
此处十分建议自行调试程序!
下面是select官方文档对方法的描述。
select.select(_rlist_, _wlist_, _xlist_[, _timeout_])[]
This is a straightforward interface to the Unix `select()` system call. The first three arguments are iterables of ‘waitable objects’: either integers representing file descriptors or objects with a parameterless method named fileno() returning such an integer:
- _rlist_: wait until ready for reading
- _wlist_: wait until ready for writing
- _xlist_: wait for an “exceptional condition” (see the manual page for what your system considers such a condition)
多线程
那么除了使用select方法之外,我们还可以通过多线程的方法来控制Socket。以下是一个简单的多线程示例。
import socket
import threading
def user_socket(usersocket):
data = b''
while str(data, encoding="utf-8") != "exit\n":
data = usersocket.recv(1024)
usersocket.send(data)
usersocket.close()
server_address = ('localhost', 9999)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(server_address)
server_socket.listen(1)
print("Listening on port 9999.")
while True:
conn, address = server_socket.accept()
clientsocket = threading.Thread(target=user_socket, args=[conn])
clientsocket.start()
这里的有一个小小的坑,当我在使用clientsocket = threading.Thread(target=user_socket, args=(conn,))
创建线程时,使用了一个偷懒的办法,就是直接写target=user_socket(conn)
,这样是万万不可的,这样会导致程序直接开始调用user_socket
函数,并阻塞在这个函数,而原本线程是不阻塞的,会导致一系列问题,其次是args=(conn,)
这里传入的必须是一个可迭代参数(可以是列表也可以是数组),但是如果传入的是args=(conn)
则会产生错误,可能只有单个元素的元组被直接认定为Socket对象而不是可迭代对象了。
总结
文章涉及的到的内容如下图所示。
参考文献如下:
- https://stackoverflow.com/questions/5308080/python-socket-accept-nonblocking
- https://docs.python.org/3/library/socket.html#other-functions
- https://docs.python.org/3/library/select.html#
- https://docs.python.org/3/library/threading.html
Python Socket 基础多用户编程的更多相关文章
- Python socket 基础(Server) - Foundations of Python Socket
Python socket 基础 Server - Foundations of Python Socket 通过 python socket 模块建立一个提供 TCP 链接服务的 server 可分 ...
- Python socket 基础(Client) - Foundations of Python Socket
Python socket 基础- Foundations of Python Socket 建立socket - establish socket import socket s = socket. ...
- python语法基础-网络编程-TCP协议和UDP协议
############### 网络编程 ############## """ 网络编程 学习了Python基础之后,包括函数,面向对象等,你就可以开发了,你 ...
- python语法基础-并发编程-进程-进程理论和进程的开启
############################################## """ 并发编程的相关概念: 进程 1,运行中的程序,就是进程,程序是没有生 ...
- python语法基础-并发编程-线程-长期维护
############### 线程和GIL,全局解释器锁 ############## """ 线程 为什么会有进程? 主要是能够同时处理多个任务,多个任务还 ...
- python语法基础-并发编程-进程-进程池以及回调函数
############### 进程池 ############## """ 进程池的概念 为什么会有进程池? 1,因为每次开启一个进程,都需要创建一个内存空间 ...
- python语法基础-并发编程-线程-线程理论和线程的启动
####################### 线程介绍 ############################## """ 线程介绍 为什 ...
- python语法基础-并发编程-进程-进程锁和进程间通信
############### 守护进程 ############## """ 守护进程 父进程中将一个子进程设置为守护进程,那么这个子进程会随着主进程的结束而结束 ...
- python语法基础-并发编程-协程-长期维护
############### 协程 ############## # 协程 # 小知识点, # 协程和进程和线程一样都是实现并发的手段, # 开启一个线程,创建一个线程,还是需要开销, ...
- python语法基础-并发编程-进程-其他
############### 多进程的信号量 ############## # 多进程的信号量 from multiprocessing import Process import ti ...
随机推荐
- 【leetcode】剑指offer04二维数组查找
很巧妙地把矩阵转化为二叉搜索树(不过好像没什用) class Solution { public: bool findNumberIn2DArray(vector<vector<int&g ...
- 七个步骤覆盖 API 接口测试
接口测试作为最常用的集成测试方法的一部分,通过直接调用被测试的接口来确定系统在功能性.可靠性.安全性和性能方面是否能达到预期,有些情况是功能测试无法覆盖的,所以接口测试是非常必要的.首先需要对接口测试 ...
- IntelliJ IDEA中我最爱的10个快捷操作
前言 欢迎关注微信公众号「JAVA旭阳」交流和学习 IntelliJ IDEA提供了一些Java的快捷键,同样也可以帮助我们提高日常的开发效率.关于这些快捷操作,你知道那几个呢? 1. psvm/ma ...
- 5、Idea同时选择多处光标进行编辑
1.按住Alt+Shift,然后用鼠标左键点击文本,可以让光标在多个位置出现2.每个光标都会同时输入你正在输入的文本3.ESC退出 搜索 复制
- HttpServletRequest获取参数和文件
从HttpServletRequest中获取上传文件及参数的方法 try { //下面这句必须加,不然报错 MultipartResolver resolver = new CommonsMultip ...
- 每个Java程序员都必须知道的四种负载均衡算法
前言 一般来说,我们在设计系统的时候,为了系统的高扩展性,会尽可能的创建无状态的系统,这样我们就可以采用集群的方式部署,最终很方便的根据需要动态增减服务器数量.但是,要使系统具有更好的可扩展性,除了无 ...
- SSM框架——Spring
Spring 轻量级.非侵入式的框架 支持控制反转(IOC).面向切面编程(AOP) 支持事务的处理和声明.框架整合 1.HelloSpring(入门) 1.1导入依赖 <!-- https:/ ...
- 华为运动健康服务Health Kit 6.9.0版本新增功能揭秘!
华为运动健康服务(HUAWEI Health Kit)6.9.0版本新鲜出炉啦! 一文了解新增功能,快来一起加入Health Kit生态大家庭! 一.更丰富:睡眠呼吸记录健康数据开放 呼吸机是用于为患 ...
- 基于DB Visualizer的hive连接配置
1.准备工作 从CDH官网下载hive使用的连接用jar包 https://www.cloudera.com/downloads/connectors/hive/jdbc/2-5-16.html 解压 ...
- windows11预览版装WSA心得
这两天心血来潮想要装个WSA(安卓windows子系统),原来一直用的安卓模拟器(mumu啊蓝叠啊逍遥啊),但感觉像wsa这种安卓系统与主系统融合的模式更带感,于是开始了我艰苦的安装(瞎捯饬)之路 我 ...