Git Product home page Git Product logo

tinywebserver's Introduction

TinyWebServer

Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器.

  • 使用 线程池 + 非阻塞socket + epoll(ET和LT均实现) + 事件处理(Reactor和模拟Proactor均实现) 的并发模型
  • 使用状态机解析HTTP请求报文,支持解析GET和POST请求
  • 访问服务器数据库实现web端用户注册、登录功能,可以请求服务器图片和视频文件
  • 实现同步/异步日志系统,记录服务器运行状态
  • 经Webbench压力测试可以实现上万的并发连接数据交换

写在前面

  • 本项目开发维护过程中,很多童鞋曾发红包支持,我都一一谢绝。我现在不会,将来也不会将本项目包装成任何课程售卖,更不会开通任何支持通道。
  • 目前网络上有人或对本项目,或对游双大佬的项目包装成课程售卖。请各位童鞋擦亮眼,辨识各大学习/求职网站的C++服务器项目,不要盲目付费。
  • 有面试官大佬通过项目信息在公司内找到我,发现很多童鞋简历上都用了这个项目。但,在面试过程中发现很多童鞋通过本项目入门了,但是对于一些东西还是属于知其然不知其所以然的状态,需要加强下基础知识的学习,推荐认真阅读下
    • 《unix环境高级编程》
    • 《unix网络编程》
  • 感谢各位大佬,各位朋友,各位童鞋的认可和支持。如果本项目能带你入门,将是我莫大的荣幸。

目录

概述 框架 Demo演示 压力测试 更新日志 源码下载 快速运行 个性化运行 庖丁解牛 CPP11实现 致谢

概述

框架

Demo演示

  • 注册演示
  • 登录演示
  • 请求图片文件演示(6M)
  • 请求视频文件演示(39M)

压力测试

在关闭日志后,使用Webbench对服务器进行压力测试,对listenfd和connfd分别采用ET和LT模式,均可实现上万的并发连接,下面列出的是两者组合后的测试结果.

  • Proactor,LT + LT,93251 QPS
  • Proactor,LT + ET,97459 QPS
  • Proactor,ET + LT,80498 QPS
  • Proactor,ET + ET,92167 QPS
  • Reactor,LT + ET,69175 QPS
  • 并发连接总数:10500
  • 访问服务器时间:5s
  • 所有访问均成功

注意: 使用本项目的webbench进行压测时,若报错显示webbench命令找不到,将可执行文件webbench删除后,重新编译即可。

更新日志

  • 解决请求服务器上大文件的Bug
  • 增加请求视频文件的页面
  • 解决数据库同步校验内存泄漏
  • 实现非阻塞模式下的ET和LT触发,并完成压力测试
  • 完善lock.h中的封装类,统一使用该同步机制
  • 改进代码结构,更新局部变量懒汉单例模式
  • 优化数据库连接池信号量与代码结构
  • 使用RAII机制优化数据库连接的获取与释放
  • 优化代码结构,封装工具类以减少全局变量
  • 编译一次即可,命令行进行个性化测试更加友好
  • main函数封装重构
  • 新增命令行日志开关,关闭日志后更新压力测试结果
  • 改进编译方式,只配置一次SQL信息即可
  • 新增Reactor模式,并完成压力测试

源码下载

目前有两个版本,版本间的代码结构有较大改动,文档和代码运行方法也不一致。重构版本更简洁,原始版本(raw_version)更大保留游双代码的原汁原味,从原始版本更容易入手.

如果遇到github代码下载失败,或访问太慢,可以从以下链接下载,与Github最新提交同步.

  • 重构版本下载地址 : BaiduYun
    • 提取码 : vsqq
  • 原始版本(raw_version)下载地址 : BaiduYun

快速运行

  • 服务器测试环境

    • Ubuntu版本16.04
    • MySQL版本5.7.29
  • 浏览器测试环境

    • Windows、Linux均可
    • Chrome
    • FireFox
    • 其他浏览器暂无测试
  • 测试前确认已安装MySQL数据库

    // 建立yourdb库
    create database yourdb;
    
    // 创建user表
    USE yourdb;
    CREATE TABLE user(
        username char(50) NULL,
        passwd char(50) NULL
    )ENGINE=InnoDB;
    
    // 添加数据
    INSERT INTO user(username, passwd) VALUES('name', 'passwd');
  • 修改main.cpp中的数据库初始化信息

    //数据库登录名,密码,库名
    string user = "root";
    string passwd = "root";
    string databasename = "yourdb";
  • build

    sh ./build.sh
  • 启动server

    ./server
  • 浏览器端

    ip:9006

个性化运行

./server [-p port] [-l LOGWrite] [-m TRIGMode] [-o OPT_LINGER] [-s sql_num] [-t thread_num] [-c close_log] [-a actor_model]

温馨提示:以上参数不是非必须,不用全部使用,根据个人情况搭配选用即可.

  • -p,自定义端口号
    • 默认9006
  • -l,选择日志写入方式,默认同步写入
    • 0,同步写入
    • 1,异步写入
  • -m,listenfd和connfd的模式组合,默认使用LT + LT
    • 0,表示使用LT + LT
    • 1,表示使用LT + ET
    • 2,表示使用ET + LT
    • 3,表示使用ET + ET
  • -o,优雅关闭连接,默认不使用
    • 0,不使用
    • 1,使用
  • -s,数据库连接数量
    • 默认为8
  • -t,线程数量
    • 默认为8
  • -c,关闭日志,默认打开
    • 0,打开日志
    • 1,关闭日志
  • -a,选择反应堆模型,默认Proactor
    • 0,Proactor模型
    • 1,Reactor模型

测试示例命令与含义

./server -p 9007 -l 1 -m 0 -o 1 -s 10 -t 10 -c 1 -a 1
  • 端口9007
  • 异步写入日志
  • 使用LT + LT组合
  • 使用优雅关闭连接
  • 数据库连接池内有10条连接
  • 线程池内有10条线程
  • 关闭日志
  • Reactor反应堆模型

庖丁解牛

近期版本迭代较快,以下内容多以旧版本(raw_version)代码为蓝本进行详解.

Star History

Star History Chart

CPP11实现

更简洁,更优雅的CPP11实现:Webserver

致谢

Linux高性能服务器编程,游双著.

感谢以下朋友的PR和帮助: @RownH@mapleFU@ZWiley@zjuHong@mamil@byfate@MaJun827@BBLiu-coder@smoky96@yfBong@liuwuyao@Huixxi@markparticle@blogg9ggg.

tinywebserver's People

Contributors

byfate avatar ek1ng avatar mamil avatar maplefu avatar qinguoyi avatar sundxu avatar yukunj avatar zjuhong avatar zwiley avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tinywebserver's Issues

m_cond.wait( )的问题

//pop时,如果当前队列没有元素,将会等待条件变量
bool pop(T &item)
{

    m_mutex.lock();
    while (m_size <= 0)
    {
        // 这里若线程wait成功,函数返回0
        if (!m_cond.wait(m_mutex.get()))
        {
            m_mutex.unlock();
            return false;
        }
    }

    m_front = (m_front + 1) % m_max_size;
    item = m_array[m_front];
    m_size--;
    m_mutex.unlock();
    return true;
}

这是源码中block_queue中的pop函数,wait那一行是不是存在逻辑错误?

而在微信公众号解析代码中:if (0 != pthread_cond_wait(m_cond, m_mutex)) ,两者有出入

some security issues in the code

sql injection

https://github.com/qinguoyi/TinyWebServer/blob/master/http/http_conn.cpp#L426
user’s inputs are directly spliced ​​into the SQL statement
Attack vector:server supports the http protocol, capture the packet in the registration interface, and modify the packet to a malicious statement.

heap overflow

https://github.com/qinguoyi/TinyWebServer/blob/master/http/http_conn.cpp#L425
Because the length of the sql statement is not limited, the variable sql_insert overflows. The memory storing sql_insert is called later, causing the program to crash.

stack overflow

https://github.com/qinguoyi/TinyWebServer/blob/master/http/http_conn.cpp#L410
The program does not limit the length of the input of variable name and variable password.Users can enter characters of any length to cause stack overflow

http_conn.cpp中http::process有问题?

void http_conn::process()
{
HTTP_CODE read_ret = process_read();
if (read_ret == NO_REQUEST)
{
modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);
return;
}
bool write_ret = process_write(read_ret); //如果返回false,close_conn()就会执行(m_sockfd=-1),连接关闭,然后还要注册写事件?
if (!write_ret)
{
close_conn();
}
modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode);
}

视频无法播放

在centos7里面用 火狐 打开 IP:9006可以正常显示界面,但是无法播放视频(显示没有找到支持的视频格式) 可能是浏览器问题
另外在win10里以 IP:9006 无法打开界面

只能主机访问,不能远程访问

我运行成功了,可是却只能在运行server的这台计算机上访问,使用其他的计算机,输入相同的ip和端口却不能访问,有人能帮我解答下这是为什么吗,谢谢。

writev 使用好像有问题

我发现使用 writev 向 socket 写数据时,如果没有一次写完,就需要调整 iov 数组,不然图片会显示不完整。

我是在虚拟机上启动 server,然后在实体机上请求图片的,这时候 writev 会连续写几次才能写完,这时候就会出问题(图片显示不完整)。而在本地(同样在虚拟机中)请求图片的话,writev 可以一次性写完,图片可以完整的显示出来。

我自己的代码如下:

/* 写 HTTP 响应 */
bool http_conn::write() {
  int tmp = 0;
  printf("---debug---%d bytes to send\n", __bytes_to_send);
  if (__bytes_to_send == 0) {
    modfd(epollfd, __sockfd, EPOLLIN);
    __init();
    return true;
  }
  while (1) {
    tmp = Writev(__sockfd, __iv, __iv_cnt);
    if (tmp < 0) {
      if (errno == EAGAIN) {
        /* 若写缓冲区没有空间,则等待缓冲区可写,在此期间无法接收客户端请求 */
        modfd(epollfd, __sockfd, EPOLLOUT);
        return true;
      }
      __unmap();
      return false;
    }
    printf("---debug---sent %d bytes\n", tmp);
    __bytes_to_send -= tmp;
    __bytes_have_sent += tmp;
    __adjust_iv();

    if (__bytes_to_send <= 0) {
      __unmap();
      /* HTTP 响应发送成功,根据 Connection 字段决定是否立即关闭连接 */
      if (__linger) {
        __init();
        modfd(epollfd, __sockfd, EPOLLIN);
        return true;
      } else {
        modfd(epollfd, __sockfd, EPOLLIN);
        return false;
      }
    }
  }
  return false;
}

___adjust_iv 函数:

/* 每次调用 writev 都需要调整 __iv */
void http_conn::__adjust_iv() {
  if (__bytes_have_sent >= __write_idx) {
    __iv[0].iov_len = 0;
    __iv[1].iov_base = __file_addr + (__bytes_have_sent - __write_idx);
    __iv[1].iov_len = __bytes_to_send;
  } else {
    __iv[0].iov_base = __write_buf + __bytes_have_sent;
    __iv[0].iov_len = __write_idx - __bytes_have_sent;
  }
}

为什么write完成后,清空读缓冲区?

http_conn.h和http_conn.cpp中,如果read_once函数读取的数据跨越了前一个请求和后一个请求的HTTP报文,那么在调用write函数在某些条件下,调用了init()函数,清空了读缓冲区,那么后一个请求的一部分数据不就丢失了?

代码调试疑问

你好,我最近在阅读调试源码时,在Ubuntu中进行make时,系统报错 :在http_conn.cpp文件中第549行,temp=writev(m_sockfd,m_iv,m_iv_count); writev函数并没有在此文件中定义,所以make失败了,烦请大佬解释一波,不甚感激!

编译出错

我修改好数据库配置后,再运行make server,出现如下报错:
image
请问要怎么解决?

http_conn.cpp中write()好像有问题

bool http_conn::write()
{
    int temp = 0;

    if (bytes_to_send == 0)
    {
        modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);
        init();
        return true;
    }

    //int mv0len = m_iv[0].iov_len;

    while (1)
    {
        temp = writev(m_sockfd, m_iv, m_iv_count);

        if (temp < 0)
        {
            if (errno == EAGAIN)
            {
                modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode);
                return true;
            }
            unmap();
            return false;
        }

        bytes_have_send += temp;
        bytes_to_send -= temp;

        if (bytes_have_send >= m_iv[0].iov_len)
        {
            m_iv[0].iov_len = 0;
            m_iv[1].iov_base = m_file_address + (bytes_have_send - m_write_idx);
            m_iv[1].iov_len = bytes_to_send;
            //printf("1: >= \n");
        }
        else
        {
            m_iv[0].iov_base = m_write_buf + bytes_have_send;
            m_iv[0].iov_len = m_iv[0].iov_len - bytes_have_send;
            //printf("2: < \n");
        }

        // if (bytes_have_send >= mv0len)
        // {
        //     m_iv[0].iov_len = 0;
        //     m_iv[1].iov_base = m_file_address + (bytes_have_send - m_write_idx);
        //     m_iv[1].iov_len = bytes_to_send;
        // }
        // else 
        // {
        //     m_iv[0].iov_base = m_write_buf + bytes_have_send;
        //     m_iv[0].iov_len = m_iv[0].iov_len - temp;
        // }

        if (bytes_to_send <= 0)
        {
            unmap();
            modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);

            if (m_linger)
            {
                init();
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}

使用作者的wirtev后的处理方式跑,图片视屏传输正常,但是printf("2\n")没有执行过,也就是writev每次都把m_iv[0]的内容一次发送完毕,但是看作者的if(bytes_have_send >= m_iv[0].iov_len) else{}这里的else逻辑明显有问题,只是因为如上原因没有执行过,
假设writev第一次没有发出iov0的全部内容if (bytes_have_send >= m_iv[0].iov_len)不执行,则第二论的while()的if (bytes_have_send >= m_iv[0].iov_len)这里的m_iv[0].iov_len明显不是iov0的全部长度,按照博主的判断流程这里应该修改我注视的代码,不知道博主怎么认为的.

http_conn类的线程安全问题

比如http_conn类中的bytes_to_send 字段,在写线程和读线程中都有用到,但时并未加锁,我觉得是有线程安全问题的。

定时器组件存在bug

Utils::u_epollfd = m_epollfd;

秦院好,这里u_epoll先于m_epoll被赋有效值前赋值,导致定时器回调函数中使用的该值为无效值,测试过好像真滴是bug,请秦院修复

不过我手头代码版本较旧(大概一个月前吧),如果已经修复了还请秦院无视

调试

想问下大佬在Ubuntu底下是如何进行调试的呢?使用Clion这种IDE还是GDB?

clang: error: cannot specify -o when generating multiple output files

g++ -o server main.c ./timer/lst_timer.h ./timer/lst_timer.cpp ./threadpool/threadpool.h ./http/http_conn.cpp ./http/http_conn.h ./lock/locker.h ./log/log.cpp ./log/log.h ./CGImysql/sql_connection_pool.cpp ./CGImysql/sql_connection_pool.h webserver.h webserver.cpp config.h config.cpp -lpthread -lmysqlclient
clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated [-Wdeprecated]
clang: warning: treating 'c-header' input as 'c++-header' when in C++ mode, this behavior is deprecated [-Wdeprecated]
clang: warning: treating 'c-header' input as 'c++-header' when in C++ mode, this behavior is deprecated [-Wdeprecated]
clang: warning: treating 'c-header' input as 'c++-header' when in C++ mode, this behavior is deprecated [-Wdeprecated]
clang: warning: treating 'c-header' input as 'c++-header' when in C++ mode, this behavior is deprecated [-Wdeprecated]
clang: warning: treating 'c-header' input as 'c++-header' when in C++ mode, this behavior is deprecated [-Wdeprecated]
clang: warning: treating 'c-header' input as 'c++-header' when in C++ mode, this behavior is deprecated [-Wdeprecated]
clang: warning: treating 'c-header' input as 'c++-header' when in C++ mode, this behavior is deprecated [-Wdeprecated]
clang: warning: treating 'c-header' input as 'c++-header' when in C++ mode, this behavior is deprecated [-Wdeprecated]
clang: error: cannot specify -o when generating multiple output files
make: *** [server] Error 1
g++ -o ./root/CGISQL.cgi ./CGImysql/sign.cpp ./CGImysql/sql_connection_pool.cpp ./CGImysql/sql_connection_pool.h -lmysqlclient -lpthread
clang: warning: treating 'c-header' input as 'c++-header' when in C++ mode, this behavior is deprecated [-Wdeprecated]
clang: error: cannot specify -o when generating multiple output files
make: *** [CGISQL.cgi] Error 1

http_conn类问题

前辈你好:
http_conn.c 文件的users(就是用户的map),是不是应该设置成staic类型的。如果是非static类型的,每次来一个链接,就应该从数据库拷贝一次用户名和密码表,但http_conn::init()函数没有进行这一操作,webserver.c也只是拷贝了一次。另外,微信群怎么加入啊,希望和大家一起讨论。

关于webserver代码问题

前辈你好,我有几个疑问
1.webserver类中定义了一个sort_timer_lst变量timer_lst,代码中操作的也都是变量timer_lst。但是最后在eventloop函数使用了utils.timer_hander()函数。utils对象的初始化使用的是最开始定义的timer_lst,后面utils对象的m_timer_lst没有进行更新.
我觉得是不是把utils对象的初始化放在webserver类的构造函数,后面统一对utils.m_timer_lst操作。要么就是改eventloop中的代码。
2.
//处理新到的客户连接
if (sockfd == m_listenfd)
{
bool flag = dealclinetdata();
if (false == flag)//?????
continue;
}
这里的if判断条件是什么作用呢?我猜测是不是你想在flag==false的情况下输出日志,代码未完成吗?感谢解答

cmakelists文件

请问有cmakelists文件吗,我根据makefile写的cmakelists但是运行后不能进入,命令行上一直显示close17 close16

log.cpp中的log::init函数中的异步问题

这里是通过条件变量来实现数组大小的同步问题吧,那个异步体现在了哪里? 代码中说的是//flush_log_thread为回调函数,这里表示创建线程异步写日志,这里只是开了一个线程来运行flush_log_thread函数吧,我认为本质应该还是线程同步问题吧,这里还请解答一下

关于写数据问题

当一个write()或read_once()阻塞时,webserver是否就会被阻塞在 这一步无法处理下一个请求和新的就绪事件

常见问题与解决方案

大家在运行项目时遇到问题,优先查阅本lssue。

如果本issue不能解决你的问题,欢迎盖楼或直接公号私信我,我会尽快回复。

代码调试中的疑问

你好:我最近一直在学习研读调试你的Tinywebserver ,想请教一下,在http_conn.cpp文件中,有关修改数据库初始化信息,中有connection_pool *connPool=connection_pool::GetInstance("localhost", "root","root","yourdb",3306,5);这其中 “localhost” 应该输入啥? 我的开发环境是在VMware workstation 12 Pro 上安装 ubuntu16.04LTS ,服务器程序实在VMware workstation12 上的ubuntu16.04上编译的,请问此处的"localhost"应该填虚拟机ubuntu16.04的ip地址还是我笔记本的ip地址?如能帮忙解答,将不胜感激!我微信号15829468779,如能与你成为微信好友,深感荣幸!

webbench测试硬件参数

请问readme中的QPS是在什么硬件条件下测试的?
另外有在云服务器上测试过QPS吗?(一般都是1核cpu配置)
想参考一下数据,谢谢!

关于线程池和请求队列的问题

在webserver中每次需要处理完一个就绪事件才能将下一个事件放入请求队列,那这样的话就会保持请求队列始终只有一个请求,线程池的线程也只用上一个?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.