ortp使用詳解1

來源:互聯網
上載者:User

標籤:io   os   使用   ar   檔案   資料   art   on   問題   

一: 關於 oRTP

oRTP 是一款開源軟體,實現了 RTP 與 RTCP 協議。目前使用 oRTP 庫的軟體主要是linphone(一款基於IP 進行視頻和語音通話的軟體)。

oRTP作為 linphone 的 RTP 庫,為基於 RTP 協議傳輸語音和視頻資料提供保障。

二: 原始碼的構建架構

類似於 mediastream2 中的 filter,在RTP 中也有比較重要的一個結構,就是 payload type,該結構用於指定編碼類別型,以及與其相關的時脈速率、採樣率等一些參數,參見。

圖 2-1 實際上在 RTP 的包頭就有專門的域用來定義當前傳輸的資料是什麼編碼類別型的。在代碼中,不同的媒體類型有不同的 payloadtype 結構體與之對應,像 h263,g729,MPEG4等。因為每種編碼都有其專屬的特點,而且許多參數也不一樣,所以 RTP 包頭中使用 payload 域標記負載的類型,一方面接收端可以就此判斷負載的類型,從而選擇對應的解碼器進行解碼播放;另一方面,代碼在進行時間戳記等方面的計算時可以更加方便一點。

Payloadtype結構體定義了 payload 的許多屬性,比如是音頻還是視頻資料,時鐘採樣率, 每次採樣的位元數,正常的位元速率,MIME類型,通道等等。代碼中已有常見音視頻轉碼器對應的 payloadtype結構體實現,應用程式在初始化 oRTP 庫時,可以根據自己的需求, 選擇其中的一部分添加到系統中。所有系統當前支援的payload 類型都被放在一個數組中, 由全域變數 av_profile 這個結構體執行個體統領,如所示:

圖 2-2 這些 payloadtype 結構體在payload 數組中的位置就是以編碼類別型的定義為索引的。編碼類別型值的定義在RFC3551 第六部分“payload type definitions”進行了描述。Avprofile.c 檔案定義了所有的payload type。而有關payload type 和 profile 的操作在檔案payloadtype.c檔案中實現。

除了 payloadtype 結構體外,一個更重要的結構體是 rtpsession。該結構體即是一個會話的抽象,與會話相關的各種資訊都定義在該結構體上或者能夠通過該結構體找到。要使用oRTP 進行媒體資料的傳輸,需要先建立一個會話,之後所有資料的傳輸都在會話上完成或基於會話完成。rtpsession結構體的定義如下:

圖 2-3 可以看到,這是一個非常大的結構體,從側面說明了要維護的與會話相關的量還是比較多的。

關於該結構體的比較詳細的說明會在後面給出。Session 的初始化通過介面 rtp_session_init 完成,外部獲得一個新的 session是通過調用介面rtp_session_new 完成。關於 session 的其他有關配置和擷取資訊的操作都可以在檔案 rtpsession.c 中找到定義。

使用 oRTP 進行資料轉送時,可以在一個任務上完成多個會話流的接收和發送。這得益於 oRTP 中調度模組的支援。要使用調度模組,應用需要在進行 oRTP 的初始化時對調度進行初始化,將需要調度管理的會話註冊到調度模組中,這樣當進行接收和發送操作時,先向調度詢問當前會話是否可以進行發送和接收,如果不能進行收發操作,則處理下一個會話。 這有點類似I/O 介面上的 select 操作。調度模組使用的資料結構主要為 rtpscheduler,如所示:

圖 2-4 List 儲存了所有要處理的會話,r\w\e 的意義類似於 select,在這裡分別代表接收、發送以及異常。posixtimer.c,rtptimer.c,scheduler.c,sessionset.c等檔案實現了調度模組。資料在底層實際的接收和發送是通過 socket 介面完成的,這些實現在 rtpsession_inet.c 檔案中。為了方便將 oRTP 移植到不同平台上,oRTP 實現了對作業系統介面的封裝,包括常用的任務的建立及銷毀,條件變數及互斥鎖,進程間的管道通訊機制等。這些在port.c 檔案中實現。

除了作業系統相關的介面外,oRTP為了便於內部操作,實現了部分資料結構,一個是雙向鏈表,在檔案utils.c 中;一個是隊列,在檔案 str_utilis.c檔案中。鏈表的實現比較簡單, 隊列的實現相對較複雜一點。首先,隊列資料結構由三部分組成:隊列頭、訊息塊以及資料 塊,圖示如下:

圖 2-5 中從左至右依次為隊列頭,訊息塊和資料區塊。隊列頭指向訊息塊,訊息塊之間可以構成雙向鏈表,這是隊列的基本要素。訊息塊本身不帶buffer,資料是由專門的資料區塊來儲存的, 並被訊息塊指向。是一個初始化後的狀態,訊息塊的讀寫指標都指向資料區塊的buffer 的開始位置。資料區塊的 base 和lim 指標則分別指向 buffer 空間的開始地址和結束位址處。向 buffer 中寫入和讀出資料後的狀態變化如:

圖 2-6 除了向隊列添加訊息塊外,上述資料結構設計還支援向一個訊息塊添加新的訊息塊,這樣可以支援一個訊息塊儲存較大塊的資料,如所示:

圖 2-7 訊息塊的 b_cont 指標用於串連新的訊息塊。

在發送上層應用的 payload 資料之前,oRTP 會構造一個訊息塊,資料指標會指向payload, 這避免了資料拷貝。較低層的介面處理資料時依賴於訊息塊結構。接收後的資料從訊息塊中 拷貝到使用者buffer。接收的 rtp和 rtcp 包的解析處理函數在檔案 rtpparse.c 和 rtcpparse.c 檔案中實現。另外,rtcp.c 檔案實現了 rtcp 資料包的構造處理。

在基於 ip 的音視頻流傳輸中,防震能力是一個重要的特性,這在一定程度上能夠保證使用者有良好的體驗。在 oRTP 中,是通過 jitter 模組完成這部分工作的。相關資料結構如所示:

圖 2-8 要使用 jitter 功能,需要使能 enabled 變數,如果要支援自適應補償,則需要使能 adaptive變數。對於資料轉送過程中產生的一些事件(比如ssrc 發生改變,資料為 dtmf 資料等),在 oRTP中是通過signaltable(訊號表)來處理的。signaltable 關聯了事件類型與其上的回呼函數。 oRTP 使用signaltable處理如下一些事件:ssrc_changed(ssrc發生改變),payload_type_changed (payload type 發生改變),telephone-event_packet(telephone event包到達),telephone-event (telephone 事件),timestamp_jump(timestamp jump事件),network_error(網路錯誤事件), 以及rtcp_bye(rtcp bye 包事件)。使用者可針對這些事件註冊回調處理函數,當底層接收函數接收到 rtp 包後,會對包進行檢查,發現是上述某些事件的話,則觸發回呼函數的執行。rtpsignaltable.c 檔案實現了對該表的操作,包括初始化,添加 callback 刪除 callback 以及執行 callback。

oRTP中對於事件的處理是基於事件結構體和事件隊列的。隊列用於存放事件結構體,結構體用於存放事件的資料。相關的處理在檔案 event.c 中定義。特別的,對於 telephone 事件的處理放在 telephone_event.c 檔案中,其中包括了如何構造用於傳輸telephone_event 的 rtp 包,如何將 telephone 事件添加到包中,如何發送dtmf 資料,以及接收到對應資料包後該如何處理。關於 telephone_event 的構成如所示:

圖 2-9 最左邊的結構體是 rtp 包中存放的有關telephone event 的資料,通過 packet 指標可以找到telephone event的詳細資料。最終放入事件隊列的也是 packet 指向的內容。

在使用oRTP 提供的 rtp 庫之前,需要先對其進行初始化,這部分的實現在 oRTP.c 檔案中。oRTP的初始化主要調用兩個介面:ortp_init 和 ortp_scheduler_init。其中 ortp_init完成了 payload 的註冊,ortp_scheduler_init完成了調度任務的初始化。

三: 有關時間戳記的說明

1 關於 RTP 傳輸中時間戳記的說明(這部分來自於網路)

時間戳記單位:RTP協議中使用的時間戳記,其單位不是秒之類的,而是以採樣頻率為基礎的。這樣做的目的就是為了使時間戳記單位更為精準。比如說一個音訊採樣頻率為 8000Hz, 那麼我們可以把時間戳記單位設為 1 / 8000。

時間戳記增量:相鄰兩個 RTP 包之間的時間差(以時間戳記單位為基準)。採樣頻率: 每秒鐘抽取樣本的次數,例如音訊採樣率一般為8000Hz幀率:每秒傳輸或者顯示幀數,例如 25f/s 在 RTP 協議中並沒有規定時間戳記的粒度,這取決於有效載荷的類型。因此RTP 的時間戳記又稱為媒體時間戳記,以強調這種時間戳記的粒度取決於訊號的類型。例如,對於8kHz 採樣的話音訊號,若每隔20ms 構成一個資料區塊,則一個資料區塊中包含有 160 個樣本(0.02× 8000=160)。因此每發送一個 RTP 分組,其時間戳記的值就增加160。

如果採樣頻率為 90000Hz,則由上面討論可知,時間戳記單位為 1/90000,我們就假設1s 鐘被劃分了 90000 個時間塊,如果每秒發送 25 幀,那麼,每一個幀的發送佔多少個時間塊呢?當然是90000/25 = 3600。因此,我們根據定義“時間戳記增量是發送第二個RTP 包相距發送第一個 RTP 包時的時間間隔”,故時間戳記增量應該為 3600。

關於 RTCP 中 NTP 時間戳記的計算問題:從 1900 年到現在的經過的秒數賦值給 NTP 時間戳記的高 32 位,這個時間的低 32 位通過當前擷取的納秒時間值計算得到。將 1 秒劃分為 2 的 32 次方來表示,則一份子持續的時間大約位 232 皮秒。如果目前時間為 x 秒 232 毫秒, 則232毫秒為232000微妙,232000000納秒,232000 000 000皮秒,即1000 000 000多個 232皮秒。也就是說在NTP時間戳記的低32位劃分的2的32次方個232皮秒塊中佔用了1000 000 000個塊,轉換為16進位表示為3b9aca00,也就是說噹噹前時間的低位為232毫秒的 話,NTP 時間戳記的低 32 位就設定為 3b9aca00。

在 linux 系統中,我們常用的一個時間是 1970 年 1 月 1 日以來的時間所經過的秒數。在 RTCP 中,我們可以將當前所獲得的上述時間加上83AA7E80(十六進位)就是 1900 年 1 月 1 日以來所經過的秒數了。換為十進位,則為 2208988800。計算方法為(70 * 365 + 17) * 24 * 60 * 60。

2 代碼中有關時間戳記變數的說明在資料的接收和發送過程中,用到了許多記錄時間的變數。通過這些時間變數,oRTP完成對 rtp 資料的流控功能。所有這些變數都定義在 rtpstream結構體中,如所示:(這裡只是截取了時間相關的變數)

圖 3-1 下面對這些變數的含義進行集中的說明:

uint32_t snd_time_offset;應用程式發送其第一個時間戳記時的調度器時間

uint32_t snd_ts_offset;被應用程式發送的第一個應用時間戳

uint32_t snd_rand_offset; 添加到使用者offset 上的一個隨機數,用來產生流的時間戳記

uint32_t snd_last_ts; 流上最後發送的時間戳記

前述三個時間變數是 offset 結尾的,分別標記了第一個時間戳記,包括調度器的時間位移, 在應用開始發送資料時,應用發送資料的時間位移,也即是自己的時間戳記,還有一個隨機數用來添加到位移上的,而第四個才是真正標記流裡面當前最新發送的資料的時間戳記。

uint32_t rcv_time_offset; 應用程式詢問其第一個時間戳記時的調度時間,這裡詢問意指擷取接收到的資料包—此應該指開始接收資料時的調度器時間

uint32_t rcv_ts_offset;第一個流的時間戳記----此應該指第一個rtp 包到來時其流上帶的時間戳記值

uint32_t rcv_query_ts_offset;被應用程式詢問的第一個user時間戳記—此應該指應用接收資料流時的時間

uint32_t rcv_last_ts; 應用程式得到的流的最後一個時間戳記—此應該指應用程式收到的最後一個rtp 包的時間戳記,是包裡的時間戳記值, 而非應用自己的時間。

uint32_t rcv_last_app_ts; 被應用程式詢問的最後一個應用時間戳—此處應該指應用收最後一個包時的應用時間,是應用按照 payload 類型及其採樣率增長的時間戳記錄,不是系統時間,也不是包裡的時間

uint32_t rcv_last_ret_ts; 最後一個返回的採樣的時間戳記,僅僅對於連續的音頻而言

接收相對於發送來講存在一個問題,就是接收資料包時當前系統有個時間,資料包裡面也有時間戳記錄的時間,調度器也有記錄時間。而對於發送,當前應用的時間就是給包的時間戳記時間,這兩個值對於發送來講是一樣的。

uint32_t hwrcv_extseq; 在socket 上最後接收的擴充的序號

uint32_t hwrcv_seq_at_last_SR;每次發送報告包後,該變數更新為hwrcv_extseq,因此是最近發送rtcp 報告包時的最高擴充序號。

uint32_t hwrcv_since_last_SR;每收到一個 rtp 包,該變數加 1,在 rtcp 報告報構造好後, 該變數就清為零,因此說明這個變數計數的是從上一個報告包以來接收的rtp 包數目。

根據上面三個變數就可以計算出丟包率。首先,最近一次丟失包數(就是自從上一次sr 或者rr發送以來)通過hwrcv_extseq – hwrcv_seq_at_last_SR – hwrcv_since_last_SR計算得到。 但是丟包率為啥要除以hwrcv_since_last_SR 比較奇怪。這個值是自從上一次發送報告包以來累計接收的包數。這個值不應該就是期望接收的包數。(最高序號減去最初序號)

累計包丟失數通過每次的丟包數累加得到。uint32_t last_rcv_SR_ts;最後一個接收到的 sr 的 NTP 時間戳記,取的是中間的 32bit。這個值也是報告包中上 LSR 值的來源。

struct timeval last_rcv_SR_time;最後一個 sr 被接收到的時間,這個時間是用系統當前的時間來表示的。這個值記錄了接收到最後一個SR時的系統時間,再發送當前報告包時,再次擷取系統目前時間,然後二者相減,得到的值乘以65536 得到以1/65536 為單位的時間值。

uint16_t snd_seq; 發送序號。累加變數,儲存會話的序號的 增長。

uint32_t last_rtcp_report_snt_r;最後一個rtcp 報告發送的時間,按照接收時間戳記單位。程式中這個值是用 rcv_last_app_ts變數的值來更新的。就是應用最後一次進行 rtp 接收時其時間戳記增長到的值。不管收沒收到就是這個值了?

uint32_t last_rtcp_report_snt_s;最後一個rtcp報告發送的時間,按照發送時間戳記單位。程式中這個值是用snd_last_ts變數的值來更新的,就是應用最後一次進行rtp 發送操作時其時間戳記增長到的值。不管有沒有發送 rtcp 報告包出去?

uint32_t rtcp_report_snt_interval; 按照時間戳記單位表示的 rtcp 報告發送的間隔。這個值程式中使用預設時間值 5 秒與 payload的 clockrate 的乘積來表示。是不是計算過於簡單了?

uint32_t last_rtcp_packet_count; 在最後發送的一個rtcp sr包中記錄的寄件者發送的 rtp 包總數。這個變數把這個值記錄了下來。記錄這個值是為了實現協議中規定的:如果之前的rtcp 包發送之後到當前再次發送 rtcp 包, 這期間如果發送了rtp 包,則發送rtcp SR 報告包,否則只需發送 rtcp RR 包就可以了。

uint32_t sent_payload_bytes; 用於rtcp 寄件者報告的 payload 位元組數,資料來源。這個變數儲存了從開始發送到發送這個 rtcp 報告包時發送的位元組總數,但不包括頭部和填充。

上面這些時間相關變數都是用於rtcp 包的。

unsigned int sent_bytes; 用於頻寬評估

struct timeval send_bw_start; 同上上面兩個變數用於計算髮送頻寬,start記錄的開始時間,sent_bytes 記錄了發送的位元組數,該值沒調用 rtp 介面發送資料後都會進行累加更新。記錄一次頻寬值後,清為零,之後進行下一次頻寬估計的計算。

unsigned int recv_bytes; 同上struct timeval recv_bw_start; 同上作用和處理邏輯都同上面發送部分。

四:調度的實現 要使用 oRTP 的調度功能,需要在初始化 oRTP 庫時調用介面 ortp_scheduler_init 對調度模組進行初始化。在該介面中建立一個RtpScheduler 類型的結構體__ortp_scheduler(參見圖2--4),並調用rtp_scheduler_init 初始化它。

在 rtp_scheduler_init 中,分配定時器 posix_timer(rtptimer類型結構體,參見圖 2-4)掛載到調度結構體上。(定時器初始間隔設定為POSIXTIMER_INTERVAL)。接著初始化 __ortp_scheduler 的其他部分,包括初始化互斥鎖、條件變數等。在調度模組啟動並執行整個過程中,相關操作都圍繞該結構體,__ortp_scheduler被定義為全域變數。

初始化完後調用rtp_scheduler_start 啟動調度任務。調度任務的執行體為 rtp_scheduler_schedule,參數為調度結構體自身。

調度任務執行後,首先初始化 timer。在這過程中將 timer 設定為運行狀態,儲存系統目前時間值。接著進入任務的while 迴圈,遍曆 scheduler 上註冊的所有會話。如果不為空白, 說明應用有會話需要調度管理。此時會調用 rtp_session_process 進行處理。所有需要調度管理的會話按上述邏輯處理完之後,廣播訊號量unblock_select_cond 喚醒所有因等待 select而睡眠的任務,意即讓這些任務去檢查自己的會話是否需要進行處理了,這塊後續還會說明。此時調度器完成了自己當前的工作開始準備進入睡眠狀態,而其他的任務開始檢查掩碼結果以決定是需要進行資料的收發還是等待下次調度。

調度的睡眠是通過調用 timer 的 timer_do 介面來完成的,這裡就是posix_timer_do 介面。在該介面中,計算系統當前的時間,並和初始啟動的時間(調度器初始化時儲存)做差運算, 結果轉換為毫秒單位。posix_timer_time記錄了下一次調度器逾時到達的時間,每次就讓 posix_timer_time減去系統目前時間與啟動時間的差值,如果大於零,說明調度時間還沒有到達,就調用 select 等待(posix_timer_time-差值)時間,然後重新擷取系統目前時間,計算新的差值。流程圖如下:

圖 4-1 直觀一點來說就是,調度器的調度精度由 POSIXTIMER_INTERVAL確定,每次調度器運行,如果處理會話集合(session set)的時間超過該間隔,就會接著處理下次調度,如果沒有用完,即剩餘diff時間,這點時間就通過 select 系統調用耗掉。因此,調度器每次進行調度的時間點基本是確定的,diff時間根據處理會話集合消耗時間的不同,每次的大小都是 不一樣的。

調度任務每次都基本上會在固定點檢查所有需要由它來管理的會話,也就是應用添加到會話集合中的所有會話。如果在處理這些會話的過程中,時間超過了調度器設定的預設間隔, 那麼調度器處理完本次迴圈後會接著進行下一輪的迴圈,否則,會等待,直到下一個調度點 時間到來。

調度器檢查每個會話是通過 rtp_session_process 介面完成的。對於某一個會話,調用該介面將按如下邏輯進行處理:首先檢查會話的發送部分的 waitpoint 結構體,將其時間與調度器目前時間進行比較(上述結構體中的時間是收發介面設定的需要喚醒的時間點)。如果該會話需要進行喚醒,也就是在等待喚醒,而且其等待的喚醒點也到了,(就是當前調度器時間已經超過了喚醒點)則清除需要進行喚醒的標識,然後在調度器結構體(調度器初始化時建立的全域變數)的w_session 集合上將該會話的掩碼位置置位,並通過條件變數喚醒該任務。同樣的邏輯檢查r_session 集合。總的來看,調度器就是檢查各個會話設定的喚醒點是否到了。如果到了則喚醒並設定其在集合中的掩碼標誌位。這樣收發任務通過檢查掩碼標識位就知道是否可以繼續進行收發了。一旦可以收發,應用會再次將這些掩碼位置重新清除掉, 這樣在下次收發前就需要再次等待調度器進行檢查並設定。

上層應用通過調用介面 rtp_session_set_scheduling_mode 將一個 session 添加到調度器中。添加過程為先獲得調度器全域資料結構,給會話的 sched 指標,即該會話的 sched 指標指向全域調度器資料結構;會話flags添加 RTP_SESSION_SCHEDULED,意即讓調度器管理會話;最後調用 rtp_scheduler_add_session 介面將會話添加註冊到調度器管理的會話集合上。 rtp_scheduler_add_session 介面中,先將會話掛到調度器資料結構的會話鏈表上(調度器每次迴圈時就從該鏈表上擷取要處理的會話),然後在all_sessions 集合中找到一個空閑位置,記錄該掩碼位置,將當前會話在該集合中的掩碼位置進行置位操作。這樣調度器通過會話鏈表就可以找到要調度的會話,進而找到會話上記錄的掩碼位置,從而在集合中對會話進行設定。類似的,將會話從集合中移除的介面為rtp_scheduler_remove_session,基本處理邏輯就是找到會話列表中的該會話,將其從鏈表中移除,並把它在集合中的位置清零。

上層應用檢查是否需要收發資料是通過檢查會話集合來完成。首先,應用調用session_set_new 介面建立一個新的集合。在該介面中我們建立一個SessionSet 結構體並將其初始化,後續的操作就在該結構體上完成。對於需要調度的會話,調用介面session_set_set將其在該集合中的掩碼位設定為 1,也就是打上標識。應用在每次接收或者發送前,調用介面session_set_select檢查是否可以發送或者接收。該介面會將 caller 掛起直到有事件到達。 session_set_select類似我們常用的系統調用 select,其使用方式也是類似的。

Session_set_select是應用與調度器打交道比較重要的一個介面,下面看看它的實現:

首先調用ortp_get_scheduler 擷取到調度器全域結構體進入 while(1)迴圈

如果接收集合不為空白,(也就是要檢查是否有接收事件), 調用session_set_init 初始化一個臨時存放結果的集合調用session_set_and 檢查會話集合。處理基於三個量,一個是初始化時添加到調度中進行接收檢測的會話集合r_sessions(這個集合代表調度器可以處理那些會話), 一個是使用者調用select 時進行檢查的會話集合,也就是應用要處理的集合(這個集合代表使用者要處理那些會話),一個就是當前調度處理的會話集合的最大值all_max (調度器從小到大檢查到 all_max 位置就檢查了其需要檢查的所有會話掩碼位)。在處理中,集合就是一個數組,數組每一個元素的每一個 bit 位代表了一個會話。這樣,以 all_max 為上限,檢查每一個會話對應的 bit 位,將調度器結構體上的接收集合和使用者集合進行與運算(注意:這裡接收集合是調度器處理完的,其中被設定的會話表明有接收事件到達。),得到的結果既是調度器處理後可以接收的會話, 也是在應用環境中添加了的要處理的會話,記為result set。同時將接收集合中被添加到 result 集合中的位清除(因為已經擷取了)。最終 session_set_and介面返回 result 集合中被設定的 bit 位元,也就是實際可以處理的會話個數。如果有會話的事件到達,即傳回值大於零,將新的結果拷貝回使用者集合,告知使用者 那些會話有事件到達了。

對於發送和 error 集合做同樣類似的處理如果最終三個集合處理完後有事件(不管是接收還是發送還是error),則直接返回, 否則在條件變數上等待,直到調度器返回有事件到達。

跳到 While(1)進行下次迴圈處理

除了session_set_select 介面供使用者調用外,oRTP 還提供了帶有逾時處理的 select 介面:session_set_timedselect,該介面可以設定跳出時間,而不是像session_set_select 那樣為死等模式。

綜合應用和調度器兩部分處理,可以看出,調度器的精度(調度間隔)在一定程度上可以影響資料接收速度。因為如果本次檢查會話上不能進行收發資料操作,那麼下次的檢查就必須等到下個調度點,即使在當前檢查剛過資料就到來了,應用也得等到下次調度點,由調度器檢查後應用才能知道,而這段時間資料就必須等待。這樣的話,如果調度間隔過大,那 麼接收速度必然減慢。

應用在收發資料時,除了可以使用調度器管理會話外,還可以設定阻塞與非阻塞模式。關於調度器與阻塞模式的關係:如果使用調度器,可以不用管阻塞模式,即調度器可以工作在阻塞模式下,也可以工作在非阻塞模式下。如果要使用阻塞模式,則需要啟動調度器,這是必須的,即阻塞模式必須工作在調度器使用的情況下。(因為阻塞功能的實現本身就依賴於調度器)。對於調度器啟動並且為非阻塞模式,當資料不能收發時,上層任務可以在應用程式層做其他動作來等待。對於調度器啟動並設定為阻塞模式,當資料不能收發時,上層應用任務會等待條件變數,該條件變數只有等到調度器 signal 之後,上層任務才能繼續運行。所以, 如果上層應用啟動了多個發送或者接收埠,那麼非阻塞模式下有一個或多個連接埠不能發送或者接收時,會嘗試其他連接埠是否可以發送,如果都不能使用,則可以空迴圈。而阻塞模式下,如果有一個連接埠被阻塞了,那麼其他連接埠都無法進行資料的收發了,即必須等待該連接埠有事件並被調度器觸發後才有機會進行其他連接埠的發送或者接收。所以,在多接收發送應用 情況下不應使用阻塞模式。

在非阻塞模式下,應用的等待時間消耗在 session_set_select 介面中了。阻塞模式下,應用可能就阻塞在發送接收介面中了。

使用目前的庫,存在一個問題,在使用調度的情況下開啟阻塞模式,則會導致程式掛住。具體原因分析來在於,阻塞模式下,包發送時其喚醒時間點packet time在調度器scheduler time後面了,這樣調度器檢查時就認為不需要進行喚醒,因為此時已經比調度器 old 了。根本原因在於阻塞時等待調度器運行,導致調度器時間超過了 packet time。而非阻塞模式下, 包會直接發送出去,這樣其實包的暫緩發送是在下次,也即是下次select 等待時,調度器趕上包的發送時間,然後喚醒包發送,而阻塞模式下下次 select 時調度器已經趕上了並超過了包的發送時間。

關於調度器與應用的關係如所示:

圖 4-2 調度器檢查 session set,喚醒到時間的接收流並設定掩碼位。應用檢查掩碼位得到接收流是否被喚醒,然後進行接收處理,在接收處理中會清掉調度器設定的掩碼位。

ortp使用詳解1

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.