什么是协程?
协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。
使用协程的好处
进程、线程、协程的对比
上下文切换
进程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户是无感知的。进程的切换内容包括页全局目录、内核栈、硬件上下文,切换内容保存在内存中。进程切换过程是由“用户态到内核态到用户态”的方式,切换效率低。
线程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户无感知。线程的切换内容包括内核栈和硬件上下文。线程切换内容保存在内核栈中。线程切换过程是由“用户态到内核态到用户态”, 切换效率中等。
协程的切换者是用户(编程者或应用程序),切换时机是用户自己的程序所决定的。协程的切换内容是硬件上下文,切换内存保存在用户自己的变量(用户栈或堆)中。协程的切换过程只有用户态,即没有陷入内核态,因此切换效率高。
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);
2.setcontext
int setcontext(const ucontext_t *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, ...);
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);
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);
}
效果图如下