一.DataChannel简介
在webrtc中通过DataChannel,可以实现点对点的消息通信,这些消息和音视频数据是通过同一udp socket被发送给对方的,因此也是具备NAT穿透功能的。DataChannel可以用来进行聊天消息的发送、点对点传送文件等,作为音视频通信外的另一种通信方式,DataChannel的意义还是很重要的。


二.背景概述
在开发MediaSoup级联服务时,我们使用了PlainTransport与其对接,也就是说不使用webrtc native提供的相关接口,直接使用纯RTP流传输音视频数据,这样的话DataChannel也需要靠自己实现了。
 

三、代码

RtpIO是一个的UDP读写类

#include "sctp_association.h"
#include "rtc_util.h"


namespace mrtc {

#define DATA_CHANNEL_PPID_STRING           (51)     //字符串数据
#define DATA_CHANNEL_PPID_BINARY           (53)     //二进制数据

bool SctpAssociation::s_sctpRunning = false;

SctpAssociation::SctpAssociation(RtpIO* rtpIO)
    : m_rtpIO(rtpIO)
{
    int ret = 0;

    Init();

    usrsctp_register_address(this);

    m_localSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, onRecvSctpData, onSendSctpData, 0, this);
    assert(NULL != m_localSock);

    usrsctp_set_upcall(m_localSock, onUpcall, this);

    struct sockaddr_conn sconn = {0};
    sconn.sconn_family = AF_CONN;
    sconn.sconn_port   = htons(5000);
    sconn.sconn_addr   = this;
    ret = usrsctp_bind(m_localSock, (struct sockaddr *)&sconn, sizeof(sconn));
    assert(0 == ret);

    ret = usrsctp_listen(m_localSock, 1);
    assert(0 == ret);

    RTC_INFO("Sctp Association Create Success!");
}

SctpAssociation::~SctpAssociation()
{
    if(NULL != m_localSock)  { usrsctp_close(m_localSock); }
    if(NULL != m_remoteSock) { usrsctp_close(m_remoteSock); }
    usrsctp_deregister_address(this);

    RTC_DEBUG("~SctpAssociation");
}

void SctpAssociation::Init()
{
    if(false == s_sctpRunning)
    {
        s_sctpRunning = true;
        usrsctp_init_nothreads(0, sctpDataToUdp, sctpDebug);
        usrsctp_sysctl_set_sctp_ecn_enable(0);
        RTC_INFO("Sctp Association Init Success!");
    }
}

int SctpAssociation::sctpDataToUdp(void* addr, void* buffer, size_t length, uint8_t tos, uint8_t set_df)
{
    SctpAssociation* p = (SctpAssociation*)addr;
    assert(nullptr != p);

    p->m_rtpIO->writeData((const char*)buffer, length);

    return 0;
}

void SctpAssociation::sctpDebug(const char *format, ...)
{
	char buffer[4 * 1024] = {0};
	va_list ap;

	va_start(ap, format);
	vsprintf(buffer, format, ap);

	// Remove the artificial carriage return set by usrsctp.
	buffer[std::strlen(buffer) - 1] = '\0';
    RTC_WARNING("sctpDebug: %s", buffer);

	va_end(ap);
}

void SctpAssociation::Destroy()
{
    usrsctp_finish();
    s_sctpRunning = false;
}

bool SctpAssociation::IsSctp(const void* data, size_t len)
{
    return (
        (len >= 12) &&
        (Get2Bytes((const uint8_t*)data, 0) == 5000) &&
        (Get2Bytes((const uint8_t*)data, 2) == 5000)
    );
}

void SctpAssociation::sendSctpMessage(const void* data, size_t len)
{
    int ret = 0;
    struct sctp_sndinfo sndinfo = {0};

	sndinfo.snd_sid = 0;
	sndinfo.snd_flags = SCTP_EOR;
	sndinfo.snd_ppid = htonl(DATA_CHANNEL_PPID_STRING);
	sndinfo.snd_context = 0;
	sndinfo.snd_assoc_id = 0;

    if(NULL == m_remoteSock)
    {
        RTC_ERROR("send Sctp Message Error! Not accept");
        return;
    }

    ret = usrsctp_sendv(m_remoteSock, data, len, NULL, 0, (void *)&sndinfo, (socklen_t)sizeof(sndinfo), SCTP_SENDV_SNDINFO, 0);
    if(0 > ret)
    {
        perror("usrsctp_sendv");
        RTC_ERROR("send Sctp Message Error! ret = %d", ret);
    }
}

void SctpAssociation::processSctpData(const void* data, size_t len)
{
    usrsctp_conninput(this, data, len, 0);
}

int SctpAssociation::onRecvSctpData(struct socket* sock, union sctp_sockstore addr, void* data, size_t datalen, struct sctp_rcvinfo rcvinfo, int flags, void* ulp_info)
{
    SctpAssociation* p = (SctpAssociation*)ulp_info;
    assert(nullptr != p);

    if(nullptr != p->m_sctpRecvDataCallback)
    {
        p->m_sctpRecvDataCallback(data, datalen);
    }

    return 1;
}

int SctpAssociation::onSendSctpData(struct socket* sock, uint32_t sb_free, void* ulp_info)
{
    return 1;
}

void SctpAssociation::onUpcall(struct socket* sock, void* arg, int flgs)
{
    SctpAssociation* p = (SctpAssociation*)arg;
    assert(nullptr != p);

    if(NULL == p->m_remoteSock)
    {
        p->m_remoteSock = usrsctp_accept(sock, NULL, NULL);
    }
}

}

参考:

1、SCTP通用报文格式

2、https://github.com/sctplab/usrsctp/blob/master/Manual.md

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐