网络摄像头使用mjpg-streamer实现,其内部的httpd.c实现了简易的http服务器。(没做特殊处理,内网)客户端可以使用网址形式访问摄像头数据。
现有要求如何实现外网访问?看了一些,p2p,都是同一个原理,“打洞”。网上有相关的API,及现成的DEMO,但要做成自己的DEMO,还要看那些别人写的(C++)API(只有简单的流程介绍,新手要利用这些API写出个APP来,有难度)。
本来想根据打洞原理写个简单的p2p(中间人)服务器,可想着想着就转到了代理去了。
原先,服务器建立两个sock,分别绑定两个端口; mjpg-streamer连接其中一个,登录服务器,同时使用keepalive心跳包保持联系,同时再创建一个socket,等待客户端连接;客户端连接服务器的另一个端口,登录服务器,获取 mjpg-streamer的地址、端口信息,也使用使用keepalive心跳包保持联系,之后与 mjpg-streamer建立连接。
后来写着写着,就成了:
首先服务器建立两个sock,分别绑定两个端口(一个8080,与mjpg-streamer相同,和客户端交互, )(一个8081,与mjpg-streamer交互)。
mjpg-streamer连接8081,在服务器上登录,然后等待。
客户端连接8080,(客户端为浏览器,)发送请求。
服务器接收到后转发给mjpg-streamer,同时将mjpg-streamer的响应传递给客户端。
服务器:
int main(void) { socklen_t optival = 1; struct sockaddr_in addr; int res; socklen_t len; int cur_num; signal(SIGINT, sig_handler); cli_num = 0; childpid = vfork(); if(0 == childpid) { /* let child use parent's space, but the atpid is setted to value 0 by child */ /* so must set it to child's pid */ childpid = getpid(); signal(SIGUSR2, server_sig_handler); /* set socket to wait httpserver to connect */ portfd = socket(AF_INET, SOCK_STREAM, 0); setsockopt(portfd, SOL_SOCKET, SO_REUSEADDR, &optival, sizeof(optival)); if(-1 == portfd) { perror("/PORT/ socket"); return -1; } fprintf(stderr, "/PORT/ create sockfd successfully !\n"); addr.sin_family = AF_INET; addr.sin_port = htons(PORT); addr.sin_addr.s_addr = INADDR_ANY; res = bind(portfd, (struct sockaddr*)&addr, sizeof(addr)); if(-1 == res) { perror("/PORT/: bind"); goto port_err; } fprintf(stderr, "/PORT/: bind sockfd successfully !\n"); res = listen(portfd, MAXLINK); if(-1 == res) { perror("/PORT/: listen"); goto port_err; } fprintf(stderr, "/PORT/: listen sockfd from port %u successfully !\n", PORT); /* create thread to wait httpserver's connecting */ do { res = pthread_create(&server.recvtid, NULL, httpserver_handler, (void*)portfd); if(res != 0) { fprintf(stderr, "/PORT/: pthread_create httpserver_handler failed !\n"); perror("/PORT/: pthread_create"); } } while(res); cur_num = 0; while(1) { proxy_sockfd = socket(AF_INET, SOCK_STREAM, 0); setsockopt(proxy_sockfd, SOL_SOCKET, SO_REUSEADDR, &optival, sizeof(optival)); if(-1 == proxy_sockfd) { perror("/PROXY/: socket"); continue; } fprintf(stderr, "/PROXY/: create sockfd successfully !\n"); addr.sin_family = AF_INET; addr.sin_port = htons(PROXYPORT); addr.sin_addr.s_addr = INADDR_ANY; res = bind(proxy_sockfd, (struct sockaddr*)&addr, sizeof(addr)); if(-1 == res) { perror("/PROXY/: bind"); goto err; } fprintf(stderr, "/PROXY/: bind sockfd successfully !\n"); res = listen(proxy_sockfd, MAXLINK); if(-1 == res) { perror("/PROXY/: listen"); goto err; } fprintf(stderr, "/PROXY/: listen sockfd from port %u successfully !\n", PROXYPORT); fprintf(stderr, "/PROXY/: waitting for the data ...\n"); cur_num = 0; while(1) { len = sizeof(struct sockaddr_in); cur_num = find_num(); fprintf(stderr, "/PROXY/: current num of client: %d\n", cur_num); if(-1 == cur_num) { fprintf(stderr, "/PROXY/: [WARNING] The number of connections has reached the maxinum !!!\n"); while((cur_num = find_num()) == -1); } p2pcli[cur_num].sockfd = accept(proxy_sockfd, (struct sockaddr*)&(p2pcli[cur_num].addr), &len); if(-1 == p2pcli[cur_num].sockfd) { perror("/PROXY/: accept"); continue; } p2pcli[cur_num].flag = USED; res = pthread_create(&(p2pcli[cur_num].recvtid), NULL, httprecv_handler, (void*)p2pcli[cur_num].sockfd); if(res != 0) { fprintf(stderr, "/PROXY/: pthread_create port to recv failed !\n"); close(p2pcli[cur_num].sockfd); p2pcli[cur_num].flag = EMPTY; continue; } res = pthread_create(&(p2pcli[cur_num].sendtid), NULL, httpsend_handler, (void*)p2pcli[cur_num].sockfd); if(res != 0) { fprintf(stderr, "/PROXY/: pthread_create port to send failed !\n"); close(p2pcli[cur_num].sockfd); p2pcli[cur_num].flag = EMPTY; continue; } usleep(20); } port_err: close(server.sockfd); server.sockfd = -1; err: close(proxy_sockfd); } } else { waitpid(childpid, NULL, 0); } return 0; }首先创建子进程,而后等待子进程结束;子进程,建立一个与 mjpg-streamer通信的socket,创建一个线程等待mjpg-streamer连接。再建立一个与客户端通信的socket,为每一个客户端的发送、接收分别创建两个线程,不断做数据转发。
测试情况:
640x480 发送的buf只有1024时,浏览器接收到一两帧时候就开始不断弹出窗口; 发送的buf为1280x960时,图像比较流畅,和直接浏览的流畅性差不多,但时而会出现图像局部不正常现象。
1024x768 发送的buf为1280x960时, 可以看到画面,但跳帧非常严重,同时也会不断弹出窗口。
虽然这样的程序出来的效果非常差,但留个纪念吧。