本文共 8249 字,大约阅读时间需要 27 分钟。
output_http.c里面
1、output_init()函数
/* 该函数只是解析参数,然后给相应的变量赋值 */
2、 output_run()函数
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]));// 创建一个线程 server_thread pthread_detach(servers[id].threadID);// 等待线程结束,以便回收它的资源 return 0; }
3、server_thread()函数
void *server_thread( void *arg ) { struct sockaddr_in addr, client_addr; int on; pthread_t client; socklen_t addr_len = sizeof(struct sockaddr_in); context *pcontext = arg; pglobal = pcontext->pglobal;// 取出globals结构体变量,globals由主函数传入,并传入输入通道,输入通道和输出通道就是通过globals来共享数据的 /* set cleanup handler to cleanup ressources */ /* 当线程结束的时候,会调用 server_cleanup 来做些清理工作 */ pthread_cleanup_push(server_cleanup, pcontext); /* open socket for server */ mjpeg-streamer的输出通道就是一个socket编程,在socket编程中充当一个服务器角色。 pcontext->sd = socket(PF_INET, SOCK_STREAM, 0);// socket相当于open函数 if ( pcontext->sd < 0 ) { fprintf(stderr, "socket failed\n"); exit(EXIT_FAILURE); } /* ignore "socket already in use" errors */ /* 设置套接字 SO_REUSEADDR:可以重复使用同一个IP和端口号 */ on = 1; if (setsockopt(pcontext->sd, SOL_SOCKET,SO_REUSEADDR, &on, sizeof(on)) < 0) { perror("setsockopt(SO_REUSEADDR) failed"); exit(EXIT_FAILURE); } /* perhaps we will use this keep-alive feature oneday */ /* setsockopt(sd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); */ /* configure server address to listen to all local IPs */ memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = pcontext->conf.port; /* (端口号)is already in right byteorder */ addr.sin_addr.s_addr = htonl(INADDR_ANY);/* 可以监听本地的所有的ip */ if ( bind(pcontext->sd, (struct sockaddr*)&addr, sizeof(addr)) != 0 )// bind函数绑定端口和IP(假若端口和IP都被使用了,可以重用) { perror("bind"); OPRINT("%s(): bind(%d) failed", __FUNCTION__, htons(pcontext->conf.port)); closelog(); exit(EXIT_FAILURE); } /* start listening on socket */ /* 启动监测数据,最多可以同时连接10个客服端 */ if ( listen(pcontext->sd,10) != 0 ) { fprintf(stderr, "listen failed\n"); 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));// 分配一个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"); /* 等待客服端的链接,如果有链接,则建立链接 */ pcfd->fd = accept(pcontext->sd, (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"); syslog(LOG_INFO, "serving client: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));// 将调试信息写入记录本(sin_addr是客服端的IP地址,sin_port是端口号) /* 创建一个线程 */ /* pthread_create: 第一个参数:线程标识符的指针 第二个参数:设置线程的属性 第三个参数:线程函数的起始地址 第四个参数:传给线程函数的参数。在client_thread函数中会用到 */ 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; }
4、client_thread()函数
/* thread for clients that connected to this server */ void *client_thread( void *arg ) { int cnt; char buffer[BUFFER_SIZE]={0}, *pb=buffer; iobuffer iobuf; request req; cfd lcfd; /* local-connected-file-descriptor */ /* we really need the fildescriptor and it must be freeable by us */ if (arg != NULL)// 如果我们传人的参数不为空,则将参数的内容拷贝到 lcfd 中(参数为 pcfd ,不为空) { memcpy(&lcfd,arg, sizeof(cfd)); free(arg); } else return NULL; /* initializes the structures */ /* 初始化iobuf、req这两个变量 */ init_iobuffer(&iobuf);// 把iobuf清为0,iobuf变量在_readline函数中被使用,起一个临时缓存的作用,iobuf的level成员表示buffer中还剩多少字节的空间,而buffer成员用于存放数据 init_request(&req);// http协议,需要客服端给服务器发送一个请求,而request就是这个请求 /* What does the client want to receive? Read the request. */ /* 从客服端接收一些数据,用来表示客服端发来的请求,才知道给客服端发什么数据 */ memset(buffer, 0, sizeof(buffer)); /* _readline:从客服端中读取一行的数据,以换行符结束 */ /* buffer中存有"abcd\n" *-/ /* 客服此时必须发送一个请求字符串,以换行符作为结束! 问:可以发送哪些字符串? 答:有 "GET /?action=snapshot\n" "GET /?action=stream\n" "GET /?action=command\n" */ if ( (cnt = _readline(lcfd.fd, &iobuf, buffer, sizeof(buffer)-1, 5)) == -1 ) { close(lcfd.fd); return NULL; } /* determine what to deliver */ /* 解析buf中的字符串 */ if ( strstr(buffer, "GET /?action=snapshot") != NULL ) { req.type = A_SNAPSHOT;// 如果请求字符串中含有"GET /?action=snapshot",则请求类型为 A_SNAPSHOT(拍照类型) } else if ( strstr(buffer, "GET /?action=stream") != NULL ) { req.type = A_STREAM;// 如果请求字符串中含有"GET /?action=stream",则请求类型为 A_STREAM(发送视频流类型)在浏览器上的网址后缀有服务器IP地址+端口号+请求类型
} else if ( strstr(buffer, "GET /?action=command") != NULL ) //命令请求 { /* 将请求后面的参数保存到 req.parameter*/
解析相关命令 int len; req.type = A_COMMAND; /* advance by the length of known string */ if ( (pb = strstr(buffer, "GET /?action=command")) == NULL ) { DBG("HTTP request seems to be malformed\n"); send_error(lcfd.fd, 400, "Malformed HTTP request"); close(lcfd.fd); return NULL; } pb += strlen("GET /?action=command"); /* only accept certain characters */ len = MIN(MAX(strspn(pb, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-=&1234567890"), 0), 100); req.parameter = malloc(len+1); if ( req.parameter == NULL ) { exit(EXIT_FAILURE); } memset(req.parameter, 0, len+1); strncpy(req.parameter, pb, len); DBG("command parameter (len: %d): \"%s\"\n", len, req.parameter); } else { /* 将请求后面的参数保存到 req.parameter */ int len; DBG("try to serve a file\n"); req.type = A_FILE; if ( (pb = strstr(buffer, "GET /")) == NULL ) { DBG("HTTP request seems to be malformed\n"); send_error(lcfd.fd, 400, "Malformed HTTP request"); close(lcfd.fd); return NULL; } pb += strlen("GET /"); len = MIN(MAX(strspn(pb, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._-1234567890"), 0), 100); req.parameter = malloc(len+1); if ( req.parameter == NULL ) { exit(EXIT_FAILURE); } memset(req.parameter, 0, len+1); strncpy(req.parameter, pb, len); //串拷贝函数,将pb后面的字符串拷贝到req.parameter,前面的内容是让pb指向请求后的字符串 DBG("parameter (len: %d): \"%s\"\n", len, req.parameter); } /* * parse the rest of the HTTP-request * the end of the request-header is marked by a single, empty line with "\r\n" */ do { memset(buffer, 0, sizeof(buffer));// 将buffer清0(存放客户端发送过来的字符串) /* 从客服端读取一行数据 */ /* 客服端必须再发送一次字符串 */ if ( (cnt = _readline(lcfd.fd, &iobuf, buffer, sizeof(buffer)-1, 5)) == -1 ) { free_request(&req); close(lcfd.fd); return NULL; } /* 开始解析 buffer 中的数据 */ if ( strstr(buffer, "User-Agent: ") != NULL ) { /* 如果buffer(客服端)中存有(发送了)用户名,则将用户名保存到 req.client 中 */ req.client = strdup(buffer+strlen("User-Agent: ")); } else if ( strstr(buffer, "Authorization: Basic ") != NULL ) { /* 如果buffer(客服端)中存有(发送了)密码,则将密码保存到 req.credentials 中 */ req.credentials = strdup(buffer+strlen("Authorization: Basic ")); decodeBase64(req.credentials);// 对密码进行解码 DBG("username:password: %s\n", req.credentials); } } while( cnt > 2 && !(buffer[0] == '\r' && buffer[1] == '\n') ); /* check for username and password if parameter -c was given */ /* 如果支持密码功能,则要检查用户名和密码是否匹配 */ if ( lcfd.pc->conf.credentials != NULL ) { if ( req.credentials == NULL || strcmp(lcfd.pc->conf.credentials, req.credentials) != 0 ) { DBG("access denied\n"); send_error(lcfd.fd, 401, "username and password do not match to configuration"); close(lcfd.fd); if ( req.parameter != NULL ) free(req.parameter); if ( req.client != NULL ) free(req.client); if ( req.credentials != NULL ) free(req.credentials); return NULL; } DBG("access granted\n"); } /* now it's time to answer */ /* 根据请求的类型,采取相应的行动 */ switch ( req.type ) { case A_SNAPSHOT: DBG("Request for snapshot\n"); send_snapshot(lcfd.fd); break; case A_STREAM: DBG("Request for stream\n"); send_stream(lcfd.fd); break; case A_COMMAND: if ( lcfd.pc->conf.nocommands ) { send_error(lcfd.fd, 501, "this server is configured to not accept commands"); break; } command(lcfd.pc->id, lcfd.fd, req.parameter); break; case A_FILE: if ( lcfd.pc->conf.www_folder == NULL ) send_error(lcfd.fd, 501, "no www-folder configured"); else send_file(lcfd.pc->id, lcfd.fd, req.parameter); break; default: DBG("unknown request\n"); } close(lcfd.fd); free_request(&req); DBG("leaving HTTP client thread\n"); return NULL; }