协程实现网络服务器

文章目录

    • 协程
    • 函数介绍
    • 协程实现网络服务器

协程

什么是协程?

协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。

使用协程的好处

  1. 在用户态执行,由程序进行调度和控制,不受系统内核管理,在切换的过程中不会像切换线程那样消耗系统资源。
  2. 协程不是进程也不是线程,而是一个特殊的函数,它可以在任意位置挂起,转去执行其他任务,而后还可以恢复到挂起处,从该位置继续向下执行。
  3. 协程的开销远远小于线程的开销

进程、线程、协程的对比

  1. 协程既不是进程也不是线程,协程仅仅是一个特殊的函数,协程它进程和进程不是一个维度的。
  2. 一个进程可以包含多个线程,一个线程可以包含多个协程。
  3. 一个线程内的多个协程虽然可以切换,但是多个协程是串行执行的,只能在一个线程内运行,没法利用CPU多核能力。即一个协程运作其他协程必须挂起。
  4. 协程与进程一样,切换是存在上下文切换问题的。

上下文切换

  • 进程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户是无感知的。进程的切换内容包括页全局目录、内核栈、硬件上下文,切换内容保存在内存中。进程切换过程是由“用户态到内核态到用户态”的方式,切换效率低。

  • 线程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户无感知。线程的切换内容包括内核栈和硬件上下文。线程切换内容保存在内核栈中。线程切换过程是由“用户态到内核态到用户态”, 切换效率中等。

  • 协程的切换者是用户(编程者或应用程序),切换时机是用户自己的程序所决定的。协程的切换内容是硬件上下文,切换内存保存在用户自己的变量(用户栈或堆)中。协程的切换过程只有用户态,即没有陷入内核态,因此切换效率高。

函数介绍

ucontext_t (协程上下文数据的结构体)

#include 
typedef struct ucontext
 {
       
    struct ucontext *uc_link; //存的上下文结束后恢复到哪个上下文,即下一次执行的地址
    sigset_t uc_sigmask;  //存的上下文的运行期间要屏蔽的信号集合	
    stack_t uc_stack;  //存的上下文的栈
    mcontext_t uc_mcontext;  //存的上下文的具体上下文: PC值、堆栈指针、寄存器值等
    ...  
} ucontext_t;

1. getcontext

int getcontext(ucontext_t *ucp);
  • 用于获取当前上下文数据信息,也可以用来初始化 ucontext_t

2.setcontext

int setcontext(const ucontext_t *ucp);
  • 将当前程序上下文置为 ucp 指向的上下文,下次执行时就转去执行 ucp 的上下文数据。
//为什么这个函数会是一个循环?
int main( ) 
{
     
	int i = 0;
	ucontext_t ctx;

	getcontext(&ctx);  //保存了ctx的上下文数据
	printf("i=%d\n", i++);
	sleep(1);
	setcontext(&ctx);   //每次执行到这都会返回到getcontext的下一句开始执行,所以变成了一个循环
	
	return 0;
}

3.makecontext

void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
  • 修改 ucp 指向的上下文, 上下文转而指向函数 func , argc是传入参数个数, …为传入参数(直接就是参数, 不用指针),它使用时必须先使用 getcontext
void fun(char str)
{
     
    printf("%cn", str);
}                                                                                               

int main()
{
     
    //设置一个栈
    char stack[1024];
    ucontext_t context;
    //初始化上下文
    getcontext(&context);
    //上下文关联到一个栈, 以后这个上下文的栈就用这个了
    context.uc_stack.ss_sp = stack;
    context.uc_stack.ss_size = sizeof(stack);
    makecontext(&context, (void (*)(void))(fun), 1, 'A');
    setcontext(&context);
}

4.swapcontext

int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);
  • 保存当前上下文, 切换到新的上下文, 等于是先执行 getcontext(oucp) 再执行setcontext(ucp)

协程实现网络服务器

1.coroutines.h

#ifndef _COROUTINES_H_
#define _COROUTINES_H_


#include
#include
#include
#include
#include 
#include 
#include 
#include 
#include 

#define CORSIZE		(1024)
#define STACKSIZE	(1024*64)

//错误提示
#define ERROR_EXIT(m) \
	do{\
	perror(m);\
	exit(EXIT_FAILURE);\
	}while(0)


//协程状态
enum State		
{
     
	RUNNING,	//运行态
	DEAD,		//死亡态
	READY,		//就绪态
	SUSPEND		//暂停态
};

struct schedule;

//协程结构体
typedef struct coroutines
{
     
	ucontext_t ctx;											//协程的上下文数据
	char stack[STACKSIZE];									//协程的栈
	enum State state;										//协程的状态
	void *(*call_back)(struct schedule* s, void* args);    //协程的回调函数
	void* args;											    //回调函数的参数
}coroutines_t;

//协程调度器
typedef struct schedule
{
     
	int RUN_ID;							//正在运行的协程下标,如果当前没有就设成 -1
	coroutines_t **coroutines;			//协程数组
	int MAX_ID;							//最大下标
	ucontext_t ctx_main;				//主函数的上下文数据
}schedule_t;

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

// 协程执行函数
static void *main_fun(schedule_t *s);

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

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

// 让出CPU
void coroutine_yield(schedule_t *s);

// 恢复CPU
void coroutine_resume(schedule_t *s, int id);

// 释放调度器
void schedule_destroy(schedule_t *s);

// 判断是否所有协程都运行完了
int schedule_finished(schedule_t *s);

#endif 

2.coroutines.c

#include"coroutines.h"

// 创建调度器
schedule_t *schedule_create() 
{
     
	schedule_t *s = (schedule_t*)malloc(sizeof(schedule_t));
	if(s == NULL)
		ERROR_EXIT("schedule_create malloc");
	else
	{
     
		s->RUN_ID = -1;
		s->MAX_ID = 0;
		s->coroutines = (coroutines_t**)malloc(sizeof(coroutines_t*) * CORSIZE);
		memset(s->coroutines,0,sizeof(coroutines_t*)* CORSIZE);
	}
	
	return s;
}

// 协程执行函数
static void *main_fun(schedule_t *s)
{
     
	int id = s->RUN_ID;
	if ( id != -1 ) {
     
		coroutines_t *c = s->coroutines[s->RUN_ID];
		c->call_back(s, c->args);
		c->state = DEAD;					// 目标函数执行完毕,就进入死亡态
		s->RUN_ID = -1;					 //协程是串行化的,所以为-1
	}
}

// 创建一个协程,该协程的会加入到schedule的协程序列中
//call_back为其执行的函数,args为call_back的执行函数。返回创建的线程在schedule中的下标
int coroutine_create(schedule_t *s, void *(*call_back)(schedule_t *s, void *args), void *args) 
{
     
	coroutines_t *c = NULL;
	int i = 0;
	for(i; i < s->MAX_ID; ++i)
	{
     
		c = s->coroutines[i];
		if(c->state == DEAD)		//只有当协程的状态是死亡态才能够使用
		{
     
			break;
		}
	}

	if(c == NULL || i == s->MAX_ID)   //当不存在使用时就新创建一个添加进调度器
	{
     
		s->coroutines[i] = (coroutines_t*)malloc(sizeof(coroutines_t));
		++s->MAX_ID;
	}

	c			 = s->coroutines[i];
	c->call_back = call_back;
	c->state     = READY;
	c->args		 = args;

	getcontext(&c->ctx);    //获取当前协程的信息

	c->ctx.uc_stack.ss_sp	 = c->stack;    //设置新的栈
	c->ctx.uc_stack.ss_size	 = STACKSIZE;
	c->ctx.uc_stack.ss_flags = 0;
	c->ctx.uc_link			 = &s->ctx_main;     //执行完成后,跳转到ctx_main继续运行

	makecontext(&c->ctx,(void (*)())&main_fun,1,s);  //修改协程的上下文,当这个上下文被恢复,会切换到main_fun继续向下执行

	return i;
}


// 启动协程
void coroutine_start(schedule_t *s, int id)
{
     
	coroutines_t *c = s->coroutines[id];
	if(c->state != DEAD)
	{
     
		c->state  = RUNNING;
		s->RUN_ID = id;
		swapcontext(&s->ctx_main,&c->ctx);     //保存主函数上下文,切换到协程的上下文去执行
	}
}

//挂起调度器schedule中当前正在执行的协程,切换到主函数。
void coroutine_yield(schedule_t *s) 
{
     
	if(s->RUN_ID != -1)
	{
     
		coroutines_t *c = s->coroutines[s->RUN_ID];
		c->state        = SUSPEND;
		s->RUN_ID	    = -1;
		swapcontext(&c->ctx,&s->ctx_main);       //保存协程上下文,切换到主函数的上下文去执行
	}
}

// 恢复运行调度器schedule中编号为id的协程
void coroutine_resume(schedule_t *s, int id)
{
     
	coroutines_t *c = s->coroutines[id];
	if(c != NULL && c->state == SUSPEND)     //只有暂停态才能被恢复
	{
     
		c->state  = RUNNING;
		s->RUN_ID = id;
		swapcontext(&s->ctx_main,&c->ctx);   //保存主函数上下文,切换到协程的上下文去执行
	}
}

//释放协程
void coroutines_delete(schedule_t *s, int id)
{
     
	coroutines_t *c = s->coroutines[id];
	if( c != NULL)
	{
     
		free(c);
		s->coroutines[id] = NULL;
	}
}


// 释放调度器
void schedule_destroy(schedule_t *s) 
{
     
	int i = 0;
	for(i; i < s->MAX_ID; ++i)
	{
     
		coroutines_delete(s,i);
	}
	free(s->coroutines);
	free(s);
}

// 判断是否所有协程都运行完了, 0 (否)  1(是)
int schedule_finished(schedule_t *s) 
{
     
	if(s->RUN_ID != -1)						//说明此时仍有协程运行
	{
     
		return 0;
	}
	else
	{
     
		int i = 0;
		for(i; i < s->MAX_ID; ++i)
		{
     
			coroutines_t *c = s->coroutines[i];
			if(c->state == DEAD)					//只要有协程的状态为运行态就否
			{
     
				return 0;
			}
		}
	}
	return 1;
}

3.handler.h

#ifndef _HANDLER_H
#define _HANDLER_H_

#include"coroutines.h"

//负责创建套接字,绑定地址信息,并返回监听到的套接字
int tcp_init();

//将套接字设置为非阻塞
void set_wnohang(int fd); 

//运行新协程或者恢复旧协程
void accept_connect(int lfd, schedule_t *s, int *fds, void *(*call_back)(schedule_t *s, void *args));

//回调函数
void *handle(schedule_t *s, void *args);

#endif

4.handler.c

#include"handler.h"


//负责创建套接字,绑定地址信息,并返回监听到的套接字
int tcp_init()
{
     
	int fd = socket(AF_INET,SOCK_STREAM,0);
	if(fd < 0)
		ERROR_EXIT("init socket");
	
	char *ip = "192.168.197.128";
	unsigned short port = 9000;
	struct sockaddr_in addr;
	addr.sin_family		 = AF_INET;
	addr.sin_port        = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip);  //字符串IPV4 -> 网络IPV4

	if((bind(fd,(struct sockaddr*)&addr,sizeof(addr))) < 0)
		ERROR_EXIT("init bind");

	if((listen(fd,SOMAXCONN)) < 0)
		ERROR_EXIT("init listen");

	return fd;
}

//将套接字设置为非阻塞
void set_wnohang(int fd)
{
     
	int flgs = fcntl(fd, F_GETFL, 0);
	flgs    |= O_NONBLOCK;
	fcntl(fd, F_SETFL, flgs);
}

//运行新协程或者恢复旧协程
 void accept_connect(int lfd,schedule_t *s, int *fds, void *(*call_back)(schedule_t *s, void *args)) 
{
     
	while (1)
	{
     		
		struct sockaddr_in addr;
		socklen_t len = sizeof(addr);

		int cfd = accept(lfd,NULL,NULL); 
		char *ip = NULL;
		unsigned short port = 0;
		
		ip	 = inet_ntoa(addr.sin_addr);   
		port = ntohs(addr.sin_port);

		if ( cfd > 0 )                         //说明cfd是新建的套接字
		{
     
			set_wnohang(cfd);				   //设置非阻塞
			int args[2] = {
     lfd, cfd};
			int id = coroutine_create(s, call_back,args);
			int i;
			for (i = 0; i < CORSIZE; ++i) 
			{
     
				if ( fds[i] == -1 )       //-1说明该位置还未使用
				{
     
					fds[i] = id;
					break;
				}
			}
			if ( i == CORSIZE )
			{
     
				printf("连接过载\n");
			}
			coroutine_start(s, id);         //运行协程
		} 
		else								//说明cfd已经被创建,需要唤醒
		{
     
			int i;
			for (i = 0; i < CORSIZE; ++i) 
			{
     
				int cid = fds[i];
				if ( cid == -1 ) 
					continue;
				coroutine_resume(s, cid);      //唤醒cfd所在的旧协程
			}	
		}
	}
}

//回调函数,主要负责通信
void *handle(schedule_t *s, void *args) 
{
     
	int *arr = (int*)args;
	int cfd = arr[1];

	char buf[1024] = {
     0};
	while (1) 
	{
     
		memset(buf,0,sizeof(buf));
		int r = recv(cfd, buf, 1024,0);
		if ( r == -1 )                     
		{
     
			coroutine_yield(s); //当没有数据读取时,主动让出CPU
		} 
		else if ( r == 0 )        
		{
     
			break;
		} 
		else
		{
     
			printf("Client  Reply :  %s \n", buf);
			if ( strncasecmp(buf, "exit", 4) == 0 )   //客户端发送的如果前4个字符是exit就退出
			{
     
				break;
			}
			printf("Server Say: \n");
            scanf("%s",buf);
		    send(cfd,buf,strlen(buf),0);
		}
	}
}

5.client.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ERROR_EXIT(m) \
	do{\
	perror(m);\
	exit(EXIT_FAILURE);\
	}while(0)

int main()
{
     
	char* ip		     = "192.168.197.128";

	int cli_fd = socket(AF_INET,SOCK_STREAM,0);
	if(cli_fd < 0)
		ERROR_EXIT("cli socket");
	
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port   = htons(9000);
	addr.sin_addr.s_addr = inet_addr(ip);  

	if((connect(cli_fd,(struct sockaddr*)&addr,sizeof(addr))) < 0)
		ERROR_EXIT("cli connect");
	
	char buf[1024] = {
     0};
    printf("Client Say :\n");
	while(scanf("%s",buf) != EOF)
	{
     
		send(cli_fd,buf,strlen(buf),0);
		memset(buf,0,sizeof(buf));
		
		int ret = recv(cli_fd, buf, sizeof(buf),0);
		if(ret <= 0) 
			break;
		else
			printf("Server Reply:  %s \n",buf);
	}
	return 0;
}

6.server.c

#include"coroutines.h"
#include"handler.h"

int main() 
{
     
	int lfd = tcp_init();
	set_wnohang(lfd);

	schedule_t *s = schedule_create();

	int co_ids[CORSIZE];
	int i;
	for (i = 0; i < CORSIZE; ++i)
		co_ids[i] = -1;

	accept_connect(lfd, s, co_ids, handle);
	
	schedule_destroy(s);
}

效果图如下

在这里插入图片描述
协程实现网络服务器_第1张图片
协程实现网络服务器_第2张图片
流程大体思想如下

协程实现网络服务器_第3张图片

你可能感兴趣的:(Linux)