mjpg-streamer框架分析

news2024/12/24 3:26:45

mjpg-streamer框架分析


文章目录

  • mjpg-streamer框架分析
  • 框架
  • main
  • input_init
  • input_run
  • output_init


框架

在这里插入图片描述

在这里插入图片描述

main

在这里插入图片描述

这个函数是一个C程序的主函数,接受命令行参数并执行相应的操作。下面是这个函数的要点:
定义了一些变量,包括输入插件数组input、输出插件数组output、是否后台运行的标志daemon等。
初始化输出插件数组的第一个元素为固定的字符串。
使用getopt_long_only函数解析命令行参数。
根据解析的选项进行相应的操作,例如显示帮助信息、将输入插件添加到输入插件数组、将输出插件添加到输出插件数组、显示版本信息、设置后台运行标志等。
打开系统日志,并记录日志信息。
如果设置了后台运行标志,调用daemon_mode函数将程序转为后台运行。
忽略SIGPIPE信号。
注册信号处理程序,用于处理+C信号。
显示MJPG Streamer的版本信息。
检查是否至少选择了一个输出插件,如果没有,则使用默认插件。
初始化并打开输入插件。其中,根据输入插件名称打开相应的动态链接库,获取插件的初始化、停止和运行函数指针,并调用初始化函数进行初始化。
打开输出插件。类似于打开输入插件,获取插件的初始化、停止和运行函数指针,并调用初始化函数进行初始化。
开始读取输入,并将图像数据推送到全局缓冲区。
启动输出插件,将图像数据从全局缓冲区发送到输出。
程序进入暂停状态,等待信号的到来。
返回0,表示程序正常结束。
总体来说,这个函数是一个简单的命令行程序,用于控制输入和输出插件的启动和运行,并提供一些选项来配置程序的行为。

/*******************************************************************************
#                                                                              #
#      MJPG-streamer allows to stream JPG frames from an input-plugin          #
#      to several output plugins                                               #
#                                                                              #
#      Copyright (C) 2007 Tom Stöveken                                         #
#                                                                              #
# This program is free software; you can redistribute it and/or modify         #
# it under the terms of the GNU General Public License as published by         #
# the Free Software Foundation; version 2 of the License.                      #
#                                                                              #
# This program is distributed in the hope that it will be useful,              #
# but WITHOUT ANY WARRANTY; without even the implied warranty of               #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                #
# GNU General Public License for more details.                                 #
#                                                                              #
# You should have received a copy of the GNU General Public License            #
# along with this program; if not, write to the Free Software                  #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA    #
#                                                                              #
*******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#include <pthread.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <syslog.h>

#include "utils.h"
#include "mjpg_streamer.h"

/* globals */
static globals global;

/******************************************************************************
Description.: Display a help message
Input Value.: argv[0] is the program name and the parameter progname
Return Value: -
******************************************************************************/
void help(char *progname)
{
    fprintf(stderr, "-----------------------------------------------------------------------\n");
    fprintf(stderr, "Usage: %s\n" \
            "  -i | --input \"<input-plugin.so> [parameters]\"\n" \
            "  -o | --output \"<output-plugin.so> [parameters]\"\n" \
            " [-h | --help ]........: display this help\n" \
            " [-v | --version ].....: display version information\n" \
            " [-b | --background]...: fork to the background, daemon mode\n", progname);
    fprintf(stderr, "-----------------------------------------------------------------------\n");
    fprintf(stderr, "Example #1:\n" \
            " To open an UVC webcam \"/dev/video1\" and stream it via HTTP:\n" \
            "  %s -i \"input_uvc.so -d /dev/video1\" -o \"output_http.so\"\n", progname);
    fprintf(stderr, "-----------------------------------------------------------------------\n");
    fprintf(stderr, "Example #2:\n" \
            " To open an UVC webcam and stream via HTTP port 8090:\n" \
            "  %s -i \"input_uvc.so\" -o \"output_http.so -p 8090\"\n", progname);
    fprintf(stderr, "-----------------------------------------------------------------------\n");
    fprintf(stderr, "Example #3:\n" \
            " To get help for a certain input plugin:\n" \
            "  %s -i \"input_uvc.so --help\"\n", progname);
    fprintf(stderr, "-----------------------------------------------------------------------\n");
    fprintf(stderr, "In case the modules (=plugins) can not be found:\n" \
            " * Set the default search path for the modules with:\n" \
            "   export LD_LIBRARY_PATH=/path/to/plugins,\n" \
            " * or put the plugins into the \"/lib/\" or \"/usr/lib\" folder,\n" \
            " * or instead of just providing the plugin file name, use a complete\n" \
            "   path and filename:\n" \
            "   %s -i \"/path/to/modules/input_uvc.so\"\n", progname);
    fprintf(stderr, "-----------------------------------------------------------------------\n");
}

/******************************************************************************
Description.: pressing CTRL+C sends signals to this process instead of just
              killing it plugins can tidily shutdown and free allocated
              resources. The function prototype is defined by the system,
              because it is a callback function.
Input Value.: sig tells us which signal was received
Return Value: -
******************************************************************************/
void signal_handler(int sig)
{
    int i;

    /* signal "stop" to threads */
    LOG("setting signal to stop\n");
    global.stop = 1;
    usleep(1000 * 1000);

    /* clean up threads */
    LOG("force cancellation of threads and cleanup resources\n");
    for(i = 0; i < global.incnt; i++) {
        global.in[i].stop(i);
    }

    for(i = 0; i < global.outcnt; i++) {
        global.out[i].stop(global.out[i].param.id);
        pthread_cond_destroy(&global.in[i].db_update);
        pthread_mutex_destroy(&global.in[i].db);
    }
    usleep(1000 * 1000);

    /* close handles of input plugins */
    for(i = 0; i < global.incnt; i++) {
        dlclose(global.in[i].handle);
    }

    for(i = 0; i < global.outcnt; i++) {
        /* skip = 0;
        DBG("about to decrement usage counter for handle of %s, id #%02d, handle: %p\n", \
            global.out[i].plugin, global.out[i].param.id, global.out[i].handle);
        for(j=i+1; j<global.outcnt; j++) {
          if ( global.out[i].handle == global.out[j].handle ) {
            DBG("handles are pointing to the same destination (%p == %p)\n", global.out[i].handle, global.out[j].handle);
            skip = 1;
          }
        }
        if ( skip ) {
          continue;
        }

        DBG("closing handle %p\n", global.out[i].handle);
        */
        dlclose(global.out[i].handle);
    }
    DBG("all plugin handles closed\n");

    LOG("done\n");

    closelog();
    exit(0);
    return;
}

int split_parameters(char *parameter_string, int *argc, char **argv)
{
    int count = 1;
    argv[0] = NULL; // the plugin may set it to 'INPUT_PLUGIN_NAME'
    if(parameter_string != NULL && strlen(parameter_string) != 0) {
        char *arg = NULL, *saveptr = NULL, *token = NULL;

        arg = strdup(parameter_string);

        if(strchr(arg, ' ') != NULL) {
            token = strtok_r(arg, " ", &saveptr);
            if(token != NULL) {
                argv[count] = strdup(token);
                count++;
                while((token = strtok_r(NULL, " ", &saveptr)) != NULL) {
                    argv[count] = strdup(token);
                    count++;
                    if(count >= MAX_PLUGIN_ARGUMENTS) {
                        IPRINT("ERROR: too many arguments to input plugin\n");
                        return 0;
                    }
                }
            }
        }
    }
    *argc = count;
    return 1;
}

/******************************************************************************
Description.:
Input Value.:
Return Value:
******************************************************************************/
int main(int argc, char *argv[])
{
    //char *input  = "input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0";
    char *input[MAX_INPUT_PLUGINS]; // 输入插件数组
    char *output[MAX_OUTPUT_PLUGINS]; // 输出插件数组
    int daemon = 0, i; // 是否后台运行
    size_t tmp = 0;

    output[0] = "output_http.so --port 8080"; // 输出插件
    global.outcnt = 0; // 输出插件数量


    /* parameter parsing */
    //解析参数getopt_long_only
    while(1) {
        int option_index = 0, c = 0;
        static struct option long_options[] = {
            {"h", no_argument, 0, 0
            },
            {"help", no_argument, 0, 0},
            {"i", required_argument, 0, 0},
            {"input", required_argument, 0, 0},
            {"o", required_argument, 0, 0},
            {"output", required_argument, 0, 0},
            {"v", no_argument, 0, 0},
            {"version", no_argument, 0, 0},
            {"b", no_argument, 0, 0},
            {"background", no_argument, 0, 0},
            {0, 0, 0, 0}
        };

        c = getopt_long_only(argc, argv, "", long_options, &option_index); //获取参数
        /* no more options to parse */
        if(c == -1) break;

        /* unrecognized option */
        if(c == '?') {
            help(argv[0]);
            return 0;
        }

        switch(option_index) {
            /* h, help */
        case 0:
        case 1:
            help(argv[0]); //显示帮助信息
            return 0;
            break;

            /* i, input */
        case 2:
        case 3:
            input[global.incnt++] = strdup(optarg); //将输入插件添加到输入插件数组中
            break;

            /* o, output */
        case 4:
        case 5:
            output[global.outcnt++] = strdup(optarg); //将输出插件添加到输出插件数组中
            break;

            /* v, version */
        case 6:
        case 7:
            printf("MJPG Streamer Version: %s\n" \
            "Compilation Date.....: %s\n" \
            "Compilation Time.....: %s\n",
#ifdef SVN_REV
            SVN_REV,
#else
            SOURCE_VERSION,
#endif
            __DATE__, __TIME__); //显示版本信息
            return 0;
            break;

            /* b, background */
        case 8:
        case 9:
            daemon = 1; //设置后台运行标志
            break;

        default:
            help(argv[0]); //显示帮助信息
            return 0;
        }
    }


    openlog("MJPG-streamer ", LOG_PID | LOG_CONS, LOG_USER); // 打开系统日志
    //openlog("MJPG-streamer ", LOG_PID|LOG_CONS|LOG_PERROR, LOG_USER);
    syslog(LOG_INFO, "starting application"); // 记录日志

    /* fork to the background */
    if(daemon) { // 后台运行
        LOG("enabling daemon mode"); // 记录日志
        daemon_mode(); // 后台运行
    }

    /* ignore SIGPIPE (send by OS if transmitting to closed TCP sockets) */
    signal(SIGPIPE, SIG_IGN); // 忽略SIGPIPE信号

    /* register signal handler for <CTRL>+C in order to clean up */
    if(signal(SIGINT, signal_handler) == SIG_ERR) { // 注册信号处理程序
        LOG("could not register signal handler\n"); // 记录日志
        closelog(); // 关闭日志
        exit(EXIT_FAILURE); // 退出程序
    }


    /*
     * messages like the following will only be visible on your terminal
     * if not running in daemon mode
     */
#ifdef SVN_REV
    LOG("MJPG Streamer Version: svn rev: %s\n", SVN_REV);
#else
    LOG("MJPG Streamer Version.: %s\n", SOURCE_VERSION); // 如果没有SVN_REV,则使用SOURCE_VERSION
#endif

    /* 检查是否至少选择了一个输出插件 */
    if(global.outcnt == 0) {
        /* 没有?那么使用默认插件 */
        global.outcnt = 1;
    }

    /* 打开输入插件 */
    for(i = 0; i < global.incnt; i++) {
        if(pthread_mutex_init(&global.in[i].db, NULL) != 0) { // 初始化互斥锁
            LOG("could not initialize mutex variable\n"); // 记录日志
            closelog(); // 关闭日志
            exit(EXIT_FAILURE); // 退出程序
        }
        if(pthread_cond_init(&global.in[i].db_update, NULL) != 0) { // 初始化条件变量
            LOG("could not initialize condition variable\n"); // 记录日志
            closelog(); // 关闭日志
            exit(EXIT_FAILURE); // 退出程序
        }


        // 获取输入插件名称
        tmp = (size_t)(strchr(input[i], ' ') - input[i]);
        // 初始化输入插件结构体
        global.in[i].stop      = 0;
        global.in[i].buf       = NULL;
        global.in[i].size      = 0;
        global.in[i].plugin = (tmp > 0) ? strndup(input[i], tmp) : strdup(input[i]);
        // 打开输入插件
        global.in[i].handle = dlopen(global.in[i].plugin, RTLD_LAZY);打开动态链接库

        if(!global.in[i].handle) {
            LOG("ERROR: could not find input plugin\n");
            LOG("       Perhaps you want to adjust the search path with:\n");
            LOG("       # export LD_LIBRARY_PATH=/path/to/plugin/folder\n");
            LOG("       dlopen: %s\n", dlerror());
            closelog();
            exit(EXIT_FAILURE);
        }
        global.in[i].init = dlsym(global.in[i].handle, "input_init");//让init函数指针等于打开的动态链接库中的init函数
        if(global.in[i].init == NULL) {
            LOG("%s\n", dlerror());
            exit(EXIT_FAILURE);
        }
        global.in[i].stop = dlsym(global.in[i].handle, "input_stop");
        if(global.in[i].stop == NULL) {
            LOG("%s\n", dlerror());
            exit(EXIT_FAILURE);
        }
        global.in[i].run = dlsym(global.in[i].handle, "input_run");
        if(global.in[i].run == NULL) {
            LOG("%s\n", dlerror());
            exit(EXIT_FAILURE);
        }
        /* 尝试找到可选命令 */
        global.in[i].cmd = dlsym(global.in[i].handle, "input_cmd");

        global.in[i].param.parameters = strchr(input[i], ' ');
        split_parameters(global.in[i].param.parameters, &global.in[i].param.argc, global.in[i].param.argv);
        global.in[i].param.global = &global;
        global.in[i].param.id = i;

        if(global.in[i].init(&global.in[i].param, i)) {
            LOG("input_init() return value signals to exit\n");
            closelog();
            exit(0);
        }
    }


    /* open output plugin */
    for(i = 0; i < global.outcnt; i++) {
        tmp = (size_t)(strchr(output[i], ' ') - output[i]);
        global.out[i].plugin = (tmp > 0) ? strndup(output[i], tmp) : strdup(output[i]);
        global.out[i].handle = dlopen(global.out[i].plugin, RTLD_LAZY);
        if(!global.out[i].handle) {
            LOG("ERROR: could not find output plugin %s\n", global.out[i].plugin);
            LOG("       Perhaps you want to adjust the search path with:\n");
            LOG("       # export LD_LIBRARY_PATH=/path/to/plugin/folder\n");
            LOG("       dlopen: %s\n", dlerror());
            closelog();
            exit(EXIT_FAILURE);
        }
        global.out[i].init = dlsym(global.out[i].handle, "output_init");
        if(global.out[i].init == NULL) {
            LOG("%s\n", dlerror());
            exit(EXIT_FAILURE);
        }
        global.out[i].stop = dlsym(global.out[i].handle, "output_stop");
        if(global.out[i].stop == NULL) {
            LOG("%s\n", dlerror());
            exit(EXIT_FAILURE);
        }
        global.out[i].run = dlsym(global.out[i].handle, "output_run");
        if(global.out[i].run == NULL) {
            LOG("%s\n", dlerror());
            exit(EXIT_FAILURE);
        }

        /* try to find optional command */
        global.out[i].cmd = dlsym(global.out[i].handle, "output_cmd");

        global.out[i].param.parameters = strchr(output[i], ' ');
        split_parameters(global.out[i].param.parameters, &global.out[i].param.argc, global.out[i].param.argv);

        global.out[i].param.global = &global;
        global.out[i].param.id = i;
        if(global.out[i].init(&global.out[i].param, i)) {
            LOG("output_init() return value signals to exit\n");
            closelog();
            exit(0);
        }
    }

    /* start to read the input, push pictures into global buffer */
    DBG("starting %d input plugin\n", global.incnt);
    for(i = 0; i < global.incnt; i++) {
        syslog(LOG_INFO, "starting input plugin %s", global.in[i].plugin);
        if(global.in[i].run(i)) {
            LOG("can not run input plugin %d: %s\n", i, global.in[i].plugin);
            closelog();
            return 1;
        }
    }

    DBG("starting %d output plugin(s)\n", global.outcnt);
    for(i = 0; i < global.outcnt; i++) {
        syslog(LOG_INFO, "starting output plugin: %s (ID: %02d)", global.out[i].plugin, global.out[i].param.id);
        global.out[i].run(global.out[i].param.id);
    }

    /* wait for signals */
    pause();//等待信号

    return 0;
}

input_init

这个input_init函数是用于初始化视频设备输入参数的函数。函数接受一个input_parameter类型的指针参数param和一个整数参数id。以下是函数的主要步骤和功能:
初始化互斥变量cams[id].controls_mutex,使用pthread_mutex_init函数。
设置默认的设备路径、宽度、高度、帧率和格式等参数。
解析命令行参数,使用getopt_long_only函数进行参数解析,并将解析的值赋给相应的参数变量。
将解析的参数值赋给param结构体。
分配并初始化cams[id].videoIn结构体。
打开视频设备并准备数据结构,调用init_videoIn函数。
如果设置了dynctrls标志,使用initDynCtrls函数初始化动态控制。
枚举V4L2控制项,调用enumerateControls函数。
在成功时返回0,否则可能显示错误消息并退出程序。

int input_init(input_parameter *param, int id)
{
    char *dev = "/dev/video0", *s;
    int width = 640, height = 480, fps = 5, format = V4L2_PIX_FMT_MJPEG, i;
    /* initialize the mutes variable */
    if(pthread_mutex_init(&cams[id].controls_mutex, NULL) != 0) {
        IPRINT("could not initialize mutex variable\n");
        exit(EXIT_FAILURE);
    }

    param->argv[0] = INPUT_PLUGIN_NAME;

    /* show all parameters for DBG purposes */
    for(i = 0; i < param->argc; i++) {
        DBG("argv[%d]=%s\n", i, param->argv[i]);
    }

    /* parse the parameters */
    reset_getopt();
    while(1) {
        int option_index = 0, c = 0;
        static struct option long_options[] = {
            {"h", no_argument, 0, 0
            },
            {"help", no_argument, 0, 0},
            {"d", required_argument, 0, 0},
            {"device", required_argument, 0, 0},
            {"r", required_argument, 0, 0},
            {"resolution", required_argument, 0, 0},
            {"f", required_argument, 0, 0},
            {"fps", required_argument, 0, 0},
            {"y", no_argument, 0, 0},
            {"yuv", no_argument, 0, 0},
            {"q", required_argument, 0, 0},
            {"quality", required_argument, 0, 0},
            {"m", required_argument, 0, 0},
            {"minimum_size", required_argument, 0, 0},
            {"n", no_argument, 0, 0},
            {"no_dynctrl", no_argument, 0, 0},
            {"l", required_argument, 0, 0},
            {"led", required_argument, 0, 0},
            {0, 0, 0, 0}
        };

        /* parsing all parameters according to the list above is sufficent */
        c = getopt_long_only(param->argc, param->argv, "", long_options, &option_index);

        /* no more options to parse */
        if(c == -1) break;

        /* unrecognized option */
        if(c == '?') {
            help();
            return 1;
        }

        /* dispatch the given options */
        switch(option_index) {
            /* h, help */
        case 0:
        case 1:
            DBG("case 0,1\n");
            help();
            return 1;
            break;

            /* d, device */
        case 2:
        case 3:
            DBG("case 2,3\n");
            dev = strdup(optarg);
            break;

            /* r, resolution */
        case 4:
        case 5:
            DBG("case 4,5\n");
            width = -1;
            height = -1;

            /* try to find the resolution in lookup table "resolutions" */
            for(i = 0; i < LENGTH_OF(resolutions); i++) {
                if(strcmp(resolutions[i].string, optarg) == 0) {
                    width  = resolutions[i].width;
                    height = resolutions[i].height;
                }
            }
            /* done if width and height were set */
            if(width != -1 && height != -1)
                break;
            /* parse value as decimal value */
            width  = strtol(optarg, &s, 10);
            height = strtol(s + 1, NULL, 10);
            break;

            /* f, fps */
        case 6:
        case 7:
            DBG("case 6,7\n");
            fps = atoi(optarg);
            break;

            /* y, yuv */
        case 8:
        case 9:
            DBG("case 8,9\n");
            format = V4L2_PIX_FMT_YUYV;
            break;

            /* q, quality */
        case 10:
        case 11:
            DBG("case 10,11\n");
            format = V4L2_PIX_FMT_YUYV;
            gquality = MIN(MAX(atoi(optarg), 0), 100);
            break;

            /* m, minimum_size */
        case 12:
        case 13:
            DBG("case 12,13\n");
            minimum_size = MAX(atoi(optarg), 0);
            break;

            /* n, no_dynctrl */
        case 14:
        case 15:
            DBG("case 14,15\n");
            dynctrls = 0;
            break;

            /* l, led */
        case 16:
        case 17:/*
        DBG("case 16,17\n");
        if ( strcmp("on", optarg) == 0 ) {
          led = IN_CMD_LED_ON;
        } else if ( strcmp("off", optarg) == 0 ) {
          led = IN_CMD_LED_OFF;
        } else if ( strcmp("auto", optarg) == 0 ) {
          led = IN_CMD_LED_AUTO;
        } else if ( strcmp("blink", optarg) == 0 ) {
          led = IN_CMD_LED_BLINK;
        }*/
            break;

        default:
            DBG("default case\n");
            help();
            return 1;
        }
    }
    DBG("input id: %d\n", id);
    cams[id].id = id;
    cams[id].pglobal = param->global;

    /* allocate webcam datastructure */
    cams[id].videoIn = malloc(sizeof(struct vdIn));
    if(cams[id].videoIn == NULL) {
        IPRINT("not enough memory for videoIn\n");
        exit(EXIT_FAILURE);
    }
    memset(cams[id].videoIn, 0, sizeof(struct vdIn));

    /* display the parsed values */
    IPRINT("Using V4L2 device.: %s\n", dev);
    IPRINT("Desired Resolution: %i x %i\n", width, height);
    IPRINT("Frames Per Second.: %i\n", fps);
    IPRINT("Format............: %s\n", (format == V4L2_PIX_FMT_YUYV) ? "YUV" : "MJPEG");
    if(format == V4L2_PIX_FMT_YUYV)
        IPRINT("JPEG Quality......: %d\n", gquality);

    DBG("vdIn pn: %d\n", id);
    /* open video device and prepare data structure */
      if(init_videoIn(cams[id].videoIn, dev, width, height, fps, format, 1, cams[id].pglobal, id) < 0) { // 如果初始化视频输入失败
        IPRINT("init_VideoIn failed\n"); // 输出错误信息
        closelog(); // 关闭日志
        exit(EXIT_FAILURE); // 退出程序
    }
    /*
     * recent linux-uvc driver (revision > ~#125) requires to use dynctrls
     * dynctrls must get initialized
     */ // 最近的Linux-UVC驱动程序(版本>〜#125)需要使用dynctrls,dynctrls必须得到初始化
    if(dynctrls) // 如果dynctrls为真
        initDynCtrls(cams[id].videoIn->fd); // 初始化dynctrls

    enumerateControls(cams[id].videoIn, cams[id].pglobal, id); // 枚举V4L2控件在UVC扩展映射之后
    return 0;
}

input_run

input_run函数用于启动摄像头线程。它接收一个整数id作为参数,并执行以下步骤:
使用malloc函数为cams[id].pglobal->in[id].buf分配内存,大小为cams[id].videoIn->framesizeIn。如果内存分配失败,则打印错误消息并退出程序。
打印调试信息,指示正在启动摄像头线程。
使用pthread_create函数创建线程,并将上下文&(cams[id])传递给线程函数cam_thread。线程的ID存储在cams[id].threadID中。
使用pthread_detach函数将线程设置为可分离状态,以便线程结束时可以自动清理资源。
在函数执行成功时返回0。

// 启动输入线程
int input_run(int id)
{
    // 为输入缓冲区分配内存
    cams[id].pglobal->in[id].buf = malloc(cams[id].videoIn->framesizeIn);
    if(cams[id].pglobal->in[id].buf == NULL) { // 如果分配内存失败
        fprintf(stderr, "could not allocate memory\n"); // 输出错误信息
        exit(EXIT_FAILURE); // 退出程序
    }

    DBG("launching camera thread #%02d\n", id);
    /* create thread and pass context to thread function */
    pthread_create(&(cams[id].threadID), NULL, cam_thread, &(cams[id])); // 创建线程并将上下文传递给线程函数
    pthread_detach(cams[id].threadID); // 分离线程
    return 0;
}

output_init

这段代码是output_init函数的实现。该函数用于初始化输出插件的参数和配置。
函数接收两个参数:一个指向output_parameter结构体的指针param和一个整数id。
函数的主要步骤如下:
打印调试信息,指示正在初始化输出插件,并打印输出插件的ID。
初始化变量
port为8080,
credentials和
www_folder为NULL,
nocommands为0。
设置
param->argv[0]为输出插件的名称。
打印调试信息,显示所有参数的值(用于调试目的)。
重置
getopt状态,准备进行命令行参数解析。
进入循环,解析命令行参数,直到没有更多的选项可解析。
如果遇到未识别的选项,则调用
help函数打印帮助信息,并返回1表示初始化失败。
根据选项的索引执行相应的操作:
对于"h"和"help"选项,打印帮助信息,并返回1表示初始化失败。
对于"p"和"port"选项,将端口号转换为网络字节序,并赋值给
port变量。
对于"c"和"credentials"选项,将参数的字符串拷贝到
credentials变量中。
对于"w"和"www"选项,分配内存并将参数的字符串拷贝到
www_folder变量中,并确保字符串末尾有"/"。
对于"n"和"nocommands"选项,将
nocommands变量设置为1。
将输出插件的ID、全局上下文指针、配置参数赋值给相应的输出服务器结构体。
打印输出插件的配置信息,包括www文件夹路径、HTTP TCP端口号、用户名和密码(如果已设置)、命令是否启用。
在函数执行成功时返回0。
需要注意的是,代码片段中缺少一些函数和变量的定义,以及头文件的包含。此外,需要查看output_parameter结构体的定义,才能完全理解这段代码的功能和上下文关系。

int output_init(output_parameter *param, int id)
{
    int i;
    int  port;
    char *credentials, *www_folder;
    char nocommands;

    DBG("output #%02d\n", param->id);

    port = htons(8080);
    credentials = NULL;
    www_folder = NULL;
    nocommands = 0;

    param->argv[0] = OUTPUT_PLUGIN_NAME;

    /* show all parameters for DBG purposes */
    for(i = 0; i < param->argc; i++) {
        DBG("argv[%d]=%s\n", i, param->argv[i]);
    }

    reset_getopt();
    while(1) {
        int option_index = 0, c = 0;
        static struct option long_options[] = {
            {"h", no_argument, 0, 0
            },
            {"help", no_argument, 0, 0},
            {"p", required_argument, 0, 0},
            {"port", required_argument, 0, 0},
            {"c", required_argument, 0, 0},
            {"credentials", required_argument, 0, 0},
            {"w", required_argument, 0, 0},
            {"www", required_argument, 0, 0},
            {"n", no_argument, 0, 0},
            {"nocommands", no_argument, 0, 0},
            {0, 0, 0, 0}
        };

        c = getopt_long_only(param->argc, param->argv, "", long_options, &option_index);

        /* no more options to parse */
        if(c == -1) break;

        /* unrecognized option */
        if(c == '?') {
            help();
            return 1;
        }

        switch(option_index) {
            /* h, help */
        case 0:
        case 1:
            DBG("case 0,1\n");
            help();
            return 1;
            break;

            /* p, port */
        case 2:
        case 3:
            DBG("case 2,3\n");
            port = htons(atoi(optarg));
            break;

            /* c, credentials */
        case 4:
        case 5:
            DBG("case 4,5\n");
            credentials = strdup(optarg);
            break;

            /* w, www */
        case 6:
        case 7:
            DBG("case 6,7\n");
            www_folder = malloc(strlen(optarg) + 2);
            strcpy(www_folder, optarg);
            if(optarg[strlen(optarg)-1] != '/')
                strcat(www_folder, "/");
            break;

            /* n, nocommands */
        case 8:
        case 9:
            DBG("case 8,9\n");
            nocommands = 1;
            break;
        }
    }

    servers[param->id].id = param->id;
    servers[param->id].pglobal = param->global;
    servers[param->id].conf.port = port;
    servers[param->id].conf.credentials = credentials;
    servers[param->id].conf.www_folder = www_folder;
    servers[param->id].conf.nocommands = nocommands;

    OPRINT("www-folder-path...: %s\n", (www_folder == NULL) ? "disabled" : www_folder);
    OPRINT("HTTP TCP port.....: %d\n", ntohs(port));
    OPRINT("username:password.: %s\n", (credentials == NULL) ? "disabled" : credentials);
    OPRINT("commands..........: %s\n", (nocommands) ? "disabled" : "enabled");
    return 0;
}

output_run
给定的代码是一个名为output_run的函数,用于在新线程中运行输出服务器。它接受一个整数id作为输入,表示服务器的ID。
以下是代码的详细解析:
函数使用
DBG宏打印输出的服务器线程的序号(id)。
函数使用
pthread_create函数创建一个新线程,并将服务器结构体&(servers[id])作为参数传递给线程函数server_thread。
函数使用
pthread_detach函数将线程标记为可分离的,这意味着线程的资源将在退出时自动释放,无需等待主线程的显式回收。
函数返回0以表示成功启动服务器线程。

总体而言,该函数负责在新线程中启动输出服务器。它创建一个线程并将服务器结构体作为参数传递给线程函数,然后将线程标记为可分离的。这样可以使服务器在独立的线程中运行,而主线程可以继续执行其他任务。
int output_run(int id)
{
DBG(“launching server thread #%02d\n”, id);

/* create thread and pass context to thread function */
pthread_create(&(servers[id].threadID), NULL, server_thread, &(servers[id]));
pthread_detach(servers[id].threadID);

return 0;

}
server_thread
给定的代码是一个名为server_thread的线程函数,用于处理客户端连接的请求。它接受一个void*类型的参数arg,在函数内部将其转换为指向context结构体的指针。
以下是代码的详细解析:
函数从传递给线程函数的参数中获取服务器的全局上下文指针
pglobal。
函数使用
pthread_cleanup_push宏设置清理处理程序server_cleanup,以确保在线程退出时释放资源。
函数使用
getaddrinfo函数根据服务器的端口号获取地址信息。
函数初始化一些变量和数据结构,如套接字描述符数组
sd和用于存储客户端信息的数据结构(如果定义了MANAGMENT)。
函数循环遍历地址信息列表,为每个地址创建套接字,并进行绑定和监听操作。

如果成功创建了一个监听套接字,将其加入到
select函数的文件描述符集合中。
函数使用
select函数阻塞等待可读事件的发生,以接受新的客户端连接。
一旦有客户端连接请求到达监听套接字,函数将接受该连接,并为每个连接创建一个新线程来处理客户端请求。

函数将客户端连接的套接字描述符和服务器的上下文信息封装到
cfd结构体中,并将其作为参数传递给client_thread线程函数。
函数打印正在为哪个客户端提供服务,并根据需要记录客户端的信息(如果定义了
MANAGMENT)。
函数使用
pthread_create函数创建一个新线程来处理客户端连接,并将cfd结构体作为参数传递给线程函数。
函数将新线程标记为可分离的,并继续等待下一个客户端连接。

当全局变量
pglobal->stop为真时,表示需要停止服务器运行,循环结束。
函数在退出之前调用清理处理程序来释放资源,并使用
pthread_cleanup_pop宏进行清理处理程序的弹出。
函数返回
NULL,线程结束。
总体而言,该函数在一个独立的线程中运行,用于接受客户端的连接请求,并为每个连接创建一个新线程来处理客户端请求。它使用select函数进行阻塞等待,以便及时响应新的客户端连接。
void *server_thread(void *arg)
{
int on;
pthread_t client;
struct addrinfo *aip, *aip2;
struct addrinfo hints;
struct sockaddr_storage client_addr;
socklen_t addr_len = sizeof(struct sockaddr_storage);
fd_set selectfds;
int max_fds = 0;
char name[NI_MAXHOST];
int err;
int i;

context *pcontext = arg;
pglobal = pcontext->pglobal;

/* set cleanup handler to cleanup ressources */
pthread_cleanup_push(server_cleanup, pcontext);

bzero(&hints, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_flags = AI_PASSIVE;
hints.ai_socktype = SOCK_STREAM;

snprintf(name, sizeof(name), "%d", ntohs(pcontext->conf.port));
if((err = getaddrinfo(NULL, name, &hints, &aip)) != 0) {
    perror(gai_strerror(err));
    exit(EXIT_FAILURE);
}

for(i = 0; i < MAX_SD_LEN; i++)
    pcontext->sd[i] = -1;

#ifdef MANAGMENT
if (pthread_mutex_init(&client_infos.mutex, NULL)) {
    perror("Mutex initialization failed");
    exit(EXIT_FAILURE);
}

client_infos.client_count = 0;
client_infos.infos = NULL;
#endif

/* open sockets for server (1 socket / address family) */
i = 0;
for(aip2 = aip; aip2 != NULL; aip2 = aip2->ai_next) {
    if((pcontext->sd[i] = socket(aip2->ai_family, aip2->ai_socktype, 0)) < 0) {
        continue;
    }

    /* ignore "socket already in use" errors */
    on = 1;
    if(setsockopt(pcontext->sd[i], SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
        perror("setsockopt(SO_REUSEADDR) failed\n");
    }

    /* IPv6 socket should listen to IPv6 only, otherwise we will get "socket already in use" */
    on = 1;
    if(aip2->ai_family == AF_INET6 && setsockopt(pcontext->sd[i], IPPROTO_IPV6, IPV6_V6ONLY,
            (const void *)&on , sizeof(on)) < 0) {
        perror("setsockopt(IPV6_V6ONLY) failed\n");
    }

    /* perhaps we will use this keep-alive feature oneday */
    /* setsockopt(sd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); */

    if(bind(pcontext->sd[i], aip2->ai_addr, aip2->ai_addrlen) < 0) {
        perror("bind");
        pcontext->sd[i] = -1;
        continue;
    }

    if(listen(pcontext->sd[i], 10) < 0) {
        perror("listen");
        pcontext->sd[i] = -1;
    } else {
        i++;
        if(i >= MAX_SD_LEN) {
            OPRINT("%s(): maximum number of server sockets exceeded", __FUNCTION__);
            i--;
            break;
        }
    }
}

pcontext->sd_len = i;

if(pcontext->sd_len < 1) {
    OPRINT("%s(): bind(%d) failed\n", __FUNCTION__, htons(pcontext->conf.port));
    closelog();
    exit(EXIT_FAILURE);
}

/* create a child for every client that connects */
while(!pglobal->stop) {
    //int *pfd = (int *)malloc(sizeof(int));
    cfd *pcfd = malloc(sizeof(cfd));

    if(pcfd == NULL) {
        fprintf(stderr, "failed to allocate (a very small amount of) memory\n");
        exit(EXIT_FAILURE);
    }

    DBG("waiting for clients to connect\n");

    do {
        FD_ZERO(&selectfds);

        for(i = 0; i < MAX_SD_LEN; i++) {
            if(pcontext->sd[i] != -1) {
                FD_SET(pcontext->sd[i], &selectfds);

                if(pcontext->sd[i] > max_fds)
                    max_fds = pcontext->sd[i];
            }
        }

        err = select(max_fds + 1, &selectfds, NULL, NULL, NULL);

        if(err < 0 && errno != EINTR) {
            perror("select");
            exit(EXIT_FAILURE);
        }
    } while(err <= 0);

    for(i = 0; i < max_fds + 1; i++) {
        if(pcontext->sd[i] != -1 && FD_ISSET(pcontext->sd[i], &selectfds)) {
            pcfd->fd = accept(pcontext->sd[i], (struct sockaddr *)&client_addr, &addr_len);
            pcfd->pc = pcontext;

            /* start new thread that will handle this TCP connected client */
            DBG("create thread to handle client that just established a connection\n");

            if(getnameinfo((struct sockaddr *)&client_addr, addr_len, name, sizeof(name), NULL, 0, NI_NUMERICHOST) == 0) {
                syslog(LOG_INFO, "serving client: %s\n", name);
                DBG("serving client: %s\n", name);
            }

            #if defined(MANAGMENT)
            pcfd->client = add_client(name);
            #endif

            if(pthread_create(&client, NULL, &client_thread, pcfd) != 0) {
                DBG("could not launch another client thread\n");
                close(pcfd->fd);
                free(pcfd);
                continue;
            }
            pthread_detach(client);
        }
    }
}

DBG("leaving server thread, calling cleanup function now\n");
pthread_cleanup_pop(1);

return NULL;

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/567138.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

知识点滴 - dBm和mW的转换

分贝毫瓦dBm&#xff0c;decibel-milliwatts&#xff0c;全写为“decibel relative to one milliwatt”&#xff0c;为一个指代功率的绝对值&#xff0c;而不同于dB只是一个相对值。 dBm或dBmW&#xff08;分贝-毫瓦&#xff09;用于表示功率的水平或级别&#xff0c;以分贝&am…

Android | 关于 OOM 的那些事

前言 Android 系统对每个app都会有一个最大的内存限制&#xff0c;如果超出这个限制&#xff0c;就会抛出 OOM&#xff0c;也就是Out Of Memory 。本质上是抛出的一个异常&#xff0c;一般是在内存超出限制之后抛出的。最为常见的 OOM 就是内存泄露(大量的对象无法被释放)导致…

精准数据分析,TeeChart为企业量身定制可视化退休预估方案

TeeChart for .NET是优秀的工业4.0 WinForm图表控件&#xff0c;官方独家授权汉化&#xff0c;集功能全面、性能稳定、价格实惠等优势于一体。TeeChart for .NET 中文版还可让您在使用和学习上没有任何语言障碍&#xff0c;至少可以节省30%的开发时间。 点击立即下载最新版Tee…

docker下不同容器的网络互相访问问题

目录 背景 ​编辑 docker网络模式 解决方法 mysql下 docker-compose下网络设置 nacos 效果 背景 我这边有两个容器&#xff0c;宿主机ip为 192.168.1.115&#xff0c;一个mysql&#xff0c;一个nacos&#xff0c;部署在主机上&#xff0c;使用的默认网络bridge&#xff…

学习SpringBoot入门知识,附带教程源码分享,快速掌握开发技巧-【imooc-java2021】体系课-Java工程师 2022版

学习SpringBoot入门知识&#xff0c;附带教程源码分享&#xff0c;快速掌握开发技巧 目录福利&#xff1a;文末有分享SpringBoot教程及源码哦 一、Spring Boot 是什么二、为什么要使用 Spring Boot三、快速入门3.1 创建 Spring Boot 项目3.1.1 通过 Spring Initializr 来创建1、…

微信小程序入门开发懂你找图小程序

文章目录 搭建 tabbar页面路径 首页模块tabs组件需求推荐组件精选大图月份热门分类模块需求 分类详情业务 首页模块专辑模块需求 专辑详情精美视频需求 视频详情需求 图片详情需求 搭建 tabbar 页面路径 页面名称路径首页index横屏horizontal精美视频video搜索search我的mine …

MySQL——存储引擎于索引应用

文章目录 一、 存储引擎1.1 MySQL结构1.2 存储引擎简介1.3 存储引擎特点1.3.1 InnoDB1.3.1.1 InnoDB 基本介绍1.3.1.2 InnoDB 逻辑存储结构 1.3.2 MyISAM1.3.3 Memory 1.4 三种引擎特点及区别1.5 存储引擎选择 二、 索引 - 重点2.1 介绍2.2 索引结构2.2.1 B-Tree 多路平衡二叉树…

【Linux:动态库与静态库】

1 动态库与静态库的概念 静态库&#xff08;.a&#xff09;&#xff1a;程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。 动态库&#xff08;.so&#xff09;&#xff1a;程序在运行的时候才去链接动态库的代码&#xff0c;多个程序共享使…

企业微信4.1.6 版本新功能介绍

一、效率工具与基础体验优化 文档 文档增加了丰富的模板&#xff0c;包含项目管理、日报周报、信息收集等多种场景&#xff0c;帮助了解更多文档功能&#xff0c;助力日常工作。 权限管理新增了「成员加入确认」开关&#xff0c;开启后需要管理员确认才能添加成员&#xff0…

相爱相杀的在线帮助文档语雀、Baklib、石墨文档,到底有何区别?

在线帮助文档是现代企业不可或缺的一部分&#xff0c;它提供了针对特定产品或服务的详细说明和指南&#xff0c;以帮助用户更好地理解产品或服务并解决问题。目前市面上有许多在线帮助文档工具&#xff0c;其中语雀、Baklib和石墨文档是比较受欢迎的三种&#xff0c;本文将对它…

卸载旧版本Keil,安装新版本的注意事项以及安装完成以后的一些问题

1. 资料 这里使用的是MDK536安装包和Keil.STM32F4xx_DFP.2.16.0安装包&#xff08;因为板子是正点原子stm32F407的&#xff09;&#xff1b; 安装包放到下面&#xff08;安装包也是在网上找的&#xff0c;里面还是比较全的&#xff0c;有C51&#xff0c;以及注册机等&#xf…

只做笔记有必要买apple pencil吗?好写的电容笔排行榜

随着科技的发展&#xff0c;出现了许多新的电子器件和数码器件。比如智能手机&#xff0c;比如ipad&#xff0c;比如电容笔等等。但实际上&#xff0c;想要让ipad发挥出最大的作用&#xff0c;就必须要有一支好的电笔。就像是我们在ipad上写字&#xff0c;总是要手写&#xff0…

硬件工程师-电路设计1-概念

学习电路设计及分析需要掌握的三大定律详解 1、遵循 源 回路 阻抗 分析回路上的节点&#xff0c;分析节点的内阻&#xff0c;电压&#xff0c;功率电流 2、分析电路上的波形&#xff1a;电路设计的过程就是波形整形的过程 波形整形&#xff1a;幅值的整形 波的…

CNNs:ZFNet之基于AlexNet特征可视化实验分析

CNNs:ZFNet之基于AlexNet特征可视化实验分析 导言基于AlexNet网络的实验分析实验一:不同卷积层特征提取分析实验二&#xff1a;不同卷积层提取特征收敛分析 ZFNet网络介绍基于ZFNet网络的实验分析实验三&#xff1a;针对AlexNet特征提取改善可视化实验四&#xff1a;特征不变性…

HW之轻量级内网资产探测漏洞扫描工具

简介 RGPScan是一款支持弱口令爆破的内网资产探测漏洞扫描工具&#xff0c;集成了Xray与Nuclei的Poc 工具定位 内网资产探测、通用漏洞扫描、弱口令爆破、端口转发、内网穿透、SOCK5 主机[IP&域名]存活检测&#xff0c;支持PING/ICMP模式 端口[IP&域名]服务扫描 网…

腾讯云图形验证码申请流程

目录 一、官方指引二、操作步骤1.步骤1&#xff1a;新建验证&#xff0c;获取验证码密钥2.步骤2&#xff1a;客户端接入验证码&#xff0c;展示验证页面3.步骤3&#xff1a;服务端接入验证码&#xff0c;调用票据校验 API 进行二次校验 一、官方指引 https://cloud.tencent.co…

为什么mac插入了u盘没反应 苹果mac插上usb后怎么找到

U盘使用简单&#xff0c;便于携带&#xff0c;几乎每个mac用户都有一个u盘。使用新的u盘的时候&#xff0c;你有没有遇到过mac插入了u盘没反应的情况呢&#xff1f;如果你是初次接触Mac电脑&#xff0c;使用u盘可能会手足无措&#xff0c;因为Mac系统和Windows存在差异&#xf…

苹果 App Store 出现山寨ChatGPT;Anthropic宣布获得4.5亿美元C轮融资

&#x1f680; 中国互联网协会提醒公众警惕“AI换脸”的新骗局 中国互联网协会提醒公众警惕“AI换脸”的新骗局&#xff0c;不法分子利用AI技术通过声音合成、伪造面部表情等实施诈骗。 公众应加强个人信息安全与防范措施&#xff0c;如加强个人信息保护、防止信息泄露、安装…

BLE连接通信

// BLE连接有关的技术分析 前言 本文的论述流程&#xff1a; 将传统的连接通信与广播进行对比&#xff0c;指出其不足说明BLE是如何制定规范解决这些问题写出完整的连接通信流程&#xff0c;并对能影响BLE连接通信的参数进行分析 1 连接通信的不足 保持连接是一个相当消耗…

代码随想录算法训练营15期 Day 2 | 977.有序数组的平方 、209.长度最小的子数组 、59.螺旋矩阵II 、总结

977.有序数组的平方 题目建议&#xff1a; 本题关键在于理解双指针思想 题目链接&#xff1a;力扣 思路一&#xff1a;暴力解算&#xff0c;直接将所有元素变成一个平方&#xff0c;然后进行排序。 class Solution { public:vector<int> sortedSquares(vector<int&g…