


This article is the implementation of C/C coroutine. We need to achieve these two goals:
Have a sequential idea of synchronous server programming to facilitate functional design and code debugging - I used the coroutine part in libco
has the performance of asynchronous I/O - I used event I/O in libevent apache php mysql
Structurally, it is The functions of libco and libevent are combined, so I named my project libcoevent, which means "synchronous coroutine server programming framework based on libevent". The word co in the name does not mean libco, but coroutine.
As for the programming language, I chose C, mainly because libco only supports Linux based on x86 or x64 architecture, and such architectures are basically PCs, or have sufficient resources and high performance. It's a good embedded system, and there is no problem in learning C. This article explains how the code is implemented.
If you want to use this project, please add the three options -lco -levent -lcoevent
to the link options.
Class relationship and basic functions
Class relationship
Class inheritance relationship
The basic inheritance relationship diagram of the class is as follows:
In actual calls, only the classes on the leaf nodes of the inheritance relationship tree will be actually used, and other classes are regarded as virtual classes.
Class affiliation
Instances of various types have affiliations during program running. Except for the top-level Base class, other leaf classes need to be attached to other classes. It can be executed in the operating environment. The dependency diagram is as follows:
Base class provides the most basic operating environment and manages Server Object;
Procedure Object ManagementClient Object. In the figure, both the Server and Session objects manage the Client object. The
Server object is created by the application and initialized to run in the Base object. You can configure the Server object to be automatically destroyed when the server terminates or when its dependent Base object is destroyed.
Session The object is automatically created by the Server object in session mode, and is run by calling the program entry specified by the application; When the session ends (function call
return
) or when its subordinate Server object service ends, the Server object is automatically destroyed.Client object is created by the application calling the interface of the Procedure object and is used to interact with third-party services. The application can call the interface in advance to request the destruction of the Client object, or it can automatically destroy it when the Procedure service ends.
Base and Event classes
##Base class is used to run various services of libcoevent. Each instance of the Base class should correspond to a thread, and all services run in the Base instance in a coroutine manner. As can be seen from the above figure, the Base class contains a event_base object of the libevent library and a series of Event objects of this coroutine library. The
Event class actually borrows the struct event name from libevent, because every
Event class An instance of , corresponding to an event object of libevent. The focus we need to focus on is the
Procedure and Client classes.
Procedure class has two key features:
- Each object has a libco coroutine, That is, it has its own independent context information and can be used to write an independent server process (procedure);
- Proceure subclasses can create
Client objects and third parties Server communication and interaction. The
Procedure class has two subclasses, namely Server and Session.
Server ClassThe Server class is created by the application and initialized to run in aBase object. The Server class has three subclasses:
SubRoutine: It does not actually serve as any server program, but provides the most basic
sleep()
function and supports the creation of Client objects of the Procedure class. function, so the application can be used as a temporarily created or resident internal program.UDPServer: After the application creates and initializes the UDPServer object, the program will automatically bind to a datagram socket interface. Applications can implement network services by sending and receiving data packets in the network interface. UDPServer provides both normal mode and session mode.
TCPServer: After the application creates and initializes the TCPPServer object, the program will automatically bind and listen to the stream socket. TCPServer only supports session mode.
The so-called "Normal mode" is the behavior in which the application registers the entry function of the Server object and the application operates the Server object.
The so-called "Session mode" refers to the UDPServer or TCPServer object. After receiving incoming data, it automatically distinguishes the client and creates a separate Session Object is processed. Each Session object only serves one client.
Session class
Session objects cannot be actively created by the application, but are automatically created on demand by the Server class in session mode. The characteristic of the Session object is that it can only communicate with a single client (compared to the UDPServer object), so there is no send()
function, only reply()
.
The Session
class and its subclasses declared in the header file coevent.h are pure virtual classes in order to prevent the application from explicitly constructing Session object and hide implementation details.
Client Class
Client Objects are created by Procedure objects and recycled by Procedure objects. The role of the Client object is to actively initiate communication with the remote server. Since this action belongs to the client from the perspective of the client-service structure, it is named Client.
DNSClient
One of the more special subclasses of Client is the DNSClient class. This class exists to solve the getaddrinfo( )
Blocking problem. For the implementation principle of DNSClient, please refer to the code and my previous article "DNS Message Structure and Personal DNS Parsing Code Implementation".
As for the DNSClient class, the specific implementation principle is to encapsulate a UDPClient object, use this object to complete the sending and receiving of DNS messages, and implement the parsing of the messages in the class.
UDPServer——Coroutine implementation based on libevent
UDPServer The principle of the common mode is a very typical synchronous coroutine server framework based on libevent. In its code implementation, the core functions are the following functions:
_libco_routine()
, the entry function of the coroutine. Using this function, it is transformed into the unity of liboevent Service entry function_libevent_callback()
, libevent time callback function, in this function, the recovery of the coroutine context is realized.UDPServer::recv_in_timeval()
, data receiving function, in this function, the key data waiting function is implemented, and the saving of the coroutine context is also realized
The total amount of code for the above three functions, including blank lines, does not exceed 200 lines. I believe it is still easy to understand. The following explains the implementation principle in detail:
libco coroutine interface
As mentioned before, I use libco as the coroutine library. Coroutines are transparent to the application, but to the library implementation, this is core.
The following explains several interfaces provided by libco's coroutine function (libco's number of documents is simply "touching", which is often complained about on the Internet...):
Creation and destruction Coroutine
Libco Use the structure struct stCoRoutine_t *
to save the coroutine, and you can create the coroutine object by calling co_create()
; use co_release()
Destroy coroutine resources.
Enter the coroutine
After creating the coroutine, call co_resume()
to start executing the coroutine from the beginning of the coroutine function.
Pause the coroutine
When the coroutine needs to hand over the CPU usage rights, you can call co_yield()
to release the coroutine and switch the context. After the call, the context will be restored to the last coroutine that called co_resume()
. The location where co_yield()
is called can be regarded as a "breakpoint".
Resume the coroutine
The functions used to restore the coroutine and create the coroutine are co_resume()
. Call this function to switch the current stack to the specified coroutine. Context, the coroutine will resume execution from the "breakpoint" mentioned above.
Coroutine Scheduling Implementation
As you can see from the previous section, the libco coroutine function we use includes the coroutine switching function, but when to switch and what happens to the CPU after the switch Distribution, this is what we need to implement and encapsulate.
The time to create and destroy coroutines is naturally when the UDPServer class is initialized and destructed. The following focuses on analyzing the operations of entering, suspending and resuming the coroutine:
Enter the coroutine
The code for entering/resuming the coroutine is in _libevent_callback()
, there are Such a line:
// handle control to user application co_resume(arg->coroutine);
If the current coroutine has not been executed, then after executing this code, the program will switch to the coroutine function specified when creating the libco coroutine and start execution. For UDPServer, that is the _libco_routine()
function. This function is very simple, with only three lines:
static void *_libco_routine(void *libco_arg) { struct _EventArg *arg = (struct _EventArg *)libco_arg; (arg->worker_func)(arg->fd, arg->event, arg->user_arg); return NULL; }
By passing in parameters, the libco callback function is converted into a server function specified by the application for execution.
But how to implement the first libevent callback? This is still very simple, just set the timeout to 0 when calling libevent's event_add()
, which will cause the libevent event to time out immediately. Through this mechanism, we also achieve the purpose of executing each Procedure service function immediately after Base is run.
Pause and resume coroutines
When to call co_yield
is the focus of this coroutine implementation. The location of calling co_yield
is a possibility The place where context switching occurs is also the key technical point in converting an asynchronous programming framework into a synchronous framework. You can refer to the recv_in_timeval() function of UDPServer
here. The basic logic of the function is as follows:
The most important branch is the judgment of the libevent event flag; and the most important logic is event_add()
and co_yield()
function calls. The function fragment is as follows:
struct timeval timeout_copy; timeout_copy.tv_sec = timeout.tv_sec; timeout_copy.tv_usec = timeout.tv_usec; ... event_add(_event, &timeout_copy); co_yield(arg->coroutine);
Here, we understand the co_yield()
function as a breakpoint. When the program is executed here, the right to use the CPU will be handed over and the program will return To the upper-level function that calls co_resume()
. Where exactly is this "upper-level function"? In fact, it is the _libevent_callback()
function mentioned earlier.
From the perspective of _libevent_callback()
, the program will return from the co_resume()
function and continue execution. At this point we can understand this: the scheduling of coroutines is actually borrowed from libevent
. Here we have to pay attention to the few sentences above co_resume()
:
// switch into the coroutine if (arg->libevent_what_ptr) { *(arg->libevent_what_ptr) = (uint32_t)what; }
Here the libevent event flag value is passed to the coroutine, and this is the previous event. important basis for judgment. When the time comes, _libevent_callback()
will call co_resume()
below, handing the CPU usage rights back to the coroutine.
Destroy the coroutine
In addition to ci_yield()
, the coroutine function call return
will also result in co_resume()
Return, so in _libevent_callback()
, we also need to determine whether the coroutine has ended. If the coroutine ends, then the related coroutine resources should be destroyed. See if (is_coroutine_end(arg->coroutine)) {...}
The code in the conditional body.
Session Mode
In the implementation of this project, a server design pattern called "Session Mode" is provided. Session mode refers to the UDPServer or TCPServer object. After receiving incoming data, it automatically distinguishes the client and creates a separate Session object for processing. Each Session object only serves one client.
For TCPServer, it is relatively simple to implement the above function, because after monitoring a TCP socket, when there is an incoming connection, just call accept()
, you can get a new file descriptor and create a new subclass of Server for this file descriptor - this is the TCPSession class.
But UDPServer is more troublesome, because UDP cannot do this. We can only implement the so-called session by ourselves.
UDPSession achieves
design goals
We need to achieve the following effects of the UDPSession class:
Class When calling the recv function, only the data sent by the corresponding remote client will be received. The
class calls the send function (the actual implementation is
reply()
), you can use the port of UDPServer to reply
recv()
In the project, UDPSession is an abstract class, and the actual implementation is UDPItnlSession. But to be precise, the implementation of UDPItnlSession is closely dependent on UDPServer. For this part, you can refer to the do-while() loop body code in the _session_mode_worker()
function of UDPServer
. The program idea is as follows:
UDPServer Maintain a UDPSession dictionary, with a combination of remote IP port names as the key.
When the data arrives, determine whether the remote IP port combination is in the dictionary. If it is, copy the data to the corresponding session; if it does not exist, create the session
For the code to copy data, see the forward_incoming_data() function implementation of the UDPItnlSession
class.
reply()
Sending data is actually very simple, just perform sendto() directly on the fd of UDPServer
.
quit
For the Server object in session mode, the code provides a function that can be called by its session and requires the server to exit and destroy resources: quit_session_mode_server()
. The implementation principle is to trigger an EV_SIGNAL
event to the server. For ordinary I/O events, this should not occur, and we use it here as an exit signal. If the server detects this signal, the exit logic is triggered.
Application Example
The sample code of this project is divided into two parts: server and client. The server uses libcoevent, while the client only uses Python A simple program written. This article will not explain the client part of the code.
Server’s code provides application examples for three subclasses of the Server class. Using logic including blank lines, debugging statements, error judgments, etc., one process and two services were implemented in less than 300 lines. It should be said that the logic is still very clear and a lot of code is saved.
SubRoutine
shows a one-time linear network logic through the function _simple_test_routine()
. In the program, the routine first creates a DNSClient object, requests a domain name from the default domain name server, and then connect()
the 80 port of the server. After success, return directly.
This function shows the usage scenario of SubRoutine and the usage of Client object, especially the simple usage of DNSClient. The entry function of
UDPServer
UDPServer is _udp_session_routine()
, which functions to provide domain name query services for clients. Clients send a string as the domain name to be queried, and then the server returns the query results to the client after requesting it through the DNSClient object.
This function shows the (more complex and complete) usage of UDPSession objects and DNSClient.
TCPServer
The entry function is _tcp_session_routine()
, the logic is relatively simple, mainly to show the usage of TCPSession.
Postscript
In principle, libcoevent has been developed and has achieved the necessary functions, and can be used to write server programs. Of course, since this is the first version, a lot of the code still looks a bit messy. The significance of this library is that it can carefully explain the more original implementation principles of C/C coroutines from a teaching perspective, and it can also be used as a usable coroutine server library.
We welcome readers to criticize this library, and we also welcome readers to put forward new requirements-for example, I decided to add a few requirements, which can be regarded as TODO:
implementationHTTPServer, as a subclass of TCPServer, provides HTTP fcgi service;
implements the SSLClient class to handle external SSL ask.
Related articles:
C# Network Programming Series Articles (8) UdpClient implements synchronized UDP server
C language to implement php server
Related videos:
The above is the detailed content of Assembly-based implementation of C/C++ coroutines (for servers). For more information, please follow other related articles on the PHP Chinese website!

近年来,随着互联网应用的日益普及,各种高并发的场景也越来越常见。在这种情况下,传统的同步I/O方式已经无法满足现代应用对高性能、高并发的需求。因此,协程成为了一种被广泛应用的解决方案。Swoole是一款面向高并发、高性能的PHP网络通信框架,可以轻松实现异步、协程等特性。swoole_smtp_auth函数是其中一个常用的函数,它可以在使用SMTP协议进行邮

随着传统的多线程模型在高并发场景下的性能瓶颈,协程成为了PHP编程领域的热门话题。协程是一种轻量级的线程,能够在单线程中实现多任务的并发执行。在PHP的语言生态中,协程得到了广泛的应用,比如Swoole、Workerman等框架就提供了对协程的支持。那么,如何在PHP中使用协程呢?本文将介绍一些基本的使用方法以及常见的注意事项,帮助读者了解协程的运作原理,以

如果你需要访问多个服务来完成一个请求的处理,比如实现文件上传功能时,首先访问Redis缓存,验证用户是否登录,再接收HTTP消息中的body并保存在磁盘上,最后把文件路径等信息写入MySQL数据库中,你会怎么做?首先可以使用阻塞API编写同步代码,直接一步步串行即可,但很明显这时一个线程只能同时处理一个请求。而我们知道线程数是有限制的,有限的线程数导致无法实现上万级别的并发连接,过多的线程切换也抢走了CPU的时间,从而降低了每秒能够处理的请求数量。于是为了达到高并发,你可能会选择一

近年来,随着移动互联网、云计算、大数据等新技术的快速发展,越来越多的企业开始使用PHP构建高并发、高性能的Web应用程序。而传统的LAMP(Linux、Apache、MySQL、PHP)架构,难以满足当前互联网快速发展的需求,因此出现了一些新的PHP框架和工具,比如Swoole。Swoole是一个PHP的网络通信框架,具有协程、异步IO、多进程等优势,可以帮

Go语言中的协程和select语句的联系是什么?随着计算机的发展,我们对于并发编程的需求也越来越迫切。然而,传统的并发编程方法——基于线程和锁——也逐渐变得复杂并容易出错。为了解决这些问题,Go语言引入了一种新的并发编程模型——协程。协程是由语言自己调度的轻量级线程,在协程中,代码的执行是基于非抢占式的协作式调度的,换句话说,每个协程都会执行一段代码

随着Web应用程序的迅速发展,开发者们不仅要关注应用程序的功能和可靠性,还要考虑应用程序的性能。而数据库操作一直是Web应用程序的一个瓶颈之一。传统的数据库查询方式通常是通过多线程或者多进程来实现,这个方法效率低下,而且不容易管理。而Swoole的协程特性可以用来优化数据库查询,并提高应用程序的性能。Swoole是一款PHP的高性能网络框架。它有一个非常重要

Swoole中如何高效使用协程?协程是一种轻量级的线程,可以在同一个进程内并发执行大量的任务。Swoole作为一个高性能的网络通信框架,对协程提供了支持。Swoole的协程不仅仅是简单的协程调度器,还提供了很多强大的功能,如协程池、协程原子操作,以及各种网络编程相关的协程封装等等,这些功能都可以帮助我们更高效地开发网络应用。在Swoole中使用协程有很多好处

随着互联网应用的普及,越来越多的应用需要面对高并发的挑战。传统的线程池或进程池方式已经不能满足这种情况下的需求。协程编程技术成为了一种解决高并发问题的有效方式,而Swoole则是目前应用最广泛的协程框架之一。本文将介绍协程编程的基本概念和原理,以及如何使用Swoole框架进行高并发接口设计。我们将以一个简单的Web服务为例,分步骤介绍如何使用协程和Swool


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

SAP NetWeaver Server Adapter for Eclipse
Integrate Eclipse with SAP NetWeaver application server.

EditPlus Chinese cracked version
Small size, syntax highlighting, does not support code prompt function

Dreamweaver Mac version
Visual web development tools

Notepad++7.3.1
Easy-to-use and free code editor

VSCode Windows 64-bit Download
A free and powerful IDE editor launched by Microsoft
