acl/lib_fiber/README.md

603 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 高性能网络协程库,支持 Linux/BSD/Mac/Windows
[English](README_en.md)
* [1、概述](#1概述)
* [2、支持的事件引擎](#2支持的事件引擎)
* [3、示例](#3示例)
* [3.1、基于协程的网络服务器](#31基于协程的网络服务器)
* [3.2、基于协程的客户端程序](#32基于协程的客户端程序)
* [3.3、使用 acl 网络库例子](#33使用-acl-网络库例子)
* [3.4、使用C++11示例](#34使用C11示例)
* [3.5、基于协程的 Windows 界面网络程序](#35基于协程的-Windows-界面网络程序)
* [3.6、更多例子](#36更多例子)
* [4、编译协程库](#4编译协程库)
* [4.1、在 Unix 平台编译](#41在-Unix-平台编译)
* [4.2、在 Windows 平台编译](#42在-Windows-平台编译)
* [4.3、编译使用 io_uring](#43编译使用-iouring)
### 4.3、编译使用 io_uring
* [5、性能测试](#5性能测试)
* [6、API 列表](#6API-列表)
* [6.1、Base API](#61Base-API)
* [6.2、IO API](#62IO-API)
* [6.3、Net API](#63Net-API)
* [6.4、Channel API](#64Channel-API)
* [6.5、Sync API](#65Sync-API)
* [6.6、关于 API Hook](#66关于-API-Hook)
* [7、更多参考](#7更多参考)
## 1、概述
本协程库为 [acl 工程](#https://github.com/acl-dev/acl) 的协程模块,该模块提供了 C 库和 C++ 库,其中 C 库实现了有关协程的核心功能C++ 库是对 C 库的封装从而更方便使用者快速开发协程类应用。目前支持的操作系统有LinuxFreeBSDMacOS 和 Windows支持的事件类型有selectpollepollkqueueiocp 及 Windows GUI 窗口消息。通过 libfiber 网络协程库,用户可以非常容易地写出高性能、高可靠的网络通信服务。因为使用了同步顺序编程的思维方式,相对于异步模式(无论是 reactor 模型还是 proactor 模型),编写网络应用更加简单。
libfiber 不仅支持常见的 IO 事件引擎,而且支持 Win32 GUI 界面消息引擎,这样当你使用 MFCwtl 或其它 GUI 界面库编写界面网络应用时,也会变得异常简单,这的确是件令人兴奋的事。
## 2、支持的事件引擎
以下为 libfiber 所支持的事件引擎:
- **Linux:** select/poll/epoll(Linux2.6+)/io_uring(Linux5.1+)
- **BSD:** select/poll/kqueue
- **Mac:** select/poll/kqueue
- **Windows:** select/poll/IOCP/Windows GUI Message
## 3、示例
### 3.1、基于协程的网络服务器
```C
// fiber_server.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "fiber/lib_fiber.h"
#include "patch.h" // in lib_fiber/samples/patch.h
static size_t __stack_size = 128000;
static const char *__listen_ip = "127.0.0.1";
static int __listen_port = 9001;
static void fiber_client(ACL_FIBER *fb, void *ctx)
{
SOCKET *pfd = (SOCKET *) ctx;
char buf[8192];
while (1) {
#if defined(_WIN32) || defined(_WIN64)
int ret = acl_fiber_recv(*pfd, buf, sizeof(buf), 0);
#else
int ret = recv(*pfd, buf, sizeof(buf), 0);
#endif
if (ret == 0) {
break;
} else if (ret < 0) {
if (acl_fiber_last_error() == FIBER_EINTR) {
continue;
}
break;
}
#if defined(_WIN32) || defined(_WIN64)
if (acl_fiber_send(*pfd, buf, ret, 0) < 0) {
#else
if (send(*pfd, buf, ret, 0) < 0) {
#endif
break;
}
}
socket_close(*pfd);
free(pfd);
}
static void fiber_accept(ACL_FIBER *fb, void *ctx)
{
const char *addr = (const char *) ctx;
SOCKET lfd = socket_listen(__listen_ip, __listen_port);
assert(lfd >= 0);
for (;;) {
SOCKET *pfd, cfd = socket_accept(lfd);
if (cfd == INVALID_SOCKET) {
printf("accept error %s\r\n", acl_fiber_last_serror());
break;
}
pfd = (SOCKET *) malloc(sizeof(SOCKET));
*pfd = cfd;
// create and start one fiber to handle the client socket IO
acl_fiber_create(fiber_client, pfd, __stack_size);
}
socket_close(lfd);
exit (0);
}
// FIBER_EVENT_KERNEL represents the event type on
// Linux(epoll), BSD(kqueue), Mac(kqueue), Windows(iocp)
// FIBER_EVENT_POLL: poll on Linux/BSD/Mac/Windows
// FIBER_EVENT_SELECT: select on Linux/BSD/Mac/Windows
// FIBER_EVENT_WMSG: Win GUI message on Windows
// acl_fiber_create/acl_fiber_schedule_with are in `lib_fiber.h`.
// socket_listen/socket_accept/socket_close are in patch.c of the samples path.
int main(void)
{
int event_mode = FIBER_EVENT_KERNEL;
#if defined(_WIN32) || defined(_WIN64)
socket_init();
#endif
// create one fiber to accept connections
acl_fiber_create(fiber_accept, NULL, __stack_size);
// start the fiber schedule process
acl_fiber_schedule_with(event_mode);
#if defined(_WIN32) || defined(_WIN64)
socket_end();
#endif
return 0;
}
```
### 3.2、基于协程的客户端程序
```C
// fiber_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "fiber/lib_fiber.h"
#include "patch.h" // in lib_fiber/samples/patch.h
static const char *__server_ip = "127.0.0.1";
static int __server_port = 9001;
// socket_init/socket_end/socket_connect/socket_close are in patch.c of the samples path
static void fiber_client(ACL_FIBER *fb, void *ctx)
{
SOCKET cfd = socket_connect(__server_ip, __server_port);
const char *s = "hello world\r\n";
char buf[8192];
int i, ret;
if (cfd == INVALID_SOCKET) {
return;
}
for (i = 0; i < 1024; i++) {
#if defined(_WIN32) || defined(_WIN64)
if (acl_fiber_send(cfd, s, strlen(s), 0) <= 0) {
#else
if (send(cfd, s, strlen(s), 0) <= 0) {
#endif
printf("send error %s\r\n", acl_fiber_last_serror());
break;
}
#if defined(_WIN32) || defined(_WIN64)
ret = acl_fiber_recv(cfd, buf, sizeof(buf), 0);
#else
ret = recv(cfd, buf, sizeof(buf), 0);
#endif
if (ret <= 0) {
break;
}
}
#if defined(_WIN32) || defined(_WIN64)
acl_fiber_close(cfd);
#else
close(cfd);
#endif
}
int main(void)
{
int event_mode = FIBER_EVENT_KERNEL;
size_t stack_size = 128000;
int i;
#if defined(_WIN32) || defined(_WIN64)
socket_init();
#endif
for (i = 0; i < 100; i++) {
acl_fiber_create(fiber_client, NULL, stack_size);
}
acl_fiber_schedule_with(event_mode);
#if defined(_WIN32) || defined(_WIN64)
socket_end();
#endif
return 0;
}
```
### 3.3、使用 acl 网络库例子
上面的例子中因为使用了系统原生的网络 API所以感觉代码有些臃肿下面的例子使用 acl 库中提供的网络 API显得更为简单些
```c++
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
class fiber_client : public acl::fiber
{
public:
fiber_client(acl::socket_stream* conn) : conn_(conn) {}
protected:
// @override
void run(void)
{
printf("fiber-%d-%d running\r\n", get_id(), acl::fiber::self());
char buf[8192];
while (true) {
int ret = conn_->read(buf, sizeof(buf), false);
if (ret == -1) {
break;
}
if (conn_->write(buf, ret) == -1) {
break;
}
}
delete conn_;
delete this;
}
private:
acl::socket_stream* conn_;
~fiber_client(void) {}
};
class fiber_server : public acl::fiber
{
public:
fiber_server(acl::server_socket& ss) : ss_(ss) {}
~fiber_server(void) {}
protected:
// @override
void run(void)
{
while (true) {
acl::socket_stream* conn = ss_.accept();
if (conn == NULL) {
printf("accept error %s\r\n", acl::last_serror());
break;
}
printf("accept ok, fd: %d\r\n", conn->sock_handle());
// create one fiber for one connection
fiber_client* fc = new fiber_client(conn);
// start the fiber
fc->start();
}
}
private:
acl::server_socket& ss_;
};
static void usage(const char* procname)
{
printf("usage: %s -h [help] -s listen_addr\r\n", procname);
}
int main(int argc, char *argv[])
{
int ch;
acl::acl_cpp_init();
acl::string addr("127.0.0.1:9006");
acl::log::stdout_open(true);
while ((ch = getopt(argc, argv, "hs:")) > 0) {
switch (ch) {
case 'h':
usage(argv[0]);
return 0;
case 's':
addr = optarg;
break;
default:
break;
}
}
acl::server_socket ss;
if (ss.open(addr) == false) {
printf("listen %s error %s\r\n", addr.c_str(), acl::last_serror());
return 1;
}
printf("listen %s ok\r\n", addr.c_str());
fiber_server fs(ss);
fs.start(); // start listen fiber
acl::fiber::schedule(); // start fiber schedule
return 0;
}
```
### 3.4、使用C++11示例
如果使用C++11的特性则示上面例更为简单如下
```c++
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
static void fiber_client(acl::socket_stream* conn)
{
printf("fiber-%d running\r\n", acl::fiber::self());
char buf[8192];
while (true) {
int ret = conn->read(buf, sizeof(buf), false);
if (ret == -1) {
break;
}
if (conn->write(buf, ret) == -1) {
break;
}
}
delete conn;
}
static void fiber_server(acl::server_socket& ss)
{
while (true) {
acl::socket_stream* conn = ss.accept();
if (conn == NULL) {
printf("accept error %s\r\n", acl::last_serror());
break;
}
printf("accept ok, fd: %d\r\n", conn->sock_handle());
go[=] {
fiber_client(conn);
};
}
}
static void usage(const char* procname)
{
printf("usage: %s -h [help] -s listen_addr\r\n", procname);
}
int main(int argc, char *argv[])
{
int ch;
acl::acl_cpp_init();
acl::string addr("127.0.0.1:9006");
acl::log::stdout_open(true);
while ((ch = getopt(argc, argv, "hs:")) > 0) {
switch (ch) {
case 'h':
usage(argv[0]);
return 0;
case 's':
addr = optarg;
break;
default:
break;
}
}
acl::server_socket ss;
if (ss.open(addr) == false) {
printf("listen %s error %s\r\n", addr.c_str(), acl::last_serror());
return 1;
}
printf("listen %s ok\r\n", addr.c_str());
go[&] {
fiber_server(ss);
};
acl::fiber::schedule(); // start fiber schedule
return 0;
}
```
### 3.5、基于协程的 Windows 界面网络程序
在[示例目录](samples/WinEchod) 下为基于协程的 Windows 界面网络程序,程序运行截屏如![图](res/winecho.png)
该 Windows 界面程序包含`网络服务器`和`网络客户端`两个功能。在运行时,服务模块和客户端模块运行在 Windows 界面线程中,因为协程库使用了 Windows 界面消息泵,所以协程模块可以与界面上元素成为`一体`而不必跨越线程,也不必使用令人烦恼的异步套接字 API。
### 3.6、更多例子
在 [acl工程中](https://github.com/acl-dev/acl/tree/master/lib_fiber/samples),有更多的示例来描述网络协程编程,当然,这些例子还大量使用了 [acl 库](https://github.com/acl-dev/acl/)中的其它库的 API。
## 4、编译协程库
### 4.1、在 Unix 平台编译
```
$cd lib_fiber
$make
$cd samples
$make
```
则会在 `lib_fiber/lib/` 目录下生成 `libfiber.a``libfiber_cpp.a`,分别对应着 C 和 C++ 版本的协程库,其中 `libfiber_cpp.a` 是用 C++ 对 `libfiber.a` 的二次封装。
<b>下面给出了一个例子的 Makefile 内容:</b>
```
fiber_server: fiber_server.c
gcc -o fiber_server fiber_server.c patch.c -I{path_of_fiber_header} -L{path_of_fiber_lib) -lfiber -ldl -lpthread
fiber_client: fiber_client.c
gcc -o fiber_client fiber_client.c patch.c -I{path_of_fiber_header} -L{path_of_fiber_lib) -lfiber -ldl -lpthread
```
如果编译并使用 acl 基础库和 libfiber_cpp.a则需要注意库的依赖关系
```
fiber_server: fiber_server.cpp
g++ -o fiber_server fiber_server.cpp -lfiber_cpp -lacl_cpp -lprotocol -lacl -lfiber
```
在该 Makefile 中,-lfiber_cpp 放到最前面,是因为其依赖于其它几个库,而 -lfiber 放在最后,是因为该库需要 hook 系统 IO 操作。
### 4.2、在 Windows 平台编译
目前可以使用 vc2012/vc2013/vc2015 分别打开 [fiber_vc2012.sln](fiber_vc2012.sln) /[fiber_vc2013.sln](fiber_vc2013.sln)/[fiber_vc2015.sln](fiber_vc2015.sln) 编译 libfiber 库。
### 4.3、编译使用 io_uring
在 Linux5.1 以上版本开始支持新的事件引擎 io_uring该引擎为IO异步完成模型同时支持网络套接口及文件IO操作效率基本与 epoll 相当,但因为 io_uring 本身提供的系统 API 较为复杂,所以 Jens Axboe磁盘压测工具fio作者提供了二次封装从而使调用过程变得简单可以从https://github.com/axboe/liburing 下载),目前在 Acl 协程中也是通过调用 liburing 中的 API 来使用系统中的 io_uring 功能;下面给出了在 Acl 协程中编译及使用 io_uring 的过程如下:
- 可从 Ubuntu 官网下下载最新版本的 Ubuntu已经支持了高版本的Linux内核从而支持了io_uring
- 从 https://github.com/axboe/liburing 下载 liburing 库,并编译安装;
- 从 https://github.com/acl-dev/acl 下载 acl 源码;
- 在 shell 环境中设置环境变量打开 acl 中支持 io_uring 的编译条件:`export HAS_IO_URING=yes`;
- 进入 acl/lib_fiber/c/ 目录运行 `make` 编译 acl 协程库因为上面已经设置了支持io_uring的环境变量在Makefile文件中自动增加编译选项 `-DHAS_IO_URING`
- 进入 acl/lib_fiber/samples/ 目录,然后再分别进入 server2, client2, file 三个例子编译(需在设置了 HAS_IO_URING=yes 的 shell 环境中编译);
- 进入 server2/ 目录运行时加上io_uring启动参数`./server -e io_uring`
- 进入 client2/ 目录运行时加上io_uring启动参数`./client -e io_uring`
## 5、性能测试
下面仅做了简单的 IOPS (网络 IO 性能)的测试,同时和其它协程库做了简单的对比:
![Benchmark](res/benchmark.png)
其它的网络协程库有:[libmill](https://github.com/sustrik/libmill)golang 和 [libco](https://github.com/Tencent/libco)。其中,各个库的压测示例:
1. 基于 libmill 和 libco 的压测用例在 [目录](benchmark) 下;
2. 基于 Golang 的压测用例在 [目录](https://github.com/acl-dev/master-go/tree/master/examples/echo)中;
3. 基于 libfiber 的压测用例:[示例](samples/server);
4. 客户端压测程序https://github.com/acl-dev/acl/tree/master/lib_fiber/samples/client2
## 6、API 列表
### 6.1、Base API
- acl_fiber_create
- acl_fiber_self
- acl_fiber_status
- acl_fiber_kill
- acl_fiber_killed
- acl_fiber_signal
- acl_fiber_yield
- acl_fiber_ready
- acl_fiber_switch
- acl_fiber_schedule_init
- acl_fiber_schedule
- acl_fiber_schedule_with
- acl_fiber_scheduled
- acl_fiber_schedule_stop
- acl_fiber_set_specific
- acl_fiber_get_specific
- acl_fiber_delay
- acl_fiber_last_error
- acl_fiber_last_serror
### 6.2、IO API
- acl_fiber_recv
- acl_fiber_recvfrom
- acl_fiber_read
- acl_fiber_readv
- acl_fiber_recvmsg
- acl_fiber_write
- acl_fiber_writev
- acl_fiber_send
- acl_fiber_sendto
- acl_fiber_sendmsg
- acl_fiber_select
- acl_fiber_poll
- acl_fiber_close
### 6.3、Net API
- acl_fiber_socket
- acl_fiber_listen
- acl_fiber_accept
- acl_fiber_connect
- acl_fiber_gethostbyname_r
- acl_fiber_getaddrinfo
- acl_fiber_freeaddrinfo
### 6.4、Channel API
- acl_channel_create
- acl_channel_free
- acl_channel_send
- acl_channel_send_nb
- acl_channel_recv
- acl_channel_recv_nb
- acl_channel_sendp
- acl_channel_recvp
- acl_channel_sendp_nb
- acl_channel_recvp_nb
- acl_channel_sendul
- acl_channel_recvul
- acl_channel_sendul_nb
- acl_channel_recvul_nb
### 6.5、Sync API
<b>ACL_FIBER_MUTEX</b>
- acl_fiber_mutex_create
- acl_fiber_mutex_free
- acl_fiber_mutex_lock
- acl_fiber_mutex_trylock
- acl_fiber_mutex_unlock
<b>ACL_FIBER_RWLOCK</b>
- acl_fiber_rwlock_create
- acl_fiber_rwlock_free
- acl_fiber_rwlock_rlock
- acl_fiber_rwlock_tryrlock
- acl_fiber_rwlock_wlock
- acl_fiber_rwlock_trywlock
- acl_fiber_rwlock_runlock
- acl_fiber_rwlock_wunlock
<b>ACL_FIBER_EVENT</b>
- acl_fiber_event_create
- acl_fiber_event_free
- acl_fiber_event_wait
- acl_fiber_event_trywait
- acl_fiber_event_notify
<b>ACL_FIBER_SEM</b>
- acl_fiber_sem_create
- acl_fiber_sem_free
- acl_fiber_sem_wait
- acl_fiber_sem_post
- acl_fiber_sem_num
## 6.6、关于 API Hook
在 Linux/MacOS/FreeBSD 平台上,很多与 IO 和网络相关的的系统 API 被 hook 了,因此,在编译连接时将 libfiber 加上,这样你的应用程序中仅需使用系统标准 IO API便可以使你的网络程序自动协程化。下面是一些被 hook 的系统 API 列表:
- socket/listen/accept/connect
- select/poll/epoll: epoll_create, epoll_ctl, epoll_wait
- read/readv/recv/recvfrom/recvmsg/write/writev/send/sendto/sendmsg
- pread/pwrite/splice/sendfile64
- gethostbyname(_r)/getaddrinfo/freeaddrinfo
- open/openat/close/unlink/rename/renameat/renameat2/stat/statx/mkdir/mkdirat
- sleep
## 7、更多参考
- Acl网络协程框架编程指南: https://blog.csdn.net/zsxxsz/article/details/89007127
- 网络协程编程https://blog.csdn.net/zsxxsz/article/details/88388457
- 编译使用 acl 协程库https://blog.csdn.net/zsxxsz/article/details/88349466
- 使用协程方式编写高并发的 WEB 服务https://blog.csdn.net/zsxxsz/article/details/88349460
- 使用 acl 协程编写高并发网络服务https://blog.csdn.net/zsxxsz/article/details/88349459
- acl开发--协程篇https://blog.csdn.net/zsxxsz/category_8736935.html