Bắt đầu kết nối với ứng dụng ngang hàng

Kết nối ngang hàng là một phần của quy cách WebRTC, liên quan đến việc kết nối hai ứng dụng trên các máy tính khác nhau để giao tiếp bằng giao thức ngang hàng. Thông tin liên lạc giữa các thiết bị ngang hàng có thể là video, âm thanh hoặc dữ liệu nhị phân tuỳ ý (đối với những ứng dụng hỗ trợ API RTCDataChannel). Để khám phá cách hai máy ngang hàng có thể kết nối, cả hai ứng dụng đều cần cung cấp cấu hình ICE Server. Đây là một máy chủ STUN hoặc TURN và vai trò của chúng là cung cấp các đề xuất ICE cho mỗi máy khách, sau đó được chuyển đến máy ngang từ xa. Quá trình chuyển các đề xuất ICE này thường được gọi là báo hiệu.

Báo hiệu

Quy cách WebRTC bao gồm các API để giao tiếp với Máy chủ ICE (Thiết lập kết nối Internet), nhưng thành phần báo hiệu không thuộc quy cách này. Cần có tín hiệu để hai người ngang hàng chia sẻ cách họ nên kết nối. Thông thường, vấn đề này được giải quyết thông qua một Web API thông thường dựa trên HTTP (tức là dịch vụ REST hoặc cơ chế RPC khác) trong đó các ứng dụng web có thể chuyển tiếp thông tin cần thiết trước khi kết nối ngang hàng được bắt đầu.

Đoạn mã sau đây cho biết cách sử dụng dịch vụ báo hiệu giả định này để gửi và nhận thông báo không đồng bộ. Tham số này sẽ được dùng trong các ví dụ còn lại trong hướng dẫn này khi cần.

// Set up an asynchronous communication channel that will be
// used during the peer connection setup
const signalingChannel = new SignalingChannel(remoteClientId);
signalingChannel.addEventListener('message', message => {
    // New message from remote client received
});

// Send an asynchronous message to the remote client
signalingChannel.send('Hello!');

Bạn có thể triển khai việc báo hiệu theo nhiều cách khác nhau và quy cách WebRTC không ưu tiên bất kỳ giải pháp cụ thể nào.

Khởi tạo kết nối ngang hàng

Mỗi kết nối ngang hàng được xử lý bằng một đối tượng RTCPeerConnection. Hàm khởi tạo cho lớp này lấy một đối tượng RTCConfiguration duy nhất làm tham số. Đối tượng này xác định cách thiết lập kết nối ngang hàng và phải chứa thông tin về các máy chủ ICE cần sử dụng.

Sau khi tạo RTCPeerConnection, chúng ta cần tạo một đề nghị hoặc câu trả lời SDP, tuỳ thuộc vào việc chúng ta là người gọi hay người nhận. Sau khi tạo đề nghị hoặc câu trả lời SDP, bạn phải gửi đề nghị hoặc câu trả lời đó đến thiết bị ngang hàng từ xa thông qua một kênh khác. Việc truyền các đối tượng SDP đến các thiết bị ngang hàng từ xa được gọi là báo hiệu và không thuộc phạm vi của quy cách WebRTC.

Để bắt đầu thiết lập kết nối ngang hàng từ phía gọi, chúng ta sẽ tạo một đối tượng RTCPeerConnection rồi gọi createOffer() để tạo một đối tượng RTCSessionDescription. Nội dung mô tả phiên này được đặt làm nội dung mô tả cục bộ bằng cách sử dụng setLocalDescription(), sau đó được gửi qua kênh báo hiệu của chúng tôi đến bên nhận. Chúng ta cũng thiết lập một trình nghe cho kênh báo hiệu để biết khi nào nhận được câu trả lời cho nội dung mô tả phiên được cung cấp từ phía nhận.

async function makeCall() {
    const configuration = {'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]}
    const peerConnection = new RTCPeerConnection(configuration);
    signalingChannel.addEventListener('message', async message => {
        if (message.answer) {
            const remoteDesc = new RTCSessionDescription(message.answer);
            await peerConnection.setRemoteDescription(remoteDesc);
        }
    });
    const offer = await peerConnection.createOffer();
    await peerConnection.setLocalDescription(offer);
    signalingChannel.send({'offer': offer});
}

Về phía nhận, chúng ta đợi một đề nghị đến trước khi tạo phiên bản RTCPeerConnection. Sau khi hoàn tất, chúng ta sẽ đặt ưu đãi nhận được bằng cách sử dụng setRemoteDescription(). Tiếp theo, chúng ta gọi createAnswer() để tạo câu trả lời cho đề nghị đã nhận được. Câu trả lời này được đặt làm nội dung mô tả cục bộ bằng cách sử dụng setLocalDescription(), sau đó được gửi đến bên gọi qua máy chủ báo hiệu của chúng tôi.

const peerConnection = new RTCPeerConnection(configuration);
signalingChannel.addEventListener('message', async message => {
    if (message.offer) {
        peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer));
        const answer = await peerConnection.createAnswer();
        await peerConnection.setLocalDescription(answer);
        signalingChannel.send({'answer': answer});
    }
});

Sau khi hai thiết bị ngang hàng đặt cả nội dung mô tả phiên cục bộ và từ xa, chúng sẽ biết các chức năng của thiết bị ngang hàng từ xa. Điều này không có nghĩa là kết nối giữa các thiết bị ngang hàng đã sẵn sàng. Để làm được điều này, chúng ta cần thu thập các ICE candidate ở mỗi peer và chuyển (qua kênh báo hiệu) đến peer khác.

Ứng cử viên ICE

Trước khi có thể giao tiếp bằng WebRTC, hai máy ngang hàng cần trao đổi thông tin kết nối. Vì điều kiện mạng có thể thay đổi tuỳ thuộc vào một số yếu tố, nên thường dùng dịch vụ bên ngoài để khám phá các ứng cử viên có thể kết nối với một thiết bị ngang hàng. Dịch vụ này được gọi là ICE và đang sử dụng máy chủ STUN hoặc TURN. STUN là viết tắt của Session Traversal Utilities for NAT (Tiện ích truyền tải phiên cho NAT) và thường được dùng gián tiếp trong hầu hết các ứng dụng WebRTC.

TURN (Truy cập bằng NAT chuyển tiếp) là giải pháp nâng cao hơn, kết hợp các giao thức STUN và hầu hết các dịch vụ thương mại dựa trên WebRTC đều sử dụng máy chủ TURN để thiết lập kết nối giữa các thiết bị ngang hàng. API WebRTC hỗ trợ trực tiếp cả STUN và TURN, đồng thời được thu thập theo thuật ngữ đầy đủ hơn là Thiết lập kết nối Internet. Khi tạo một kết nối WebRTC, chúng ta thường cung cấp một hoặc nhiều máy chủ ICE trong cấu hình cho đối tượng RTCPeerConnection.

Trickle ICE

Sau khi tạo đối tượng RTCPeerConnection, khung cơ sở sẽ dùng các máy chủ ICE được cung cấp để thu thập các ứng viên cho việc thiết lập kết nối (các ứng viên ICE). Sự kiện icegatheringstatechange trên RTCPeerConnection báo hiệu trạng thái của quá trình thu thập ICE (new, gathering hoặc complete).

Mặc dù một thiết bị ngang hàng có thể đợi cho đến khi quá trình thu thập ICE hoàn tất, nhưng thường thì việc sử dụng kỹ thuật "trickle ice" (truyền từng phần) và truyền từng ICE candidate đến thiết bị ngang hàng từ xa khi phát hiện ra sẽ hiệu quả hơn nhiều. Điều này sẽ giảm đáng kể thời gian thiết lập cho kết nối ngang hàng và cho phép bắt đầu cuộc gọi video mà ít bị trễ hơn.

Để thu thập các đề xuất ICE, bạn chỉ cần thêm một trình nghe cho sự kiện icecandidate. RTCPeerConnectionIceEvent được phát ra trên trình nghe đó sẽ chứa thuộc tính candidate đại diện cho một ứng viên mới cần được gửi đến thiết bị ngang hàng từ xa (Xem phần Truyền tín hiệu).

// Listen for local ICE candidates on the local RTCPeerConnection
peerConnection.addEventListener('icecandidate', event => {
    if (event.candidate) {
        signalingChannel.send({'new-ice-candidate': event.candidate});
    }
});

// Listen for remote ICE candidates and add them to the local RTCPeerConnection
signalingChannel.addEventListener('message', async message => {
    if (message.iceCandidate) {
        try {
            await peerConnection.addIceCandidate(message.iceCandidate);
        } catch (e) {
            console.error('Error adding received ice candidate', e);
        }
    }
});

Đã thiết lập kết nối

Sau khi nhận được các ICE candidate, chúng ta nên dự kiến trạng thái của peer connection sẽ chuyển sang trạng thái đã kết nối. Để phát hiện điều này, chúng ta sẽ thêm một trình nghe vào RTCPeerConnection, nơi chúng ta nghe các sự kiện connectionstatechange.

// Listen for connectionstatechange on the local RTCPeerConnection
peerConnection.addEventListener('connectionstatechange', event => {
    if (peerConnection.connectionState === 'connected') {
        // Peers connected!
    }
});

Tài liệu về RTCPeerConnection API