1.协程简介
提起协程,我们不免要和多进程和多线程进行比较,我粗浅的理解为用户态的轻量级线程;并且由程序员控制调度策略,非抢占式的特点使得不用加锁,用户态控制执行流,相比于系统调用非常简便。
关于多线程和多进程我找了一张图
2.设计思路
协程的实现,需要一个协程结构体和一个调度器。
协程结构体:回调函数,回调函数参数,协程状态,上下文信息,栈
协程调度器结构体:指向协程数据的指针,当前运行协程id,协程最大数,主函数上下文信息
我们需要用到的函数:getcontext、setcontext、makecontext、swapcontext
int getcontext(ucontext_t *ucp)—保存当前寄存器信息到ucp中
int setcontext(const ucontext_t *ucp)—将ucp中保存的寄存器信息恢复
void makecontext(ucontext_t *ucp, void (*func)(), int argc)
改造ucp上下文信息,下一步执行func,func执行完毕之后执行 link之后的函数
int swapcontext(ucontext_t *oucp, ucontext_t *ucp)
将oucp寄存器信息保存起来,将ucp的寄存器信息加载到CPU中
3.代码实现
- main.c
#include"coroutine.h"
//获取监听描述符
int tcp_init()
{
printf(" tcp_init()\n");
//创建套接字
int lfd = socket(AF_INET,SOCK_STREAM,0);
if (lfd == -1)
{
perror("socket");
exit(1);
}
//设置套接字复用
int op = 1;
setsockopt(lfd,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 = htonl(INADDR_ANY);
int fd = bind(lfd,(struct sockaddr*)&addr,sizeof(addr));
if (fd == -1)
{
perror("bind");
exit(1);
}
//开始监听
listen(lfd,SOMAXCONN);
printf("tcp_init-> lfd = %d\n",lfd);
return lfd;
}
//设置成非阻塞
void set_nonblock(int fd)
{
//获取描述符的访问模式和文件状态
int flgs = fcntl(fd, F_GETFL, 0);
//设置为非阻塞
flgs |= O_NONBLOCK;
fcntl(fd,F_SETFL, flgs);
}
//获取新连接
void accept_conn(int lfd, schedule_t* s, int co_ids[], void*(*call_back)(schedule_t* s, void *argc))
{
while (1)
{
sleep(3);
int cfd = accept(lfd,NULL, NULL);
printf("accept_conn-> cfd = %d\n",cfd);
if (cfd > 0)
{
set_nonblock(cfd);//设置为非阻塞
int argc[] = {
lfd,cfd};
int id = coroutine_create(s,call_back,argc);//创建协程,并且设置好上下文等信息
int i=0;
for (i=0 ;i<CORSZ; ++i)
{
if (co_ids[i] == -1)
{
co_ids[i] = id;
break;
}
}
if (i == CORSZ)
{
printf("连接太多\n");
}
printf("accept_conn -> coroutine_running\n");
//启动协程
coroutine_running(s,id);
}
else