docker学习(三)常用命令

这篇具有很好参考价值的文章主要介绍了docker学习(三)常用命令。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

ZLMediaKit源码分析(一)服务启动
ZLMediaKit源码分析(二)推流创建
ZLMediaKit源码分析(三)拉流创建
ZLMediaKit源码分析(四)重封装
ZLMediaKit源码分析(五)代理服务
ZLMediaKit源码分析(六)http 点播

ZLMediaKit 使用 http1.1,而 http1.1 是基于 TCP 的。

HttpSession::onRecv() 接收数据

这里是 http 服务的数据入口

src/Http/HttpSession.cpp
void HttpSession::onRecv(const Buffer::Ptr &pBuf) {
    _ticker.resetTime();
    input(pBuf->data(),pBuf->size());
}

HttpSession 继承自 HttpRequestSplitter
HttpRequestSplitter::input() 调用 onRecvHeader()

Http/HttpRequestSplitter.cpp
void HttpRequestSplitter::input(const char *data,size_t len) {
    {
        auto size = remainDataSize();
        if (size > kMaxCacheSize) {
            //缓存太多数据无法处理则上抛异常
            reset();
            throw std::out_of_range("remain data size is too huge, now cleared:" + to_string(size));
        }
    }
    const char *ptr = data;
    if(!_remain_data.empty()){
        _remain_data.append(data,len);
        data = ptr = _remain_data.data();
        len = _remain_data.size();
    }

    splitPacket:

    /*确保ptr最后一个字节是0,防止strstr越界
     *由于ZLToolKit确保内存最后一个字节是保留未使用字节并置0,
     *所以此处可以不用再次置0
     *但是上层数据可能来自其他渠道,保险起见还是置0
     */

    char &tail_ref = ((char *) ptr)[len];
    char tail_tmp = tail_ref;
    tail_ref = 0;

    //数据按照请求头处理
    const char *index = nullptr;
    _remain_data_size = len;
    while (_content_len == 0 && _remain_data_size > 0 && (index = onSearchPacketTail(ptr,_remain_data_size)) != nullptr) {
        if (index == ptr) {
            break;
        }
        if (index < ptr || index > ptr + _remain_data_size) {
            throw std::out_of_range("上层分包逻辑异常");
        }
        //_content_len == 0,这是请求头
        const char *header_ptr = ptr;
        ssize_t header_size = index - ptr;
        ptr = index;
        _remain_data_size = len - (ptr - data);
        _content_len = onRecvHeader(header_ptr, header_size);
    }

    // 到这里返回了,以下未执行
    if(_remain_data_size <= 0){
        //没有剩余数据,清空缓存
        _remain_data.clear();
        return;
    }
    // 以下均为执行 省略部分
    ......
    //_content_len < 0;数据按照不固定长度content处理
    onRecvContent(ptr,_remain_data_size);//消费掉所有剩余数据
    _remain_data.clear();
}
src/Http/HttpSession.cpp
ssize_t HttpSession::onRecvHeader(const char *header,size_t len) {
    typedef void (HttpSession::*HttpCMDHandle)(ssize_t &);
    static unordered_map<string, HttpCMDHandle> s_func_map;
    static onceToken token([]() {
        s_func_map.emplace("GET",&HttpSession::Handle_Req_GET);
        s_func_map.emplace("POST",&HttpSession::Handle_Req_POST);
        s_func_map.emplace("HEAD",&HttpSession::Handle_Req_HEAD);
        s_func_map.emplace("OPTIONS",&HttpSession::Handle_Req_OPTIONS);
    }, nullptr);

    _parser.Parse(header);
    urlDecode(_parser);
    string cmd = _parser.Method();
    auto it = s_func_map.find(cmd);
    if (it == s_func_map.end()) {
        WarnP(this) << "不支持该命令:" << cmd;
        sendResponse(405, true);
        return 0;
    }

    //跨域
    _origin = _parser["Origin"];

    //默认后面数据不是content而是header
    ssize_t content_len = 0;
    auto &fun = it->second;
    try {
        (this->*fun)(content_len);
    }catch (exception &ex){
        shutdown(SockException(Err_shutdown,ex.what()));
    }

    //清空解析器节省内存
    _parser.Clear();
    //返回content长度
    return content_len;
}

HttpSession::Handle_Req_GET()

src/Http/HttpSession.cpp
void HttpSession::Handle_Req_GET(ssize_t &content_len) {
    Handle_Req_GET_l(content_len, true);
}
// 直接执行 Handle_Req_GET_l
void HttpSession::Handle_Req_GET_l(ssize_t &content_len, bool sendBody) {
    //先看看是否为WebSocket请求
    if (checkWebSocket()) {
        content_len = -1;
        _contentCallBack = [this](const char *data, size_t len) {
            WebSocketSplitter::decode((uint8_t *) data, len);
            //_contentCallBack是可持续的,后面还要处理后续数据
            return true;
        };
        return;
    }

    if (emitHttpEvent(false)) {
        //拦截http api事件
        return;
    }

    if (checkLiveStreamFlv()) {
        //拦截http-flv播放器
        return;
    }

    if (checkLiveStreamTS()) {
        //拦截http-ts播放器
        return;
    }

    if (checkLiveStreamFMP4()) {
        //拦截http-fmp4播放器
        return;
    }

    bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
	weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
	// 回调是这里的 lamda 函数
    HttpFileManager::onAccessPath(*this, _parser, [weakSelf, bClose](int code, const string &content_type,
                                                                     const StrCaseMap &responseHeader, const HttpBody::Ptr &body) {
        auto strongSelf = weakSelf.lock();
        if (!strongSelf) {
            return;
        }
        // HttpSession继承自 TcpSession 继承自 Session 继承自SocketHelper
        strongSelf->async([weakSelf, bClose, code, content_type, responseHeader, body]() {
            auto strongSelf = weakSelf.lock();
            if (!strongSelf) {
                return;
            }
            strongSelf->sendResponse(code, bClose, content_type.data(), responseHeader, body);
        });
    });
}

HttpFileManager::onAccessPath() 鉴权

src/Http/HttpFileManager.cpp
/**
 * 访问文件或文件夹
 * @param sender 事件触发者
 * @param parser http请求
 * @param cb 回调对象
 */
void HttpFileManager::onAccessPath(TcpSession &sender, Parser &parser, const HttpFileManager::invoker &cb) {
    auto fullUrl = string(HTTP_SCHEMA) + "://" + parser["Host"] + parser.FullUrl();
    MediaInfo mediaInfo(fullUrl);
	auto strFile = getFilePath(parser, mediaInfo, sender);

	// strFile:/home/*/bin/www/proxy/file/720x1280p30.wintersecret59s_file.mp4
	// 这里只分析文件,未执行下面文件夹操作
    //访问的是文件夹
    if (File::is_dir(strFile.data())) {
        auto indexFile = searchIndexFile(strFile);
        if (!indexFile.empty()) {
            //发现该文件夹下有index文件
            strFile = pathCat(strFile, indexFile);
            parser.setUrl(pathCat(parser.Url(), indexFile));
            accessFile(sender, parser, mediaInfo, strFile, cb);
            return;
        }
        string strMenu;
        //生成文件夹菜单索引
        if (!makeFolderMenu(parser.Url(), strFile, strMenu)) {
            //文件夹不存在
            sendNotFound(cb);
            return;
        }
        //判断是否有权限访问该目录
        canAccessPath(sender, parser, mediaInfo, true, [strMenu, cb](const string &errMsg, const HttpServerCookie::Ptr &cookie) mutable{
            if (!errMsg.empty()) {
                strMenu = errMsg;
            }
            StrCaseMap headerOut;
            if (cookie) {
                headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
            }
            cb(errMsg.empty() ? 200 : 401, "text/html", headerOut, std::make_shared<HttpStringBody>(strMenu));
        });
        return;
    }

    //访问的是文件
    accessFile(sender, parser, mediaInfo, strFile, cb);
};

accessFile() 判断文件访问权限

静态函数。

src/Http/HttpFileManager.cpp
/**
 * 访问文件
 * @param sender 事件触发者
 * @param parser http请求
 * @param mediaInfo http url信息
 * @param strFile 文件绝对路径
 * @param cb 回调对象
 */
static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo &mediaInfo, const string &strFile, const HttpFileManager::invoker &cb) {
    bool is_hls = end_with(strFile, kHlsSuffix);
    bool file_exist = File::is_file(strFile.data());
    if (!is_hls && !file_exist) {
        //文件不存在且不是hls,那么直接返回404
        sendNotFound(cb);
        return;
    }

    if (is_hls) {
        //hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
        const_cast<string &>(mediaInfo._schema) = HLS_SCHEMA;
        replace(const_cast<string &>(mediaInfo._streamid), kHlsSuffix, "");
    }

    weak_ptr<TcpSession> weakSession = sender.shared_from_this();
	//判断是否有权限访问该文件
	// canAccessPath 函数内部有鉴权操作
    canAccessPath(sender, parser, mediaInfo, false, [cb, strFile, parser, is_hls, mediaInfo, weakSession , file_exist](const string &errMsg, const HttpServerCookie::Ptr &cookie) {
        auto strongSession = weakSession.lock();
        if (!strongSession) {
            //http客户端已经断开,不需要回复
            return;
        }
        if (!errMsg.empty()) {
            //文件鉴权失败
            StrCaseMap headerOut;
            if (cookie) {
                auto lck = cookie->getLock();
                headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
            }
            cb(401, "text/html", headerOut, std::make_shared<HttpStringBody>(errMsg));
            return;
        }

         // 主要执行里面的回调函数
        auto response_file = [file_exist](const HttpServerCookie::Ptr &cookie, const HttpFileManager::invoker &cb, const string &strFile, const Parser &parser) {
            StrCaseMap httpHeader;
            if (cookie) {
                auto lck = cookie->getLock();
                httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
            }
            HttpSession::HttpResponseInvoker invoker = [&](int code, const StrCaseMap &headerOut, const HttpBody::Ptr &body) {
                if (cookie && file_exist) {
                    auto lck = cookie->getLock();
                    auto is_hls = (*cookie)[kCookieName].get<HttpCookieAttachment>()._is_hls;
                    if (is_hls) {
                        (*cookie)[kCookieName].get<HttpCookieAttachment>()._hls_data->addByteUsage(body->remainSize());
                    }
                }
                // run 
                // Handle_Req_GET() 注册lamda函数, 最终执行HttpSession::sendResponse()
                cb(code, HttpFileManager::getContentType(strFile.data()), headerOut, body);
            };
            invoker.responseFile(parser.getHeader(), httpHeader, strFile);
        };

        if (!is_hls) {
            //不是hls,直接回复文件或404
            // 执行这里
            response_file(cookie, cb, strFile, parser);
            return;
        }
        // 以下未执行

        //是hls直播,判断HLS直播流是否已经注册
        bool have_find_media_src = false;
        if (cookie) {
            auto lck = cookie->getLock();
            have_find_media_src = (*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source;
            if (!have_find_media_src) {
                (*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source = true;
            }
        }
        if (have_find_media_src) {
            //之前该cookie已经通过MediaSource::findAsync查找过了,所以现在只以文件系统查找结果为准
            response_file(cookie, cb, strFile, parser);
            return;
        }
        //hls文件不存在,我们等待其生成并延后回复
        MediaSource::findAsync(mediaInfo, strongSession, [response_file, cookie, cb, strFile, parser](const MediaSource::Ptr &src) {
            if (cookie) {
                auto lck = cookie->getLock();
                //尝试添加HlsMediaSource的观看人数(HLS是按需生成的,这样可以触发HLS文件的生成)
                (*cookie)[kCookieName].get<HttpCookieAttachment>()._hls_data->addByteUsage(0);
            }
            if (src && File::is_file(strFile.data())) {
                //流和m3u8文件都存在,那么直接返回文件
                response_file(cookie, cb, strFile, parser);
                return;
            }
            auto hls = dynamic_pointer_cast<HlsMediaSource>(src);
            if (!hls) {
                //流不存在,那么直接返回文件(相当于纯粹的HLS文件服务器,但是会挂起播放器15秒左右(用于等待HLS流的注册))
                response_file(cookie, cb, strFile, parser);
                return;
            }

            //流存在,但是m3u8文件不存在,那么等待生成m3u8文件(HLS源注册后,并不会立即生成HLS文件,有人观看才会按需生成HLS文件)
            hls->waitForFile([response_file, cookie, cb, strFile, parser]() {
                response_file(cookie, cb, strFile, parser);
            });
        });
    });
}

NoticeCenter::Instance().emitEvent() 触发 hook 鉴权函数

这里执行空操作。

src/Http/HttpFileManager.cpp
/**
 * 判断http客户端是否有权限访问文件的逻辑步骤
 * 1、根据http请求头查找cookie,找到进入步骤3
 * 2、根据http url参数查找cookie,如果还是未找到cookie则进入步骤5
 * 3、cookie标记是否有权限访问文件,如果有权限,直接返回文件
 * 4、cookie中记录的url参数是否跟本次url参数一致,如果一致直接返回客户端错误码
 * 5、触发kBroadcastHttpAccess事件
 */
static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaInfo &mediaInfo, bool is_dir,
                          const function<void(const string &errMsg, const HttpServerCookie::Ptr &cookie)> &callback) {
    //获取用户唯一id
    auto uid = parser.Params();
    auto path = parser.Url();

    //先根据http头中的cookie字段获取cookie
    HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, parser.getHeader());
    //如果不是从http头中找到的cookie,我们让http客户端设置下cookie
    bool cookie_from_header = true;
    if (!cookie && !uid.empty()) {
        //客户端请求中无cookie,再根据该用户的用户id获取cookie
        cookie = HttpCookieManager::Instance().getCookieByUid(kCookieName, uid);
        cookie_from_header = false;
    }

    // 未走这里
    if (cookie) {
        //找到了cookie,对cookie上锁先
        auto lck = cookie->getLock();
        auto attachment = (*cookie)[kCookieName].get<HttpCookieAttachment>();
        if (path.find(attachment._path) == 0) {
            //上次cookie是限定本目录
            if (attachment._err_msg.empty()) {
                //上次鉴权成功
                if (attachment._is_hls) {
                    //如果播放的是hls,那么刷新hls的cookie(获取ts文件也会刷新)
                    cookie->updateTime();
                    cookie_from_header = false;
                }
                callback("", cookie_from_header ? nullptr : cookie);
                return;
            }
            //上次鉴权失败,但是如果url参数发生变更,那么也重新鉴权下
            if (parser.Params().empty() || parser.Params() == cookie->getUid()) {
                //url参数未变,或者本来就没有url参数,那么判断本次请求为重复请求,无访问权限
                callback(attachment._err_msg, cookie_from_header ? nullptr : cookie);
                return;
            }
        }
        //如果url参数变了或者不是限定本目录,那么旧cookie失效,重新鉴权
        HttpCookieManager::Instance().delCookie(cookie);
    }

    bool is_hls = mediaInfo._schema == HLS_SCHEMA;

    SockInfoImp::Ptr info = std::make_shared<SockInfoImp>();
    info->_identifier = sender.getIdentifier();
    info->_peer_ip = sender.get_peer_ip();
    info->_peer_port = sender.get_peer_port();
    info->_local_ip = sender.get_local_ip();
    info->_local_port = sender.get_local_port();

    //该用户从来未获取过cookie,这个时候我们广播是否允许该用户访问该http目录
    HttpSession::HttpAccessPathInvoker accessPathInvoker = [callback, uid, path, is_dir, is_hls, mediaInfo, info]
            (const string &errMsg, const string &cookie_path_in, int cookieLifeSecond) {
        HttpServerCookie::Ptr cookie;
        if (cookieLifeSecond) {
            //本次鉴权设置了有效期,我们把鉴权结果缓存在cookie中
            string cookie_path = cookie_path_in;
            if (cookie_path.empty()) {
                //如果未设置鉴权目录,那么我们采用当前目录
                cookie_path = is_dir ? path : path.substr(0, path.rfind("/") + 1);
            }

            cookie = HttpCookieManager::Instance().addCookie(kCookieName, uid, cookieLifeSecond);
            //对cookie上锁
            auto lck = cookie->getLock();
            HttpCookieAttachment attachment;
            //记录用户能访问的路径
            attachment._path = cookie_path;
            //记录能否访问
            attachment._err_msg = errMsg;
            //记录访问的是否为hls
            attachment._is_hls = is_hls;
            if (is_hls) {
                //hls相关信息
                attachment._hls_data = std::make_shared<HlsCookieData>(mediaInfo, info);
                //hls未查找MediaSource
                attachment._have_find_media_source = false;
            }
            (*cookie)[kCookieName].set<HttpCookieAttachment>(std::move(attachment));
            callback(errMsg, cookie);
        } else {
            callback(errMsg, nullptr);
        }
    };

    if (is_hls) {
        //是hls的播放鉴权,拦截之
        emitHlsPlayed(parser, mediaInfo, accessPathInvoker, sender);
        return;
    }

	//事件未被拦截,则认为是http下载请求
	//直接执行了这里,鉴权相关。因为没有开启鉴权,这里执行了空判断,直接返回了。
    bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, static_cast<SockInfo &>(sender));
    if (!flag) {
        //此事件无人监听,我们默认都有权限访问
        callback("", nullptr);
    }
}

发送响应数据

同步操作。

3rdpart/ZLToolKit/src/Network/Socket.cpp
Task::Ptr SocketHelper::async(TaskIn task, bool may_sync) {
    return _poller->async(std::move(task), may_sync);
}

HttpSession::sendResponse() 响应数据

src/Http/HttpSession.cpp
void HttpSession::sendResponse(int code,
                               bool bClose,
                               const char *pcContentType,
                               const HttpSession::KeyValue &header,
                               const HttpBody::Ptr &body,
                               bool no_content_length ){
    GET_CONFIG(string,charSet,Http::kCharSet);
    GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);

    //body默认为空
    ssize_t size = 0;
    if (body && body->remainSize()) {
        //有body,获取body大小
        size = body->remainSize();
    }

    if(no_content_length){
        //http-flv直播是Keep-Alive类型
        bClose = false;
    }else if((size_t) size >= SIZE_MAX || size < 0 ){
        //不固定长度的body,那么发送完body后应该关闭socket,以便浏览器做下载完毕的判断
        bClose = true;
    }

    HttpSession::KeyValue &headerOut = const_cast<HttpSession::KeyValue &>(header);
    headerOut.emplace(kDate, dateStr());
    headerOut.emplace(kServer, SERVER_NAME);
    headerOut.emplace(kConnection, bClose ? "close" : "keep-alive");
    if(!bClose){
        string keepAliveString = "timeout=";
        keepAliveString += to_string(keepAliveSec);
        keepAliveString += ", max=100";
        headerOut.emplace(kKeepAlive,std::move(keepAliveString));
    }

    if(!_origin.empty()){
        //设置跨域
        headerOut.emplace(kAccessControlAllowOrigin,_origin);
        headerOut.emplace(kAccessControlAllowCredentials, "true");
    }

    if(!no_content_length && size >= 0 && (size_t)size < SIZE_MAX){
        //文件长度为固定值,且不是http-flv强制设置Content-Length
        headerOut[kContentLength] = to_string(size);
    }

    if(size && !pcContentType){
        //有body时,设置缺省类型
        pcContentType = "text/plain";
    }

    if((size || no_content_length) && pcContentType){
        //有body时,设置文件类型
        string strContentType = pcContentType;
        strContentType += "; charset=";
        strContentType += charSet;
        headerOut.emplace(kContentType,std::move(strContentType));
    }

    //发送http头
    string str;
    str.reserve(256);
    str += "HTTP/1.1 " ;
    str += to_string(code);
    str += ' ';
    str += getHttpStatusMessage(code) ;
    str += "\r\n";
    for (auto &pr : header) {
        str += pr.first ;
        str += ": ";
        str += pr.second;
        str += "\r\n";
    }
    str += "\r\n";
    SockSender::send(std::move(str));
    _ticker.resetTime();

    if(!size){
        //没有body
        if(bClose){
            shutdown(SockException(Err_shutdown,StrPrinter << "close connection after send http header completed with status code:" << code));
        }
        return;
    }

    GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize);
    if(body->remainSize() > sendBufSize){
        //文件下载提升发送性能
        setSocketFlags();
    }

    //发送http body
    AsyncSenderData::Ptr data = std::make_shared<AsyncSenderData>(shared_from_this(),body,bClose);
getSock()->setOnFlush([data](){
        // 先走下面onSocketFlushed, 然后再走这里
        return AsyncSender::onSocketFlushed(data);
	});
	// 先走这里,然后再走上面的回调onSocketFlushed
    AsyncSender::onSocketFlushed(data);
}

AsyncSenderData 组装数据

src/Http/HttpSession.cpp
class AsyncSenderData {
public:
    friend class AsyncSender;
    typedef std::shared_ptr<AsyncSenderData> Ptr;
    AsyncSenderData(const TcpSession::Ptr &session, const HttpBody::Ptr &body, bool close_when_complete) {
        _session = dynamic_pointer_cast<HttpSession>(session);
        _body = body;
        _close_when_complete = close_when_complete;
    }
    ~AsyncSenderData() = default;
private:
std::weak_ptr<HttpSession> _session;
    // HttpBody 派生出三个类 HttpStringBody、HttpFileBody、HttpMultiFormBody
    HttpBody::Ptr _body;
    bool _close_when_complete;
    bool _read_complete = false;
};

AsyncSender::onSocketFlushed() 读取并发送数据

src/Http/HttpSession.cpp
class AsyncSender {
public:
    typedef std::shared_ptr<AsyncSender> Ptr;
    static bool onSocketFlushed(const AsyncSenderData::Ptr &data) {
        if (data->_read_complete) {
            if (data->_close_when_complete) {
                //发送完毕需要关闭socket
                shutdown(data->_session.lock());
            }
            return false;
        }

        GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize);
        // HttpBody 派生出三个类 HttpStringBody、HttpFileBody、HttpMultiFormBody
        // 这里取 HttpBody::readDataAsync()
        // 从文件中读取数据,然后调用回调返回
        data->_body->readDataAsync(sendBufSize, [data](const Buffer::Ptr &sendBuf) {
            auto session = data->_session.lock();
            if (!session) {
                //本对象已经销毁
                return;
            }
            session->async([data, sendBuf]() {
                auto session = data->_session.lock();
                if (!session) {
                    //本对象已经销毁
                    return;
                }
                // 发送数据。
                onRequestData(data, session, sendBuf);
            }, false);
        });
        return true;
    }

private:
    static void onRequestData(const AsyncSenderData::Ptr &data, const std::shared_ptr<HttpSession> &session, const Buffer::Ptr &sendBuf) {
        session->_ticker.resetTime();
        // HttpSession继承自 TcpSession 继承自 Session 继承自SocketHelper
        if (sendBuf && session->send(sendBuf) != -1) {
            //文件还未读完,还需要继续发送
            if (!session->isSocketBusy()) {
                //socket还可写,继续请求数据
                onSocketFlushed(data);
            }
            return;
        }
        //文件写完了
        data->_read_complete = true;
        if (!session->isSocketBusy() && data->_close_when_complete) {
            shutdown(session);
        }
    }

    static void shutdown(const std::shared_ptr<HttpSession> &session) {
        if(session){
            session->shutdown(SockException(Err_shutdown, StrPrinter << "close connection after send http body completed."));
        }
    }
};

HttpBody::readDataAsync() 读取文件

src/Http/HttpBody.h
/**
 * http content部分基类定义
 */
class HttpBody : public std::enable_shared_from_this<HttpBody>{
public:
    typedef std::shared_ptr<HttpBody> Ptr;
    HttpBody(){}

    virtual ~HttpBody(){}

    /**
     * 剩余数据大小,如果返回-1, 那么就不设置content-length
     */
    virtual ssize_t remainSize() { return 0;};

    /**
     * 读取一定字节数,返回大小可能小于size
     * @param size 请求大小
     * @return 字节对象,如果读完了,那么请返回nullptr
     */
    virtual Buffer::Ptr readData(size_t size) { return nullptr;};

    /**
     * 异步请求读取一定字节数,返回大小可能小于size
     * @param size 请求大小
     * @param cb 回调函数
     */
    virtual void readDataAsync(size_t size,const function<void(const Buffer::Ptr &buf)> &cb){
        //由于unix和linux是通过mmap的方式读取文件,所以把读文件操作放在后台线程并不能提高性能
        //反而会由于频繁的线程切换导致性能降低以及延时增加,所以我们默认同步获取文件内容
        //(其实并没有读,拷贝文件数据时在内核态完成文件读)
        cb(readData(size));
    }
};
src/Http/HttpBody.cpp
Buffer::Ptr HttpFileBody::readData(size_t size) {
    size = MIN((size_t)remainSize(),size);
    if(!size){
        //没有剩余字节了
        return nullptr;
    }
    if(!_map_addr){
        //fread模式
        ssize_t iRead;
        auto ret = _pool.obtain();
        ret->setCapacity(size + 1);
        do{
            iRead = fread(ret->data(), 1, size, _fp.get());
        }while(-1 == iRead && UV_EINTR == get_uv_error(false));

        if(iRead > 0){
            //读到数据了
            ret->setSize(iRead);
            _offset += iRead;
            return std::move(ret);
        }
        //读取文件异常,文件真实长度小于声明长度
        _offset = _max_size;
        WarnL << "read file err:" << get_uv_errmsg();
        return nullptr;
    }

    //mmap模式
    auto ret = std::make_shared<BufferMmap>(_map_addr,_offset,size);
    _offset += size;
    return ret;
}

SocketHelper::send() 发送数据

3rdpart/ZLToolKit/src/Network/Socket.cpp
ssize_t SocketHelper::send(Buffer::Ptr buf) {
    if (!_sock) {
        return -1;
    }
    return _sock->send(std::move(buf), nullptr, 0, _try_flush);
}

http 点播实例

config.ini

[http]
#http服务器字符编码,windows上默认gb2312
charSet=utf-8
#http链接超时时间
keepAliveSecond=30
#http请求体最大字节数,如果post的body太大,则不适合缓存body在内存
maxReqSize=40960
#404网页内容,用户可以自定义404网页
#notFound=<html><head><title>404 Not Found</title></head><body bgcolor="white"><center><h1>您访问的资源不存在!</h1></center><hr><center>ZLMediaKit-4.0</center></body></html>
#http服务器监听端口
#port=80
port=806
#http文件服务器根目录
#可以为相对(相对于本可执行程序目录)或绝对路径
rootPath=./www
#http文件服务器读文件缓存大小,单位BYTE,调整该参数可以优化文件io性能
sendBufSize=65536
#https服务器监听端口
#sslport=443
sslport=4436
#是否显示文件夹菜单,开启后可以浏览文件夹
dirMenu=1
#虚拟目录, 虚拟目录名和文件路径使用","隔开,多个配置路径间用";"隔开
#例如赋值为 app_a,/path/to/a;app_b,/path/to/b 那么
#访问 http://127.0.0.1/app_a/file_a 对应的文件路径为 /path/to/a/file_a
#访问 http://127.0.0.1/app_b/file_b 对应的文件路径为 /path/to/b/file_b
#访问其他http路径,对应的文件路径还是在rootPath内
virtualPath=app_a,./www/proxy/file
#禁止后缀的文件使用mmap缓存,使用“,”隔开
#例如赋值为 .mp4,.flv
#那么访问后缀为.mp4与.flv 的文件不缓存
forbidCacheSuffix=
#可以把http代理前真实客户端ip放在http头中:https://github.com/ZLMediaKit/ZLMediaKit/issues/1388
#切勿暴露此key,否则可能导致伪造客户端ip
forwarded_ip_header=

port 默认为 80 可以不用改,我这里改成了 806;
rootPath 是 http 服务的根目录地址,默认是./www(相对于 MediaServer),需要自行创建目录,如下:

test@zlmediakit:~$ tree build/
build/
├── bin
│   ├── log
│   │   └── 2023-07-20_00.log
│   ├── MediaServer
│   └── www
│       ├── proxy
│       │   ├── 720x1280p30.wintersecret59s.mp4
│       │   ├── 720x1280p30.wintersecret59s.flv
│       │   └── file
│       │       └── 720x1280p30.wintersecret59s_file.mp4
│       └── record
├── config
│   └── config.ini
├── include
│   ├── mk_common.h
│   ├── mk_events.h
│   ├── mk_events_objects.h
│   ├── mk_export.h
│   ├── mk_frame.h
│   ├── mk_h264_splitter.h
│   ├── mk_httpclient.h
│   ├── mk_media.h
│   ├── mk_mediakit.h
│   ├── mk_player.h
│   ├── mk_proxyplayer.h
│   ├── mk_pusher.h
│   ├── mk_recorder.h
│   ├── mk_rtp_server.h
│   ├── mk_tcp.h
│   ├── mk_thread.h
│   ├── mk_track.h
│   ├── mk_transcode.h
│   └── mk_util.h
└── lib
    └── libmk_api.so

9 directories, 26 files
test@zlmediakit:~$ 

vlc 拉流:

# proxy 为 appName,对应.www/proxy
http://10.49.44.60:806/proxy/720x1280p30.wintersecret59s.mp4
http://10.49.44.60:806/proxy/720x1280p30.wintersecret59s.flv
# ./www/proxy/目录下还可以再创建目录,但是需要在拉流的时候对应添加
http://10.49.44.60:806/proxy/file/720x1280p30.wintersecret59s_file.mp4
# 使用 virtualPath
http://10.49.44.60:806/app_a/720x1280p30.wintersecret59s_file.mp4

Ps. FFmpeg MP4 转 FLV

ffmpeg -i 720x1280p30.wintersecret59s.mp4 -vcodec copy -acodec copy 720x1280p30.wintersecret59s.mp4.flv

参考官方wiki:
https://gitee.com/xia-chu/ZLMediaKit/wikis/Home

遇到的疑惑

使用 VLC 拉流的时候,服务器日志显示:有两次 http 请求。不知是何含义。
docker学习(三)常用命令
使用 FFmpeg 拉流的时候,http 请求次数更多。文章来源地址https://www.toymoban.com/news/detail-505782.html

ffmpeg -y -i http://10.49.44.60:806/proxy/720x1280p30.wintersecret59s.mp4  -c:v copy -c:a copy -f mp4 /dev/null 

到了这里,关于docker学习(三)常用命令的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • docker学习(三)常用命令

    ZLMediaKit源码分析(一)服务启动 ZLMediaKit源码分析(二)推流创建 ZLMediaKit源码分析(三)拉流创建 ZLMediaKit源码分析(四)重封装 ZLMediaKit源码分析(五)代理服务 ZLMediaKit源码分析(六)http 点播 ZLMediaKit 使用 http1.1,而 http1.1 是基于 TCP 的。 这里是 http 服务的数据入口 Ht

    2024年02月11日
    浏览(30)
  • docker compose 管理应用服务的常用命令

    一 、docker compose 是什么 Docker Compose是一个用来管理多个关联容器的工具,可以根据配置文件自动构建、管理、编排一组容器。 Docker Compose语境下的“服务”是指一组容器共同构成的一个应用服务后端。 Docker Compose语境下的“项目”是由一个或多个应用服务组成的。 Docker Com

    2024年02月07日
    浏览(35)
  • 华为云云耀云服务器L实例评测|docker 常用操作命令

    ​ 前面讲到了docker环境的安装,这是我们可以直接打开远程连接华为云云耀云服务器L实例,直接连接公网IP,就可以使用。我们先查看docker版本,可以看到版本为24.0.4。 当您购买了云耀云服务器L实例后,可以根据业务需要搭建为不同的环境、网站或应用。本文汇总了基于云

    2024年02月07日
    浏览(55)
  • Linux 常用操作命令(CentOS 7.0)- 故障定位:服务器负载、进程管理、日志分析

    系统经研发测试上线后,如果运行期间出现了BUG,需要对服务故障进行定位,一般会查看服务器负载、服务状态、进程管理、服务日志等。 本文以CentOS 7.0 操作系统上的命令操作作为示例进行记录。 #服务器负载 完整参见:http://www.laobingbiji.com/note/detail.html?note_id=20231115154337

    2024年01月17日
    浏览(69)
  • Redis——基础篇(包含redis在云服务上的docker化安装和连接以及常用命令)

    Redis为键值型数据库,数据以键值形式存储。没有表,没有约束。  mysql就是典型的关系型数据库(SQL)。 目的都是数据的增删改查,但数据存储方式不一样。   关系型和非关系型在结构上有差异 关系型的结构一般定好后就很少修改,非关系型的就更加自由  关系型数据库的关

    2024年02月13日
    浏览(49)
  • SpringCloud源码学习笔记3——Nacos服务注册源码分析

    系列文章目录和关于我 实现服务治理、服务动态扩容,以及调用时能有负载均衡的效果。 如果我们将服务提供方的ip地址配置在服务消费方的配置文件中,当服务提供方实例上线下线,消费方都需要重启服务,导致二者耦合度过高。注册中心就是在二者之间加一层,实现解耦

    2023年04月08日
    浏览(44)
  • Python 与机器学习,在服务器使用过程中,常用的 Linux 命令包括哪些?

    🍉 CSDN 叶庭云 : https://yetingyun.blog.csdn.net/ 本博客旨在分享在实际开发过程中,开发者需要了解并熟练运用的 Linux 操作系统常用命令。Linux 作为一种操作系统,与 Windows 或 MacOS 并驾齐驱,尤其在服务器和开发环境中占据重要地位。Linux 命令,简而言之,就是指导计算机执行

    2024年04月12日
    浏览(60)
  • ZLMediaKit 重建docker包

    修改配置文件,配置项参考:ZLMediaKit 配置文件说明 上传步骤2中导出来的media文件夹到/opt/nms/目录下 加载镜像  各种端口直接用配置文件中配置的,不需要映射

    2024年02月11日
    浏览(34)
  • Docker | Docker常用命令

    ✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏:Docker系列 ✨特色专栏: MySQL学习 🥭本文内容: Docker | Docker常用命令 📚个人知识库: [Leo知识库]https://gaoziman.gitee.io/blogs/),欢迎大家访问 大家

    2024年02月05日
    浏览(47)
  • Docker实战:Docker常用命令

    参考:https://blog.csdn.net/weixin_45509582/article/details/125599521 输出依次为,进程pid、容器ID、容器名、存储work路径,即可确定是哪个容器。 参考:https://blog.csdn.net/sebeefe/article/details/123732823

    2024年02月12日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包