最近在考慮基于udp
做一個用于網(wǎng)游戰(zhàn)斗中的數(shù)據(jù)同步協(xié)議,為了前期測試數(shù)據(jù),決定先做一個外部的代理
tunnel
,原理是在
server
端和
client
端分別建立網(wǎng)絡(luò)轉(zhuǎn)發(fā)
proxy
,即原來的
C/S
連接改為兩個
proxy
之間數(shù)據(jù)快速傳輸。因為
udp
庫是用
C++
寫的代碼,在測試數(shù)據(jù)的時候需要不斷地修改參數(shù),重新編譯,修改輸出統(tǒng)計數(shù)據(jù)制表等,不勝其煩,最終決定導(dǎo)出接口由
python
腳本來進(jìn)行邏輯調(diào)用。
C/C++
導(dǎo)出到
python
有多種方法,根據(jù)不同的需求,可以使用下面不同的方式:
1.ctypes
綁定。
ctypes
就包含在萬能的
python
標(biāo)準(zhǔn)庫模塊里面,它可以運(yùn)行時載入動態(tài)鏈接庫(
dll
,
so
),在
CPython 2.x/3.x
和
PyPy
上都支持。這種方式好處就是不用針對性地用
python api
寫導(dǎo)出函數(shù),可以直接加載動態(tài)鏈接庫的符號表,在
python
中就可以直接調(diào)用了。
2.
第三方的
python binding
。例子有
boost-python
,實現(xiàn)方式是工具自動化用
Python/C api
生成一系列
C++ wrapper
函數(shù)。特別適用于大型的庫或引擎導(dǎo)出到
python
。
3.
手動寫
python binding
函數(shù)。如果對
Python C api
熟悉的話,這種方式應(yīng)該是最靈活的,讀一遍
API
文檔就可以使用。理論上效率應(yīng)該是最好的,但對于我這種
python
初學(xué)者,可能需要花上不少時間。
以之前折騰C
函數(shù)導(dǎo)出到
Lua
腳本的經(jīng)歷,本以為要先研究一番
python c api
,再搞上半天才能搞定。后面發(fā)現(xiàn)
python
標(biāo)準(zhǔn)庫模塊的
ctypes
已經(jīng)非常強(qiáng)大,雖然性能應(yīng)該是三種方式里面最差的,但在這個最高
60fps
的
tunnel
里面,
C/Python
接口邊界調(diào)用的損耗先忽略。跟其他兩種方式設(shè)計不一樣的是,
ctypes
采用的是非入侵式調(diào)用接口的方式,不需要修改原來的
C
接口或者寫一些綁定代碼,直接對編譯出來的動態(tài)庫進(jìn)行調(diào)用。
ctypes
使用過程也是非常愉悅的。
下面介紹下ctypes
的使用:
1.
加載
DLL
動態(tài)鏈接庫
這里需要注意區(qū)分動態(tài)鏈接庫函數(shù)是使用cdecl
還是
stdcall
的調(diào)用約定,分別使用
cdll
或
windll
加載動態(tài)庫。
例如:
#
加載
udp
庫函數(shù)
udp_server = cdll.LoadLibrary("./udp_server.so") init_udp_server = udp_server.init_udp_server destroy_udp_server = udp_server.destroy_udp_server update_udp_server = udp_server.update_udp_server SendMsg = udp_server.SendMsg
SetConnectCallback = udp_server.SetConnectCallback SetDisconnectCallback = udp_server.SetDisconnectCallback SetTimeoutCallback = udp_server.SetTimeoutCallback SetRecvCallback = udp_server.SetRecvCallback
2.
數(shù)據(jù)類型映射
除了ctypes
定義的基本數(shù)據(jù)類型(
c_char, c_int, c_double
等),還能使用
pointer
函數(shù)轉(zhuǎn)換成指針類型。對于要導(dǎo)出的網(wǎng)絡(luò)庫,設(shè)置回調(diào)函數(shù)是必不可少的,在
C++
庫里面,回調(diào)函數(shù)是通過設(shè)置一個函數(shù)指針完成的,
ctypes
同樣支持函數(shù)指針的聲明。如:
recv_cb = CFUNCTYPE( None, c_char_p, c_int )
,表示一個返回值為
void
,參數(shù)為
char*
和
int
類型的回調(diào)函數(shù)。
def
__init__(
self, port, ip="127.0.0.1"):
self._port = port
self._ip = ip
self._clients = {}
self.c_connect_cb = connect_cb(
self.server_connect)
self.c_disconnect_cb = disconnect_cb(
self.server_disconnect)
self.c_timeout_cb = timeout_cb(
self.server_timeout)
self.c_recv_cb = recv_cb(
self.server_recv)
def
create(
self):
if
self._port:
if init_udp_server(
self._ip,
self._port) == 0:
print "server listen %s:%d" % (
self._ip,
self._port)
SetConnectCallback(
self.c_connect_cb )
SetDisconnectCallback(
self.c_disconnect_cb )
SetTimeoutCallback(
self.c_timeout_cb )
SetRecvCallback(
self.c_recv_cb )
return True
print "[error] init_udp_server error",
self._ip,
self._port
return False
綁定回調(diào)參數(shù)需要注意的是,綁定的回調(diào)函數(shù)需要保存為成員變量(上面的寫法),目的是避免python
垃圾回收導(dǎo)致回調(diào)函數(shù)變成野指針。這算是一個小小的坑吧?;旧弦粋€小小的庫也就用到這些功能。
來源:簡書