所有ICE的部署需要符合RFC5389中STUN的規(guī)范(已經(jīng)更新為RFC8489)。大家知道,全部署場景的agent才是ICE部署中核心內(nèi)容。全部署場景agent需要扮演STUN客戶端(生成檢查)和STUN服務(wù)器端(接收檢查)的兩種角色。輕量級的agent則作為服務(wù)器端只能接收檢查(check)。因此,我們所討論的內(nèi)容將以SUTN客戶端生成流程和服務(wù)器端接收流程兩個部分的內(nèi)容進行討論。其中,STUN客戶端流程主要包括:
- 創(chuàng)建權(quán)限支持轉(zhuǎn)
- 發(fā)候選地址發(fā)送請求
- 處理響應(yīng)
服務(wù)器端主要流程包括:
- 全場景部署agent的其他額外步驟處理
- 輕量級部署agent的其他額外步驟處理
筆者會根據(jù)以上的內(nèi)容逐一進行討論。首先,筆者將介紹關(guān)于STUN客戶端的流程處理。
1STUN客戶端流程總覽
這里,筆者首先說明一下關(guān)于STUN客戶端的發(fā)送流程。STUN客戶端發(fā)送連接檢查(connectivity checks),確認連接檢查是一個ordinary還是triggered check。另外,筆者說明一下,以下討論的流程僅支持全部署場景agent。
這里,筆者首先說明一下關(guān)于STUN客戶端的發(fā)送流程。STUN客戶端發(fā)送連接檢查(connectivity checks),確認連接檢查是一個ordinary還是triggered check。另外,筆者說明一下,以下討論的流程僅支持全部署場景agent。
為了保證安全,筆者在前面的文章中也提到過,轉(zhuǎn)發(fā)也需要一個權(quán)限要求。如果使用relayed 本地候選地址發(fā)送連接檢查的話,如果以前沒有創(chuàng)建轉(zhuǎn)發(fā)權(quán)限的話,客戶端必須首先需要創(chuàng)建一個轉(zhuǎn)發(fā)權(quán)限,允許通過relayed本地候選地址進行轉(zhuǎn)發(fā)。如果已經(jīng)有已創(chuàng)建的轉(zhuǎn)發(fā)權(quán)限的話,agent可以使用這個權(quán)限。如果需要創(chuàng)建轉(zhuǎn)發(fā)權(quán)限的話,agent轉(zhuǎn)發(fā)權(quán)限需要根據(jù)一定的流程來實現(xiàn)權(quán)限處理。具體的權(quán)限創(chuàng)建流程讀者可以參與RFC5766-8和9章節(jié)。創(chuàng)建好的權(quán)限必須支持遠端候選地址。一種情況需要特別處理,agent在正常情況下,發(fā)送CreatePermission請求進行連接檢查創(chuàng)建時,RFC5245規(guī)范推薦agent要延遲TURN通道創(chuàng)建,直到ICE完成后再開始創(chuàng)建。一旦權(quán)限創(chuàng)建完成以后,agent必須維護權(quán)限的活動狀態(tài),一直到ICE結(jié)束此流程。
2發(fā)送請求
在STUN客戶端處理流程中,本地候選地址對遠端候選地址發(fā)送綁定請求(Binding request),發(fā)送此請求后生成一個check。關(guān)于綁定請求的具體構(gòu)建和生成,讀者可以參與RFC8489。連接檢查的安全策略必須使用STUN短期安全機制來實現(xiàn)。關(guān)于STUN短期安全機制(用戶名稱/密碼+時間限定)和長期安全機制筆者在以前也做過介紹,讀者也可以查閱RFC8489-9.1章節(jié)了解更多細節(jié)。注意,不能使用RFC3489實現(xiàn)向后兼容的支持能力。連接檢查必須使用FINGERPRINT機制來實現(xiàn)STUN消息區(qū)分。關(guān)于FINGERPRINT機制的定義,讀者可以查閱RFC8489-7。
在STUN客戶端處理流程中,本地候選地址對遠端候選地址發(fā)送綁定請求(Binding request),發(fā)送此請求后生成一個check。關(guān)于綁定請求的具體構(gòu)建和生成,讀者可以參與RFC8489。連接檢查的安全策略必須使用STUN短期安全機制來實現(xiàn)。關(guān)于STUN短期安全機制(用戶名稱/密碼+時間限定)和長期安全機制筆者在以前也做過介紹,讀者也可以查閱RFC8489-9.1章節(jié)了解更多細節(jié)。注意,不能使用RFC3489實現(xiàn)向后兼容的支持能力。連接檢查必須使用FINGERPRINT機制來實現(xiàn)STUN消息區(qū)分。關(guān)于FINGERPRINT機制的定義,讀者可以查閱RFC8489-7。
ICE通過定義四個新屬性拓展了對STUN支持,這四個屬性分別是PRIORITY,USE-CANDIDATE,ICE-CONTROLLED,和ICE-CONTROLLING。在接下來的章節(jié)中筆者分別介紹這四個新定義的拓展屬性。注意,這四個STUN拓展的新屬性僅支持ICE的連接檢查。
3ICE定義的四個新STUN拓展說明
如果agent發(fā)送綁定請求時,它必須在其綁定請求中包含一個PRIORITY拓展屬性。agent必須設(shè)定這個PRIORITY等于候選優(yōu)先級排序中設(shè)定的那個權(quán)限設(shè)置。對于反射候選地址來說,這個優(yōu)先級可以通過檢查結(jié)果的學習來獲得。除了反射候選地址中的偏好類型設(shè)置為此偏好設(shè)置以外,此優(yōu)先級值計算和本地候選地址配對計算也是一樣的。
如果agent發(fā)送綁定請求時,它必須在其綁定請求中包含一個PRIORITY拓展屬性。agent必須設(shè)定這個PRIORITY等于候選優(yōu)先級排序中設(shè)定的那個權(quán)限設(shè)置。對于反射候選地址來說,這個優(yōu)先級可以通過檢查結(jié)果的學習來獲得。除了反射候選地址中的偏好類型設(shè)置為此偏好設(shè)置以外,此優(yōu)先級值計算和本地候選地址配對計算也是一樣的。
在綁定請求中,被控方agent可以包含一個USE-CANDIDATE。但是,主控方一定不能在綁定請求中包含此拓展屬性。此STUN拓展屬性USE-CANDIDATE表示的意思是被控方agent希望退出此構(gòu)件中的檢查,使用來自于此構(gòu)件中的候選配對執(zhí)行檢查。這里涉及了一個配對推薦的具體流程,筆者在未來的文章中將具體討論此指導(dǎo)原則和此STUN拓展的使用。
其余兩個拓展屬性根據(jù)角色不同選擇包含不同的屬性。具體來說,如果agent是在一個主控方角色中的話,在agent發(fā)送綁定請求時,它必須在請求中包含ICE-CONTROLLED拓展屬性。如果是在被控方agent中的話,它必須在綁定請求中包含一個ICE-CONTROLLING拓展屬性。當然,兩種角色可能會因為會話不同其內(nèi)容也可能不同,具體關(guān)于其角色決定的內(nèi)容處理,筆者在前面的文章中有非常詳細說明。
4構(gòu)建安全信息
除了agent發(fā)送綁定請求中需要各自包含所需要的拓展屬性以外,agent同時也需要發(fā)送一定的安全信息來實現(xiàn)安全驗證。綁定請求作為一種連接檢查,它也必須使用STUN短期安全機制。STUN短期安全機制需要用戶名稱和密碼。安全策略中的用戶名稱是本地agent的用戶名稱和遠端peer的用戶名稱的合并名稱(通過括號分開),密碼是遠端peer提供的密碼。這里的策略設(shè)置比較迷惑,讀者一定要注意。
除了agent發(fā)送綁定請求中需要各自包含所需要的拓展屬性以外,agent同時也需要發(fā)送一定的安全信息來實現(xiàn)安全驗證。綁定請求作為一種連接檢查,它也必須使用STUN短期安全機制。STUN短期安全機制需要用戶名稱和密碼。安全策略中的用戶名稱是本地agent的用戶名稱和遠端peer的用戶名稱的合并名稱(通過括號分開),密碼是遠端peer提供的密碼。這里的策略設(shè)置比較迷惑,讀者一定要注意。
連接檢查用戶安全構(gòu)件組合具體連接檢查的處理方式如上圖示例,根據(jù)連接檢查的方向不同,用戶名稱和密碼的選擇組合是不同的。如果從L側(cè)發(fā)起,L端agent需要對R端agent進行連接檢查的話,它的用戶名稱組合方式是(RFRAG:LFRAG),密碼是RPASS;反之亦然,如果從R端發(fā)起連接檢查,其用戶名稱組合是(LFRAG:RFRAG),密碼是LPASS密碼。返回響應(yīng)時,響應(yīng)處理使用的用戶名稱/密碼和請求中的相同(USERNAME屬性不會出現(xiàn))。
5區(qū)分服務(wù)處理
如果agent在媒體數(shù)據(jù)中(例如在QoS中)使用了區(qū)分服務(wù)-Diffserv(遵從RFC2475),agent也應(yīng)該對連接檢查使用同樣的標識方式。因為區(qū)分服務(wù)不在我們討論的范圍,具體關(guān)于Diffserv,讀者查閱RFC2475,這里不做進一步討論。
如果agent在媒體數(shù)據(jù)中(例如在QoS中)使用了區(qū)分服務(wù)-Diffserv(遵從RFC2475),agent也應(yīng)該對連接檢查使用同樣的標識方式。因為區(qū)分服務(wù)不在我們討論的范圍,具體關(guān)于Diffserv,讀者查閱RFC2475,這里不做進一步討論。
6處理響應(yīng)
當收到響應(yīng)以后,這個響應(yīng)需要使用事務(wù)ID和其綁定請求關(guān)聯(lián)在一起。這個綁定關(guān)聯(lián)的處理流程在RFC5389或RFC8489中定義。然后agent把響應(yīng)綁定到候選配對上,這個候選配對是綁定請求以前發(fā)送的候選配對。針對具體的STUN使用中關(guān)于失敗響應(yīng)和成功響應(yīng)的不同場景,這個部分定義了額外的步驟來處理這些流程。下面,我們專門針對失敗響應(yīng)處理和成功響應(yīng)處理做進一步的介紹。
當收到響應(yīng)以后,這個響應(yīng)需要使用事務(wù)ID和其綁定請求關(guān)聯(lián)在一起。這個綁定關(guān)聯(lián)的處理流程在RFC5389或RFC8489中定義。然后agent把響應(yīng)綁定到候選配對上,這個候選配對是綁定請求以前發(fā)送的候選配對。針對具體的STUN使用中關(guān)于失敗響應(yīng)和成功響應(yīng)的不同場景,這個部分定義了額外的步驟來處理這些流程。下面,我們專門針對失敗響應(yīng)處理和成功響應(yīng)處理做進一步的介紹。
7失敗響應(yīng)場景和成功響應(yīng)場景詳解
失敗響應(yīng)有很多種。如果STUN事務(wù)生成一個487錯誤碼,這表示agent角色沖突。agent需要檢查在請求綁定中是否包含了前面所說的拓展屬性ICE-CONTROLLED或ICE-CONTROLLING。如果請求包含的是ICE-CONTROLLED屬性,agent還沒有完成角色切換的話,它必須切換到主控方角色。如果請求包含的是ICE-CONTROLLING屬性,agent還沒有完成角色切換的話,它必須切換到被控方角色。一旦agent完成切換的話,它必須對生成487錯誤的候選配對進行入隊處理,然后這個隊列進入到triggered check queue中。然后將此配對狀態(tài)設(shè)置為等待狀態(tài)。當triggered check發(fā)送以后,它將包含一個ICE-CONTROLLED或ICE-CONTROLLING屬性,這個屬性反映它新的角色。這里注意,tie-breaker的值一定不能重選。
失敗響應(yīng)有很多種。如果STUN事務(wù)生成一個487錯誤碼,這表示agent角色沖突。agent需要檢查在請求綁定中是否包含了前面所說的拓展屬性ICE-CONTROLLED或ICE-CONTROLLING。如果請求包含的是ICE-CONTROLLED屬性,agent還沒有完成角色切換的話,它必須切換到主控方角色。如果請求包含的是ICE-CONTROLLING屬性,agent還沒有完成角色切換的話,它必須切換到被控方角色。一旦agent完成切換的話,它必須對生成487錯誤的候選配對進行入隊處理,然后這個隊列進入到triggered check queue中。然后將此配對狀態(tài)設(shè)置為等待狀態(tài)。當triggered check發(fā)送以后,它將包含一個ICE-CONTROLLED或ICE-CONTROLLING屬性,這個屬性反映它新的角色。這里注意,tie-breaker的值一定不能重選。
角色切換以后,需要進一步的計算。因為不同角色具有不同的功能,因此agent需要重新計算配對優(yōu)先級(前面文章有介紹)。另外,角色切換還會影響agent所負責的其他功能,例如基于ICE結(jié)果選擇推薦配對,和生成更新的offer消息。除了ICMP錯誤以外,因為環(huán)境不同,可能STUN事務(wù)還能生成其他的錯誤響應(yīng)。Agent也可以支持針對連接檢查的ICMP錯誤接收的工作。如果STUN事務(wù)生成ICMP錯誤,agent將會設(shè)置此配對為錯誤狀態(tài)。如果STUN事務(wù)生成的錯誤是不可恢復(fù)的錯誤或者超時錯誤的話,agent也會設(shè)置此配對的狀態(tài)為錯誤狀態(tài)。關(guān)于不可恢復(fù)的錯誤,讀者可以查閱RFC5389-7.3.4章節(jié)關(guān)于錯誤的詳解。
Agent必須檢查源IP地址和響應(yīng)端口是否等同于目的地地址和端口(目的地地址和端口是綁定請求發(fā)送過去的地址和端口),并且響應(yīng)中的目的地地址和端口是否匹配源地址和端口(源地址和端口是從綁定請求中發(fā)送的)。換句話說,在請求和響應(yīng)中的源地址和目的地傳輸?shù)刂肥菍ΨQ的。如果這兩組地址不對稱,agent將會設(shè)置此配對是失敗狀態(tài)。
以上筆者討論的是檢查失敗響應(yīng)的處理。現(xiàn)在,筆者討論成功響應(yīng)的處理。如果檢查成功的話,檢查成功,以下所有條件必須為真:
- STUN事務(wù)生成成功響應(yīng)。
- 響應(yīng)中的源IP地址和端口等同于目的地IP地址和端口(綁定請求中發(fā)送的)。
- 響應(yīng)中的目的地IP地址和端口匹配源IP地址和端口(從綁定請求發(fā)送的)。
獲得成功響應(yīng)以后,agent需要進一步對響應(yīng)消息和候選配對進行處理。其流程需要經(jīng)過四個步驟的處理。筆者繼續(xù)分別介紹這幾個流程。
第一個步驟是發(fā)現(xiàn)peer的反射候選地址。Agent需要從響應(yīng)中檢查映射地址。如果傳輸?shù)刂凡荒芷ヅ鋋gent獲知的任何本地候選地址,映射地址表示一個新候選地址,此地址就是peer反射候選地址。此反射候選地址和其他的候選地址一樣,同樣具有類型,基準地址,優(yōu)先級和foundation。這四個屬性的計算方式如下:
- 它的類型等同于peer的反射地址。
- 它的基準地址等同于候選配對中的本地候選地址,從地址來自于STUN檢查發(fā)送地址。
- 它的優(yōu)先級設(shè)置為綁定請求中的PRIORITY屬性值。
- 它的foundation是通過foundation計算獲得。
計算以后,peer反射候選地址(peer reflexive candidate)會添加到媒體流的本地候選地址列表中。對此媒體流來說,其用戶名稱和密碼和其他本地候選地址的相同。但是,peer反射候選地址不會和其他遠端候選地址配對。在此流程中,也沒有必要進行反射地址和遠端地址的配對處理。一對有效的配對會立刻通過構(gòu)建有效配對的流程進行處理(此章節(jié)第二個步驟介紹)。如果agent希望peer反射候選地址和其他遠端候選進行配對的話(這個遠端候選地址不在將生成的有效配對內(nèi)),agent可以生成一個更新的offer,在此offer中包含peer反射候選地址。通過這樣的方式,agent可以完成此peer反射候選地址和其他遠端候選地址配對的流程。
發(fā)現(xiàn)了反射候選地址以后,第二步的流程就是構(gòu)建有效配對。agent可以開始構(gòu)建一對候選配對。候選配對中,它的本地候選地址等同于響應(yīng)中的映射地址,它的遠端候選地址等同于綁定請求中發(fā)送的目的地地址。因為它們的有效性已經(jīng)通過STUN連接檢查驗證,這樣的配對稱之為有效配對。讀者需要注意,有效配對可能來自于不同的配對。具體來說,有效配對可能等同于檢查生成的配對,可能等同于檢查列表中不同的配對,或也可能是一對當前不在檢查列表中的配對。如果這個配對是來自于檢查流程生成的配對或者在當前檢查列表的配對,它也可以被添加到有效列表中(VALID LIST)。agent為每個媒體流維護此列表狀態(tài)。在ICE處理流程啟動時,這個列表是為空狀態(tài),檢查流程開始執(zhí)行后在列表中逐漸增加配對,最后生成一個有效候選配對。
Agent經(jīng)常也會遇到一些特殊狀態(tài)-配對不在任何檢查列表中。為什么會出現(xiàn)這樣的情況呢?我們可以回顧一下這樣的情景,檢查列表有一對配對,其本地候選地址從來就不是一個反射候選地址。這種配對已經(jīng)獲得了它們的本地候選地址(這些地址已經(jīng)轉(zhuǎn)換成了服務(wù)器端反射候選地址的基準地址),如果這些配對重疊的話,需要對這些配對進行過濾篩選處理。當針對STUN檢查的響應(yīng)返回時,如果兩個agent之間存在一個NAT時,映射地址將會是一個反射地址。這種情況下,有效配對將有一個本地候選地址,這個地址不能匹配檢查列表中的配對的任何候選地址。
如果一對配對不在任何檢查列表中的話,還要首先進行關(guān)于優(yōu)先級的計算處理。Agent將會為此配對計算優(yōu)先級。優(yōu)先級計算基于每個候選地址優(yōu)先級,其計算方式根據(jù)構(gòu)建檢查列表的流程來進行。本地候選地址的優(yōu)先級基于其類型來決定。如果它不是peer reflexive,優(yōu)先級等同于SDP中候選地址指示的優(yōu)先級。如果它是peer reflexive,優(yōu)先級等同于agent完成的綁定請求中的PRIORITY屬性值。遠端候選地址的優(yōu)先級來自于對端peer的SDP中。如果沒有出現(xiàn)遠端候選地址,檢查流程必須啟動一個triggered check來生成一個遠端候選地址。這種情況下,優(yōu)先級來自于triggered check完成的綁定請求中的PRIORITY屬性值。然后,這個有效配對被添加到有效列表中(VALID LIST)。
完成有效配對的構(gòu)建,agent還要進行第三步處理流程。這個步驟就是更新有效配對的狀態(tài)。Agent設(shè)置配對狀態(tài),這個狀態(tài)是一個生成檢查成功的流程。這里一定要注意,作為一種響應(yīng)結(jié)果,這里的配對(生成檢查流程)和構(gòu)建有效配對是不同的處理方式(前面介紹過)。檢查的成功可能引起其他檢查的狀態(tài)也發(fā)生改變。Agent必須按照以下兩個步驟來執(zhí)行:
- 針對同樣媒體流和同樣foundation,agent修改所有其他封凍狀態(tài)的配對到一個等待狀態(tài)。通常來說,也不總是這樣,它們的其他配對將有不同的component IDs。
- 對此媒體流的每個構(gòu)件來說,如果在有效列表中有一對配對,這個檢查的成功可能對其他媒體流關(guān)閉封凍檢查。注意,按照這一步的流程執(zhí)行的話,對每個媒體流的構(gòu)件來說,不僅是第一次有效列表配對在這種情況下所需要考慮按照這些流程執(zhí)行,每一個后續(xù)檢查獲得的檢查成功結(jié)果獲得的配對都要添加到有效列表中。agent按照順序?qū)ζ渌襟w流查詢檢查列表:
如果檢查列表是在活動狀態(tài),agent修改檢查列表中所有封凍狀態(tài)的配對(它們的foundation匹配了有效列表中等待狀態(tài)的配對的foundation值)。
如果檢查列表是封凍狀態(tài),并且在檢查列表中至少有一對配對,它的foundation匹配了有效列表的配對的foundation,在檢查列表中所有配對中,如果其foundation匹配了有效列表中的一對配對的foundation的話,所有列表中配對的狀態(tài)會設(shè)置為等待狀態(tài)。這樣的處理結(jié)果會導(dǎo)致檢查列表變?yōu)榛顒訝顟B(tài),ordinary checks開始為其工作。具體細節(jié)查閱筆者歷史文檔中的定時檢查設(shè)置。
如果檢查列表是封凍狀態(tài),并且檢查列表中沒有配對,它的foundation匹配有效列表中的配對的foundation的話,agent將執(zhí)行以下處理流程,agent將會對所有的配對分組設(shè)置,所有配對有同樣的foundation,并且,針對每個組設(shè)置,具有最低component ID的則設(shè)置其配對狀態(tài)為等待狀態(tài),如果有一個以上這樣的配對的話,則啟用最高優(yōu)先級的配對。
Agent完成了更新配對狀態(tài)以后,最后agent將會執(zhí)行第四步進行更新推薦配對處理。如果agent是一個被控方agent,它已經(jīng)在綁定請求中包含了USE- CANDIDATE屬性的話,從check生成的有效配對已經(jīng)有一個推薦配對設(shè)置flag,這個設(shè)置為true狀態(tài)。如果此配對的優(yōu)先級在所有標識的配對中具有最高的優(yōu)先級,這個標識則表示此媒體流或所有媒體流將使用這一對有效配對。如果agent是一個主控方agent,這個響應(yīng)可能是triggered check的結(jié)果,這個triggered check在響應(yīng)中返回到請求,此請求自己包含一個USE-CANDIDATE屬性。這樣的情況就是一個更新推薦配對示例,它可能導(dǎo)致對這個配對(這個配對是從原始請求學習獲得)進行推薦flag設(shè)置。
以上介紹了失敗響應(yīng)和成功響應(yīng)的處理。在處理響應(yīng)的流程中,除了對失敗響應(yīng)和成功響應(yīng)進行處理以外,還要對檢查列表和定時器更新進行處理。無論檢查是否是成功還是失敗,最后事務(wù)完成需要更新檢查列表和定時器的狀態(tài)。如果所有在檢查列表中的配對是失敗或者成功的狀態(tài):
- 針對每個媒體構(gòu)件來說,在有效列表中沒有一對配對,檢查列表的這個狀態(tài)設(shè)置為失敗狀態(tài)。
- 對每個封凍的檢查列表,agent以同樣的foundation對所有配對進行分組
- 并且針對每個組,把帶最低component ID的配對的狀態(tài)設(shè)置為等待狀態(tài)。如果有超過一個以上這樣的配對,則使用具有最高優(yōu)先級的配對。
如果在檢查列表中沒有任何配對在封凍或者等待狀態(tài)的話,這個檢查列表則不再認為是活動的檢查列表,并且針對ordinary checks,檢查列表不會在定時器計算中計入N的值。這里N是指活動檢查列表數(shù)量。具體就是流程,讀者可以查閱筆者上一篇歷史文檔關(guān)于設(shè)定定時檢查的討論。
本章節(jié)重點介紹了關(guān)于ICE連接檢查中的STUN客戶端的處理流程。為了避免讓讀者引起歧義,方便讀者閱讀,筆者將STUN服務(wù)器端處理流程獨立分為另外一篇文章發(fā)布,關(guān)于STUN服務(wù)器端的處理流程將在下一篇文章中加以介紹。STUN服務(wù)器端主要流程包括兩個場景的處理流程:首先是關(guān)于全場景部署agent的其他額外步驟處理(檢測和修復(fù)角色沖突,計算映射地址,通過學習獲得peer反射候選地址,Triggered Checks討論和更新推薦配對標識),然后是關(guān)于輕量級部署agent的其他額外步驟處理。
參考資料:
https://www.rfc-editor.org/rfc/rfc5389https://www.rfc-editor.org/rfc/rfc8489https://www.rfc-editor.org/rfc/rfc5766https://ir.nctu.edu.tw/bitstream/11536/43505/1/655201.pdfhttps://www.cisco.com/c/en/us/td/docs/solutions/PA/ICE/icepa125.htmlhttps://dev.w3.org/2011/webrtc/editor/archives/20130830/webrtc.html
關(guān)注微信公眾號:asterisk-cn,獲得有價值的Asterisk行業(yè)分享
Asterisk freepbx FreeSBC技術(shù)文檔: www.freepbx.org.cn
融合通信/IPPBX商業(yè)解決方案:www.hiastar.com
如何使用FreeSBC,qq技術(shù)分享群:334023047, www.freesbc.cn