四、 串流伺服器實作細節
4.1 串流伺服器的多工
4.1.2 Single-Process ( Event-Based ),Concurrent Servers…. 80
如下圖 4.2 所示,Single-Process 的意思是主行程(master process)需同時擔任接待以 及服務的工作,因為我們只有一個行程(Single process)。Live 的 RTSPServer 便是屬於此
種串流多工架構,這種串流伺服器會由這個單一的行程來控制多個 sockets 的 I/O。如下 圖 4.2 所示,listening socket 接收來自 client 端的連線並以 accept()建立了 connected socket 後,負責連線的後續工作。
通常這種 Non-blocking socket 會搭配 select()函數來使用,select()目的是事先詢問各 個 socket 的狀態,判斷目前 socket 是否已經可以讀取,確定有之後我們再去讀取,避免 類似 polling I/O 浪費 CPU 資源。
圖 4.2(a) Single-Process ( Event-Triggered ),Concurrent Servers
(註)欲讓原本預設為 blocking 的 socket 變成 Non-blocking socket 的設定如下所示:
4.1.2.1 Select()說明
select 這個 system call 可以告知 kernel 它想監控的 socketfd,當 socketfd 所 index 的 socket 有事件發生時,kernel 會叫醒該程式來處理之,可以監控的事件有三種,分別是
『讀取』、『寫入』以及『例外』,select 的敘述以及 Single-Process ( Event-Triggered )流程 圖如下所示:
圖 4.2(b) Single-Process with select flowchart
(使用 select 來達成單一行程的多工步驟如下)
1.針對欲監控的 fd_set 宣告結構變數,三種情形:讀、寫、例外的集合視需要宣告之。
2. 宣告 readSet 以及 freadSet,並使用 FZ_ ZERO 把結構的 bit array 內容清為 0。
3.使用 FD_SET 把我們的 Listen socketfd 加入 fReadSet 的集合中,當進入迴圈之後,把 fReadSet 的值設定給 readSet。
4.呼叫 select(),使行程 block 住直到所監控的 socketfd 有動靜(由 kernel 主動喚醒程式),
程式碼如下所示。
Live RTSPServer 只把先前宣告好的 readSet 放到 select 的第二個參數中,表示我們 只等待『讀取』的事件,因此我們把參數三及參數四 *writeSet、*exceptSet 都設為 NULL;
第五個參數在 Live RTSPServer 的程式中是設為一百萬秒(11.5 天),原因是 select 所 block 的時間如果超過一百萬秒(此段時間沒有用戶連線),則 select 函數會失效。
故最佳的設定便是設成一百萬秒,如果這段時間都沒有用戶連線,Select()便會回傳 一次結果,如此 Timer 便可以重新計數而不會導致 Select()失效;同時 Live555 也替每一 個 connected socket 設定了一個『通關』的函數 timeToNextAlarm(),目的是使伺服器可 以同時接收新的用戶連線並且對舊有的用戶連線進行封包的傳送,此地方的設計需要特 別注意。
5.直到所監控的 socketfd 有動靜,行程就會從 select loop 中跳出,因 select 回傳的是符合 監控狀況的 socketfd 數目,因此尚須由 FZ_ISSET 巨集來判斷是哪個 socketfd 有動靜。
6. 使用迴圈並配合 FZ_ISSET 來檢查是 readSet 中的那個 connected socketfd 有反應,這
表示 kernel 所監控的 readSet 有讀取事件發生,也就是有封包抵達 NIC(Network InterfaceCard,網路介面卡)。
7.當完成 TCP 3WHS 建立完 RTSP 的 connected socket 後,我們便可以使用 BSD socket API 的 recv()來接收送到 Server socket 上的 TCP/RTSP 封包;當 RTSP connected socket 收到 RTSP PLAY 信令後,Server 便會建立 UDP socket 以作為 RTP 封包傳送之用。
4.1.2.2 Select()虛擬碼範例 int socketfd = socket(…);
bind(socketfd, …);
int curFlags = fcntl(listen_fd, F_GETFL, 0); //(make Socket Non-Blocking ) fcntl(newSocket, F_SETFL, curFlags|O_NONBLOCK);
listen(socketfd, …);
fd_set fReadSet;
FD_ZERO(&fReadSet);
FD_SET(socketfd, & fReadSet);
int max_fd =socketfd;
while(1) {
fd_set readSet=fReadSet;
/*只等待 read 的事件,故*writefds,*exceptfds 都設為 NULL*/
int selectResult = select(max_fd + 1, &readfds, NULL, NULL, &tv_timeToDelay);
/*Do loop on all fds to look for RTSP connected socket or accept a new connection request.*/
for (int i = 0; i <= max_fd; i++) { if (FD_ISSET(i, &readSet)= =1) {
/*ex: recvfrom(connfd, buf, sizeof(buf)); */
<TODO:RTSP negotiation and creates UDP socket for RTP > } }//end if FD_ISSET
}//end loop on all fds //For each UDP socket
<TODO:send RTP packet by calling BSD socket API sendto() >
……….………