原文地址:http://darkbull.net/python/bca/pyiocp/
題記
以前用C++封裝過iocp的python模組,寫過兩篇文章。儘管當前公司的項目最終將部署在linux上,但現在的開發是在window上。用select模型寫了個簡單的net io wrapper,但好像很不穩定,老是出現一些莫名其妙的問題。這兩天,決定把這以前寫的pyiocp代碼翻出來,整整再用。但當我開啟項目的時候,我暈了(大腦缺氧,極需美女給我做人工呼吸!)好久沒用C++了,本來我的C++基礎就不咋的,再加上用了這麼長時間的python,看以前的C++代碼盡然會頭暈,雖然有詳細的注釋,但似乎還是回憶不起來。!@#$$#!@ 以前封裝的pyiocp,只在公司項目的測試中使用過一段時間,問題也比較多,沒用多久就廢了。這兩天時間,我用C語言又重新寫了一個封裝。使用的是gcc。本來想讓代碼在vs下也能編譯通過,但後來發現vc2008對c99的支援極差,所有的變數都必須放在塊語句開始處、執行代碼的前面。我無語,不想對代碼做大調整,一來怕麻煩,二來我覺得把變數放在塊語句的開始處會降低代碼的可讀性。於是不再提供在vc的支援。
實現模式
關於IOCP的資料在網上有一卡車一卡車的,這裡我不再做重複的勞動,只分享一下pyiocp的實現原理。第一次封裝的時候,有一張圖來描述使用pyiocp收發資料的過程:
由可以看出,伺服器接收、發送的資料都通過pyiocp。在實際使用過程中,我發現這種實現模式有一個問題:遊戲伺服器發送的資料包,一般都比較小(最大的也不過幾K),而每個socket的低層協議棧緩衝區大小預設是32K,除非出現下面兩種情況會使緩衝區填滿而收到相應的socket錯誤,一是在極短的時間內發送的資料量超過32K,二是用戶端的網路極差,放在緩衝區裡的資料半天也沒發出去。第一種情況在我們的遊戲伺服器中是不會出現的,而對於第二種情況,如果不及時的關閉這種串連,那麼發送給這個串連的資料將不斷的堆積在記憶體裡,使記憶體佔用越來越大。所以,我覺得沒有必要通過pyiocp發送資料,這樣增加了程式的複雜性的同時,也沒帶來多少好處。索性直接通過socket發送,如果發送的時候收到socket錯誤(在send時很少會發生錯誤,即使出錯,緩衝區滿是主要原因),就把該串連斷掉。我曾經讀我領導寫的代碼時,他就是這麼乾的。所以,新設計之後的pyiocp內部工作模式是這樣的:
除了上面講的實現模式不一樣之後,新封裝的pyiocp使用了記憶體池。記憶體池是一年前寫的,實現參考了python源碼的記憶體池實現。
pyiocp模組的使用
下面是pyiocp模組提供的方法:
- init(): 初始化內部使用的資源。在調用其他函數之前,必須先調用該方法。如果調用成功,返回0,否則,返回一個錯誤碼,可以通過errinfo(errno)來查看錯誤碼的描述。一般在程式啟動的時候調用該方法。
- cleanup(): 釋放內部使用的資源。
- run(ip, port): 啟動pyiocp監聽。參數ip為字串,表示綁定的本地ip地址,參數port是一個整數,表示監聽的連接埠。函數調用成功,返回0,否則返回一個錯誤碼。
- stop(): 停止監聽。調用該方法後,原先在事件隊列中未處理的事件將被清空。
- send(fileno, data): 發送資料。參數fileno表示socket控制代碼,data為發送的資料。上面講到資料是直接發送給用戶端的,那麼與這裡講的豈不矛盾。其實,send內部實現只是簡單地調用winsocket的send()方法,而沒用使用WSASend投遞。所以並不矛盾。函數返回0表示資料發送成功,否則應該通過errinfo(errno)來查看錯誤資訊。
- close(fileno):關閉串連。參數fileno表示將要關閉的socket控制代碼。函數返回0表示操作成功,否則應該通過errinfo(errno)來查看錯誤資訊。
- event():擷取一個事件。如果隊列中沒有事件,返回None。事件是一個包含三個元素的元組,[0]表示事件的類型,0: NET_NEW,1: NET_LEAVE,2: DATA_NEW。[1]表示對應的socket控制代碼。[2]表示相應的資料,NET_NEW事件的資料是ip,port,NET_LEAVE事件的資料為None,DATA_NEW事件的資料為二進位字串。
- count(): 返回當前串連的數量
- errinfo(errno): 查看錯誤碼對應的資訊。
樣本
#!/usr/bin/env python# -*- coding: utf-8 -*-import pyiocpimport timeNET_NEW = 0NET_LEAVE = 1DATA_NEW = 2pyiocp.init()pyiocp.run('127.0.0.1', 1234)while True: event = pyiocp.event() if event is None: time.sleep(0.1) continue type, fileno, data = event if type == NET_NEW: ip = data print '---->', ip elif type == NET_LEAVE: print '<----' else: print 'recv data:', data pyiocp.stop()pyiocp.cleanup()
源碼下載與編譯
pyiocp_new:使用gcc編譯,在編譯的時候,修改makefile中的python版本
pyiocp_old:使用c++編寫,裡面有vs與CodeBlocks的專案檔