最简单流媒体服务器设计

这些年用了不少开源的流媒体服务器,也看过一些以前同事写的,都感觉不是太臃肿就是太复杂,反正就是各种不爽,遂自行实现一个简洁高效的轻量级流媒体服务器骨架,以备不时之需。

 

一、目标

1、最简单连接响应,用线程池批量处理

2、最简单通信方式,以socket传输为例

3、最简单通信加密,数据序列化,用google开源的protobuf

4、最简单数据处理方式,用环形缓冲队列

5、最简单负载均衡,LVS、Tengine、Nginx、Haproxy等,可选的太多,目前还没确定哪个最优

6、最简单分布式部署, 信令服务器和数据服务器组成一个单元,服务单元之间可以是树状或者网状结构

 

二、各模块设计,整合

1、首先开发服务器必定会有大量的客户端来连接,所以用一个线程池来批量处理这些连接是非常合适的。

线程池咱就不设计了,在github上找了一圈,以下这个最好,附上demo

thrdtest.c

#define THREAD 32
#define QUEUE  256

#include 
#include 
#include 
#include 

#include "threadpool.h"

int tasks = 0, done = 0;
pthread_mutex_t lock;

void dummy_task(void *arg) {
    usleep(10000);
    pthread_mutex_lock(&lock);
    done++;
    pthread_mutex_unlock(&lock);
}

int main(int argc, char **argv)
{
    threadpool_t *pool;

    pthread_mutex_init(&lock, NULL);

    assert((pool = threadpool_create(THREAD, QUEUE, 0)) != NULL);
    fprintf(stderr, "Pool started with %d threads and "
            "queue size of %d\n", THREAD, QUEUE);

    while(threadpool_add(pool, &dummy_task, NULL, 0) == 0) {
        pthread_mutex_lock(&lock);
        tasks++;
        pthread_mutex_unlock(&lock);
    }

    fprintf(stderr, "Added %d tasks\n", tasks);

    while((tasks / 2) > done) {
        usleep(10000);
    }
    assert(threadpool_destroy(pool, 0) == 0);
    fprintf(stderr, "Did %d tasks\n", done);

    return 0;
}

源码包在这里

https://github.com/mbrossard/threadpool

 

2、通信,数据交互,就以最简单的socket为例,平时做多媒体开发,大多用rtsp/rtmp/gb28181之类的协议,跟服务器架构本身没什么关系,这里只做个骨架,枝叶是无穷尽的。

直接搜 socket programming in C ,出来一堆,都比自己写得好。

Socket Server Example

#include 
#include in.h>
#include 
#include 
#include 
#include 
#include 
#include <string.h>
#include 
#include  

int main(int argc, char *argv[])
{
    int listenfd = 0, connfd = 0;
    struct sockaddr_in serv_addr; 

    char sendBuff[1025];
    time_t ticks; 

    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    memset(&serv_addr, '0', sizeof(serv_addr));
    memset(sendBuff, '0', sizeof(sendBuff)); 

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(5000); 

    bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 

    listen(listenfd, 10); 

    while(1)
    {
        connfd = accept(listenfd, (struct sockaddr*)NULL, NULL); 

        ticks = time(NULL);
        snprintf(sendBuff, sizeof(sendBuff), "%.24s\r\n", ctime(&ticks));
        write(connfd, sendBuff, strlen(sendBuff)); 

        close(connfd);
        sleep(1);
     }
}

 

Socket Client Example

#include 
#include 
#include in.h>
#include 
#include 
#include <string.h>
#include 
#include 
#include 
#include  

int main(int argc, char *argv[])
{
    int sockfd = 0, n = 0;
    char recvBuff[1024];
    struct sockaddr_in serv_addr; 

    if(argc != 2)
    {
        printf("\n Usage: %s  \n",argv[0]);
        return 1;
    } 

    memset(recvBuff, '0',sizeof(recvBuff));
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("\n Error : Could not create socket \n");
        return 1;
    } 

    memset(&serv_addr, '0', sizeof(serv_addr)); 

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(5000); 

    if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr)<=0)
    {
        printf("\n inet_pton error occured\n");
        return 1;
    } 

    if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
       printf("\n Error : Connect Failed \n");
       return 1;
    } 

    while ( (n = read(sockfd, recvBuff, sizeof(recvBuff)-1)) > 0)
    {
        recvBuff[n] = 0;
        if(fputs(recvBuff, stdout) == EOF)
        {
            printf("\n Error : Fputs error\n");
        }
    } 

    if(n < 0)
    {
        printf("\n Read error \n");
    } 

    return 0;
}

以下socket实例都写得不错,都收藏在这里,我不是处女座,还是纠结了半天才选了上面这个。

https://www.thegeekstuff.com/2011/12/c-socket-programming/

http://www.linuxhowtos.org/C_C++/socket.htm

https://www.binarytides.com/socket-programming-c-linux-tutorial

https://www.binarytides.com/winsock-socket-programming-tutorial

 

3、服务器一般都需要处理大量的数据,网络吞吐量需求较高,比如此次设计的初始版本虽然只是一个最简单的视频服务器,但是多几路连接数据量也很大,负荷很重。

用一个ringbuf来处理是比较常规的做法,每个连接可以用上面的线程池添加两个线程来配合处理数据,比如一个负责生产数据,一个消费数据,就是设计模式中典型的生产者和消费者模式

simple.c

#include 
#include 
#include "../ringbuffer.h"

int main(void) {
  int i, cnt;
  char buf;
  char buf_arr[50];
  
  /* Create and initialize ring buffer */
  ring_buffer_t ring_buffer;
  ring_buffer_init(&ring_buffer);
  
  /* Add elements to buffer; one at a time */
  for(i = 0; i < 100; i++) {
    ring_buffer_queue(&ring_buffer, i);
  }

  /* Verify size */
  assert(ring_buffer_num_items(&ring_buffer) == 100);

  /* Peek third element */
  cnt = ring_buffer_peek(&ring_buffer, &buf, 3);
  /* Assert byte returned */
  assert(cnt == 1);
  /* Assert contents */
  assert(buf == 3);

  /* Dequeue all elements */
  for(cnt = 0; ring_buffer_dequeue(&ring_buffer, &buf) > 0; cnt++) {
    /* Do something with buf... */
    assert(buf == cnt);
    printf("Read: %d\n", buf);
  }
  printf("\n===============\n");

  /* Add array */
  ring_buffer_queue_arr(&ring_buffer, "Hello, Ring Buffer!", 20);

  /* Is buffer empty? */
  assert(!ring_buffer_is_empty(&ring_buffer));

  /* Dequeue all elements */
  while(ring_buffer_dequeue(&ring_buffer, &buf) > 0) {
    /* Print contents */
    printf("Read: %c\n", buf);
  }
  
  /* Add new array */
  ring_buffer_queue_arr(&ring_buffer, "Hello again, Ring Buffer!", 26);
  
  /* Dequeue array in two parts */
  printf("Read:\n");
  cnt = ring_buffer_dequeue_arr(&ring_buffer, buf_arr, 13);
  printf("%d\n", cnt);
  assert(cnt == 13);
  /* Add \0 termination before printing */
  buf_arr[13] = '\0';
  printf("%s\n", buf_arr);
  /* Dequeue remaining */
  cnt = ring_buffer_dequeue_arr(&ring_buffer, buf_arr, 13);
  assert(cnt == 13);
  printf("%s", buf_arr);  
  

  printf("\n===============\n");

  /* Overfill buffer */
  for(i = 0; i < 1000; i++) {
    ring_buffer_queue(&ring_buffer, (i % 127));
  }
  
  /* Is buffer full? */
  if(ring_buffer_is_full(&ring_buffer)) {
    cnt = ring_buffer_num_items(&ring_buffer);
    printf("Buffer is full and contains %d bytes\n", cnt);
  }
  
  /* Dequeue all elements */
  while(ring_buffer_dequeue(&ring_buffer, &buf) > 0) {
    /* Print contents */
    printf("Read: 0x%02x\n", buf);
  }
  
  return 0;
}

https://github.com/AndersKaloer/Ring-Buffer

 

4、以上三个主要的轮子都有了,先整合成一个完整得服务器程序,后面的模块再在这个工程上添加。

实现从socket客户端接收数据,转发到rtmp服务器上,也就是做了个视频数据接收——>封装——>转发的服务器

调试服务器本身可以把转发rtmp改成写个hello,world这种简单的任务,毕竟是设计服务器,而不是rtmp推流器。

因为我把rtmp推送封装好了,用起来跟写个hello,world一样简单,所以就拿来当个示例了。

main.c

#include 
#include 
#include 
#include 
#include <string.h>
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include in.h>

#include "threadpool.h"
#include "ringbuffer.h"
#include "rtmp_relay.h"

#define THREAD 32
#define QUEUE  256

char* rtmp_url = "rtmp://182.61.45.149:1935/live/movie";

pthread_mutex_t lock;

typedef struct task
{
    ring_buffer_t ring_buffer;
    int connfd;
    int index;
    
}task_t;

void source_task(void *arg) {
    
    task_t* task = (task_t*)arg;

    char buf[1024*1024*4];
    int n;
    int len;
    uint32_t tick;

    while(1){
        if((n=read(task->connfd, buf+len, 1024*50)) > 0){    
            char url[64]; sprintf(url,"%s%d   %d   %d",rtmp_url,task->index,n,tick++);
            write(task->connfd, url, strlen(url));
            len+=n;
            if(len > 1024*50){    
                pthread_mutex_lock(&lock);    
                ring_buffer_queue_arr(&(task->ring_buffer), buf, len);
                pthread_mutex_unlock(&lock);
                len =0;
            }                                                    
        }
        printf("socket idle %d\n",n);
    }        
}

void sink_task(void *arg) {

    task_t* task = (task_t*)arg;
    uint32_t tick;

    char buf[1024*1024*4];
    
    char url[64]; sprintf(url,"%s%d",rtmp_url,task->index);
    
    rtmp_relay_t *rtmp_relay = new rtmp_relay_t();
    rtmp_relay->rtmp_connect(url);

    while(1){
        pthread_mutex_lock(&lock);
        int len = ring_buffer_num_items(&(task->ring_buffer));
        if(ring_buffer_dequeue_arr(&(task->ring_buffer), buf, len) > 0) {
            printf("push %s   %d   %d \n",url, len, tick++);
            rtmp_relay->rtmp_send_buf((uint8_t*)buf,len);
        }
        pthread_mutex_unlock(&lock);
    }

    rtmp_relay->rtmp_close();
    delete rtmp_relay;
}


int main(int argc, char **argv)
{
    //threadpool
    threadpool_t *pool;

    assert((pool = threadpool_create(THREAD, QUEUE, 0)) != NULL);
    fprintf(stderr, "Pool started with %d threads and "
            "queue size of %d\n", THREAD, QUEUE);

    //socket
    int listenfd;
    int connfd;
    int index;
    struct sockaddr_in servaddr;

    listenfd = socket(PF_INET, SOCK_STREAM, 0);

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(50001);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, 32);
    
    while(1){
        connfd = accept(listenfd, (struct sockaddr *)NULL, NULL);

        task_t* task = (task_t*)malloc(sizeof(task_t));
        ring_buffer_init(&(task->ring_buffer));
        task->connfd = connfd;
        task->index = index++;

        if(threadpool_add(pool, &sink_task, task, 0) == 0) {
        }
        if(threadpool_add(pool, &source_task, task, 0) == 0) {
        }
        
        //always hold tasks memery until socket disconnect
        //free(task);    
    }

    close(connfd);
    close(listenfd);

    assert(threadpool_destroy(pool, 0) == 0);

    return 0;
}

服务器设计好了,还需要个配套的客户端来测试

提供一个socket客户端demo,实现循环向服务器发送视频数据流的功能

test_client.c

#include 
#include <string.h>
#include 
#include 
#include 
#include 
#include 
#include in.h>

int main(int argc, char **argv)
{
    int sockfd;
    struct sockaddr_in servaddr;

    sockfd = socket(PF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(50001);
    servaddr.sin_addr.s_addr = inet_addr("182.61.45.149");

    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
        
    unsigned int buf_size = 1024*1024*8;
    unsigned char *buf = (unsigned char*)malloc(buf_size);
    
    FILE *fp = fopen("./tc10.264", "rb");
    
    int n = 0;
    while(1){

        while(!feof(fp)){
            n=fread(buf,1,1024,fp);    
            write(sockfd, buf, n);
            usleep(10000);

            if((n=read(sockfd, buf, 1024*50)) > 0)
            {
                buf[n] = 0;
                printf("%s\n",buf);
            }
        }
        fseek(fp,0,SEEK_SET);    
    }
        
    free(buf);
    fclose(fp);
    close(sockfd);

    return 1;
}

 

 

4、负载均衡,直接上实例

负载均衡可以通过dns,硬件或者软方式来实现,常见的软件负载均衡有LVS、Nginx、Haproxy,

阿里云负载均衡用的是LVS和Tengine,可以参考一下。

http://www.linuxvirtualserver.org

http://tengine.taobao.org/

阿里云负载均衡

https://help.aliyun.com/product/27537.html?spm=a2c4g.11186623.6.540.3e2336e3Lnu8kI

 

5、分布式部署

...

 

先整理到这里, 2019-07-23 02:24:46,先睡了,明天还上班。

 

 

2019-07-24 23:38:55

我打算看一本书《jinpingmei》,这个项目先晾着......

 

转载于:https://www.cnblogs.com/dong1/p/11235482.html

你可能感兴趣的:(最简单流媒体服务器设计)