1 Star 0 Fork 0

散漫小主 / Sinetlib

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

Sinetlib: A High Perfermance C++ Network Library

Build Status license

Introduction

Sinetlib是一个仿照Muduo实现的基于Reactor模式的多线程网络库,附有异步日志,要求Linux 2.6以上内核版本。同时内嵌一个简洁HTTP服务器,可实现路由分发及静态资源访问。

Feature

  • 底层使用Epoll LT模式实现I/O复用,非阻塞I/O
  • 多线程、定时器依赖于c++11提供的std::thread、std::chrono库
  • Reactor模式,主线程accept请求后,使用Round Robin分发给线程池中线程处理
  • 基于小根堆的定时器管理队列
  • 双缓冲技术实现的异步日志
  • 使用智能指针及RAII机制管理对象生命期
  • 使用状态机解析HTTP请求,支持HTTP长连接
  • HTTP服务器支持URL路由分发及访问静态资源,可实现RESTful架构

Envoirment

  • OS: Ubuntu 16.04
  • Complier: g++ 5.4
  • Build: CMake

Tutorial

C++网络编程实战项目--Sinetlib网络库(1)——概述

C++网络编程实战项目--Sinetlib网络库(2)——I/O复用与事件分发

C++网络编程实战项目--Sinetlib网络库(3)——事件循环与跨线程调用

C++网络编程实战项目--Sinetlib网络库(4)——线程池和整体框架

C++网络编程实战项目--Sinetlib网络库(5)——HTTP服务器设计与实现

Model

主线程负责连接的建立,并通过Round Robin方式将连接分配给工作线程处理

1

2

Build

需先安装Cmake:

$ sudo apt-get update
$ sudo apt-get install cmake

开始构建

$ git clone git@github.com:silence1772/Sinetlib.git
$ cd Sinetlib
$ ./build.sh

执行完上述脚本后编译结果在新生成的build文件夹内,示例程序在build/bin下。

库和头文件分别安装在/usr/local/lib和/usr/local/include,该库依赖c++11及pthread库,使用方法如下:

$ g++ main.cpp -std=c++11 -lSinetlib -lpthread

在执行生成的可执行文件时可能会报找不到动态库文件,需要添加动态库查找路径,首先打开配置文件:

$ sudo vim /etc/ld.so.conf

在打开的文件末尾添加这句,保存退出:

include /usr/local/lib

使修改生效:

$ sudo /sbin/ldconfig

Usage

net

Sinetlib的使用十分简单,用户只需设置四个回调即可。四个回调抽象的是TCP连接建立后、有消息到达、答复消息完成、连接关闭这四个状态,用户可以设置各个状态对应的执行函数。

在此之前,用户需要先创建Looper和Server,Server的第二和第三个参数分别是监听的端口号和线程池中线程数,一般情况下建议线程数与CPU核心数接近,以最大程度发挥多线程性能。

#include "server.h"
#include <iostream>

void OnConnection(const std::shared_ptr<Connection>& conn)
{
    std::cout << "OnConnection" << std::endl;
}
void OnMessage(const std::shared_ptr<Connection>& conn, IOBuffer* buf, Timestamp t)
{
    std::cout << "OnMessage" << std::endl;
}
void OnReply(const std::shared_ptr<Connection>& conn)
{
    std::cout << "OnReply" << std::endl;
}
void OnClose(const std::shared_ptr<Connection>& conn)
{
    std::cout << "OnClose" << std::endl;
}

int main()
{
    Looper loop;
    Server s(&loop, 8888, 4);

    s.SetConnectionEstablishedCB(OnConnection);
    s.SetMessageArrivalCB(OnMessage);
    s.SetReplyCompleteCB(OnReply);
    s.SetConnectionCloseCB(OnClose);

    s.Start();
    loop.Start();
}

可通过telnet测试上述程序,测试结果如下(左边为telnet程序,带有$号为用户输入内容,右边为服务器输出):

$ telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.              
                                            OnConnection
$ test message
                                            OnMessage
$ ^]
telnet> quit
                                            OnClose

在提供给用户设置的各个回调函数中,都有一个指向当前连接的指针,在消息到达回调中还暴露了保存接收到的消息的IOBuffer缓冲区指针。

用户可以对这两个指针进行操作,通过读取缓冲区和调用Connection::Send()实现消息的收发。

Connection类:

// 发送数据到连接的对端
void Send(const void* data, size_t len);
void Send(const std::string& message);
void Send(IOBuffer& buffer);
// 关闭连接中自己的这一端,这会激发TCP的四次挥手进而关闭整个连接
void Shutdown();
// 获取连接描述符
const int GetFd()
// 获取输入输出缓冲区
const IOBuffer& GetInputBuffer()
const IOBuffer& GetOutputBuffer()

IOBuffer:

// 获取可读、可写、预留区的大小
size_t GetReadableSize()
size_t GetWritableSize()
size_t GetPrependSize()
// 获取可读、可写区的首指针
const char* GetReadablePtr()
const char* GetWritablePtr()
// 寻找回车换行符/r/n
const char* FindCRLF()
const char* FindCRLF(const char* start_ptr)
// 寻找换行符/n
const char* FindEOL()
const char* FindEOL(const char* start_ptr)
// 向后移动可读区指针到实际位置
void Retrieve(size_t len)
// 移动可读区指针到指定位置
void RetrieveUntil(const char* end)
// 将可读、可写区指针均移到初始位置,即重置缓冲区
void RetrieveAll()
// 添加数据进缓冲区
void Append(const char* data, size_t len)
void Append(const std::string& str)
// 添加数据进预留区
void Prepend(const void* data, size_t len)

http

Sinetlib内嵌了一个HTTP服务器,设计上借鉴了golang的mux包实现路由分发的思想,同时还支持静态资源访问。

用户可以根据需要设置路由及匹配条件,目前支持URL、请求参数、请求头及请求方法的匹配,并且可以从中提取出参数。 一个请求必须满足所有条件才能匹配这个路由,得到它的Handler。

首先要设置相应的路由处理函数,该函数由用户实现,如果要实现静态资源访问,则需使用HttpServer的文件处理函数,该函数会将访问映射到用户指定的路径。

Route::SetHandler(YourHandler);
Route::SetHandler(HttpServer::GetFileHandler("/home/mys/"));

可以对url进行匹配,并且可以设置正则表达式匹配规则,比如下面第一句匹配对于/path/xxx的访问,并且可以通过key来获取到xxx,此处限定了key必须为字母。 也可以不设置正则匹配条件,如第二句的key可以匹配任何字符。

Route::SetPath("/path/{key:[a-zA-Z]+}");
Route::SetPath("/path/{key}");

匹配方法头、头部字段、参数:

Route::SetMethod("GET");
Route::SetHeader("Header-Name", "Header-Value");
Route::SetQuery("Filed", "Value");

匹配前缀,可通过“file_path"获取”/file/"后面的路径:

Route::SetPrefix("/file/");

完整的程序使用如下,该程序有两个路由,其中第二个为静态资源服务。

#include "httpserver.h"

void MyHandler(const HttpRequest& request, std::unordered_map<std::string, std::string>& match_map, HttpResponse* response)
{
    response->SetStatusCode(HttpResponse::OK);
    response->SetStatusMessage("OK");
    response->SetContentType("text/html");

    std::string body = "temp test page";
    response->AddHeader("Content-Length", std::to_string(body.size()));
    response->AppendHeaderToBuffer();
    response->AppendBodyToBuffer(body);
}

int main()
{
    Looper loop;
    HttpServer s(&loop, 8888, 4);

    s.NewRoute()
    ->SetPath("/path/{name:[a-zA-Z]+}")
    ->SetQuery("query", "t")
    ->SetHeader("Connection", "keep-alive")
    ->SetHandler(MyHandler);
    
    s.NewRoute()
    ->SetPrefix("/file/")
    ->SetHandler(s.GetFileHandler("/home/mys/"));

    s.Start();
    loop.Start();
}

该程序可匹配下面两个HTTP请求样例,第二个请求对应访问位于/home/mys/test.jpg的文件,可通过浏览器访问127.0.0.1:8888/path/myname?query=t和127.0.0.1:8888/file/test.jpg实现下列请求

GET /path/myname?query=t HTTP/1.1
Connection: keep-alive

GET /file/test.jpg HTTP/1.1

用户使用时可通过HttpServer::NewRoute()创建一个新路由,并设置相应的匹配条件及处理函数。在有请求到来时会按照用户创建路由的顺序进行匹配,当有一个路由下的条件全部匹配,即可得到该路由的处理函数并执行。

对于静态资源访问,需要使用Route::SetPrefix("/prefix/")设置前缀,并配合使用HttpServer::GetFileHandler(“/map/path/")设置映射路径,那么对于/prefix/my/src的访问将会被映射到/map/path/my/src

成功匹配请求后即可执行对应的处理函数,这里暴露给了用户HttpRequest、map<string, string>、HttpResponse三个类,大概的使用就是从HttpRequest中取得请求的各项内容,同时可以从map中根据之前设置的key取得相应的value,比如上述程序的第一个路由就可取出‘name’的值。然后用户再把响应的内容写入HttpResponse即可。

需要注意的是HttpResponse的使用,HttpResponse内有一个发送缓冲区,用户需要先设置该类的头部信息,然后执行AppendHeaderToBuffer()把这些信息写入缓冲区,然后再直接往缓冲区中写入消息的主体。

HttpRequest接口如下:

// 获取方法头
const char* GetMethodStr()
// 获取HTTP版本
Version GetVersion()
// 获取URL
const std::string& GetPath()
// 获取参数
const std::string GetQuery(const std::string& field)
// 获取头部字段
std::string GetHeader(const std::string& field)
// 获取接收时间
Timestamp GetReceiveTime()

HttpResponse接口如下:

// 设置短连接
void SetCloseConnection(bool on)
// 设置响应状态码
void SetStatusCode(HttpStatusCode code)
// 设置状态信息
void SetStatusMessage(const std::string& message)
// 添加头部字段
void AddHeader(const std::string& field, const std::string& value)
// 设置Content-Type
void SetContentType(const std::string& content_type)
// 将响应头写入到缓冲区中
void AppendHeaderToBuffer();
// 将消息主体写入到缓冲区中
void AppendBodyToBuffer(std::string& body);

Fix List

  • 2018-11-15 修复对于短连接未写完数据就关闭连接的错误

Connection::Shutdown()没有判断当前数据是否发送完就直接关闭连接,导致当数据一次不能写完而连接又被shutdown时,一直在写一个已关闭的连接,产生死循环。

  • 2018-11-15 修复文件句柄泄漏

当连接总数超过1000左右时程序异常终止,检查core文件定位到fopen()打开文件失败,查看errno发现打开的文件数超过系统限制1024。重新运行进程,cd 到 /proc/进程号/fd,查看目录内容即为打开的文件描述符,发现当新连接建立时新增socket描述符,当连接断开时却不减少,最终定位到connection.cpp中析构函数没有close掉socket。

  • 2018-11-15 修复HTTP服务器中打开dir没有关闭导致泄漏的问题

  • 2018-11-29 修复多线程下unordered_map不安全的错误

旧版本对于每个connection的解析器parser都是由主线程管理,存放在map里,当工作线程同时去map里去parser时就会产生错误,新版模仿muduo使用类似c++17的std::any,将parser直接嵌入到connection里,以避免出现不可重入现象

  • 2018-11-29 修复cpu占用100%错误

当连接发生EPOLLERR时未能调用关闭连接回调,由于使用LT模式导致一直产生EPOLLERR事件,陷入死循环占用cpu;在eventbase的事件分发里对EPOLLERR事件调用关闭回调即可解决

  • 2019-02-19 使用无锁队列替换原有任务队列

使用一个高性能并发队列moodycamel::ConcurrentQueue替代原有vector with mutex,经测试,在单生产者单消费者下性能提升10%-50%,多生产者情况下性能提升可达到100%-500%,并随着线程数的增多提升。

Contact

More

to be continued

MIT License Copyright (c) 2018 silence1772 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

暂无描述 展开 收起
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/YuXiaoXiang_930/Sinetlib.git
git@gitee.com:YuXiaoXiang_930/Sinetlib.git
YuXiaoXiang_930
Sinetlib
Sinetlib
master

搜索帮助