协程实现网络服务器

协程

什么是协程?

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

使用协程的好处

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

进程、线程、协程的对比

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

上下文切换

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

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

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

函数介绍

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

#include <ucontext.h>
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<stdio.h>
#include<ucontext.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>

#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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
libevent是一个开源的网络IO框架,它使用了事件驱动的方式来实现高效的网络处理。协程是一种轻量级的线程,可以在单线程中实现多个任务的并发执行,可以用来实现libevent中的并发处理。 在使用协程实现libevent时,我们需要做以下步骤: 1. 定义事件和回调函数 我们需要定义事件和回调函数,用来处理网络连接和数据传输。在协程模型中,我们需要将回调函数封装为一个协程,用来实现并发处理。可以使用协程库(如libco)提供的API来创建协程。 2. 初始化事件循环 我们需要初始化事件循环,用来监听网络事件并调用回调函数。在协程模型中,我们可以使用一个无限循环来实现事件循环,并在每个循环中切换到不同的协程来处理事件。 3. 注册事件回调函数 我们需要注册事件回调函数,使它们可以被事件循环监听到。在协程模型中,我们可以将事件回调函数封装为一个协程,并将其注册到事件循环中。 4. 运行事件循环 最后,我们需要运行事件循环,开始监听和处理网络事件。在协程模型中,我们可以使用协程库提供的API来启动事件循环,并在其中调用切换协程的函数,以实现并发处理。 下面是一个简单的使用协程实现libevent的示例代码: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include "co.h" #include "event.h" // 回调函数1 void* handle_client(void* arg) { int client_fd = (int)arg; char buf[1024]; int n = recv(client_fd, buf, sizeof(buf), 0); if (n > 0) { buf[n] = '\0'; printf("Received data: %s\n", buf); send(client_fd, buf, n, 0); } close(client_fd); // 切换回事件循环协程 co_yield_ct(); } // 回调函数2 void* handle_accept(void* arg) { int server_fd = (int)arg; struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len); if (client_fd < 0) { perror("accept"); return NULL; } // 创建协程来处理客户端连接 int ret = co_create(handle_client, (void*)client_fd); if (ret < 0) { fprintf(stderr, "co_create error\n"); close(client_fd); return NULL; } // 切换回事件循环协程 co_yield_ct(); } // 初始化事件循环 event_base_t* init_event() { event_base_t* base = event_base_new(); if (!base) { return NULL; } return base; } // 注册事件回调函数 int register_events(event_base_t* base, int server_fd) { // 注册accept事件 event_t* ev_accept = event_new(base, server_fd, EV_READ|EV_PERSIST, handle_accept, (void*)server_fd); if (!ev_accept) { return -1; } event_add(ev_accept, NULL); return 0; } int main() { const char* ip = "0.0.0.0"; int port = 8888; // 创建服务器套接字 int server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd < 0) { perror("socket"); exit(1); } // 绑定服务器地址和端口 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(ip); server_addr.sin_port = htons(port); int ret = bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)); if (ret < 0) { perror("bind"); exit(1); } // 开始监听客户端连接 ret = listen(server_fd, 128); if (ret < 0) { perror("listen"); exit(1); } // 初始化事件循环 event_base_t* base = init_event(); if (!base) { fprintf(stderr, "init_event error\n"); exit(1); } // 注册事件回调函数 ret = register_events(base, server_fd); if (ret < 0) { fprintf(stderr, "register_events error\n"); exit(1); } // 运行事件循环 event_base_loop(base, 0); return 0; } ``` 在上面的代码中,我们使用了libco和libevent两个库来实现协程和事件处理。在handle_accept函数中,当有客户端连接时,我们使用co_create函数创建一个新协程来处理客户端连接。在handle_client函数中,我们处理客户端发送的数据,并使用co_yield_ct函数切换回事件循环协程。在main函数中,我们初始化事件循环并注册事件回调函数,并使用event_base_loop函数开始事件循环。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值