最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

Python Socket 基礎(chǔ)多用戶編程

2023-03-01 18:22 作者:吳小敏63  | 我要投稿

簡介

??寫下這篇小記的原因是想記錄一下自己學(xué)習(xí)Python Socket編程的心路歷程。之前在中專的時間學(xué)過一些基礎(chǔ)的Socket編程,知道了一些比較基礎(chǔ)的內(nèi)容比如基礎(chǔ)的socket.bind()類似簡單方法的使用。編寫了較為基礎(chǔ)的應(yīng)用程序,例如DNS的客戶端(能夠發(fā)出正確請求,但是解析數(shù)據(jù)沒有成功)。

??這次學(xué)習(xí)呢,是借著大專中Python網(wǎng)絡(luò)編程課的契機(jī),我決定重新學(xué)習(xí)一下之前的內(nèi)容,并且將內(nèi)容分析整理記錄下來。

highlighter- code-theme-dark

由于這是一篇小記,因此它包含了我大量的主觀想法和猜想在其中。讀者可以通過查看文末的知識總結(jié)來刨去我的主觀看法來獲得需要的內(nèi)容。

起因

??那么為什么要重新深入學(xué)習(xí)Socket編程呢?因為在之前的學(xué)習(xí)中我發(fā)現(xiàn),我的寫出的服務(wù)端程序往往只能服務(wù)單個用戶,而不能用于多個用戶,從老師的提醒中我知道了一個東西叫做阻塞。

什么是阻塞?

??一開始我也不清楚什么是阻塞,我便有了個猜想,那既然一個Socket只能服務(wù)于一個用戶,那么阻塞是否就是分隔多個用戶的原因呢?因為當(dāng)時在我的腦海中,我認(rèn)為用戶發(fā)出的請求數(shù)據(jù)是像流一般的東西,它們到達(dá)了Socket,就像一堆人要進(jìn)一個門,而他們只能一個一個進(jìn),而這個門就是Socket。但當(dāng)我去查閱相關(guān)內(nèi)容的時候,阻塞的含義與我想象的內(nèi)容不同。

??那么我們回到正題——什么是阻塞?

??阻塞的概念其實并不只是存在Socket編程中,但我們可以用Socket編程舉個例子。如同下方的代碼,當(dāng)我們創(chuàng)建Socket之后,conn, address = sock.accept(),這一行,返回了兩個對象,conn是用于在連接上發(fā)送和接受數(shù)據(jù)而產(chǎn)生的新的socket對象,而address則是綁定到對端套接字的地址。

??當(dāng)程序運(yùn)行到data = conn.recv(1024)時,此時我們作為服務(wù)端正在等待對端發(fā)送內(nèi)容,那么這個等待的時候就處于阻塞狀態(tài)。只有當(dāng)客戶端發(fā)送了內(nèi)容,有數(shù)據(jù)返回后,程序才能進(jìn)行下去。

highlighter- code-theme-dark Bash

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 = "你輸入的內(nèi)容是" + str(data, encoding="utf-8") ? ? ?conn.send(rep.encode("utf-8"))

??上為代碼樣例,下為Netcat工具測試。

cmd

C:\Users\77653>chcp 65001Active code page: 65001C:\Users\77653>nc 127.0.0.1 9999HelloWorld你輸入的內(nèi)容是HelloWorldexit

highlighter- code-theme-dark Dockerfile

踩坑點(diǎn):我個人使用Win11環(huán)境,喜歡使用PowerShell的終端,此處我使用Netcat工具進(jìn)行連接,在CMD下能夠正常顯示中文而在PowerShell中則不能顯示中文。原因可能是PowerShell并不支持原始字節(jié)流。CMD需要切換字符集為UTF-8才能正常顯示中文,chcp 65001即為切換的命令(臨時命令,如需要永久切換則需要更改注冊表,再次不多贅述。)

??在創(chuàng)建Socket的外部嵌套一個循環(huán)即可完成持續(xù)創(chuàng)建Socket,不過同時只能服務(wù)一個用戶。

非阻塞Socket

??Python Socket庫提供了非阻塞Socket的功能,那么非阻塞Socket和阻塞Socket有什么區(qū)別呢?conn, address = sock.accept()當(dāng)運(yùn)行到這一行代碼時,程序會阻塞在這一行等待一個連接,而如果我們使用非阻塞Socket則是會報錯,并繼續(xù)向下執(zhí)行,這意味著我們可以通過try...except和循環(huán)來實現(xiàn)一個簡單的服務(wù)器。代碼如下。

highlighter- code-theme-dark Bash

import socket ? ?data = '' ?ip_port = ("localhost", 9999) ? sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, proto=0, fileno=None) ? sock.setblocking(False) ?#setblocking方法可以設(shè)置Socket類型,設(shè)置為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 = "你輸入的內(nèi)容是" + str(data, encoding="utf-8") ? ? ? ? ? ? ? ? ?conn.send(rep.encode("utf-8")) ? ? ? ? ? ? ?except BlockingIOError as e: ? ? ? ? ? ? ? ? ?continue ? ? ?except BlockingIOError as e: ? ? ? ? ?continue

??這樣寫的好處在于循環(huán)是一直在運(yùn)行的,不會阻塞在某一個方法中,我們可以在循環(huán)中運(yùn)行其他的內(nèi)容。但這并沒有解決服務(wù)多用戶的問題。接下來我們來思考如何服務(wù)多用戶。

多用戶

??那么如何能夠支持多用戶呢,單個Socket只能支持一個用戶,那我們多創(chuàng)建幾個Socket不就好了?那我們?nèi)绾喂芾矶鄠€Socket呢?有兩種方法,多線程或使用select庫。

select

??它可以檢查文件描述符的讀寫情況,因此我們可以利用它來管理我們的Socket,Socket本質(zhì)上也屬于文件,所以也有文件描述符。具體的代碼如下。

highlighter- code-theme-dark Go

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()是程序中的關(guān)鍵函數(shù),它需要三個可等待對象的可迭代對象作為參數(shù),然后返回三個列表,分別是可讀列表、可寫列表、錯誤列表。它的作用是檢查文件描述符的狀態(tài),在不設(shè)置可選參數(shù)時,它是阻塞的,當(dāng)出現(xiàn)可讀的文件描述符時阻塞結(jié)束。

??那么server_socket.listen(5)執(zhí)行后,程序開始監(jiān)聽端口,隨后在readable, writable, errored = select.select(read_list, [], [])阻塞,那么當(dāng)我們連接到端口后,server_socket變?yōu)榭蓪憼顟B(tài),程序?qū)⒗^續(xù)執(zhí)行。

??那么我們創(chuàng)建的server_socket變?yōu)榭蓪憼顟B(tài),程序進(jìn)入到client_socket, address = server_socket.accept(),這里我們獲得了client_socket,并被加入了read_list,程序繼續(xù)執(zhí)行回到readable, writable, errored = select.select(read_list, [], [])阻塞,如果客戶端開始發(fā)送數(shù)據(jù),那么client_socket變?yōu)榭勺x狀態(tài),阻塞結(jié)束,client_socket被添加到readable中,進(jìn)行數(shù)據(jù)的交互。如果server_socket又收到了一個連接,阻塞取消,將繼續(xù)上面client_socket的過程。

highlighter- code-theme-dark

此處十分建議自行調(diào)試程序!

??下面是select官方文檔對方法的描述。

highlighter- code-theme-dark CSS

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。以下是一個簡單的多線程示例。

highlighter- code-theme-dark CSS

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()

??這里的有一個小小的坑,當(dāng)我在使用clientsocket = threading.Thread(target=user_socket, args=(conn,))創(chuàng)建線程時,使用了一個偷懶的辦法,就是直接寫target=user_socket(conn),這樣是萬萬不可的,這樣會導(dǎo)致程序直接開始調(diào)用user_socket函數(shù),并阻塞在這個函數(shù),而原本線程是不阻塞的,會導(dǎo)致一系列問題,其次是args=(conn,)這里傳入的必須是一個可迭代參數(shù)(可以是列表也可以是數(shù)組),但是如果傳入的是args=(conn)則會產(chǎn)生錯誤,可能只有單個元素的元組被直接認(rèn)定為Socket對象而不是可迭代對象了。

總結(jié)

文章涉及的到的內(nèi)容如下圖所示。


Python Socket 基礎(chǔ)多用戶編程的評論 (共 條)

分享到微博請遵守國家法律
中江县| 渭源县| 电白县| 高碑店市| 乌审旗| 维西| 兰州市| 元阳县| 自贡市| 罗田县| 通河县| 灵石县| 平利县| 平乐县| 黄浦区| 沿河| 浏阳市| 阿鲁科尔沁旗| 辛集市| 两当县| 宁都县| 响水县| 永寿县| 池州市| 甘谷县| 天台县| 玉树县| 郎溪县| 民乐县| 青田县| 稻城县| 土默特左旗| 阿拉善右旗| 泗阳县| 巴南区| 河北区| 洛宁县| 防城港市| 禄劝| 榆树市| 进贤县|