---WebCam网络摄像头10 socket
生活随笔
收集整理的這篇文章主要介紹了
---WebCam网络摄像头10 socket
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
如果使用如下指令啟動的mjpg_streamer
./mjpg_streamer -o "output_http.so -w ./www" -i "input_s3c2410.so -d /dev/camera" 則在mjpg_streamer.c中的兩條指令
for (i=0; i<global.outcnt; i++) {//只指定了一個-o,global.outcnt = 1global.out[i].init(&global.out[i].param);global.out[i].run(global.out[i].param.id);}
分別是執行output_http.c中的
output_init(output_parameter *param)// param.parameter_string="-w ./www" output_run(int id) //id=0
搜索"見下面"取得線索。
***********************************************************init***************************************************************************
在output_http.c里,output_init源碼如下
int output_init(output_parameter *param) {char *argv[MAX_ARGUMENTS]={NULL};int argc=1, 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;/* convert the single parameter-string to an array of strings */argv[0] = OUTPUT_PLUGIN_NAME;if ( param->parameter_string != NULL && strlen(param->parameter_string) != 0 ) {char *arg=NULL, *saveptr=NULL, *token=NULL;arg=(char *)strdup(param->parameter_string);if ( strchr(arg, ' ') != NULL ) {token=strtok_r(arg, " ", &saveptr);if ( token != NULL ) {argv[argc] = strdup(token);argc++;while ( (token=strtok_r(NULL, " ", &saveptr)) != NULL ) {argv[argc] = strdup(token);argc++;if (argc >= MAX_ARGUMENTS) {OPRINT("ERROR: too many arguments to output plugin\n");return 1;}}}}}/* show all parameters for DBG purposes */for (i=0; i<argc; i++) {DBG("argv[%d]=%s\n", i, 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(argc, 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;}}從此也可看出-o可以接受什么參數,一般要指定-p 8080(默認),-w /www
***********************************************************run***************************************************************************
在output_http.c里,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]));//見下面pthread_detach(servers[id].threadID);return 0; }由于在mjpg_streamer.c中是根據-o的數量使用for循環調用的output_run(),所以有幾個-o就會創建幾個服務線程,每個服務線程對應一個線程上下文servers[id],id是線程的序號(即-o的序號)
pthread_create的
參數1.servers[id].threadID.第id個線程對應的線程號
參數4.servers[id] 第id個線程的上下文參數,成員如下
/* context of each server thread */ typedef struct {int sd[MAX_SD_LEN];int sd_len;int id;globals *pglobal;pthread_t threadID;config conf; } context;//httpd.h #define MAX_OUTPUT_PLUGINS 10//mjpg-streamer.h 可知最多支持10個 -o context servers[MAX_OUTPUT_PLUGINS];//output_http.c
然后進入線程函數
/****************************************************************************** Description.: Open a TCP socket and wait for clients to connect. If clientsconnect, start a new thread for each accepted connection. Input Value.: arg is a pointer to the globals struct Return Value: always NULL, will only return on exit ******************************************************************************/ 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;//tcp //為調用getaddrinfo()準備hintssnprintf(name, sizeof(name), "%d", ntohs(pcontext->conf.port)); //端口號 8080if((err = getaddrinfo(NULL, name, &hints, &aip)) != 0) { //取得指定類型的socket address(addrinfo),以便后面的函數使用 //參數1 主機名或ip //參數2 服務名或端口號 //參數3 指定需要返回的地址類型 //參數4 返回的第一個addrinfo結構體(通過遍歷addrinfo結構體的鏈表得到所有符合條件的addrinfo) //hints.ai_flags = AI_PASSIVE;和主機名設為NULL,則此函數會返回本機所有ip的addrinfo,包括回環地址127.0.0.1和本地地址如192.168.1.230 //refer to man getaddrinfo , http://blog.csdn.net/lgtnt/article/details/3745194perror(gai_strerror(err));exit(EXIT_FAILURE);}for(i = 0; i < MAX_SD_LEN; i++)pcontext->sd[i] = -1; //httpd.c #define MAX_SD_LEN 50 //初始化所有的套接字描述符為-1/* open sockets for server (1 socket / address family) */i = 0;for(aip2 = aip; aip2 != NULL; aip2 = aip2->ai_next){ //遍歷所有的套接字地址,為每一個地址創建一個套接字(服務器套接字)。最多可以建立MAX_SD_LEN個(50)--即最多支持本機的50個ip。但 //通過上面的getaddrinfo()返回的是兩個socket地址(ip),一個是回環ip 127.0.0.1一個是本地ip比如192.168.1.230 //所以執行2次if((pcontext->sd[i] = socket(aip2->ai_family, aip2->ai_socktype, 0)) < 0) { //創建socket,返回套接字描述符 pcontext->sd[i] //每個-o 會創建2個(也是所有了)socket,即會監視本機的所有ip的8080端口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");}/* 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");}/* 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) { //為上面創建的socket綁定地址perror("bind");pcontext->sd[i] = -1;continue;}if(listen(pcontext->sd[i], 10) < 0) { //創建一個可以容納2個請求者的監聽隊列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", __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); //將上面創建的socket加入selectfds描述符集合if(pcontext->sd[i] > max_fds)max_fds = pcontext->sd[i];}}err = select(max_fds + 1, &selectfds, NULL, NULL, NULL); //使用select監聽文件文件描述符集合,沒有動靜就阻塞在這里。有動靜繼續執行。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); //accept函數會自動創建一個新的套接字于這個客戶端套接字通信,并且返回新套接字的文件描述符。原有的套接字繼續執行監聽。pcfd->pc = pcontext; /*httpd.c typedef struct {context *pc;int fd; } cfd; *//* 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);}if( pthread_create(&client, NULL, &client_thread, pcfd) != 0 ) {//見下面 //為客戶端創建服務線程 //參數4 pcfd //pcfd->fd 套接字的文件描述符 //pcfd->pc 套接字上下文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; }可以看出線程函數(對應一個-o的)server_thread里面是為每個addrinfo(最多50個)創建一個套接字,然后去監聽。每當一個套接字上有客戶端鏈接請求,就會再創建一個線程去傳輸數據。這里的套接字地址與beginning linux programming上講的不太一樣,在ipv6新加的吧。。。
./mjpg_streamer? -i "input_s3c2410.so -d /dev/camera"? -o "output_http.so -p 8080"? -o "output_http.so -p 8081"
這樣就會創建2個線程,
一個線程里面會創建2個套接字,一個在偵聽127.0.0.1:8080,一個在偵聽192.168.1.230:8080
另一個線程也會創建2個套接字,一個在偵聽127.0.0.1:8081,一個在偵聽192.168.1.230:8081
然后在客戶端的瀏覽器中同時訪問如下兩個網址
http://192.168.1.230:8080/?action=stream
http://192.168.1.230:8081/?action=stream
則服務器上監聽192.168.1.230:8080和監聽192.168.1.230:8081的socket就會accept()---
函數會自動創建一個新的套接字(和一個線程)與這個客戶端套接字通信,并且返回新套接字的文件描述符。原有的套接字繼續執行監聽。所以之后再開多個瀏覽器去訪問比如http://192.168.1.230:8080/?action=stream也可以訪問得到數據。
以上是個人理解僅供參考
線程函數如下
/****************************************************************************** Description.: Serve a connected TCP-client. This thread function is calledfor each connect of a HTTP client like a webbrowser. It determinesif it is a valid HTTP request and dispatches between the differentresponse options. Input Value.: arg is the filedescriptor and server-context of the connected TCPsocket. It must have been allocated so it is freeable by thisthread function. Return Value: always NULL ******************************************************************************/ /* 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) {memcpy(&lcfd, arg, sizeof(cfd));free(arg);}elsereturn NULL;/* initializes the structures */init_iobuffer(&iobuf);init_request(&req);/* What does the client want to receive? Read the request. */memset(buffer, 0, sizeof(buffer));if ( (cnt = _readline(lcfd.fd, &iobuf, buffer, sizeof(buffer)-1, 5)) == -1 ) { //從描述符(客戶端)讀取一行數據到bufferclose(lcfd.fd);return NULL;}/* determine what to deliver */if ( strstr(buffer, "GET /?action=snapshot") != NULL ) {req.type = A_SNAPSHOT;}else if ( strstr(buffer, "GET /?action=stream") != NULL ) {req.type = A_STREAM; //比如瀏覽器中輸入 http://192.168.1.230:8080/?action=stream}else if ( strstr(buffer, "GET /?action=command") != NULL ) {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);if ( unescape(req.parameter) == -1 ) {free(req.parameter);send_error(lcfd.fd, 500, "could not properly unescape command parameter string");LOG("could not properly unescape command parameter string\n");close(lcfd.fd);return NULL;}DBG("command parameter (len: %d): \"%s\"\n", len, req.parameter);}else {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);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));if ( (cnt = _readline(lcfd.fd, &iobuf, buffer, sizeof(buffer)-1, 5)) == -1 ) {free_request(&req);close(lcfd.fd);return NULL;}if ( strstr(buffer, "User-Agent: ") != NULL ) {req.client = strdup(buffer+strlen("User-Agent: "));}else if ( strstr(buffer, "Authorization: Basic ") != NULL ) {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");elsesend_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; }
下面是服務器響應客戶端的?action=stream請求所發送的全部數據-----一個web服務器發送數據的實現
比如 http://192.168.1.230:8081/?action=stream
從這個函數可以看出,在運行程序時即使不使能www 路徑也可以觀看圖像。因為它發送了完整的http標記。
/****************************************************************************** Description.: Send a complete HTTP response and a stream of JPG-frames. Input Value.: fildescriptor fd to send the answer to Return Value: - ******************************************************************************/ void send_stream(int fd) {unsigned char *frame=NULL, *tmp=NULL;int frame_size=0, max_frame_size=0;char buffer[BUFFER_SIZE] = {0};DBG("preparing header\n");sprintf(buffer, "HTTP/1.0 200 OK\r\n" \STD_HEADER \"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n" \"\r\n" \"--" BOUNDARY "\r\n");if ( write(fd, buffer, strlen(buffer)) < 0 ) { //發送http頭free(frame);return;}DBG("Headers send, sending stream now\n");while ( !pglobal->stop ) { //只要沒停止就一直發送圖像,所以在瀏覽器中看到的是 視頻/* wait for fresh frames */pthread_cond_wait(&pglobal->db_update, &pglobal->db);/* read buffer */frame_size = pglobal->size;/* check if framebuffer is large enough, increase it if necessary */if ( frame_size > max_frame_size ) {DBG("increasing buffer size to %d\n", frame_size);max_frame_size = frame_size+TEN_K;if ( (tmp = realloc(frame, max_frame_size)) == NULL ) {free(frame);pthread_mutex_unlock( &pglobal->db );send_error(fd, 500, "not enough memory");return;}frame = tmp;}memcpy(frame, pglobal->buf, frame_size);DBG("got frame (size: %d kB)\n", frame_size/1024);pthread_mutex_unlock( &pglobal->db );/** print the individual mimetype and the length* sending the content-length fixes random stream disruption observed* with firefox*/sprintf(buffer, "Content-Type: image/jpeg\r\n" \"Content-Length: %d\r\n" \"\r\n", frame_size);DBG("sending intemdiate header\n");if ( write(fd, buffer, strlen(buffer)) < 0 ) break; //發送內容類型,內容大小DBG("sending frame\n");if( write(fd, frame, frame_size) < 0 ) break; //發送內容--圖像數據DBG("sending boundary\n");sprintf(buffer, "\r\n--" BOUNDARY "\r\n");if ( write(fd, buffer, strlen(buffer)) < 0 ) break; //發送http尾}free(frame); }
而如果客戶端想要訪問www下的文件,則在服務器端啟動mjpg-streamer時需要指定www路徑,發送文件的函數是send_file()
一個簡單的web服務器的實現
/****************************************************************************** Description.: Send HTTP header and copy the content of a file. To keep thingssimple, just a single folder gets searched for the file. Justfiles with known extension and supported mimetype get served.If no parameter was given, the file "index.html" will be copied. Input Value.: * fd.......: filedescriptor to send data to* parameter: string that consists of the filename* id.......: specifies which server-context is the right one Return Value: - ******************************************************************************/ void send_file(int id, int fd, char *parameter) {char buffer[BUFFER_SIZE] = {0};char *extension, *mimetype=NULL;int i, lfd;config conf = servers[id].conf;/* in case no parameter was given */if ( parameter == NULL || strlen(parameter) == 0 )parameter = "index.html";/* find file-extension */if ( (extension = strstr(parameter, ".")) == NULL ) {send_error(fd, 400, "No file extension found");return;}/* determine mime-type */for ( i=0; i < LENGTH_OF(mimetypes); i++ ) {if ( strcmp(mimetypes[i].dot_extension, extension) == 0 ) {mimetype = (char *)mimetypes[i].mimetype;break;}}/* in case of unknown mimetype or extension leave */if ( mimetype == NULL ) {send_error(fd, 404, "MIME-TYPE not known");return;}/* now filename, mimetype and extension are known */DBG("trying to serve file \"%s\", extension: \"%s\" mime: \"%s\"\n", parameter, extension, mimetype);/* build the absolute path to the file */strncat(buffer, conf.www_folder, sizeof(buffer)-1);strncat(buffer, parameter, sizeof(buffer)-strlen(buffer)-1);/* try to open that file */if ( (lfd = open(buffer, O_RDONLY)) < 0 ) {DBG("file %s not accessible\n", buffer);send_error(fd, 404, "Could not open file");return;}DBG("opened file: %s\n", buffer);/* prepare HTTP header */sprintf(buffer, "HTTP/1.0 200 OK\r\n" \"Content-type: %s\r\n" \STD_HEADER \"\r\n", mimetype); //發送的這些數據在瀏覽器中看不到的,是給瀏覽器一個提供的一個版本識別信息 //瀏覽器中可以觀察到的后面真正的數據(比如index.html的內容) //上面send_stream()發送圖像流也是一樣,瀏覽器中只呈現出圖像i = strlen(buffer);/* first transmit HTTP-header, afterwards transmit content of file */do {if ( write(fd, buffer, i) < 0 ) {close(lfd);return;}} while ( (i=read(lfd, buffer, sizeof(buffer))) > 0 );/* close file, job done */close(lfd); } 可以看到此函數會按照瀏覽器地址指定的文件在www目錄尋找這個文件,然后發送出去。所以可以按照項目要求自己加一些網頁進去就可以擴增功能啦
比如在板子上
[root@FriendlyARM www]# touch a.html [root@FriendlyARM www]# echo hhheh > a.html 然后客戶端訪問
http://192.168.1.230:8080/a.html
同樣可以想到,如果在www目錄下放一個cgi文件,是否也可以訪問呢?
不支持。看上面line 36 --line39,有識別的。如果注釋掉那個return,則瀏覽到的是亂碼。
boa是支持的,可以參考一下boa的源碼,修改一下send_file()估計就可以了。
http詳細部分見下文。
轉載于:https://www.cnblogs.com/-song/archive/2011/11/27/3331922.html
總結
以上是生活随笔為你收集整理的---WebCam网络摄像头10 socket的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ---WebCam网络摄像头6 编译We
- 下一篇: ---WebCam网络摄像头11 htt