網(wǎng)站首頁 編程語言 正文
“?使用python實(shí)現(xiàn)協(xié)議中常見的TCP長連接框架。”
分析多了協(xié)議就會(huì)發(fā)現(xiàn),很多的應(yīng)用,特別是游戲類和IM類應(yīng)用,它們的協(xié)議會(huì)使用長連接的方式,來保持客戶端與服務(wù)器的聯(lián)系,這些長連接,通常是TCP承載的。
如果我們要模擬這個(gè)客戶端的行為,根據(jù)不同應(yīng)用服務(wù)器的實(shí)現(xiàn)情況,有些長連接不是必須的,但有些長連接,就必須去實(shí)現(xiàn)它。例如最近分析的某應(yīng)用,雖然它主要使用HTTP協(xié)議進(jìn)行交互,但它在TCP長連接中傳輸了一些必須的信息,如果不實(shí)現(xiàn)長連接,就會(huì)有很多信息無法處理。
在python中,很容易實(shí)現(xiàn)HTTP協(xié)議,當(dāng)然,也容易實(shí)現(xiàn)TCP協(xié)議,它的TCP實(shí)現(xiàn),使用socket庫就可以了,只是需要注意,TCP長連接中通常傳輸?shù)氖鞘M(jìn)制數(shù)據(jù),協(xié)議非標(biāo)準(zhǔn)的,需要自行根據(jù)協(xié)議分析結(jié)果來封裝數(shù)據(jù)格式。
這里以一個(gè)使用到TCP長連接的協(xié)議為樣例,來給出協(xié)議的TCP長連接框架,大家有需要可以參考實(shí)現(xiàn),當(dāng)然,代碼也是從樣例中摘出來的,并不是完整的。
我的TCP長連接框架,首先是外部的包裝,初始化一些參數(shù),例如長連接使用到的ip端口及socket套接字等:
self.longip='im.langren001.com' self.longport= 6656 self.threadLock = threading.Lock() self.sockmain = socket.socket(socket.AF_INET, socket.SOCK_STREAM); self.longlinktcpstart2() tlonglink = threading.Thread(target=lrsuser.longlinktcpth2,name='mainlink_'+ self.playinfo['uid'], args=(self,)) tlonglink.start() self.threadinfo.append(tlonglink)
這個(gè)里面調(diào)用了兩個(gè)函數(shù),一個(gè)是longlinktcpstart2函數(shù),作用是建立socket連接,并對(duì)一些連接建立初始時(shí)的交互進(jìn)行實(shí)現(xiàn),另一個(gè)是longlinktcpth2函數(shù),是一個(gè)線程,實(shí)現(xiàn)對(duì)連接內(nèi)的數(shù)據(jù)進(jìn)行收發(fā)處理。一般來說,這兩個(gè)可以在一起實(shí)現(xiàn),但為了方便socket異常斷開的處理,分成了兩個(gè)函數(shù)。
?longlinktcpstart2的實(shí)現(xiàn)如下:
def longlinktcpstart2(self): server_address = (self.longip, int(self.longport)) self.savelogs('longlinktcpstart2', 'Connecting to %s:%d.' % server_address) self.sockmain.connect(server_address) self.databuf = b'' message = genbaseinfo.genalive() self.sockmain.sendall(message) message = genbaseinfo.genfirstdata() if len(message)==0: self.savelogs('longlinktcpstart2', 'genfirstdata error ') return False self.sockmain.sendall(message) self.longlinkcnt=2 cnt = 0 while (cnt < 2): try: buf = self.sockmain.recv(2048) sz = len(buf) self.savelogs('longlinktcpstart2', "recv data len "+str(sz) ) if sz > 0: self.databuf +=buf self.dealdatabuf() if cnt == 0: alivemsg = genbaseinfo.genalive() self.sockmain.sendall(alivemsg) self.savelogs('longlinktcpstart2', "sendalive") regtime=int(round(time.time() * 1000))-random.randint(14400000,25200000) regtime=regtime*1000 pcode = self.versionstr + '.0' message = genbaseinfo.genseconddata() if len(message) == 0: self.savelogs('longlinktcpstart2', 'genseconddata error ') return False self.sockmain.sendall(message) self.longlinkcnt = self.longlinkcnt + 1 elif cnt == 1: pcode = self.versionstr + '.0' message = genbaseinfo.genotherdata() if len(message) == 0: self.savelogs('longlinktcpstart2', 'genthirddata error ') return False self.sockmain.sendall(message) self.longlinkcnt = self.longlinkcnt + 1 cnt = cnt + 1 else: self.savelogs('longlinktcpstart2', 'recv data alive') except: # socket.error self.savelogs('longlinktcpstart2', 'socket error,do connect fail') return False return True
這里面的genbaseinfo 相關(guān)的函數(shù)可以忽略,是用來生成發(fā)送的消息數(shù)據(jù)的實(shí)現(xiàn),用自己的函數(shù)去替換即可。dealdatabuf函數(shù)是用來處理收到的消息數(shù)據(jù)實(shí)現(xiàn),這兩個(gè)都要根據(jù)具體的協(xié)議分析情況去實(shí)現(xiàn),注意,生成的用來發(fā)送的數(shù)據(jù)和接收到的需要處理的數(shù)據(jù),都需要按十六進(jìn)制處理,這里不做詳述。
線程longlinktcpth2是一個(gè)循環(huán),協(xié)議不退出,循環(huán)不結(jié)束,實(shí)現(xiàn)如下:
def longlinktcpth2(self): tmalive = 0; r_inputs = set() r_inputs.add(self.sockmain) w_inputs = set() w_inputs.add(self.sockmain) e_inputs = set() e_inputs.add(self.sockmain) tm=int(round(time.time())) self.savelogs('longlinktcpth2', 'enter' ) while (self.quitflag==0): try: r_list, w_list, e_list = select.select(r_inputs, w_inputs, e_inputs, 1) for event in r_list: try: buf = event.recv(2048) sz = len(buf) self.savelogs('longlinktcpth2', "loop recv data len:"+ str(sz) ) if sz > 0: self.databuf += buf self.dealdatabuf() alivemsg = genbaseinfo.genalive() self.sockmain.sendall(alivemsg) self.savelogs('longlinktcpth2', "sendalive") else: self.savelogs('longlinktcpth2', "遠(yuǎn)程斷開連接,do reconnect") r_inputs.clear() time.sleep(3) self.sockmain = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.longlinktcpstart2() r_inputs = set() r_inputs.add(self.sockmain) w_inputs = set() w_inputs.add(self.sockmain) e_inputs = set() e_inputs.add(self.sockmain) except Exception as e: self.savelogs('longlinktcpth2', str(e)) self.threadLock.acquire() if (len(self.msglist) > 0): msg = self.msglist.pop(0) self.threadLock.release() self.sockmain.sendall(msg) self.savelogs('longlinktcpth2',"send a msg") else: self.threadLock.release() tmnow=int(round(time.time())) if tmnow-tm>30: message = genbaseinfo.genotherdata() if len(message) == 0: self.savelogs('longlinktcpth2', 'genalivedata error ') return False self.sockmain.sendall(message) self.savelogs('longlinktcpth2', "send alivemsg"+str(self.longlinkcnt)) self.longlinkcnt = self.longlinkcnt + 1 #這個(gè)要一條連接統(tǒng)一,不能亂,回頭加鎖 tm=tmnow if len(w_list) > 0: # 產(chǎn)生了可寫的事件,即連接完成 self.savelogs('longlinktcpth2',str(w_list)) w_inputs.clear() # 當(dāng)連接完成之后,清除掉完成連接的socket if len(e_list) > 0: # 產(chǎn)生了錯(cuò)誤的事件,即連接錯(cuò)誤 self.savelogs('longlinktcpth2', str(e_list)) e_inputs.clear() # 當(dāng)連接有錯(cuò)誤發(fā)生時(shí),清除掉發(fā)生錯(cuò)誤的socket except OSError as e: self.savelogs('longlinktcpth2', 'socket error,do reconnect') time.sleep(3) self.sockmain = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.longlinktcpstart2() r_inputs = set() r_inputs.add(self.sockmain) w_inputs = set() w_inputs.add(self.sockmain) e_inputs = set() e_inputs.add(self.sockmain) self.savelogs('longlinktcpth2', 'leave')
由于這個(gè)代碼主要是在windows上使用,因此,longlinktcpth2線程采用了select來實(shí)現(xiàn),而沒有使用epoll。在循環(huán)中,對(duì)異常進(jìn)行了處理,如果發(fā)生異常,連接被斷開,則調(diào)用longlinktcpstart2重新連接,而不退出循環(huán),其余的和longlinktcpstart2里面一致。
由于TCP連接是流的概念,因此,需要對(duì)數(shù)據(jù)進(jìn)行緩存拼接,這就是上面代碼中databuf的作用,防止每次收到的數(shù)據(jù)不完整或者太多,方便后續(xù)的處理,這才是一個(gè)合格的碼農(nóng)的信仰的自我升華。
原文鏈接:https://blog.csdn.net/yeyiqun/article/details/122852505
相關(guān)推薦
- 2022-09-15 Python?torch.onnx.export用法詳細(xì)介紹_python
- 2022-06-01 Python中plt.plot()、plt.scatter()和plt.legend函數(shù)的用法示例_
- 2023-01-15 詳解Qt中線程的使用方法_C 語言
- 2022-06-01 配置ABP框架使用對(duì)象映射_實(shí)用技巧
- 2023-11-20 python獲取當(dāng)前路徑所有文件
- 2022-06-16 Air實(shí)現(xiàn)Go程序?qū)崟r(shí)熱重載使用過程解析示例_Golang
- 2022-08-27 .net+FusionChart實(shí)現(xiàn)動(dòng)態(tài)顯示的柱狀圖和餅狀圖_實(shí)用技巧
- 2022-09-27 golang?防緩存擊穿singleflight的實(shí)現(xiàn)_Golang
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支