用select模組實現的socket server

2021-09-21 04:20:32 字數 4359 閱讀 7739

之前筆記裡面記錄的比較亂,最後我寫了乙個類,試著封裝成乙個模組的樣子。

使用的時候通過繼承生成乙個子類,然後呼叫run執行。

你應該需要重構其中的部分方法,另外可能還需要在子類中建立新的方法。

至少需要重構onrecv方法,接收到資料後的處理。

另外要發資料,呼叫send_data介面,把conn連線和bytes型別的data傳入。

import logging

import queue

import select

import socket

class

server

(object):

def__init__

(self, host, port, data_size=1024):

self.host = host

self.port = port

self.data_size = data_size

self.server = socket.socket()

self.server.setblocking(false)

self.server.setsockopt(socket.sol_socket, socket.so_keepalive, 1)

self.server.setsockopt(socket.sol_socket, socket.so_reuseaddr, 1)

self.server.bind((self.host, self.port))

self.server.listen()

logging.critical("監聽已經開啟")

self.inputs = [self.server, ]

self.outputs =

self.data_queue = {}

defrun(self):

while

true:

self.loop()

defloop

(self):

"""呼叫一次select並處理

run方法中就是迴圈呼叫執行此方法

可以重構此方法:

呼叫執行父類中的這個方法後,加入一次迴圈中需要執行的其他步驟

"""readable, writeable, exceptional = select.select(self.inputs, self.outputs, self.inputs)

logging.debug("select返回: "

% (readable, writeable, exceptional))

for r in readable:

if r is self.server:

conn, addr = r.accept()

logging.info("接收到新的客戶端連線: " % (conn, addr))

conn.setblocking(false)

self.data_queue[conn] = queue.queue()

else:

try:

data = r.recv(self.data_size)

except connectionreseterror as e:

logging.error("recv時捕獲到異常: %s" % e)

self.clean(r)

if r in writeable: writeable.remove(r)

except exception as e:

logging.critical("recv時捕獲未知異常: %s" % e)

self.clean(r)

else:

if data: # 如果有收到資料

logging.debug(data)

self.onrecv(r, data)

else: # 可能是收到了空,就是客戶端斷開了

logging.info("客戶端已斷開: %s" % r)

self.clean(r)

# 只在writeable之前才從writeable列表中清除

if r in writeable: writeable.remove(r)

for w in writeable:

try:

data = self.data_queue[w].get_nowait()

except keyerror as e: # 客戶端突然斷開,這個連線可能會同時出現在讀和寫列表中,而讀中已經將它刪掉了

logging.error("獲取訊息佇列時捕獲到異常: %s" % e)

# self.clean(w) # 這裡就是被刪了才出的異常

except queue.empty: # 如果佇列空了才說明訊息都發完了,從讀列表中remove

# if w in self.outputs: self.outputs.remove(w)

self.outputs.remove(w) # 這裡應該不用判斷,一定在列表裡。因為如果不在會包keyerror

except exception as e:

logging.critical("獲取訊息佇列時捕獲未知異常: %s" % e)

self.clean(w)

else:

if data:

try:

w.sendall(data) # 可能一次發不完,所以send之後不從讀列表中remove。下次會發現隊列為空

except connectionreseterror as e:

logging.error("send時捕獲到異常: %s" % e)

except exception as e:

logging.critical("send時捕獲未知異常: %s" % e)

for e in exceptional:

logging.critical("異常列表有返回: %s" % e)

self.clean(e)

defclean

(self, conn):

"""清理客戶端連線資訊

連線斷開時處理以下4步

1. 從讀列表中去掉,不再去監聽這個連線

2. 從寫列表中去掉,如果還有沒發出的訊息,那麼也不再發了

3. 關閉這個連線

4. 從訊息佇列字典中中刪除這個佇列,可能還有未傳送的訊息

可以重構此方法:

如果還有其他在子類中定義的列表或字典需要清理,

那麼重構此方法,呼叫執行父類的次方法後,新增自定義的內容

"""if conn in self.inputs: self.inputs.remove(conn)

if conn in self.outputs: self.outputs.remove(conn)

conn.close()

if conn in self.data_queue: del self.data_queue[conn]

defonrecv

(self, conn, data):

"""收到訊息後呼叫執行此方法

需要重構此方法

否則是呼叫send_data方法,原樣發回

"""self.send_data(conn, data)

defsend_data

(self, conn, data):

"""傳送資料"""

if conn not

self.data_queue[conn].put(data)

if __name__ == '__main__':

server('localhost', 9999).run()

下面是測試併發的客戶端程式,通過多執行緒實現併發。

import socket

import threading

host = 'localhost'

port = 9999

defclient

(i):

client = socket.socket()

client.connect((host, port))

for j in range(500):

msg = "hello %s %s" % (i, j)

client.send(msg.encode('utf-8'))

data = client.recv(1024)

print('received:', data.decode('utf-8'))

client.close()

if __name__ == '__main__':

for i in range(50):

t = threading.thread(target=client, args=(i,))

t.start()

用select實現點菜功能

bin bash echo a.txt ps3 請問您要吃什麼?echo 滿60元打八 dazhe do let sa n done echo 總計 sa元 if sa gt 60 then sa echo scale 4 sa 0.8 bc fiecho 折後 sa元 fenshu select ...

用select模式實現TCP和UDP的混合監聽

selecttestserver.cpp 定義控制台應用程式的入口點。tcp udp復用server select非阻塞模式 ip 127.0.0.1 tcp port 5001 udp port 5000 include stdafx.h include pragma comment lib,ws...

用verilog實現的串列埠通訊模組

串列埠功能 1 8個資料位 1個停止為 無校驗位 2 空閒時資料線為高電平,從高電平跳向低電平表示啟動訊號 3 波特率可以通過parameter引數實現可調 有兩個檔案 uart.v 串列埠模組 module uart 全域性時鐘復位訊號 iclk,irst n,序列資料線 irx,序列接收 otx...