物联网设备常见的web服务器——uhttpd源码分析(二)

0x00 前言

uHTTPd 是一个 OpenWrt/LUCI 开发者从头编写的 Web 服务器。 它着力于实现一个稳定高效的服务器,能够满足嵌入式设备的轻量级任务需求,且能够与 OpenWrt 的配置框架 (UCI) 整合。默认情况下它被用于 OpenWrt 的 Web 管理接口 LuCI。当然,uHTTPd 也能提供一个常规 Web 服务器所需要的所有功能。

0x01 简介

物联网设备常见的web服务器——uhttpd源码分析(一)

0x02 run_server函数

位置:main–>run_server

static int run_server(void)
{
	uloop_init();//epoll等函数的初始化
	uh_setup_listeners();//设置监听回调函数
	uh_plugin_post_init();//插件初始化
	uloop_run(); //uloop_run轮询处理定时器、进程、描述符事件等。

	return 0;
}

0x03 uloop_init函数

主要位置:main–>run_server–>uloop_init–>uloop_init_pollfd

static int uloop_init_pollfd(void)
{
	if (poll_fd >= 0)
		return 0;

	poll_fd = epoll_create(32);
	if (poll_fd < 0)
		return -1;

	fcntl(poll_fd, F_SETFD, fcntl(poll_fd, F_GETFD) | FD_CLOEXEC);
	return 0;
}

0x00 epoll_create函数

位置:main–>run_server–>uloop_init–>uloop_init_pollfd–>epoll_create

该函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。size就是你在这个epoll fd上能关注的最大socket fd数。这里最大数量为32。
参考:
https://blog.csdn.net/yusiguyuan/article/details/15027821

0x01 fcntl函数

位置:main–>run_server–>uloop_init–>uloop_init_pollfd–>fcntl

通过fcntl可以改变已打开的文件性质。fcntl针对描述符提供控制。参数fd是被参数cmd操作的描述符。
参考:
https://baike.baidu.com/item/fcntl/6860021?fr=aladdin
https://blog.csdn.net/qq_37414405/article/details/83690447

0x04 uh_setup_listeners函数

位置:main–>run_server–>uh_setup_listeners

void uh_setup_listeners(void)
{
	struct listener *l;
	int yes = 1;

	list_for_each_entry(l, &listeners, list) {
		int sock = l->fd.fd;

		/* TCP keep-alive */
		if (conf.tcp_keepalive > 0) {
#ifdef linux
			int tcp_ka_idl, tcp_ka_int, tcp_ka_cnt, tcp_fstopn;

			tcp_ka_idl = 1;
			tcp_ka_cnt = 3;
			tcp_ka_int = conf.tcp_keepalive;
			tcp_fstopn = 5;
			/*检测网线非法断开*/
			setsockopt(sock, SOL_TCP, TCP_KEEPIDLE,  &tcp_ka_idl, sizeof(tcp_ka_idl));
			setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &tcp_ka_int, sizeof(tcp_ka_int));
			setsockopt(sock, SOL_TCP, TCP_KEEPCNT,   &tcp_ka_cnt, sizeof(tcp_ka_cnt));
			setsockopt(sock, SOL_TCP, TCP_FASTOPEN,  &tcp_fstopn, sizeof(tcp_fstopn));
#endif

			setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes));
		}

		l->fd.cb = listener_cb;//accept接受数据函数就在listener_cb中
		uloop_fd_add(&l->fd, ULOOP_READ);
	}
}

最主要关注 l->fd.cb = listener_cb

0x00 setsockopt函数

位置:main–>run_server–>uloop_init–>uloop_init_pollfd–>epoll_create

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
sockfd:标识一个套接口的描述字。
level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
optname:需设置的选项。
optval:指针,指向存放选项待设置的新值的缓冲区。
optlen:optval缓冲区长度。
主要是用来检查网络异常后的操作。
参考:
https://www.oschina.net/question/128542_2149349
https://baike.baidu.com/item/setsockopt/10069288?fr=aladdin

0x05 listener_cb函数

位置:0x04 所述

当通过epoll有数据过来后,会去调用listener_cb函数,并执行accept函数去对应的数据。

static void listener_cb(struct uloop_fd *fd, unsigned int events)
{
	struct listener *l = container_of(fd, struct listener, fd);//获取struct listener结构体的起始地址

	while (1) {
		if (!uh_accept_client(fd->fd, l->tls))//accept接受数据,并设置对应的回调函数
			break;
	}
	/*如果连接数大于或等于最大连接数则执行删除对应的fd操作,最大连接数默认为100*/
	if (conf.max_connections && n_clients >= conf.max_connections)
		uh_block_listener(l);//删除全局变量对应的fd,主要操作就是将对应的全局变量的fd设置为NULL,和设置事件的值
}

0x00 uh_accept_client函数

位置:listener_cb–>uh_accept_client

/*accept接受数据,并设置对应的回调函数*/
bool uh_accept_client(int fd, bool tls)
{
	static struct client *next_client;
	struct client *cl;
	unsigned int sl;
	int sfd;
	static int client_id = 0;
	struct sockaddr_in6 addr;

	if (!next_client)
		next_client = calloc(1, sizeof(*next_client));//申请内存

	cl = next_client;

	sl = sizeof(addr);
	sfd = accept(fd, (struct sockaddr *) &addr, &sl);
	if (sfd < 0)
		return false;

	set_addr(&cl->peer_addr, &addr);//addr拷贝到cl->peer_addr中
	sl = sizeof(addr);
	getsockname(sfd, (struct sockaddr *) &addr, &sl);//服务器端可以通过它得到相关客户端地址
	set_addr(&cl->srv_addr, &addr);

	cl->us = &cl->sfd.stream;
	if (tls) {
		uh_tls_client_attach(cl);
	} else {
		cl->us->notify_read = client_ustream_read_cb;//读操作
		cl->us->notify_write = client_ustream_write_cb;//写操作
		cl->us->notify_state = client_notify_state;//通知状态
	}

	cl->us->string_data = true;
	ustream_fd_init(&cl->sfd, sfd);//将sfd赋值给cl中的fd,以便后续使用

	uh_poll_connection(cl);//主要用来设置超时定时器
	list_add_tail(&cl->list, &clients);

	next_client = NULL;
	n_clients++;//作用可以看listener_cb函数,主要作用是总共连接数
	cl->id = client_id++;
	cl->tls = tls;

	return true;
}

0x01 calloc函数

位置:listener_cb–>uh_accept_client–>calloc

void *calloc(size_t nitems, size_t size)
分配所需的内存空间,并返回一个指向它的指针。malloc 和 calloc 之间的不同点是,malloc 不会设置内存为零,而 calloc 会设置分配的内存为零。
参考:
https://www.runoob.com/cprogramming/c-function-calloc.html

0x02 accept函数

位置:listener_cb–>uh_accept_client–>accept

int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
如果函数执行正确的话,将会返回新的套接字描述符,用于指向与当前通信的客户端发送或接收数据。
参考:
http://blog.chinaunix.net/uid-28595538-id-4919587.html

0x03 getsockname函数

位置:listener_cb–>uh_accept_client–>getsockname

int getsockname(int s, struct sockaddr *name, socklen_t *namelen);
服务器端可以通过它得到相关客户端地址。
参考:
https://blog.csdn.net/qinrenzhi/article/details/94043263
https://www.yiibai.com/unix_system_calls/getsockname.html

0x04 ustream_fd_init函数

位置:listener_cb–>uh_accept_client–>ustream_fd_init

void ustream_fd_init(struct ustream_fd *sf, int fd)
{
	struct ustream *s = &sf->stream;

	ustream_init_defaults(s);

	sf->fd.fd = fd;
	sf->fd.cb = ustream_uloop_cb;
	s->set_read_blocked = ustream_fd_set_read_blocked;
	s->write = ustream_fd_write;
	s->free = ustream_fd_free;
	s->poll = ustream_fd_poll; //读写操作
	ustream_fd_set_uloop(s, false);
}

通过ustream_fd_poll函数会调用uh_accept_client中的回调函数client_ustream_read_cb,真正读取客户端数据的是ustream_fd_poll函数,该函数使用read读取,而client_ustream_read_cb仅仅是操作read出来的数据。

0x06 client_ustream_read_cb函数

位置:listener_cb–>uh_accept_client–>client_ustream_read_cb

未完待续…

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值