Project coroutine - implement non-preemptive TCP server

Coroutines are non-preemptive lightweight threads in user mode, and are a component that handles multitasking in program development

Project Introduction

Project development environment

Linux

Project development languages ​​and tools

C、vim、gcc、gdb、Makefile

Project Features

  1. The coroutine is completely controlled by the program and executed in user mode, so there is no switching overhead from user mode to kernel mode
  2. The coroutine is a non-preemptive scheduling, and the user can implement the scheduling by himself. Only one coroutine can be executed at the same time, and the coroutine actively surrenders control
  3. The execution efficiency of the coroutine is very high. Because subroutine switching is not thread switching, but controlled by the program itself, compared with the same number of threads, the execution efficiency of coroutines will be higher

Applicable scene of the project

Coroutines are mainly used for I/O-intensive programs, such as TCP servers. Traditionally, multi-process/thread + IO multiplexing is used to implement TCP servers. When a client comes, a thread or process is created, which takes up a lot of H. Whenever it is used, it will be accompanied by the overhead caused by scheduling switching between user mode and kernel mode. The use of coroutines will solve the problem of traditional methods

Project Description

It is equivalent to a thread group in user mode. Each coroutine has its own coroutine stack. When there is a task, the task is put into the coroutine in the form of callback. In the callback function of the coroutine, when there is Execute the task when it is ready, switch itself out when the task is not ready, and then let other processes run, resume the coroutine when a certain condition is reached, and then judge whether the tasks in the coroutine are ready in turn. Execute when ready, switch out when not ready

Summary of project realization

Use a structure to encapsulate the properties of the coroutine, and then use a structure to manage the scheduling coroutine. Among them, the coroutine has 5 attributes, namely the callback function, the parameters of the callback function, the context information of the coroutine, the stack used by the coroutine alone, and the state of the coroutine. The scheduler that manages and schedules coroutines has 4 attributes, which are a pointer array of coroutine type. The elements in the array are all coroutines, the subscript of the array where the coroutine is running, and the pointer array. The largest subscript in , and the last one is the context of the main process.
When creating a coroutine, you need to pass in the dispatch manager of the coroutine and the callback function to be bound to the coroutine, and then assign values ​​to each attribute of the coroutine. At this time, we need to use the ucontext function family. In the makecontext function, the The context callback function of the coroutine is set to a unified function, and then the function bound to the coroutine itself is called in this function. When the function finishes running, modify the state of the coroutine to DEAD state.
When starting the coroutine, change the state of the coroutine to RUNNING, then use the swapcontext function to save the context of the main process, and then execute the callback function of the coroutine.
When switching the coroutine, change the coroutine state to SUSPEND state, then use the swapcontext function to save the context of the coroutine, and then execute the context of the main process.
When replying to the coroutine, change the state of the coroutine to RUNNING, then use the swapcontext function to save the context of the main process, and then execute the context where the coroutine was executed last time

Links

The principle and use of linux ucontext family functions

Coroutine implementation

Source code----coroutine header
file and required macro definition

#include <ucontext.h>

#define COR_SIZE (1024) //协程数量
#define STACK_SIZE (1024*64) //每个协程的栈大小

The states of the coroutine are: dead state, ready state, running state, paused state

enum State {
    
    DEAD, READY, RUNNING, SUSPEND};

insert image description here
Coroutines and schedulers

//协程
typedef struct coroutine
{
    
    
	void *(*call_back)(struct schedule *s, void* args);//回调函数
	void *args; //回调函数参数
	ucontext_t ctx; //协程上下文
	char stack[STACK_SIZE]; //协程栈
	enum State state; //协程状态
}coroutine_t;

//协程调度器
typedef struct schedule
{
    
    
	coroutine_t** coroutines;//协程数组
	int cur_id;//当前运行的协程
	int max_id;//数组的最大协程数
	ucontext_t ctx_main;//主流程上下文
}schedule_t;

implement interface declaration

//创建协程调度器
schedule_t* schedule_create();

//创建协程,返回当前协程在调度器的下标
int coroutine_create(schedule_t* s, void*(*call_back)(schedule_t* s, void *args), void *args);

//启动协程
int coroutine_running(schedule_t* s, int id);

//协程让出CPU,返回主流程上下文
void coroutine_yield(schedule_t* s);

//恢复协程上下文
void coroutine_resume(schedule_t* s, int id);

//销毁调度器
void schedule_destroy(schedule_t* s);

//判断调度器中的协程是否都全部运行结束,全结束返回1,否则返回0
int schedule_finished(schedule_t *s);

Implementing a TCP server using coroutines

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include "coroutine.h"

int tcp_init()
{
    
    
	int lst_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (lst_fd == -1) 
	{
    
    
		perror("socket error\n");
		exit(1);
	}
	int op = 1;
	setsockopt(lst_fd, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op));

	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(9999);
	addr.sin_addr.s_addr = inet_addr("192.168.73.130");
	int ret = bind(lst_fd, (struct sockaddr*)&addr, sizeof(addr));
	if (ret == -1)
	{
    
    
		perror("bind error\n");
		exit(1);
	}

	listen(lst_fd, SOMAXCONN);

	return lst_fd;
}

void set_nonblock(int fd)
{
    
    
	int flgs = fcntl(fd, F_GETFL, 0);
	flgs |= O_NONBLOCK;
	fcntl(fd, F_SETFL, flgs);
}

void accept_conn(int lst_fd, schedule_t* s, int co_ids[], void* (*call_back)(schedule_t* s, void* args))
{
    
    
	while (1)
	{
    
    
		int cli_fd = accept(lst_fd, NULL, NULL);
		//如果有新的客户端到来,就创建一个新的协程来管理这个连接
		if (cli_fd > 0)
		{
    
    
			set_nonblock(cli_fd);

			int args[] = {
    
    lst_fd, cli_fd};
			int id = coroutine_create(s, call_back, args);
			
			if (id == COR_SIZE)
			{
    
    
				printf("too many connections.\n");
				return;
			}

			co_ids[id] = id;
			coroutine_running(s, id);
		}
		//当前没有客户端连接,则切换至协程的上下文继续运行
		else
		{
    
    
			int i;
			for (i = 0; i < COR_SIZE; ++i)
			{
    
    
				int cid = co_ids[i];
				if (cid == -1)
					continue;
				coroutine_resume(s, i);
			}
		}
	}
}

void* handle(schedule_t* s, void* args)
{
    
    
	int* arr = (int*)args;
	int cli_fd = arr[1];

	char buf[1024] = {
    
     0 };
	while (1)
	{
    
    
		memset(buf, 0x00, sizeof(buf));
		int ret = recv(cli_fd, buf, 1024, 0);
		if (ret == -1)
			//如果此时没有数据,则不再等待,直接切回主流程
			coroutine_yield(s);
		else if (ret == 0)
		{
    
    
			//通信结束
			break;
		}
		else
		{
    
    
			printf("client:%s", buf);
			if (strncasecmp(buf, "exit", 4) == 0)
			{
    
    
				break;
			}
			memset(buf, 0x00, sizeof(buf));
			printf("send:");
			fgets(buf, 1024, stdin);
			send(cli_fd, buf, ret, 0);
		}
	}
}

int main()
{
    
    
	int lst_fd = tcp_init();
	set_nonblock(lst_fd);

	schedule_t* s = schedule_create();
	static int co_idx[COR_SIZE];
	int i;
	for (i = 0; i < COR_SIZE; ++i)
	{
    
    
		co_idx[i] = -1;
	}

	accept_conn(lst_fd, s, co_idx, handle);

	schedule_destroy(s);

	return 0;
}

Test Results:
insert image description here

Guess you like

Origin blog.csdn.net/qq_44443986/article/details/117744123