C语言编写高并发Http文件上传下载服务器
前言
前段时间学习tinyhttpd和libevent开源库。
别人的代码写的再好终究是别人的,自以为看懂了,等到自己真正写的时候就会发现有各种问题。于是准备参考libevent里面最最最基础的功能(捡了芝麻丢了西瓜?),自己写一个event,用于熟悉libevent的I/O多路复用思想。然后再加上http。就有了这篇博客的Http高并发文件上传下载服务器(这里其实是伪高并发,下文会具体描述,原谅我的标题党,哈哈)。
共享代码给大家。希望可以帮助初学者熟悉http协议和libevent基础知识。如有问题,请大家不吝指出,谢谢!
项目效果图
- 服务器启动
- 使用客户端(浏览器)访问效果,这里是chrome
项目介绍
这里只是简单介绍下,具体细节请大家看代码,文章最后贴有代码地址。
环境介绍
系统平台:windows
开发工具:vs2010
开发语言:C
程序结构之:event相关
单线程,使用I/O多路复用实现并发。main函数进来后直接调用http_startup()。
int main()
{
UINT16 port = 80;
http_startup(&port);
return 0;
}
http_startup()里面创建一个socket用于listen,然后把这个socket扔到event里面,设置回调函数为accept_callback,等待客户端(这里就是各种浏览器)连接。所贴代码为了逻辑清晰,去掉了一些代码。
int http_startup(uint16_t *port)
{
SOCKET fd;
event_t ev = {0};
network_listen(port, &fd);
ev.fd = fd;
ev.type = EV_READ | EV_PERSIST;
ev.callback = accept_callback;
event_add(&ev);
// dispatch里面就是个死循环,保证程序不退出
event_dispatch();
closesocket(fd);
return SUCC;
}
下面贴上event的核心,也就是event_dispatch()
为了逻辑清晰,也去掉了一些代码。
这里就是所谓的伪高并发之一了(后面还有之二):
由于是windows系统没有epoll,为了简单使用了select模型。尽管重新定义了FD_SETSIZE为1024,但是还是无关痛痒。1024个连接就满了,而且select是轮询机制,效率受限。
开始准备使用iocp,一来api的名字太难看了,就懒得研究了。二来我就是用来写个demo练练手,select也能凑合着用。
伪高并发之二:
网络I/O使用的是阻塞I/O,比如recv,会阻塞。上传文件时每次读取BUFFER_UNIT个数据,测试时log打印发现还是会偶尔阻塞一会。把BUFFER_UNIT改小的可能会有所改善,但是也不是解决办法。应该改成非阻塞I/O。这里也不讨论这个问题了。
#define BUFFER_UNIT 4096
ret_code_t event_dispatch()
{
fd_set readfds;
fd_set writefds;
fd_set exceptfds;
struct timeval timeout = { 0, 500000 };
int ret;
uint32_t i;
while (TRUE)
{
_active_size = 0;
memcpy(&readfds, &_readfds, sizeof(_readfds.fd_count) + _readfds.fd_count * sizeof(SOCKET));
memcpy(&writefds, &_writefds, sizeof(_writefds.fd_count) + _writefds.fd_count * sizeof(SOCKET));
memcpy(&exceptfds, &_exceptfds, sizeof(_exceptfds.fd_count) + _exceptfds.fd_count * sizeof(SOCKET));
ret = select(0, &readfds, &writefds, &exceptfds, &timeout);
switch (ret)
{
case 0: // the time limit expired
break;
case SOCKET_ERROR: // an error occurred
log_error("{%s:%d} an error occurred at select. WSAGetLastError=%d", __FUNCTION__, __LINE__, WSAGetLastError());
return FAIL;
default: // the total number of socket handles that are ready
for (i=0; i<readfds.fd_count; i++)
{
_active_ns[_active_size++] = find_rbnode(readfds.fd_array[i], &_read_evs);
}
for (i=0; i<writefds.fd_count; i++)
{
_active_ns[_active_size++] = find_rbnode(writefds.fd_array[i], &_write_evs);
}
for (i=0; i<exceptfds.fd_count; i++)
{
_active_ns[_active_size++] = find_rbnode(exceptfds.fd_array[i], &_except_evs);
}
break;
}
for (i = 0; i < _active_size; i++)
{
_active_ns[i]->ev->callback(_active_ns[i]->ev);
}
}
return SUCC;
}
再下面就是event_add()和event_del()。这三个函数基本上就是event驱动模型的全部了,贴代码。老规矩,去掉部分空指针判断的代码。影响阅读代码逻辑
ret_code_t event_add(event_t *ev)
{
struct rbnode_t k;
struct rbnode_t *n = NULL;
struct rbtree_t *t = NULL;
fd_set *s = NULL;
if (ev->type & EV_READ)
{
t = &_read_evs;
s = &_readfds;
}
else if (ev->type & EV_WRITE)
{
t = &_write_evs;
s = &_writefds;
}
else if (ev->type & EV_EXCEPT)
{
t = &_except_evs;
s = &_exceptfds;
}
k.ev = ev;
n = RB_FIND(rbtree_t, t, &k);
if (n)
{
log_warn("{%s:%d} event is already exist, fd=%d", __FUNCTION__, __LINE__, ev->fd);
return EXIS;
}
n = create_rbnode(ev);
RB_INSERT(rbtree_t, t, n);
FD_SET(ev->fd, s);
return SUCC;
}
实际代码里面由于业务逻辑,这段代码与所贴不一致,哈哈
static int event_del(uint32_t fd, struct rbtree_t *t, fd_set *s)
{
struct rbnode_t k;
struct rbnode_t *n = NULL;
event_t e = { 0 };
e.fd = fd;
k.ev = &e;
n = RB_FIND(rbtree_t, t, &k);
if (n)
{
RB_REMOVE(rbtree_t, t, n);
FD_CLR(fd, s);
release_rbnode(n);
}
return SUCC;
}
程序结构之:http相关
本程序目前能支持的客户端请求有三种:
第一种 客户端(浏览器)上传文件类 POST请求
判断逻辑如下:uri以 /upload 开头,
本来开始是没有后面的 ?path=,后来发现服务器不知道保存到哪级目录下面,于是就加上了这个。
如下url:
http://localhost/upload?path=
http://localhost/upload?path=Debug/
使用form表单提交,html上传代码如下:
html代码都是服务端按逻辑生成的。
<form action="/upload?path=Debug/" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="Upload" />
</form>
第二种 获取文件列表类 GET请求
代码里面的response_home_page()函数。
判断逻辑如下:request头里面的uri以 / 结尾,如下url:
http://localhost/
http://localhost/Debug/
http://localhost/Debug/httpd.tlog/
第三种 获取文件内容类 GET请求
代码里面的response_send_file_page()函数。
判断逻辑如下:非以上两种情况,如下url:
http://localhost/event.c
http://localhost/Debug/event.obj
http://localhost/Debug/httpd.tlog/link.write.1.tlog
先写这么多了,以后有时间再补充吧。(耐不住懒啊 ~)大家有问题请留言。
源码地址:
CSDN下载: https://download.csdn.net/download/yu1121jm/10695695
Github: https://github.com/binbyu/httpd