acl/lib_fiber/README.md
2020-03-26 13:20:53 +08:00

15 KiB
Raw Blame History

高性能网络协程库,支持 Linux/BSD/Mac/Windows

English

1、概述

本协程库为 acl 工程 的协程模块,该模块提供了 C 库和 C++ 库,其中 C 库实现了有关协程的核心功能C++ 库是对 C 库的封装从而更方便使用者快速开发协程类应用。目前支持的操作系统有LinuxFreeBSDMacOS 和 Windows支持的事件类型有selectpollepollkqueueiocp 及 Windows GUI 窗口消息。通过 libfiber 网络协程库,用户可以非常容易地写出高性能、高可靠的网络通信服务。因为使用了同步顺序编程的思维方式,相对于异步模式(无论是 reactor 模型还是 proactor 模型),编写网络应用更加简单。
libfiber 不仅支持常见的 IO 事件引擎,而且支持 Win32 GUI 界面消息引擎,这样当你使用 MFCwtl 或其它 GUI 界面库编写界面网络应用时,也会变得异常简单,这的确是件令人兴奋的事。

2、支持的事件引擎

以下为 libfiber 所支持的事件引擎:

Event Linux BSD Mac Windows
select yes yes yes yes
poll yes yes yes yes
epoll yes no no no
kqueue no yes yes no
iocp no no no yes
Win GUI message no no no yes

3、示例

3.1、基于协程的网络服务器

// 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、基于协程的客户端程序

// 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显得更为简单些

#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的特性则示上面例更为简单如下

#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 界面网络程序

示例目录 下为基于协程的 Windows 界面网络程序,程序运行截屏如图

该 Windows 界面程序包含网络服务器网络客户端两个功能。在运行时,服务模块和客户端模块运行在 Windows 界面线程中,因为协程库使用了 Windows 界面消息泵,所以协程模块可以与界面上元素成为一体而不必跨越线程,也不必使用令人烦恼的异步套接字 API。

3.6、更多例子

acl工程中,有更多的示例来描述网络协程编程,当然,这些例子还大量使用了 acl 库中的其它库的 API。

4、编译协程库

4.1、在 Unix 平台编译

$cd lib_fiber
$make
$cd samples
$make

则会在 lib_fiber/lib/ 目录下生成 libfiber.alibfiber_cpp.a,分别对应着 C 和 C++ 版本的协程库,其中 libfiber_cpp.a 是用 C++ 对 libfiber.a 的二次封装。

下面给出了一个例子的 Makefile 内容:

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_vc2013.sln/fiber_vc2015.sln 编译 libfiber 库。

5、性能测试

下面仅做了简单的 IOPS (网络 IO 性能)的测试,同时和其它协程库做了简单的对比:
Benchmark
其它的网络协程库有:libmillgolang 和 libco。其中,各个库的压测示例:

  1. 基于 libmill 和 libco 的压测用例在 目录 下;
  2. 基于 Golang 的压测用例在 目录中;
  3. 基于 libfiber 的压测用例:示例;
  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

ACL_FIBER_MUTEX

  • acl_fiber_mutex_create
  • acl_fiber_mutex_free
  • acl_fiber_mutex_lock
  • acl_fiber_mutex_trylock
  • acl_fiber_mutex_unlock

ACL_FIBER_RWLOCK

  • 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

ACL_FIBER_EVENT

  • acl_fiber_event_create
  • acl_fiber_event_free
  • acl_fiber_event_wait
  • acl_fiber_event_trywait
  • acl_fiber_event_notify

ACL_FIBER_SEM

  • 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 列表:

  • close
  • sleep
  • read
  • readv
  • recv
  • recvfrom
  • recvmsg
  • write
  • writev
  • send
  • sendto
  • sendmsg
  • sendfile64
  • socket
  • listen
  • accept
  • connect
  • select
  • poll
  • epoll: epoll_create, epoll_ctl, epoll_wait
  • gethostbyname(_r)
  • getaddrinfo/freeaddrinfo

7、更多参考