J2440 mjpg-streamer学习笔记7------输出通道

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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值