Python基于unix socket的并發(fā)技巧

背景

在開發(fā)Mock中心的過程中及汉,每個(gè)serverclient通訊的時(shí)候志秃,需要使用unix socket這種高效的本機(jī)通訊協(xié)議來交換數(shù)據(jù)圈膏,但是unix socket通訊協(xié)議是基于文件的澳厢,也就是當(dāng)并發(fā)量大的時(shí)候思瘟,單個(gè)文件作為通訊信道會(huì)出現(xiàn)擁堵的情況荸百。

思路

解決的思路很簡(jiǎn)單,不使用單文件作為通訊信道潮太。
TCP協(xié)議中管搪,應(yīng)對(duì)并發(fā)是有多種方式的。最常規(guī)的方式就是以多線程的方式铡买,監(jiān)聽多個(gè)通訊信道更鲁,還有Linux上比較經(jīng)典的epoll的方式。
從本質(zhì)上來說奇钞,多線程的方式澡为,其實(shí)就是開啟了多個(gè)通信信道,在Linux系統(tǒng)底層看來景埃,就是多個(gè)socket文件媒至。而epoll的方式顶别,其實(shí)就是極致的壓榨單信道的性能。
在設(shè)計(jì)這個(gè)通訊方式的時(shí)候拒啰,其實(shí)就是為了簡(jiǎn)便的實(shí)現(xiàn)高性能驯绎。
server監(jiān)聽的時(shí)候,僅監(jiān)聽一個(gè)文件谋旦,但是回包的時(shí)候剩失,client在請(qǐng)求之前先監(jiān)聽一個(gè)文件,然后把文件地址帶到請(qǐng)求串中册着,server在收到這個(gè)請(qǐng)求之后拴孤,回包就不通過原路返回,而是回到這個(gè)client監(jiān)聽的地址甲捏。

代碼

  • client
class UnixSocketUDPServer(object):
    """由于unix socket的特殊模式演熟,如果有返回值的,必須在發(fā)包前監(jiān)聽一個(gè)socket文件"""

    def __init__(self, srv_addr, soc_model=socket.SOCK_DGRAM):
        try:
            os.unlink(srv_addr)
        except OSError:
            if os.path.exists(srv_addr):
                raise EnvironmentError("path is exist")
        self.srv_addr = srv_addr
        self.rsp_data = ""
        self.sock = socket.socket(socket.AF_UNIX, soc_model, 0)
        self.sock.bind(self.srv_addr)

    def __del__(self):
        os.unlink(self.srv_addr)
        
def unpack_package(data):
    """
    unix socket 的解包方法司顿,對(duì)應(yīng)下面的pack_package芒粹。
    由于struck的unpack方法解出來的數(shù)據(jù)都是tuple類型,所以取數(shù)據(jù)的時(shí)候需要注意忽略tuple的第二個(gè)參數(shù)
    :param data:
    :return: tuple免猾,tuple
    """
    total_len, addr_len, body_len = struct.unpack("iii", data[:4 * 3])
    addr = struct.unpack("{addr_len}s".format(addr_len=addr_len), data[12:addr_len + 12])
    body = struct.unpack("{body_len}s".format(body_len=body_len), data[addr_len + 12:])
    return addr, body


def pack_package(addr, body):
    """
    unix socket 的打包數(shù)據(jù)的方法是辕,打包的內(nèi)容是:地址+數(shù)據(jù)。
    打包的格式是: 包總長(zhǎng)度+地址長(zhǎng)度+數(shù)據(jù)長(zhǎng)度+地址+數(shù)據(jù)
    :param addr:
    :param body:
    :return: 打包好的二進(jìn)制數(shù)據(jù)
    """
    addr_len = len(addr)
    body_len = len(body)
    total_len = addr_len + body_len + 12
    _package = struct.pack("iii{addr_len}s{body_len}s".format(addr_len=addr_len,
                                                              body_len=body_len),
                           total_len, addr_len, body_len, addr, body)
    return _package
    
addr = "/tmp/{_addr}.sock".format(_addr=random_utils.get_uuid())
# 這里在請(qǐng)求時(shí)需要先監(jiān)聽一個(gè)unix socket文件猎提。
us = network_utils.UnixSocketUDPServer(addr)
us.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 這里使用unicode把數(shù)據(jù)包起來是為了防止解析后某些數(shù)據(jù)非unicode導(dǎo)致數(shù)據(jù)轉(zhuǎn)換失敗
_req_data = unicode(self.method) + u"|" + unicode(str(getattr(self.t, "m_data")), errors="ignore")
logger.info("===========call unix socket to agent===========")
req_data = string_utils.pack_package(addr, str(_req_data))
logger.info(repr(req_data))
us.sock.sendto(req_data, 0, US_ADDR)
sec = 0
usec = 10000
timeval = struct.pack('ll', sec, usec)
us.sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, timeval)
raw_data, _ = us.sock.recvfrom(20480)    
  • server

s = net_util.unix_socket_server("/tmp/mock_mgr_agent_addr.sock")
while True:
    data = s.recv(20480)
    _addr, _body = unpack_package(data)
    addr = _addr[0]
    body = _body[0]
    
    ......
    
    ret_info = pack_package(addr, str(_ret_info))
    unix_socket_send(addr, ret_info)

說明

  • 打包和解包

這里打包和解包用了strunt方法获三,把字符串打成二進(jìn)制,這樣可以加快傳輸?shù)男氏撬铡M瑫r(shí)也把地址的長(zhǎng)度位和數(shù)據(jù)的長(zhǎng)度位打包進(jìn)去疙教,方便server截取。

  • client

在client的代碼中伞租,先生成了一個(gè)unix socket的對(duì)象贞谓,監(jiān)聽了一個(gè)用uuid生成的隨機(jī)文件地址,然后把這個(gè)地址信息和需要傳遞的數(shù)據(jù)一起發(fā)送給服務(wù)端葵诈。

  • server

服務(wù)端就是傳統(tǒng)的socket服務(wù)裸弦,只是在回包的時(shí)候,發(fā)往的地址是收到的數(shù)據(jù)中的地址作喘。

  • socket端口和地址復(fù)用

一般的理疙,socket綁定了一個(gè)地址,那么就不會(huì)變泞坦,但是我們這里的client端窖贤,需要監(jiān)聽一個(gè)地址的同時(shí),發(fā)送消息到另一個(gè)地址,這里就需要使用這個(gè)socket.SOL_SOCKET赃梧,通過setsockopt滤蝠,對(duì)socket進(jìn)行設(shè)置,允許使用端口和地址復(fù)用授嘀。socket.SO_REUSEADDR這個(gè)參數(shù)提供如下功能:

SO_REUSEADDR允許啟動(dòng)一個(gè)監(jiān)聽服務(wù)器并捆綁其眾所周知端口物咳,即使以前建立的將此端口用做他們的本地端口的連接仍存在。這通常是重啟監(jiān)聽服務(wù)器時(shí)出現(xiàn)粤攒,若不設(shè)置此選項(xiàng)所森,則bind時(shí)將出錯(cuò)。
 
SO_REUSEADDR允許在同一端口上啟動(dòng)同一服務(wù)器的多個(gè)實(shí)例夯接,只要每個(gè)實(shí)例捆綁一個(gè)不同的本地IP地址即可。對(duì)于TCP纷妆,我們根本不可能啟動(dòng)捆綁相同IP地址和相同端口號(hào)的多個(gè)服務(wù)器盔几。
 
SO_REUSEADDR允許單個(gè)進(jìn)程捆綁同一端口到多個(gè)套接口上,只要每個(gè)捆綁指定不同的本地IP地址即可掩幢。這一般不用于TCP服務(wù)器逊拍。
 
SO_REUSEADDR允許完全重復(fù)的捆綁:當(dāng)一個(gè)IP地址和端口綁定到某個(gè)套接口上時(shí),還允許此IP地址和端口捆綁到另一個(gè)套接口上际邻。一般來說芯丧,這個(gè)特性僅在支持多播的系統(tǒng)上才有,而且只對(duì)UDP套接口而言(TCP不支持多播)世曾。

簡(jiǎn)單的理解就是缨恒,這個(gè)參數(shù)后面的值不等于0,就可以在監(jiān)聽的同時(shí)轮听,發(fā)送數(shù)據(jù)到其他地址骗露。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市血巍,隨后出現(xiàn)的幾起案子萧锉,更是在濱河造成了極大的恐慌,老刑警劉巖述寡,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柿隙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鲫凶,警方通過查閱死者的電腦和手機(jī)禀崖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掀序,“玉大人帆焕,你說我怎么就攤上這事。” “怎么了叶雹?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵财饥,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我折晦,道長(zhǎng)钥星,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任满着,我火速辦了婚禮谦炒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘风喇。我一直安慰自己宁改,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布魂莫。 她就那樣靜靜地躺著还蹲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪耙考。 梳的紋絲不亂的頭發(fā)上谜喊,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音倦始,去河邊找鬼斗遏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鞋邑,可吹牛的內(nèi)容都是我干的诵次。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼炫狱,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼藻懒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起视译,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤嬉荆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后酷含,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鄙早,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年椅亚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了限番。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呀舔,死狀恐怖弥虐,靈堂內(nèi)的尸體忽然破棺而出扩灯,到底是詐尸還是另有隱情,我是刑警寧澤霜瘪,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布珠插,位于F島的核電站,受9級(jí)特大地震影響颖对,放射性物質(zhì)發(fā)生泄漏捻撑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一缤底、第九天 我趴在偏房一處隱蔽的房頂上張望顾患。 院中可真熱鬧,春花似錦个唧、人聲如沸江解。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽膘流。三九已至,卻和暖如春鲁沥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背耕魄。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工画恰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吸奴。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓允扇,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親则奥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子考润,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 網(wǎng)絡(luò)編程 一.楔子 你現(xiàn)在已經(jīng)學(xué)會(huì)了寫python代碼糊治,假如你寫了兩個(gè)python文件a.py和b.py,分別去運(yùn)...
    go以恒閱讀 2,024評(píng)論 0 6
  • 說明 本文 翻譯自 realpython 網(wǎng)站上的文章教程 Socket Programming in Pytho...
    keelii閱讀 2,129評(píng)論 0 16
  • 最近在學(xué)習(xí)Python看了一篇文章寫得不錯(cuò)罚舱,是在腳本之家里的井辜,原文如下,很有幫助: 一管闷、網(wǎng)絡(luò)知識(shí)的一些介紹 soc...
    qtruip閱讀 2,725評(píng)論 0 6
  • 7.2 面向套接字編程我們已經(jīng)通過了解Socket的接口粥脚,知其所以然,下面我們就將通過具體的案例包个,來熟悉Socke...
    lucas777閱讀 1,187評(píng)論 0 2
  • 官網(wǎng)投的簡(jiǎn)歷刷允,截至目前視覺接觸得其實(shí)不算很多,投來試試,感受一下面試树灶。 一面 1纤怒、自我介紹2、項(xiàng)目(我這項(xiàng)目確實(shí)沒...
    hdychi閱讀 967評(píng)論 0 1