프로젝트
ZLMediaKit는 현재 RTMP / RTSP / HLS / HTTP-지원하는 고성능 스트리밍 미디어 서비스 프레임 워크 FLV 스트리밍 프로토콜을. 이 프로젝트는 리눅스, 맥 OS, 윈도우, IOS, 지원하고 안드로이드 (만 RTSP 지원 H265) H264, AAC, H265 포함한 플랫폼, 지원되는 인코딩 형식을, 모델은 비 차단 멀티 스레드 프로그래밍 멀티플렉서 IO를 사용 (리눅스 epoll 파일을 사용하여 다른 플랫폼)을 선택하여.
골격은 리눅스 플랫폼의 메모리 복사 간소화 코드 신뢰성 높은 동시성을 줄여 원료 포인터를 방지하기 위해 개발 된 C ++ (11)에 기초하고, 하나의 프로세스는 멀티 코어 CPU를 이용할 수있는 최대 소모 CPU, 네트워크 카드 성능은, 쉽게 기가비트 네트워크 카드의 성능 한계에 도달합니다. 뿐만 아니라 성능의 높은 수준, 그래서 매우 낮은 대기 시간 (초)는 화면을 엽니 다.
ZLMediaKit 현재 버전, 많은 업그레이드 프로그래밍 모델 최적화를 여러 번 반복 한 후, 확인 된 성숙하고 안정뿐만 아니라 생산 환경의 다양한되었다,이 문서는 ZLMediaKit 원리에 초점을 맞추고 프로젝트의 고성능 특성을 얻을 수 있습니다.
네트워크 모델 비교
단일 스레드 멀티 SRS 코 루틴 달리 / 단일 스레드, Nginx의 다중 프로세스 모델 Node.js를 레디 스; ZLMediaKit 단일 스레드 프로세스 모델을 사용. 그런데 왜 ZLMediaKit는 프로그래밍 모델을 채택?
서버 백엔드 개발 엔지니어로, 작업 다년간의 경험 안정성에 대한 요구, 서버 프로그램으로, 나에게 말한다 ++ C의 년, 서버 성능을 할 수있는 거의,하지만 결코 쉽게 핵심 덤프, 서비스 중단, 재시작, 예외에 대한 광고가 작동 프로젝트왔다 결과는 최악이다. 그렇다면 우리는 서버의 안정성을 보장하기 위해입니까? 현재 다음과 같은 방법이 있습니다 :
- 단일 스레드 모델
- + 스레드 코 루틴
- + 단일 멀티 스레드 프로세스
- 멀티 스레드 잠금 +
- 사용되지 않는 C / C ++
단일 스레드 모델을 사용의 장점에 관계없이 자원 경합 문제의 상호 배타적 간단하고 안정적인 서버, 그래서보다 쉽게 높은 안정성을 달성 할 수 있으며,이 모델을 사용하여 일반적인 프로젝트 레디 스, node.js.은 이 단일 스레드 모델이기 때문에하지만, 단점도 분명 그것은 단지 멀티 코어 CPU에서 멀티 코어 CPU 수의 힘을 최대한 활용 할 수없는, 성능 병목 현상은 주로 CPU (우리는 레디 스에서 키의 구현에 경험이 있어야 * 천천히 대기) .
+ 단일 스레딩 모델 코 루틴, 스타일을 프로그래밍 그들 사이의 주요 차이점의 순수 솔루션을 구별하지 않고 스레드. 사용 순수 단일 스레드 모델은 비 차단 높은 동시성을 달성하기 위해 콜백 방식으로 사방이 모델은 소위 콜백 지옥를 발급합니다, 프로그래밍은 더 문제가 될 것입니다. + 코 루틴 단일 스레드 프로그램은 자연 차단 프로그래밍 스타일, 코 루틴 라이브러리 내부 관리 작업 스케줄링, 또한 본질적으로 비 블로킹을 사용하여 프로그래밍을 단순화된다. 그러나 시스템과 관련된 상대적으로 낮은 코 루틴 라이브러리는 밀접 때문에 크로스 플랫폼이 더 높은 임계 코 루틴 라이브러리를 달성하기 위해 매우 할 좋은, 그리고 디자인 아니라, 관련이 있습니다. SRS는 SRS 윈도우에서 실행되지 않습니다 인해 제한, 코 루틴 라이브러리를이 프로그래밍 모델을 사용하고 있습니다.
위의 단일 스레드 모델을 해결하기 위해, 많은 서버는 단일 스레드 멀티 프로세스 프로그래밍 모델을 사용하여,이 모델에서는 기존의 단일 스레드 모델의 간단하고 신뢰할 수있는 성능뿐만 아니라, 프로세스가 멀티 코어 CPU의 성능을 극대화하고 Nginx에이 프로그래밍 모델 인 것처럼, 다른 프로세스에 영향을 미치지 않습니다 걸,하지만이 모델은 한계가있다. 이 모델에서, 두 개의 세션이 다른 프로세스에서 실행 할 수있다, 세션 사이의 절연되어, 이것은 세션 사이의 통신에 어려움을 주도하고있다. 예를 들어, 사용자 A가 서버 B 프로세스에 접속 된 서버 프로세스 A, 사용자 B에 연결되고, 만약 둘 사이의 데이터 교환을 수행 할 경우, 매우 어려울 것,이 프로세스 간 통신을 통해 수행되어야한다. 프로세스 간 통신 오버 헤드 비용은 또한 프로그램에 더 어렵고, 비교적 크다. 데이터가 세션에 걸쳐 상호 작용을 할 필요가 없습니다하지만 (예를 들어, HTTP 서버),이 모델은 http 서버도 매우 성공적으로 Nginx에 있도록, 특히 적합하지만, 세션,이 사이에 의사 소통 인스턴트 메시징 서비스와 같은 요구의 종류 인 경우 개발 모델의 종류는 매우 적합하지 않습니다. 하지만 지금은 점점 더 많은 서비스는 분산 된 클러스터의 배포를 지원하는 데 필요한, 그래서 결함 단일 스레드 멀티 프로세스 솔루션은 점점 더 분명하게된다.
는 C 이후 / C ++는 코어 덤프에 빠르고, 간단하고 원유를 처리하는 정적 강력한 형식의 언어, 예외의 일종이다. 당신이 조기 발견을 찾아 문제를 해결하기보다는 문제를 지연 할 수 있도록이 해결 될 수없는, 당신의주의를 야기하기 때문에 C / C ++ 디자인 철학은 어떤 의미에서, 붕괴 좋은 일의 종류, 초기 노출 오류를 발견하는 것입니다 다시 노출되었을 때. 그러나 보통 사람을 위해 이렇게, C / C ++가 매우 친절하지 않고, 인간이 기계처럼 엄격하지 않은, 약간의 과실은 코어 덤프를 다루는 파괴가 필요하지 않습니다, 또한, 몇 가지 작은 문제가 해치지 않을 것 불가피하다. 그리고 C / C ++의 곡선 비정상적인 어려움을 배우고, 많은 사람들이, 구덩이를 포기 표명 한 많은 사람들이 / 얼랑 / Node.js를 등을 이동되어, 아무 소용이 몇 년이있다.
그러나, C / C ++의 때문에 성능 이점뿐만 아니라, 역사적인 이유의 일부 장면에서 최선의 선택이며, C / C ++의 진정한 크로스 플랫폼 언어입니다, 또한, 스마트 포인터의 도입으로, 메모리 관리는 더 이상 문제가되지 않습니다 그리고 지원 lambad 구문, 프로그램이 어려워 더 이상 컨텍스트 바인딩이 없습니다. C ++ 컴파일러의 정적 완벽 한 반사의 새로운 기능의 지원으로, 현대 C ++는 점점 더 간단하고 빠른 프로그램. C ++ 11을 사용 ZLMediaKit 고성능 스트리밍 미디어 서비스를위한 새로운 표준뿐만 아니라 완료 관련 개념의 프레임 워크입니다.
다른 기존의 멀티 스레딩 모델; 다른 다른 프로그래밍 모델보다도 ZLMediaKit은 멀티 스레드 개발 모델을 사용 ZLMediaKit를 메모리 관리를 할 ++ 스마트 포인터 (11) C를 사용하여, 메모리 관리는 다중 스레드 스위치 경우에 완벽 할 수 있습니다 스레드 및 수명주기를 공유 할 수 있습니다. 극단적 한편 뮤텍스 입자 크기 감소, 거의 무시할. 따라서, 멀티 스레딩 모델 ZLMediaKit 성능 손실의 사용은 각 스레드의 성능은 단일 스레드 모델과 거의 비슷하지만, 또한 완전히 모든 코어 CPU의 성능을 배수 할 수있다, 매우 낮다.
자세한 네트워크 모델
ZLMediaKit가 자동으로 시작시 CPU 코어의 수에 따라 여러 epoll에 예 (선택이 아닌 리눅스 플랫폼)을 생성;이는 epoll 인스턴스를 실행하는 스레드가있을 것입니다 epoll_wait
트리거 이벤트를 기다리는 기능을.
RTMP는 서비스의 ZLMediaKit에, 예를 들어, 생성에 TcpServer
새 RTMP 재생 요청을 수신하는 경우, 그래서 시간, ZLMediaKit이의 TCP 서비스는 각 청취 소켓의 epoll 인스턴스에 추가됩니다 epoll에 커널에서 다음 여러 인스턴스 일정에 따라 자동 가벼운 부하 스레드가 트리거 이벤트를 받아, 다음 코드는 다음과 같습니다
template <typename SessionType>
void start(uint16_t port, const std::string& host = "0.0.0.0", uint32_t backlog = 1024) {
start_l<SessionType>(port,host,backlog);
//自动加入到所有epoll线程监听
EventPollerPool::Instance().for_each([&](const TaskExecutor::Ptr &executor){
EventPoller::Ptr poller = dynamic_pointer_cast<EventPoller>(executor);
if(poller == _poller || !poller){
return;
}
auto &serverRef = _clonedServer[poller.get()];
if(!serverRef){
//绑定epoll实例
serverRef = std::make_shared<TcpServer>(poller);
}
serverRef->cloneFrom(*this);
});
}
void cloneFrom(const TcpServer &that){
if(!that._socket){
throw std::invalid_argument("TcpServer::cloneFrom other with null socket!");
}
_sessionMaker = that._sessionMaker;
//克隆一个相同fd的Socket对象
_socket->cloneFromListenSocket(*(that._socket));
_timer = std::make_shared<Timer>(2, [this]()->bool {
this->onManagerSession();
return true;
},_poller);
this->mINI::operator=(that);
_cloned = true;
}
(가) 이벤트 서버를 접수받은 후, 그것을 생성 TcpSession
epoll 파일 인스턴스 대상 인드 (동일한 시간에 대응하는 peer fd
관련 epoll에 모니터를 추가). 각각의 TCP 연결은에 해당됩니다 TcpSession
클라이언트와 서버 후 데이터 교환에서, 대상 TcpSession
오브젝트가 그들과 관련된 모든 비즈니스 데이터를 처리하고 객체가 epoll에 스레드에 의해 트리거 된 후 모든 이벤트의 수명 기간 동안되도록 서버 각 스레드는 고객의 합리적인 번호에 할당 유니폼을 epoll에 있습니다. 다음 이벤트 처리 로직 서버는 조각을 동의 :
// 接收到客户端连接请求
virtual void onAcceptConnection(const Socket::Ptr & sock) {
weak_ptr<TcpServer> weakSelf = shared_from_this();
//创建一个TcpSession;这里实现创建不同的服务会话实例
auto sessionHelper = _sessionMaker(weakSelf,sock);
auto &session = sessionHelper->session();
//把本服务器的配置传递给TcpSession
session->attachServer(*this);
//TcpSession的唯一识别符,可以是guid之类的
auto sessionId = session->getIdentifier();
//记录该TcpSession
if(!SessionMap::Instance().add(sessionId,session)){
//有同名session,说明getIdentifier生成的标识符有问题
WarnL << "SessionMap::add failed:" << sessionId;
return;
}
//SessionMap中没有相关记录,那么_sessionMap更不可能有相关记录了;
//所以_sessionMap::emplace肯定能成功
auto success = _sessionMap.emplace(sessionId, sessionHelper).second;
assert(success == true);
weak_ptr<TcpSession> weakSession(session);
//会话接收数据事件
sock->setOnRead([weakSession](const Buffer::Ptr &buf, struct sockaddr *addr){
//获取会话强引用
auto strongSession=weakSession.lock();
if(!strongSession) {
//会话对象已释放
return;
}
//TcpSession处理业务数据
strongSession->onRecv(buf);
});
//会话接收到错误事件
sock->setOnErr([weakSelf,weakSession,sessionId](const SockException &err){
//在本函数作用域结束时移除会话对象
//目的是确保移除会话前执行其onError函数
//同时避免其onError函数抛异常时没有移除会话对象
onceToken token(nullptr,[&](){
//移除掉会话
SessionMap::Instance().remove(sessionId);
auto strongSelf = weakSelf.lock();
if(!strongSelf) {
return;
}
//在TcpServer对应线程中移除map相关记录
strongSelf->_poller->async([weakSelf,sessionId](){
auto strongSelf = weakSelf.lock();
if(!strongSelf){
return;
}
strongSelf->_sessionMap.erase(sessionId);
});
});
//获取会话强应用
auto strongSession=weakSession.lock();
if(strongSession) {
//触发onError事件回调
strongSession->onError(err);
}
});
}
通过上诉描述,我们应该大概了解了ZLMediaKit的网络模型,通过这样的模型基本上能榨干CPU的算力,不过CPU算力如果使用不当 ,也可能白白浪费,使之做一些无用的事物,那么在ZLMediaKit中还有那些技术手段来提高性能呢?我们在下节展开论述。
닫기 뮤텍스
토론에, 우리가 알고있는 TcpSession
대부분의 계산이 TcpSession에서 수행하는 서버에 ZLMediaKit 핵심 요소입니다. TcpSession
라이프 사이클의 전하의 epoll 예는 다른 스레드가 직접 조작 할 수 TcpSession
있으므로 어떤 의미에서, 객체 (동작을 완료하기 위해 대응하는 나사 epoll에 의해 스위칭되는 스레드)를 TcpSeesion
위한 ZLMediaKit되도록 상기 스레드 모델은 TcpSession
대응 네트워크 보호 뮤텍스의 IO-없는 동작은 서버 모드로 실질적으로 잠금을 실행하지 ZLMediaKit,이 경우, 성능에 잠금 미치는 영향은 거의 무시할 수있다. 다음은 폐쇄 ZLMediaKit 뮤텍스 코드 조각입니다 :
virtual Socket::Ptr onBeforeAcceptConnection(const EventPoller::Ptr &poller){
/**
* 服务器模型socket是线程安全的,所以为了提高性能,关闭互斥锁
* Socket构造函数第二个参数即为是否关闭互斥锁
*/
return std::make_shared<Socket>(poller,false);
}
//Socket对象的构造函数,第二个参数即为是否关闭互斥锁
Socket::Socket(const EventPoller::Ptr &poller,bool enableMutex) :
_mtx_sockFd(enableMutex),
_mtx_bufferWaiting(enableMutex),
_mtx_bufferSending(enableMutex) {
_poller = poller;
if(!_poller){
_poller = EventPollerPool::Instance().getPoller();
}
_canSendSock = true;
_readCB = [](const Buffer::Ptr &buf,struct sockaddr *) {
WarnL << "Socket not set readCB";
};
_errCB = [](const SockException &err) {
WarnL << "Socket not set errCB:" << err.what();
};
_acceptCB = [](Socket::Ptr &sock) {
WarnL << "Socket not set acceptCB";
};
_flushCB = []() {return true;};
_beforeAcceptCB = [](const EventPoller::Ptr &poller){
return nullptr;
};
}
//MutexWrapper对象定义,可以选择是否关闭互斥锁
template <class Mtx = recursive_mutex>
class MutexWrapper {
public:
MutexWrapper(bool enable){
_enable = enable;
}
~MutexWrapper(){}
inline void lock(){
if(_enable){
_mtx.lock();
}
}
inline void unlock(){
if(_enable){
_mtx.unlock();
}
}
private:
bool _enable;
Mtx _mtx;
};
메모리 복사를 피하십시오
전통적인 모델에서 멀티 스레드 때문에 데이터 스레드 안전성 확보하기 위해서는, 문제의 스위칭 스레드 전달되며, 메모리 복사는 일반적으로 문제를 회피하기 위해 사용하고, 외주 데이터 처리부는 메모리 카피를 사용하지 않고 달성하기 매우 어렵다. 그러나이 스트리밍 미디어 비즈니스 로직은 동일한 사용자의 라이브 방송은 메모리의 복사본을 배포 할 때마다, 그 비용이 매우 인상적 경우, 그것은 심각하게 서버 성능에 드래그 것, 대규모입니다 볼 수 있었다.
ZLMediaKit 메모리, 기존의 다중 스레드 C ++이 작업을 수행하기 어려운 프로그램을 복사하지 마십시오, 미디어 데이터 전송을하고 있지만, 우리는 C ++ 11 축복하는, 참조 카운팅의 사용, 독창적 인 솔루션에 멀티 스레드 메모리 문제 수명주기 관리, 다음은 RTMP 서버는 미디어가 코드의 메모리 복사를 방지 배포 할 수 있습니다 :
void RtmpProtocol::sendRtmp(uint8_t ui8Type, uint32_t ui32StreamId,
const Buffer::Ptr &buf, uint32_t ui32TimeStamp, int iChunkId){
if (iChunkId < 2 || iChunkId > 63) {
auto strErr = StrPrinter << "不支持发送该类型的块流 ID:" << iChunkId << endl;
throw std::runtime_error(strErr);
}
//是否有扩展时间戳
bool bExtStamp = ui32TimeStamp >= 0xFFFFFF;
//rtmp头
BufferRaw::Ptr bufferHeader = obtainBuffer();
bufferHeader->setCapacity(sizeof(RtmpHeader));
bufferHeader->setSize(sizeof(RtmpHeader));
//对rtmp头赋值,如果使用整形赋值,在arm android上可能由于数据对齐导致总线错误的问题
RtmpHeader *header = (RtmpHeader*) bufferHeader->data();
header->flags = (iChunkId & 0x3f) | (0 << 6);
header->typeId = ui8Type;
set_be24(header->timeStamp, bExtStamp ? 0xFFFFFF : ui32TimeStamp);
set_be24(header->bodySize, buf->size());
set_le32(header->streamId, ui32StreamId);
//发送rtmp头
onSendRawData(bufferHeader);
//扩展时间戳字段
BufferRaw::Ptr bufferExtStamp;
if (bExtStamp) {
//生成扩展时间戳
bufferExtStamp = obtainBuffer();
bufferExtStamp->setCapacity(4);
bufferExtStamp->setSize(4);
set_be32(bufferExtStamp->data(), ui32TimeStamp);
}
//生成一个字节的flag,标明是什么chunkId
BufferRaw::Ptr bufferFlags = obtainBuffer();
bufferFlags->setCapacity(1);
bufferFlags->setSize(1);
bufferFlags->data()[0] = (iChunkId & 0x3f) | (3 << 6);
size_t offset = 0;
uint32_t totalSize = sizeof(RtmpHeader);
while (offset < buf->size()) {
if (offset) {
//发送trunkId
onSendRawData(bufferFlags);
totalSize += 1;
}
if (bExtStamp) {
//扩展时间戳
onSendRawData(bufferExtStamp);
totalSize += 4;
}
size_t chunk = min(_iChunkLenOut, buf->size() - offset);
//分发流媒体数据包,此处规避了内存拷贝
onSendRawData(std::make_shared<BufferPartial>(buf,offset,chunk));
totalSize += chunk;
offset += chunk;
}
_ui32ByteSent += totalSize;
if (_ui32WinSize > 0 && _ui32ByteSent - _ui32LastSent >= _ui32WinSize) {
_ui32LastSent = _ui32ByteSent;
sendAcknowledgement(_ui32ByteSent);
}
}
//BufferPartial对象用于rtmp包的chunk大小分片,规避内存拷贝
class BufferPartial : public Buffer {
public:
BufferPartial(const Buffer::Ptr &buffer,uint32_t offset,uint32_t size){
_buffer = buffer;
_data = buffer->data() + offset;
_size = size;
}
~BufferPartial(){}
char *data() const override {
return _data;
}
uint32_t size() const override{
return _size;
}
private:
Buffer::Ptr _buffer;
char *_data;
uint32_t _size;
};
우리는 RTP 패킷을 보낼 때 또한 메모리 복사를 방지하기 위해 같은 원칙을 적용합니다.
풀을 순환 사용 목적
메모리 성능이 저하 될뿐만 아니라 메모리 조각화가 발생할뿐만 아니라 너무 많은 삭제 / 새로운 프로그램의 파괴를 열어 글로벌 뮤텍스이다. 다음 코드를 사용하여 목욕을 순환 할 때, RTP 패킷을 이러한 문제를 방지하기 위해 순환 탱크를 사용합니다 ZLMediaKit :
RtpPacket::Ptr RtpInfo::makeRtp(TrackType type, const void* data, unsigned int len, bool mark, uint32_t uiStamp) {
uint16_t ui16RtpLen = len + 12;
uint32_t ts = htonl((_ui32SampleRate / 1000) * uiStamp);
uint16_t sq = htons(_ui16Sequence);
uint32_t sc = htonl(_ui32Ssrc);
//采用循环池来获取rtp对象
auto rtppkt = ResourcePoolHelper<RtpPacket>::obtainObj();
unsigned char *pucRtp = rtppkt->payload;
pucRtp[0] = '$';
pucRtp[1] = _ui8Interleaved;
pucRtp[2] = ui16RtpLen >> 8;
pucRtp[3] = ui16RtpLen & 0x00FF;
pucRtp[4] = 0x80;
pucRtp[5] = (mark << 7) | _ui8PlayloadType;
memcpy(&pucRtp[6], &sq, 2);
memcpy(&pucRtp[8], &ts, 4);
//ssrc
memcpy(&pucRtp[12], &sc, 4);
//playload
memcpy(&pucRtp[16], data, len);
rtppkt->PT = _ui8PlayloadType;
rtppkt->interleaved = _ui8Interleaved;
rtppkt->mark = mark;
rtppkt->length = len + 16;
rtppkt->sequence = _ui16Sequence;
rtppkt->timeStamp = uiStamp;
rtppkt->ssrc = _ui32Ssrc;
rtppkt->type = type;
rtppkt->offset = 16;
_ui16Sequence++;
_ui32TimeStamp = uiStamp;
return rtppkt;
}
소켓 세트 관련 마크
TCP_NODELAY가 서버의 응답 성을 향상시킬 수 연 후, 더 중요한 것은 개방 TCP_NODELAY 표시 (예 : SSH 서비스 등) 민감한 서비스에 대한 요구 사항을 지연합니다. 그러나 스트리밍 서비스, 데이터의 양이 상대적으로 크기 때문에 꾸준한 위해, TCP_NODELAY ACK 패킷 번호 오프를 줄일 대역폭 자원을 최대한 활용하는 것이 가능하다.
다른 마커 MSG_MORE 네트워크 처리량을 향상이다 데이터가 전송 될 때이 효과가 표시되고, 서버는 특정 하나의 전송 패키지 전에 데이터 캐시, 및 RTSP와 같은 비즈니스 시나리오, 그것은 특히 적합 MSG_MORE 마커이고; RTP 패킷 (a MTU 미만)은 일반적으로 작기 때문에, 패킷의 수를 대폭 MSG_MORE 마커에 의해 감소 될 수있다.
핸드 쉐이크 중에 선수를 다루는 ZLMediaKit가 켜져 있고 MSG_MORE TCP_NODELAY를 해제, 목표는 시간이 많이 소요되는 링크를 확립 비디오를 개방의 속도를 증가를 줄일 핸드 셰이크 중에 데이터 교환의 대기 시간을 개선하는 것입니다. 성공적인 핸드 쉐이크 후 ZLMediaKit TCP_NODELAY 닫히고 MSG_MORE를 개방, 이는 데이터 패킷의 수를 줄여 네트워크의 활용도를 향상시킬 수있다.
벌크 데이터 전송
네트워크 프로그래밍, 우리는 보내기 / sendto를 / 쓰기 기능을 사용 할 뻔했지만, writev는 / sendmsg 기능을 많이 사용한다. ZLMediaKit 네트워크가 좋지 않거나 서버 부하가 상대적으로 높을 때 상당히 프로그램 성능을 향상시키기 위해 시스템 호출 (시스템 호출 오버 헤드가 비교적 크기) 횟수를 줄일 수 있고, 이렇게 대량의 데이터 전송 기능을 이용 sendmsg. 다음은 코드 조각입니다 :
int BufferList::send_l(int fd, int flags,bool udp) {
int n;
do {
struct msghdr msg;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &(_iovec[_iovec_off]);
msg.msg_iovlen = _iovec.size() - _iovec_off;
if(msg.msg_iovlen > IOV_MAX){
msg.msg_iovlen = IOV_MAX;
}
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = flags;
n = udp ? send_iovec(fd,&msg,flags) : sendmsg(fd,&msg,flags);
} while (-1 == n && UV_EINTR == get_uv_error(true));
if(n >= _remainSize){
//全部写完了
_iovec_off = _iovec.size();
_remainSize = 0;
return n;
}
if(n > 0){
//部分发送成功
reOffset(n);
return n;
}
//一个字节都未发送
return n;
}
배치 스레드 스위치
다중 스레드 모델, 미디어 데이터 분포를하고 스트리밍 미디어 서버는 확실히 스레드 스위치을한다. 먼저 스레드 안전 전환 스레드의 목적은, 복수의 스레드가 동시에 개체 또는 리소스를 작동시키지 않게하기 번째 전달 성능 병목 현상이 단일 스레드 방지 력 멀티 과금을 최대한 활용한다. 미디어 전달 하므로써 ZLMediaKit 또한 다수의 스레드에 데이터 분배를위한 스레드 스위칭을 사용한다. 그러나 스레드 스위칭 오버 헤드가 스레드 스위치를 너무 많이하는 경우, 그것은 심각하게 서버의 성능에 영향을 미칠 것입니다, 상대적으로 크다.
이제 우리는 시나리오를 가정 RTMP 스트리밍 클라이언트 A가 서버에 방송을 누르면,이 방송은 뜨겁다, 가정이 모두 10K 사용자가이 라이브를 시청하는 최대 시간 10K에 스레드 수있다 필요, 우리는 RTMP 패킷을 배포 전환 후 데이터를 전송? ZLMediaKit 쓰레드 스위칭은 상대적 광이지만, 이는 또한 빈번한 스레드 스위칭 휴대 없었다 않는다.
이러한 문제를 다루는 ZLMediaKit는 일괄 전환 스레드는 스레드 스위치의 수를 최소화합니다. 당신이 사용자의 10K 32 코어 CPU에 분산되어 말한다면 ZLMediaKit 데이터를 배포하는 여러 스레드를 사용하는 동안 크게, 스레드 스위치의 수를 줄일 크게 네트워크 처리량이 향상됩니다 최대 32 배 스레드 스위치, 그래서, 다음, 다음이 될 ZLMediaKit 일괄 스레드 스위칭 코드 단편
void emitRead(const T &in){
LOCK_GUARD(_mtx_map);
for (auto &pr : _dispatcherMap) {
auto second = pr.second;
//批量线程切换
pr.first->async([second,in](){
second->emitRead(in);
},false);
}
}
//线程切换后再做遍历
void emitRead(const T &in){
for (auto it = _readerMap.begin() ; it != _readerMap.end() ;) {
auto reader = it->second.lock();
if(!reader){
it = _readerMap.erase(it);
--_readerSize;
onSizeChanged();
continue;
}
//触发数据分发操作
reader->onRead(in);
++it;
}
}
사용 r- 수치 참조 사본
또한 복사 참조 값의 메모리 복사를 회피 할 수있는 권리를 사용합니다 ZLMediaKit, 여기 토론을 시작하지 않습니다.
다른 기능
최적화 유량을 엽니 다 적시 푸시
일부 응용 프로그램은 장치 측 시나리오는 즉시 다음, 스트림을 밀어 APP 응용 프로그램 시나리오를보고 시작해야합니다. 성공률이 비디오를 엽니 줄일 수 있도록 APP 플레이 요청이 흐름이 푸시에 도달하기 전에 설정되지 않은 경우이 시나리오에 대한 기존의 RTMP 서버가 어떤 최적화없이, 그것은 실패의 APP 플레이어로 이어질 것입니다, 사용자 경험은 매우 나쁘다.
장면에 응용 프로그램, 특별한 최적화를 만들 때 ZLMediaKit는, 다음과 같이 구현 원리는 :
1은 즉시 재생 성공적인 리턴이있는 경우, 미디어 소스가 있었다 여부를 확인, 그렇지 않으면 2 단계로 이동, 재생 요청을 받았습니다.
2, 스눕 미디어 소스 등록 이벤트, 플레이어 타임 아웃 타이머를 추가하고, 플레이어에 응답 한 후 반환하지 않습니다 동안. 로직은 3 단계 또는 4 단계로 진행한다.
3, 미디어 소스는 즉시, 성공적으로 재생하는 플레이어에 응답 플레이 타임 아웃 타이머를 삭제하고, 이동식 미디어 이벤트 리스너를 등록, 등록.
응답 플레이어를 트리거 4 시간 제한 타이머가 삭제 타임 아웃 타이머를 재생하는 동안, 재생하는 데 실패하고 이동식 미디어 이벤트 리스너를 등록 할 수 있습니다.
사용 ZLMediaKit 미디어 스트리밍 서버와 동시에 재생 장치 APP 단부 플러그 흐름을 요청할 수있다.
성능 비교 테스트
현재 ZLMediaKit 일부 성능 테스트를했다, 주소 참조 : 벤치 마크
테스트를 발견했을 때, 부하가 상대적으로 낮은 ZLMediaKit는 자사의 단일 스레드 성능은 로컬 사이클 네트워크 덕분에 선도, 약 50 % SRS는 단일 스레드가 5K 플레이어를 지원할 수있을 것입니다, 네트워크 상태 때 성능 차이의 주된 이유 이상, 다음 sendmsg 주위에 최적화 대부분을 보낼 여유가 없다, 그리고 SRS는 시스템은 호출의 수를 줄일 수 있습니다 (즉, 약 300 밀리 초 후에 한 번 캐시 데이터를 전송한다) 합병 쓰기 특성을 이용하여, 실제 네트워크 환경을 낮은 부하가 상대적으로 낮은 경우, 그리고, ZLMediaKit는 SRS의 차이가 작으로 단일 스레드 성능을해야한다, 우리는 또한 클라이언트에서 테스트 보고서에 상대적으로 긴 시간, ZLMediaKit 단일 스레드 스레드 성능을 상대적으로 큰 증가를 찾을 수 있습니다.
ZLMediaKit는 멀티 스레딩을 지원하므로, 당신은 멀티 코어 CPU의 성능을 활용할 수 있으며, 멀티 코어 서버에서 CPU 대기 시간 라이브를 줄이기 위해, 우리는 현재 합병 쓰기 특성에 가입하지 않으려는 더 이상 성능 병목 없습니다.
프로젝트 주소
현재 열려 ZLMediaKit, 주소는 다음과 같습니다 ZLMediaKit
QQ 채팅 그룹
542509000