服務(wù)器將用戶的數(shù)據(jù)包進(jìn)行識(shí)別和區(qū)分
多線程優(yōu)化方案,主線程監(jiān)聽在一個(gè)端口,并使用前面提到方案在服務(wù)器將用戶的數(shù)據(jù)包進(jìn)行識(shí)別和區(qū)分。
首先我們服務(wù)器端主線程會(huì)創(chuàng)建一個(gè)UDP的socket,綁定到7000端口,并注冊(cè)監(jiān)聽可讀事件,WebRTC客戶端會(huì)給服務(wù)端7000端口發(fā)數(shù)據(jù)包。當(dāng)然在之前SDP和ICE Candidate的協(xié)商流程已經(jīng)做完了,我們從stun的第一個(gè)數(shù)據(jù)包(stun binding request)中就可以拿到這個(gè)WebRTC的web address(IP+端口)以及區(qū)分會(huì)議的RoomID和區(qū)分與會(huì)者的UserID。
拿到RoomID以后,我們可以把客戶端的web address和RoomID綁定,建立一個(gè)映射關(guān)系。為什么要做這個(gè)綁定關(guān)系?
這是為了后續(xù)我們可以把同一個(gè)會(huì)議的用戶全部投遞到一個(gè)業(yè)務(wù)邏輯處理線程來處理。如果用戶的數(shù)據(jù)是在不同邏輯處理線程上處理,我們?cè)谧鰯?shù)據(jù)處理的時(shí)候就要做跨線程的處理,跨線程處理就一定要加鎖,這是我們不太愿意看到的。
如果同一會(huì)議的用戶數(shù)據(jù)都在一個(gè)邏輯線程里面處理,其實(shí)它是在一個(gè)單線程里面的順序處理,沒有并發(fā)的問題,也不需要加鎖,所以它的性能會(huì)比較高。
有了剛剛我們建立的映射關(guān)系,后續(xù)處理同一個(gè)會(huì)議的用戶上行的stun包、dtls、SRTP、SRTCP,我們都根據(jù)RoomID做同樣的Hash算法,Hash到我們后面的業(yè)務(wù)主線程里面,這樣每一次主線程把I/O做完后,一次性將主線程收到的各個(gè)數(shù)據(jù)包按照Hash算法投遞給對(duì)應(yīng)的后端的邏輯處理多線程,這樣同一個(gè)會(huì)議的SFU邏輯都在同一個(gè)線程里面處理完了。
這個(gè)是我們一開始使用的方案,但是大家也能看到,這個(gè)方案還有一個(gè)缺陷,就是I/O都是在主線程做的,后續(xù)的數(shù)據(jù)都需要通過跨線程的投遞方式讓后續(xù)的邏輯處理現(xiàn)場(chǎng)來做,那么有沒有辦法不要做跨線程的投遞,讓用戶的數(shù)據(jù)包可以直接在對(duì)應(yīng)的邏輯處理多線程里面處理。
其實(shí)傳統(tǒng)的TCP服務(wù)器是比較容易做到的,它可以在主線程上Accept后為每個(gè)客戶端創(chuàng)建一些新的fd,并分配一個(gè)子線程處理這個(gè)新fd的讀寫。那UDP能不能這么做?UDP在常規(guī)的編程方法是無法做到的,針對(duì)這個(gè)問題,我們?cè)O(shè)計(jì)了一個(gè)進(jìn)階方案。