srs代码学习(1)--listen建立过程

本文详细解析了SRS服务器RTMP服务监听的建立过程。从全局变量_srs_server的listen()函数开始,逐步深入到SrsServer、SrsStreamListener及SrsTcpListener等关键类的实现细节。

srs的服务侦听的建立过程。

以rtmp服务为例 srs服务侦听的建立依靠从上到下的三个类。分别是

SrsServer  

SrsStreamListener  

SrsTcpListener


端口侦听过程为

1)main函数中调用全局变量_srs_server的  listen()函数

if ((ret = _srs_server->listen()) != ERROR_SUCCESS) {
        return ret;
    }
在SrsServer的listen()函数如下
<pre name="code" class="cpp">int SrsServer::listen()
{
    int ret = ERROR_SUCCESS;
    
    if ((ret = listen_rtmp()) != ERROR_SUCCESS) {
        return ret;
    }
    
    if ((ret = listen_http_api()) != ERROR_SUCCESS) {
        return ret;
    }
    
    if ((ret = listen_http_stream()) != ERROR_SUCCESS) {
        return ret;
    }
    
    if ((ret = listen_stream_caster()) != ERROR_SUCCESS) {
        return ret;
    }
    
    return ret;
}



listen_rtmp()会建立一个rtmp的steamlistener
<pre name="code" class="cpp">int SrsServer::listen_rtmp()
{
    int ret = ERROR_SUCCESS;
    
    // stream service port.
    std::vector<std::string> ip_ports = _srs_config->get_listens();
    srs_assert((int)ip_ports.size() > 0);
    
    close_listeners(SrsListenerRtmpStream);
    
    for (int i = 0; i < (int)ip_ports.size(); i++) {
        SrsListener* listener = new SrsStreamListener(this, SrsListenerRtmpStream);
        listeners.push_back(listener);
        
        std::string ip;
        int port;
        srs_parse_endpoint(ip_ports[i], ip, port);
        
        if ((ret = listener->listen(ip, port)) != ERROR_SUCCESS) {
            srs_error("RTMP stream listen at %s:%d failed. ret=%d", ip.c_str(), port, ret);
            return ret;
        }
    }
    
    return ret;
}



这个streamlistener并不是真正的底层监听层。只是一个业务封装层。其类的继承顺序如下

在整个代码中,和其相识的类有SrsRtspListener  SrsHttpFlvListener两个类。这种类的主要作用是在底层链接建立有。给不同的上层放回链接信息。

代码走到这一步,侦听的socket还么米有建立起来,看SrsStreamListener的listen()函数

int SrsStreamListener::listen(string i, int p)
{
    int ret = ERROR_SUCCESS;
    
    ip = i;
    port = p;

    srs_freep(listener);
    listener = new SrsTcpListener(this, ip, port);

    if ((ret = listener->listen()) != ERROR_SUCCESS) {
        srs_error("tcp listen failed. ret=%d", ret);
        return ret;
    }
    
    srs_info("listen thread current_cid=%d, "
        "listen at port=%d, type=%d, fd=%d started success, ep=%s:%d",
        _srs_context->get_id(), p, type, listener->fd(), i.c_str(), p);

    srs_trace("%s listen at tcp://%s:%d, fd=%d", srs_listener_type2string(type).c_str(), ip.c_str(), port, listener->fd());

    return ret;
}
在这段代码里面。创建了真正的底层监听类

 listener = new SrsTcpListener(this, ip, port);
然后调用其listen()函数。代码如下

int SrsTcpListener::listen()
{
    int ret = ERROR_SUCCESS;
    
    if ((_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        ret = ERROR_SOCKET_CREATE;
        srs_error("create linux socket error. port=%d, ret=%d", port, ret);
        return ret;
    }
    srs_verbose("create linux socket success. port=%d, fd=%d", port, _fd);
    
    int reuse_socket = 1;
    if (setsockopt(_fd, SOL_SOCKET, SO_REUSEADDR, &reuse_socket, sizeof(int)) == -1) {
        ret = ERROR_SOCKET_SETREUSE;
        srs_error("setsockopt reuse-addr error. port=%d, ret=%d", port, ret);
        return ret;
    }
    srs_verbose("setsockopt reuse-addr success. port=%d, fd=%d", port, _fd);
    
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip.c_str());
    if (bind(_fd, (const sockaddr*)&addr, sizeof(sockaddr_in)) == -1) {
        ret = ERROR_SOCKET_BIND;
        srs_error("bind socket error. ep=%s:%d, ret=%d", ip.c_str(), port, ret);
        return ret;
    }
    srs_verbose("bind socket success. ep=%s:%d, fd=%d", ip.c_str(), port, _fd);
    
    if (::listen(_fd, SERVER_LISTEN_BACKLOG) == -1) {
        ret = ERROR_SOCKET_LISTEN;
        srs_error("listen socket error. ep=%s:%d, ret=%d", ip.c_str(), port, ret);
        return ret;
    }
    srs_verbose("listen socket success. ep=%s:%d, fd=%d", ip.c_str(), port, _fd);
    
    if ((_stfd = st_netfd_open_socket(_fd)) == NULL){
        ret = ERROR_ST_OPEN_SOCKET;
        srs_error("st_netfd_open_socket open socket failed. ep=%s:%d, ret=%d", ip.c_str(), port, ret);
        return ret;
    }
    srs_verbose("st open socket success. ep=%s:%d, fd=%d", ip.c_str(), port, _fd);
    
    if ((ret = pthread->start()) != ERROR_SUCCESS) {
        srs_error("st_thread_create listen thread error. ep=%s:%d, ret=%d", ip.c_str(), port, ret);
        return ret;
    }
    srs_verbose("create st listen thread success, ep=%s:%d", ip.c_str(), port);
    
    return ret;
}
到此,rtmp的监听端口就建立起来了。

在仔细看SrsTcpListener类。继承关系如下



这个类中很有趣的有了个线程类SrsReusableThread* pthread,这个线程是什么时候建立的?答案是在构造函数中

SrsTcpListener::SrsTcpListener(ISrsTcpHandler* h, string i, int p)
{
    handler = h;
    ip = i;
    port = p;

    _fd = -1;
    _stfd = NULL;

    pthread = new SrsReusableThread("tcp", this);
}
这个类的作用是什么呢?首先我们发现,SrsTcpListener 这个类继承了一个线程回调接口ISrsReusableThreadHandler。这个说明可以拥有可以在线程中运行的能力

观察线程类的结构



很奇怪,SrsReusableThread竟然也继承了一个handler类。这就表示。这个类还是一个封装类,并不是底层的真正的线程类。果然我们发现了其一个变量 internal::SrsThread* pthread,这个应该靠近点底层了吧。先放下不讲。我们是来追这个线程类也我的业务类啥关系的。这时另外一个有意思的变量ISrsReusableThreadHandler* handler,还记得
SrsTcpListener 类么,其有一个ISrsReusableThreadHandler的接口。通过这个接口,把线程和业务链接起来。看看其cycle()函数

int SrsReusableThread::cycle()
{
    return handler->cycle();
}
这个是线程这执行函数。它会调用上层类,具体到我们的例子里,就是SrsTcpListener 的cycle().看看是什么

int SrsTcpListener::cycle()
{
    int ret = ERROR_SUCCESS;
    
    st_netfd_t client_stfd = st_accept(_stfd, NULL, NULL, ST_UTIME_NO_TIMEOUT);
    
    if(client_stfd == NULL){
        // ignore error.
        if (errno != EINTR) {
            srs_error("ignore accept thread stoppped for accept client error");
        }
        return ret;
    }
    srs_verbose("get a client. fd=%d", st_netfd_fileno(client_stfd));
    
    if ((ret = handler->on_tcp_client(client_stfd)) != ERROR_SUCCESS) {
        srs_warn("accept client error. ret=%d", ret);
        return ret;
    }
    
    return ret;
}
原来是accept函数。在有链接后直接上调给上层,具体来讲就是SrsStreamListener类的on_tcp_client()函数

int SrsStreamListener::on_tcp_client(st_netfd_t stfd)
{
    int ret = ERROR_SUCCESS;
    
    if ((ret = server->accept_client(type, stfd)) != ERROR_SUCCESS) {
        srs_warn("accept client error. ret=%d", ret);
        return ret;
    }

    return ret;
}
欧耶,这下调用到最上层去了。server里,建立一个链接。


到现在还有两个问题要搞明白:

1)srs的线程模型

2)如何管理各个链接。


这个下一次在摸索







### 安卓 SRS 客户端与服务端的实现及配置 #### 1. **SRS(Simple RTMP Server)概述** SRS 是一款开源的高性能 RTMP 流媒体服务器,支持多种协议,包括 RTMP、HLS 和 WebRTC 等。它广泛应用于直播、点播以及实时通信领域。对于安卓客户端而言,可以通过 SDK 或自定义开发的方式接入 SRS 提供的服务。 --- #### 2. **安卓 SRS 客户端实现** ##### (1)**依赖库引入** 为了在安卓应用中实现与 SRS 的交互,通常需要引入第三方库来处理音视频数据传输和编码解码操作。常用的库有: - FFmpeg:用于音视频编解码[^4]。 - LibRtmp:专门针对 RTMP 协议的支持[^4]。 - JSMedia:提供更高级别的封装接口,简化开发者的工作流程。 以下是 Maven 中央仓库中的部分依赖项: ```gradle dependencies { implementation 'com.github.wseemann:FFmpegMediaMetadataRetriever:1.0.15' implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8' } ``` ##### (2)**推流逻辑** 当用户启动摄像头并准备向 SRS 推送音视频流时,可以按照以下方式构建推流过程: ```java public class RtmpPusher { private String rtmpUrl; private MediaRecorder mediaRecorder; public void startRecording(String url) throws IOException { this.rtmpUrl = url; mediaRecorder = new MediaRecorder(); // 设置音频源 mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 设置视频源 mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH); mediaRecorder.setProfile(profile); // 输出格式设为 MP4 mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); // 使用 RTMP URL 作为输出路径 mediaRecorder.setOutputFile(rtmpUrl); try { mediaRecorder.prepare(); mediaRecorder.start(); } catch (Exception e) { Log.e("RTMPPusher", "Error starting recording: ", e); } } public void stopRecording() { if (mediaRecorder != null) { mediaRecorder.stop(); mediaRecorder.reset(); mediaRecorder.release(); mediaRecorder = null; } } } ``` 此代码片段展示了如何利用 Android 原生 API `MediaRecorder` 将音视频录制下来并通过指定的 RTMP 地址上传到 SRS 服务器--- #### 3. **SRS 服务端配置** ##### (1)**安装部署** SRS 支持 Linux 平台下的快速搭建。以下是基本步骤: 1. 下载最新版本的 SRS 源码包; 2. 编译并运行服务程序; 3. 修改默认配置文件以适配实际需求。 典型命令如下所示: ```bash git clone https://github.com/ossrs/srs.git srs_trunk cd srs_trunk/trunk ./configure && make ./objs/srs -c conf/srs.conf ``` ##### (2)**核心参数调整** 编辑位于项目根目录下名为 `conf/srs.conf` 的主配置文档,重点关注以下几个方面的内容设定: - **监听地址与端口**: 默认情况下,SRS 绑定的是本机 IP 上的 1935 TCP 端口号用来接受来自外部设备推送过来的数据流。 ```ini http_server { enabled on; listen 8080; dir ./objs/nginx/html; } vhost __defaultVhost__ { hls { enabled on; nestegg off; } rtc { enabled on; } } ``` - **跨域资源共享 CORS**: 若前端网页尝试访问后端资源,则需开启相应的策略以便顺利加载所需素材[^4]。 --- #### 4. **常见问题排查** 如果遇到无法成功建立连接或者播放失败的情况,可以从以下几个角度入手分析原因所在: - 网络连通性测试:确认目标主机之间是否存在防火墙拦截现象[^3]。 - 日志记录审查:查看 SRS 执行过程中产生的日志消息是否有异常提示信息存在[^3]。 - 版本兼容验证:确保所使用的各个组件均处于稳定状态并且相互匹配良好[^3]。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值