您當(dāng)前的位置是:  首頁 > 資訊 > 國內(nèi) >
 首頁 > 資訊 > 國內(nèi) >

完整SIP/SDP媒體協(xié)商概論-ICE初始o(jì)ffer接收詳解

2020-04-13 09:07:16   作者:james.zhu   來源:Asterisk開源派   評論:0  點擊:


  在前面的章節(jié)中,筆者具體討論了關(guān)于發(fā)送初始o(jì)ffer的細(xì)節(jié)。這里,我們將討論接收初始o(jì)ffer的一些具體內(nèi)容。關(guān)于接收初始o(jì)ffer的流程主要包括幾個部分的討論:驗證ICE支持,決定主控/被控方角色,采集候選地址,候選地址優(yōu)先級排序,選擇默認(rèn)的候選地址,SDP解碼, 檢查列表的構(gòu)建和定時檢查。其中,在接收offer的內(nèi)容中,候選地址采集,候選地址排序,選擇默認(rèn)候選地址和SDP解碼和前面關(guān)于發(fā)送offer的流程非常相似,因此,在本章節(jié)中,這些內(nèi)容的介紹可能相對比較簡潔,筆者將花費更多時間在驗證ICE,決定接收角色,檢查列表構(gòu)建和定時檢查的討論中。下面,我們將根據(jù)agent接收offer的處理步驟,開始討論這些具體的處理流程。
  1、驗證ICE支持能力
  Agent收到初始o(jì)ffer以后,首先,它需要驗證ICE的支持能力。對于每個在SDP中收到的媒體流來說,如果媒體流的每個構(gòu)件的默認(rèn)目的地地址出現(xiàn)在了候選地址屬性中,agent將會根據(jù)RFC5245規(guī)范中ICE的處理流程來進行處理。如果我們進一步做具體理解的話,我們可以參考RTP的處理方式來說明。例如,使用RTP時,在c行和m=行的IP地址和端口出現(xiàn)在了候選地址屬性參數(shù)中;使用RTCP時,RTCP值也會出現(xiàn)在候選地址屬性中。如果前面所說的這個條件不成立,或者這些媒體構(gòu)件沒有出現(xiàn)在候選地址屬性中的話,agent必須按照另外一個流程來處理SDP(RFC3264)值,而無需按照ICE的處理機制來處理。這個流程就是我們在前面文章中所介紹的關(guān)于SIP針對offer/answer交互模式來處理。讀者可以查看歷史文檔來回顧筆者介紹的相關(guān)基礎(chǔ)內(nèi)容。如果按照RFC3264處理的話,讀者也需要注意幾個例外條件:
  所有agent必須遵從RFC5245-10中的會話存活保持流程。
  如果agent沒有根據(jù)RFC5245 ICE的處理流程處理的話,是因為有a=候選地址屬性,但是這些a=候選屬性沒有匹配媒體流的默認(rèn)目的地地址,agent在其answer消息中必須包括一個a=icemismatch屬性(無匹配)。
  如果默認(rèn)候選地址是一個從TURN服務(wù)器學(xué)習(xí)獲得的轉(zhuǎn)發(fā)地址,agent必須在TRUN服務(wù)器端創(chuàng)建一個授權(quán)許可,這個許可支持SDP中收到的,從對端的peer學(xué)習(xí)到的IP地址。大家需要注意,如果權(quán)限設(shè)置有問題的話,對端發(fā)過來的初始數(shù)據(jù)包將會丟失。
  2、決定主控/被控制方角色
  在上一篇文章中,筆者介紹了主控agent和被控方agent的角色。在會話中,每個agent需要充當(dāng)一個角色來執(zhí)行不同的操作流程。主控方負(fù)責(zé)選擇最終候選配對和被控方進行通信,如果是全部署環(huán)境下的agent,這表示ICE使用挑選的候選配對對每個媒體流進行傳輸,并且,當(dāng)agent需要時,可基于ICE的選擇生成更新的offer消息。如果是輕量級的部署環(huán)境中的agnet,選定為主控方agent表示選擇了候選地址配對,這個候選配對是基于offer和answer消息中的配對(在IPv4中僅有一對),并且,當(dāng)agent需要時,生成一個更新的offer消息來反映這個選擇。被控方agent被告知使用的候選配對來傳輸媒體流,并且針對這個單個信息不會生成一個更新offer。下面,筆者將討論決定雙方角色的規(guī)則和處理流程對雙方的的影響。關(guān)于決定雙方角色的規(guī)則和其流程的影響,事實上,這取決于雙方agent所處的部署環(huán)境,這里有三種不同的部署環(huán)境需要討論:雙方都在全部署場景,雙方各自在全部署場景/輕量級部署場景,雙方都在輕量級部署場景。
  雙方都在全部署場景中,如果一個agent生成了一個offer消息,并且啟動了ICE處理流程,這個agent必須扮演一個主控方agent的角色。另外一側(cè)agent則必須扮演被控方角色。雙方agent將會構(gòu)建檢查列表,運行ICE狀態(tài)機和連接檢查的流程。主控方將會根據(jù)全部署場景流程執(zhí)行處理邏輯,挑選候選配,ICE根據(jù)這些候選配對提供進一步的選擇,最后雙方agent通過更新offer來更新或者結(jié)束ICE處理流程。當(dāng)然,在實際部署場景中,不排除一些特殊使用環(huán)境,例如因為其他通信因素,雙方的角色認(rèn)定發(fā)生沖突。雙方agnet錯誤地認(rèn)為自己是主控方或者自己是被控方。因此,為了避免類似情況的發(fā)送,每個agent必須選擇一個任意號碼,RFC5245規(guī)范稱之為tie-breaker,在連接檢查中使用此數(shù)值檢測修復(fù)這種情況,其取值范圍一律發(fā)布在0和(2**64) - 1之間(它是一個64位的正整數(shù))。
  雙方一方是全部署場景agent,另外一方是輕量級部署場景agent中,全部署場景的agent必須扮演主控方agent的角色,輕量級agent則必須扮演被控方agent的角色。全部署agent將會構(gòu)建檢查列表,運行ICE狀態(tài)機和生成連接檢查。主控方將會根據(jù)全部署場景流程執(zhí)行處理邏輯,挑選候選配對,ICE根據(jù)這些候選配對提供進一步的選擇。輕量級agent將會監(jiān)聽連接檢查,接收響應(yīng)檢查消息,按照輕量級部署的結(jié)束ICE處理流程,最后對ICE進行結(jié)束處理。因為雙方的角色不同,從某種程度來說,主控角色一般都是一直處于運行狀態(tài)。因此,對輕量級部署agent來說,針對每個媒體流來說,ICE處理狀態(tài)被認(rèn)為是運行狀態(tài),所有ICE流程也是運行狀態(tài)。
  Agent雙方都是輕量級部署的agent的話,如果一個agent生成了offer消息,并且啟動了ICE處理流程,這個agent必須扮演一個主控方agent的角色。另外一側(cè)agent則必須扮演被控方角色。這種環(huán)境中,雙方從來都不會發(fā)送連接檢查。準(zhǔn)確地說,一旦雙方的offer/answer交互模式完成以后,每個agent將執(zhí)行ICE結(jié)束處理流程,它們無需經(jīng)過連接檢查的流程。和第一種場景中所描述的一樣,同樣的角色沖突問題也可能出現(xiàn)在雙方都是輕量級部署agent的環(huán)境中。它們都可能認(rèn)為自己是主控方agent或者被控方agent。這種情況下的處理方式和全場景部署中的角色決定的處理方式不同。輕量級部署環(huán)境中角色沖突時,雙方agent通過在信令中承載的offer/answer交互,和交互消息中所支持的檢測能力來確定雙方角色。對輕量級部署agent來說,針對每個媒體流來說,ICE處理狀態(tài)被認(rèn)為是運行狀態(tài),所有ICE流程也是運行狀態(tài)。
  在會話中,一旦雙方角色確定以后,除非ICE重新啟動,否則,它們將一直持續(xù)充當(dāng)各自的角色。補充說明,因為ICE重新啟動以后,雙方需要重新決定各自的角色,如果是全部署場景agent的話,它們需要重新對tie-breaker賦值計算。
  3、候選地址采集
  關(guān)于針對候選地址的采集處理流程,answerer應(yīng)答方和offerer提供方的處理方式是完全一樣的。筆者在前面的文章中已經(jīng)非常詳細(xì)地做出了說明。用戶可以閱讀此文章來了解全部署場景中的場景流程和輕量級部署的要求等內(nèi)容。根據(jù)RFC5245的推薦,提供方收到offer,早于對用戶提醒之前馬上執(zhí)行采集流程。當(dāng)agent啟動時,這樣的候選地址采集方式就可能開始。
  4、候選地址優(yōu)先級排序
  關(guān)于針對候選地址的排序處理流程,answerer應(yīng)答方和offerer提供方的處理方式是完全一樣的。筆者在前面的文章中已經(jīng)非常詳細(xì)地做出了說明。用戶可以閱讀此文章來了解全部署場景中的場景流程和輕量級部署的要求等內(nèi)容。
  5、默認(rèn)候選地址選擇
  關(guān)于針對候選地址的默認(rèn)候選地址選擇的處理流程,answerer應(yīng)答方和offerer提供方的處理方式是完全一樣的。筆者在前面的文章中已經(jīng)非常詳細(xì)地做出了說明。用戶可以閱讀此文章來了解全部署場景中的場景流程和輕量級部署的要求等內(nèi)容。
  6、SDP解碼
  關(guān)于針對SDP解碼的處理流程,answerer應(yīng)答方和offerer提供方的處理方式是完全一樣的。筆者在前面的文章中針對全部署場景和輕量級部署場景的規(guī)定已經(jīng)非常詳細(xì)地做出了說明。用戶可以閱讀此文章來了解全部署場景中的場景流程和輕量級部署的要求等內(nèi)容。
  7、檢查列表構(gòu)建
  構(gòu)建檢查列表是由全部署場景來實現(xiàn)的,如果是輕量級的部署場景,無需構(gòu)建檢查列表。因為offer/answer交互模式的使用,在媒體使用的過程中需要一個檢查列表。為了對媒體流構(gòu)建一個檢查列表,agent需要經(jīng)過幾個必要的步驟來構(gòu)建檢查列表,agent需要經(jīng)過的步驟是:構(gòu)建候選地址配對,計算候選配對優(yōu)先級和排序,優(yōu)選配對,最后計算配對狀態(tài)。接下來,筆者將分別討論這四個主要的步驟。
  首先,為了實現(xiàn)對媒體流的支持,agent選擇自己本地候選地址和從對端peer收到的候選地址進行配對處理。這里,本地候選地址稱之為LOCAL CANDIDATES,遠(yuǎn)端的候選地址稱之為REMOTE CANDIDATES。選擇過程中,agent同時還要考慮安全的問題,為了防止選擇的地址被攻擊,agent可以設(shè)置從offer或answer中接收候選地址的數(shù)量。關(guān)于STUN被攻擊的可能性討論,請讀者參考RFC5245-18,后期文章中我們將討論這個話題,現(xiàn)在不做討論。如果本地候選地址和遠(yuǎn)端候選地址配對成功的話,它們必須具有相同的component ID,和同樣的IP地址版本。當(dāng)然,實際環(huán)境中,本地候選地址和遠(yuǎn)端候選地址也完全可能存在不匹配或者匹配不成功的可能,或者,遠(yuǎn)端候選地址和本地候選地址匹配不成功的可能。有時也可能發(fā)生這樣的問題,例如,針對一個媒體流來說,agent沒有包含候選地址來支持此媒體流的所有構(gòu)件模塊。如果是這樣的情況的話,此媒體流的構(gòu)件數(shù)量就會受到影響而減少,并且會認(rèn)為這個數(shù)量等于雙方agent所要求的構(gòu)件最低數(shù)量,此最低數(shù)量值針對媒體流的所有component構(gòu)件,并且來源于雙方agent所提供的最大component ID(關(guān)于ID取值范圍讀者可參考前面的介紹和歷史文檔)。
  除了上面所介紹的一些情況以外,還有幾個比較特殊的情況和讀者說明。在涉及到RTP/RTCP的使用場景中有可能發(fā)生這樣的情況,一方agent提供了RTCP的候選地址,但是對端可能沒有提供類似地址。有時,offer消息提供方可在同一端口多路復(fù)用RTP/RTCP數(shù)據(jù),并且通過SDP屬性中指示了這樣的實現(xiàn)方式(多路復(fù)用RTP/RTCP參考RFC5761-5和RFC8035)。但是,如果answerer執(zhí)行了多路復(fù)用RTP/RTCP的話,offerer則不知道answerer執(zhí)行了這樣的流程,offerer就會按照默認(rèn)的設(shè)置方式使用各自獨立的RTP和RTCP,這樣處理的結(jié)果就會導(dǎo)致每個媒體流中在offer消息中包含兩個components。answerer方執(zhí)行了多路復(fù)用RTP/RTCP,對每個候選地址來說,它將僅包含單個component。因此,此component將是RTP/RTCP mux的合并值。如果此候選地址只有單個component的話,ICE結(jié)束執(zhí)行候選地址配對。毫無疑問,如果配對中本地候選地址和遠(yuǎn)端候選地址都是默認(rèn)候選地址時,這個候選配對被認(rèn)為是默認(rèn)的候選配對。
  如果agent雙方不是ICE感知的agent的話,媒體流構(gòu)件使用此默認(rèn)候選配對傳輸媒體流。讀者可以參考以下示例來理解check list(檢查列表)核心概念和與其他模塊之間的關(guān)系。
  check list列表和其他模塊之間的關(guān)系匯總
  構(gòu)建候選地址配對完成以后,需要進行后續(xù)地址配對的優(yōu)先級計算。優(yōu)先級計算是根據(jù)以下格式來計算的。其中,G表示主控方agent提供的候選優(yōu)先級,D表示被控制方提供的候選地址優(yōu)先級。這里,G>D?1:0是一個表達(dá)式,如果G大于D,則取值為1,否則為0。
  pair priority = 2 ^ 32 *MIN(G,D) + 2 *MAX(G,D) + (G > D?1:0)
  關(guān)于以上優(yōu)先級的計算,很多開源項目有類似的處理方式,讀者可以參考一些開源項目來做進一步了解。以下一段代碼是一個配對計算源代碼示例,讀者可以參考:
  // PairPriority computes Pair Priority as in RFC 8445  func PairPriority(controlling, controlled int) int64 {
  var (
  g = int64(controlling)
  d=int64(controlled) )
  // pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0) v := (1<<32)*min(g, d) + 2*max(g, d)
  if g > d {
  v++ }
  return v }
  https://github.com/gortc/ice/blob/master/pair.go
  一旦優(yōu)先級被設(shè)定以后,agent就會按照順序?qū)蜻x地址配對進行排序。排序的規(guī)則按照優(yōu)先級順序遞減的方式進行。如果兩組候選地址配對有相同的優(yōu)先級,它們兩組配對的實現(xiàn)可以任意排序。
  獲得了候選地址配對排序以后,此優(yōu)選候選地址會生成一個排序后的配對列表。ICE將會按照排序列表逐一進行連接檢查。在每個檢查流程將會涉及發(fā)送請求的流程,agent需要從本地候選地址發(fā)送檢查請求到遠(yuǎn)端候選地址。這里注意,因為agent不能直接從反射地址發(fā)送請求,它僅能從base基準(zhǔn)地址發(fā)送請求,所以agent需要通過排序后的配對列表來發(fā)送請求。對每個配對來說,如果本地候選地址是一個反射地址的話,base基準(zhǔn)地址必須替換這個反射候選地址。一旦替換流程完成后,agent必須過濾或篩選此列表。過濾配對列表的流程通過移除配對的方式來實現(xiàn)。具體來說,如果它(需要移除的配對)的本地候選地址/遠(yuǎn)端候選地址和優(yōu)先級列表中較高優(yōu)先級的一對配對相同的話,則需要移除配對相同的較低優(yōu)先級的配對。
  另外,為了安全的考慮,防止STUN服務(wù)器被攻擊,agent必須限制連接檢查數(shù)量,在一定的數(shù)值設(shè)置環(huán)境中,agent的檢查將會覆蓋所有連接列表的地址,這個特定數(shù)值必須是可配置的數(shù)值。規(guī)范RFC5245推薦默認(rèn)數(shù)值是100。候選配對列表一直保持低于100的限定設(shè)置,一些低優(yōu)先級的候選配對將被強制丟棄。在可能的情況下,RFC5245推薦盡量使用比較低的設(shè)置限定,在實際生產(chǎn)環(huán)境中也可能看到一些用戶針對配對檢查設(shè)置了最大檢查限定。要求支持可配置的限定設(shè)置也是為系統(tǒng)提供了一個工具,如果發(fā)現(xiàn)問題以后,可以通過此限定值來排查問題。
  完成了候選配對地址的挑選以后,檢查列表需要進行狀態(tài)計算。每個候選配對支持了一個foundation和一個state(狀態(tài))。實際上,這里的foundation是合并了本地候選地址的foundation和遠(yuǎn)端的候選地址的fundation而生成的一個新的fundation。一旦開始計算foundation時,每個配對將會設(shè)定一個狀態(tài)值,根據(jù)檢查結(jié)果的不同,候選配對需要經(jīng)過五個可能或潛在的狀態(tài)計算(歷史文章也有介紹這五個計算步驟)。
  候選配對狀態(tài)計算
  ICE開始運行時,一個候選配對將遷移到以下任何一種狀態(tài)。筆者再次重復(fù)一次每個狀態(tài)的任務(wù)然后介紹具體流程處理:
  • Frozen:處于鎖定狀態(tài),等待檢查
  • Waiting:檢查還沒有啟動,等待從check list中選擇最高優(yōu)先級的候選配對進行處理
  • In-Progress:為這個配對發(fā)送check請求,事務(wù)在處理流程中
  • Succeeded:配對檢查成功,生成成功結(jié)果
  • Failed:配對檢查失敗,既沒有生成任何響應(yīng)也沒有生成任何還原響應(yīng)
  在檢查列表中的每個候選配對的初始狀態(tài)需要經(jīng)過一個狀態(tài)計算。其狀態(tài)計算需要按序經(jīng)過以下幾個步驟:
  • agent將會把每個檢查列表中所有的候選配對設(shè)置為Frozen 封凍狀態(tài)。
  • agent將會為第一個媒體流查詢檢查列表(第一個媒體流出現(xiàn)在SDP offer和answer中的第一個m行中)。然后針對此媒體流做狀態(tài)設(shè)定處理。對所有具有同樣foundation的配對,agent將會設(shè)置一個比較低級別component ID的配對狀態(tài),設(shè)定這些配對進入等待狀態(tài)。如果有多個類似這樣的配對,agent則首先使用具有最高優(yōu)先級的配對。
  這里有兩個特定的列表稱謂需要讀者注意。其中檢查列表中的一部分配對會進入等待狀態(tài),另外一部分則進入到封凍狀態(tài)。如果檢查列表中至少有一對配對是在等待狀態(tài)的,這樣的列表稱之為活動檢查列表。如果檢查列表中所有配對都是封凍狀態(tài)的,這樣的列表稱之為封凍檢查列表。
  除了檢查列表中的配對檢查有狀態(tài)存在,檢查列表自己本身也關(guān)聯(lián)一個狀態(tài),針對正在工作的媒體流,這個狀態(tài)用來捕捉ICE檢查的狀態(tài)。檢查列表具有三種狀態(tài):
  • 運行狀態(tài):針對正在運行的媒體流,ICE檢查狀態(tài)也在運行中。
  • 完成狀態(tài):在這個狀態(tài)下,ICE檢查已經(jīng)生成了一個經(jīng)過挑選的配對支持媒體流構(gòu)件模塊。接下來,ICE成功完成處理任務(wù),開始發(fā)送媒體流。
  • 失敗狀態(tài):在這個狀態(tài)下,針對此媒體流的支持,ICE檢查還沒有成功。
  作為一個offer/answer交互的結(jié)果,檢查列表首先構(gòu)建的就會被ICE遷移到運行狀態(tài)中。ICE處理流程覆蓋所有的媒體流過程中,因此,ICE也和這個流程本身有一個關(guān)聯(lián)綁定的狀態(tài)。當(dāng)ICE運行時,這個狀態(tài)等于運行狀態(tài)。當(dāng)ICE處理流程完成后,這個狀態(tài)將是完成狀態(tài),如果ICE處理流程失敗的話,這個狀態(tài)就是失敗狀態(tài)。關(guān)于以上幾個狀態(tài)之間切換的規(guī)則筆者將在定時檢查的討論中做更多說明。
  8、定時檢查
  前面我們一直在討論關(guān)于check的流程,check是由全部署場景生成的,所以,如果agent是輕量級的部署方式的話,可以跳過此內(nèi)容討論。Agent執(zhí)行兩種check,一種是ordinary checks,另外一種是triggered checks。兩種check都是由一個定時器來控制,針對媒體流數(shù)據(jù),定時器會周期性地觸發(fā)check生成事件。
  除了定時器以外,agent也會維護一個先進先出的隊列,這個隊列稱之為triggered check隊列,隊列維護候選配對,如果有check的機會的話,這個隊列將會發(fā)送配對進行檢查。當(dāng)定時器觸發(fā)以后,agent將會從triggered checks隊列頂部移除一個配對,對這個配對執(zhí)行連接檢查,最后把這對候選配對的狀態(tài)設(shè)置為正在處理的狀態(tài)(In-Progress)。如果在triggered checks隊列中沒有配對的話,agent將會發(fā)送ordinary checks。
  一旦完成構(gòu)建檢測列表計算的流程,agent將會針對每個active check list設(shè)置一個定時器。每Ta*N秒觸發(fā)一次定時器,這里的N是active check list的數(shù)量(初始階段,至少有一個active check list)。Ta和RTO這兩個定時器會在未來的討論中加以介紹,這里不再展開討論。Ta乘以N將允許整個check的吞吐量發(fā)布到所有active check list中。在實際部署環(huán)境中,這個定時器觸發(fā)的頻率可以低于上面的設(shè)置,同時,也應(yīng)該考慮定時器傳播的問題,盡量不要同時對每個媒體發(fā)布定時器。第一個定時器馬上觸發(fā)后,agent在這一瞬間(offer/answer交互模式已完成)執(zhí)行連接檢查,然后在Ta秒后執(zhí)行下一個檢查(因為在第一個定時器觸發(fā)時只有一個active check list)。
  如果定時器觸發(fā)以后,agent沒有triggered checks需要發(fā)送的話,它必須按照以下規(guī)則選擇一個ordinary checks:
  • 找到在等待狀態(tài)的check list中最高優(yōu)先級的配對(注意以下兩種狀態(tài)下配對處理流程)。
  • 如果有這樣的配對的話:首先,從本地候選配對發(fā)送一個STUN check請求到遠(yuǎn)端候選配對。此STUN check請求將會進行相關(guān)處理。關(guān)于STUN check請求的處理流程,筆者在后續(xù)文章中介紹。然后對候選配對的狀態(tài)進行設(shè)置,設(shè)置其狀態(tài)為In-Progress狀態(tài)。
  • 如果沒有這樣的配對的話:agent需要找到在封凍狀態(tài)的check list中最高優(yōu)先級的配對。如果有這樣的配對的話,對此配對解除封凍狀態(tài),然后對此配對執(zhí)行check流程,使其狀態(tài)切換為In-Progress狀態(tài)。如果沒有這樣的配對的話,結(jié)束針對此check list的定時器。
  • 配對流程完成以后,需要保證其檢查數(shù)據(jù)的完整性。如果要計算check消息的完整性,agent需要使用遠(yuǎn)端用戶名稱和密碼來完成計算。遠(yuǎn)端用戶名稱和密碼是agent學(xué)習(xí)遠(yuǎn)端peer發(fā)送的SDP中的用戶名稱和密碼獲得。
  在接下來的章節(jié)中,筆者將繼續(xù)分享初始應(yīng)答接收的話題(驗證ICE支持,決定角色等話題)。
  參考資料:
  https://www.rfc-editor.org/rfc/rfc5245
  https://www.rfc-editor.org/rfc/rfc8445
  https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePairStats/state
  https://github.com/gortc/ice/blob/master/pair.go
  關(guān)注微信公眾號:asterisk-cn,獲得有價值的Asterisk行業(yè)分享
  Asterisk freepbx FreeSBC技術(shù)文檔: www.freepbx.org.cn
  融合通信/IPPBX商業(yè)解決方案:www.hiastar.com
  如何使用FreeSBC,qq技術(shù)分享群:334023047
【免責(zé)聲明】本文僅代表作者本人觀點,與CTI論壇無關(guān)。CTI論壇對文中陳述、觀點判斷保持中立,不對所包含內(nèi)容的準(zhǔn)確性、可靠性或完整性提供任何明示或暗示的保證。請讀者僅作參考,并請自行承擔(dān)全部責(zé)任。

相關(guān)閱讀:

專題

CTI論壇會員企業(yè)