框架
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;
}
如果文章对您有帮助,点赞👍支持,感谢🤝